标签:
ParticleEffect effect = new ParticleEffect(); ParticleEmitter emitter = new ParticleEmitter(); effect.getEmitters().add(emitter); emitter.setAdditive(true); emitter.getDelay().setActive(true); emitter.getDelay().setLow(0.5f); // ... more code for emitter initialzation ...可也确定的说上述代码没有任何错误,但是我们不推荐这样初始化粒子发射器。粗略的估算,粒子发射器大概包含20个用户可以设置的属性,如果按照上述方法进行初始化的话,那么每个发射器的初始化代码将迅速变为一个庞大的代码片段,很明显,这样的代码难以理解和维护。一个清晰有趣的解决方案就是使用LibGDX的图形化粒子编辑器创建或修改粒子效果。该编辑器的特点是包含一个预览界面,我们可以通过该界面实时观察当前设置下的效果,该特性大大的简化了设计阶段的工作。
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.g2d.ParticleEffect;完成之后,根据下面代码修改BunnyHead类:
public class BunnyHead extends AbstractGameObject { public ParticleEffect dustParticles = new ParticleEffect(); public void init() { ... // 飞翔特效 hasFeatherPowerup = false; timeLeftFeatherPowerup = 0; // Particles dustParticles.load(Gdx.files.internal("particles/dust.pfx"), Gdx.files.internal("particles")); } @Override public void update(float deltaTime) { super.update(deltaTime); ... dustParticles.update(deltaTime); } @Override public void render(SpriteBatch batch) { TextureRegion reg = null; // Draw Particles dustParticles.draw(batch); // Apply Skin Color ... } }
@Override protected void updateMotionY(float deltaTime) { switch (jumpState) { case GROUNDED: jumpState = JUMP_STATE.FALLING; if(velocity.x != 0) { dustParticles.setPosition(position.x + dimension.x / 2, position.y); dustParticles.start(); } break; ... } if(jumpState != JUMP_STATE.GROUNDED) { dustParticles.allowCompletion(); super.updateMotionY(deltaTime); } }下面截图就是我们完成之后的效果:
private Cloud spawnCloud() { Cloud cloud = new Cloud(); cloud.dimension.set(dimension); // select random cloud image cloud.setRegion(regClouds.random()); // position Vector2 pos = new Vector2(); pos.x = length + 10; // postition after end of level pos.y = 1.75f; // base postition pos.y += MathUtils.random(0.0f, 0.2f) * (MathUtils.randomBoolean() ? 1 : -1); cloud.position.set(pos); // speed Vector2 speed = new Vector2(); speed.x += 0.5f; // base speed // random additional speed speed.x += MathUtils.random(0.0f, 0.75f); cloud.terminalVelocity.set(speed); speed.x *= -1; // move left cloud.velocity.set(speed); return cloud; }接下来,为Clouds添加下面代码:
@Override public void update(float deltaTime) { for (int i = clouds.size - 1; i >= 0; i--) { Cloud cloud = clouds.get(i); cloud.update(deltaTime); if(cloud.position.x < -10) { // cloud moved outside of world. // destroy and spawn new cloud at end of level. clouds.removeIndex(i); clouds.add(spawnCloud()); } } super.update(deltaTime); }spawnCloud()方法现在将使用我们刚刚添加的代码创建一个新的Cloud对象。update()方法迭代所有存在的Cloud对象,分别为每个对象调用update()方法更新最新状态,接着,检测每个对象的最新位置,如果满足条件,则从列表中移除该对象,并为列表添加一个位于关卡最右侧的新对象。
private final float FOLLOW_SPEED = 4.0f;之后,修改下面方法:
public void update(float deltaTime) { if(!hasTarget()) return ; position.lerp(target.position, FOLLOW_SPEED * deltaTime); // 防止camera移动太远 position.y = Math.max(-1f, position.y); }幸运的是,LibGDX的Vector2类已经提供了lerp()方法,这使得Lerp操作非常容易实现。上面代码中,为当前相机的位置向量调用lerp()方法具体发生的变化:一个2D坐标,并传入一个目标位置和一个alpha值。该alpha值用于描述当前位置和目标位置之间的比例。记住,Lerp就是假想将当前位置和目标位置连接成一条直线,然后通过alpha值确定最新坐标在该直线上的位置。如果alpha值等于0.5,则意味着新坐标位于当前位置和目标位置连线的中点处。
import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2;接下来为Rock添加下面几个成员变量:
private final float FLOAT_CYCLE_TIME = 2.0f; private final float FLOAT_AMPLITUDE = 0.25f; private float floatCycleTimeLeft; private boolean floatingDownwards; private Vector2 floatTargetPosition;然后,修改init()方法:
private void init() { dimension.set(1, 1.5f); regEdge = Assets.instance.rock.edge; regMiddle = Assets.instance.rock.middle; // 设定rock初始长度 setLength(1); floatingDownwards = false; floatCycleTimeLeft = MathUtils.random(0, FLOAT_CYCLE_TIME / 2); floatTargetPosition = null; }上述代码确保与浮动过程相关的变量都得了到正确的初始化。初始化浮动方向被设定为竖直向上;循环周期被设定为位于0到1秒之间的一个随机数。使用随机循环周期可以让最终效果看起来更加自然,这是因为每个石块的移动过程看起来都是互不相关的。floatTargetPositon变量用于存储下一个目标位置,如下代码所示:
ss @Override public void update(float deltaTime) { super.update(deltaTime); floatCycleTimeLeft -= deltaTime; if(floatTargetPosition == null) floatTargetPosition = new Vector2(position); if(floatCycleTimeLeft <= 0) { floatCycleTimeLeft = FLOAT_CYCLE_TIME; floatingDownwards = !floatingDownwards; floatTargetPosition.y += FLOAT_AMPLITUDE * (floatingDownwards ? -1 : 1); } position.lerp(floatTargetPosition, deltaTime); }为山丘添加视差滚动效果
import com.badlogic.gdx.math.Vector2;接下来,为Mountains添加下面新方法:
public void updateScrollPosition(Vector2 camPosition) { position.set(camPosition.x, position.y); }接下来修改drawMountain()和render()方法:
private void drawMountain (SpriteBatch batch, float offsetX, float offsetY, float tintColor, float parallaxSpeedX) { TextureRegion reg = null; batch.setColor(tintColor, tintColor, tintColor, 1); float xRel = dimension.x * offsetX; float yRel = dimension.y * offsetY; // mountains 跨越整个关卡 int mountainLength = 0; mountainLength += MathUtils.ceil( length / (2 * dimension.x) * (1 - parallaxSpeedX)); mountainLength += MathUtils.ceil(0.5f + offsetX); for(int i = 0; i < mountainLength; i++) { // mountain 左侧 reg = regMountainLeft; batch.draw(reg.getTexture(), origin.x + xRel + position.x * parallaxSpeedX, origin.y + yRel + position.y, origin.x, origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(), reg.getRegionHeight(), false, false); xRel += dimension.x; // mountain 右侧 reg = regMountainRigth; batch.draw(reg.getTexture(), origin.x + xRel + position.x * parallaxSpeedX, origin.y + yRel + position.y, origin.x, origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(), reg.getRegionHeight(), false, false); xRel += dimension.x; } // 重置颜色为白色 batch.setColor(1, 1, 1, 1); } @Override public void render(SpriteBatch batch) { // distant mountains(dark gray) drawMountain(batch, 0.5f, 0.5f, 0.5f, 0.8f); // distant mountains(gray) drawMountain(batch, 0.25f, 0.25f, 0.7f, 0.5f); // distant mountains(light gray) drawMountain(batch, 0.0f, 0.0f, 0.9f, 0.3f); }我们为drawMountain()添加了第五个参数,该参数用于描述滚动距离或者称为滚动速度,取值范围限定在0.0到1.0之间。滚动的距离等于相机当前位置乘以距离因子。这就是我们为什么添加了一个新方法updateScrollPosition(),为了获得相机最新位置,我们需要在每个更新循环调用该方法。
public void update(float deltaTime) { handleDebugInput(deltaTime); if(isGameOver()) { timeLeftGameOverDelay -= deltaTime; if(timeLeftGameOverDelay < 0) backToMenu(); } else { handleInputGame(deltaTime); } level.update(deltaTime); testCollisions(); cameraHelper.update(deltaTime); if(!isGameOver() && isPlayerInWater()) { lives--; if(isGameOver()) { timeLeftGameOverDelay = Constants.TIME_DELAY_GAME_OVER; } else { initLevel(); } } level.mountains.updateScrollPosition( cameraHelper.getPosition());现在三个mountain层将分别以不同的速度滚动:30%,50%,80%。
public float livesVisual;接下来按照下面代码修改WorldController类:
private void init() { Gdx.input.setInputProcessor(this); cameraHelper = new CameraHelper(); lives = Constants.LIVES_START; livesVisual = lives; timeLeftGameOverDelay = 0; initLevel(); } public void update(float deltaTime) { handleDebugInput(deltaTime); if(isGameOver()) { timeLeftGameOverDelay -= deltaTime; if(timeLeftGameOverDelay < 0) backToMenu(); } else { handleInputGame(deltaTime); } level.update(deltaTime); testCollisions(); cameraHelper.update(deltaTime); if(!isGameOver() && isPlayerInWater()) { lives--; if(isGameOver()) { timeLeftGameOverDelay = Constants.TIME_DELAY_GAME_OVER; } else { initLevel(); } } level.mountains.updateScrollPosition( cameraHelper.getPosition()); if(livesVisual > lives) { livesVisual = Math.max(lives, livesVisual - 1 * deltaTime); } }我们为WorldController类引入了一个新的成员变量livesVisual,该变量包含许多有关生命的信息。但是,当任意时间失去生命时livesVisual只会随之时间慢慢减小。这就允许我们在livesVisual还没有达到当前生命的数量时播放相应的动画。
private void renderGuiExtraLive(SpriteBatch batch) { float x = cameraGUI.viewportWidth - 50 - Constants.LIVES_START * 50; float y = -15; for(int i = 0; i < Constants.LIVES_START; i++) { if(i >= worldController.lives) batch.setColor(0.5f, 0.5f, 0.5f, 0.5f); batch.draw(Assets.instance.bunny.head, x + i * 50, y, 50, 50, 120, 100, 0.35f, -0.35f, 0); batch.setColor(1, 1, 1, 1); } if(worldController.lives >= 0 && worldController.livesVisual > worldController.lives) { int i = worldController.lives; float alphaColor = Math.max(0, worldController.livesVisual - worldController.lives - 0.5f); float alphaScale = 0.35f * (2 + worldController.lives - worldController.livesVisual) * 2; float alphaRotate = -45 * alphaColor; batch.setColor(1.0f, 0.7f, 0.7f, alphaColor); batch.draw(Assets.instance.bunny.head, x + i * 50, y, 50, 50, 120, 100, alphaScale, -alphaScale, alphaRotate); batch.setColor(1, 1, 1, 1); }上面新添加的代码将绘制一个兔子头图标,并且将随着时间自动播放alpha颜色、缩放和旋转动画。该动画的过程(时间)是通过livesVisual的当前值控制的。
public float scoreVisual;接下来根据下面代码修改WorldController类:
private void initLevel() { score = 0; scoreVisual = score; level = new Level(Constants.LEVEL_01); cameraHelper.setTarget(level.bunnyHead); } public void update(float deltaTime) { ... level.mountains.updateScrollPosition( cameraHelper.getPosition()); if(livesVisual > lives) { livesVisual = Math.max(lives, livesVisual - 1 * deltaTime); } if(scoreVisual < score) scoreVisual = Math.min(score, scoreVisual + 250 * deltaTime); }
// 游戏分数渲染方法 private void renderGuiScore(SpriteBatch batch) { float x = -15; float y = -15; float offsetX = 50; float offsetY = 50; if(worldController.scoreVisual < worldController.score) { long shakeAlpha = System.currentTimeMillis() % 360; float shakeDist = 1.5f; offsetX += MathUtils.sinDeg(shakeAlpha * 2.2f) * shakeDist; offsetY += MathUtils.sinDeg(shakeAlpha * 2.9f) * shakeDist; } batch.draw(Assets.instance.goldCoin.goldCoin, x, y, offsetX, offsetY, 100, 100, 0.35f, -0.35f, 0); Assets.instance.fonts.defaultBig.draw(batch, "" + (int) worldController.scoreVisual, x + 75, y + 37); }scoreVisual被强制转换为整型数值是为了省略小数。转换之后的中间数字就是我们准备实现的动画。为了让金币图标发生震动,我们使用了一个正弦函数和两个不同的角度因子作为正弦函数的输入值,最后计算出临时位移。
标签:
原文地址:http://blog.csdn.net/artzok/article/details/50707786