[libGDX游戏开发教程]使用libGDX进行游戏开发(12)-Action动画
本章打包的素材:https://files.cnblogs.com/mignet/animate.7z
在这一章我们将通过动画来丰富我们的游戏效果。
使用action来操作actor:
很多同学应该都很清楚action是什么东东,所以这里仅仅举个最常见的例子。
moveTo (x, y);
moveTo (x, y, duration);
moveTo (x, y, duration, interpolation);
rotateTo (rotation);
rotateTo (rotation, duration);
rotateTo (rotation, duration, interpolation);
来个连续动作:
Actor actor = new Actor(); float x = 100.0f, y = 100.0f, rotation = 0.0f, duration = 1.0f; actor.addAction(sequence( moveTo(x, y), rotateTo(rotation), delay(duration), parallel(moveBy(50.0f, 0.0f, 5.0f), rotateBy(90.0f, 5.0f, Interpolation.swingOut))));
插值算法(Interpolation)和前文提到的一样。现在,发现actor+action的威力了吗,哈哈
Libgdx支持的action列表:
•add(): This adds an action to an actor.
•alpha(): This sets the alpha value of an actor's color.
•color(): This sets the color of an actor.
•fadeIn() and fadeOut(): These are convenience actions to set the alpha value of an actor's color to 1 or 0, respectively.
•hide(): This sets the visibility of an actor to false.
•layout(): This sets the actor's layout to enabled or disabled.
•moveBy() and moveTo(): These move an actor by a relative amount or to a specific location.
•removeActor(): This removes the actor to which this action is attached. Alternatively, another actor that is to be removed can be specified.
•rotateBy() and rotateTo(): These rotate an actor by a relative amount or to a specific angle of rotation.
•run(): This runs a Runnable (the code will be executed in a separate thread).
•scaleBy() and scaleTo(): These scale an actor by a relative amount or to a specific scale.
• show(): This sets the visibility of an actor to true.
• sizeBy() and sizeTo(): These resize an actor by a relative amount or to a specific size.
• touchable(): This sets the touchability of an actor(refer to Touchable enumerator).
• visible(): This sets the visibility of an actor.
控制执行的顺序和时间:
• after(): This waits for other actions of an actor to finish before its action is executed. Note that this action will only wait for other actions that were already added to an actor prior to this.
• delay(): This delays the execution of an action.
• forever(): This repeats an action forever.
• parallel():This executes a list of actions at the same time.
• repeat(): This repeats an action for a given number of times.
• sequence(): This executes a list of actions one after another.
让游戏菜单动起来:
动画效果:首先金币和兔子头是看不见的,接着金币淡入和比例从0%提高到100%,这样看起来好像是跳出了水面到中心屏幕上一样,短暂的停顿之后,兔子头出现在右上角,兔子头移动,就好像它是跳跃到它前面的其他的岩石上。
首先,修改MenuScreen:
private Table buildObjectsLayer() { Table layer = new Table(); // + Coins imgCoins = new Image(skinCanyonBunny, "coins"); layer.addActor(imgCoins); imgCoins.setOrigin(imgCoins.getWidth() / 2, imgCoins.getHeight() / 2); imgCoins.addAction(sequence( moveTo(135, -20), scaleTo(0, 0), fadeOut(0), delay(2.5f), parallel(moveBy(0, 100, 0.5f, Interpolation.swingOut), scaleTo(1.0f, 1.0f, 0.25f, Interpolation.linear), alpha(1.0f, 0.5f)))); // + Bunny imgBunny = new Image(skinCanyonBunny, "bunny"); layer.addActor(imgBunny); imgBunny.addAction(sequence( moveTo(655, 510), delay(4.0f), moveBy(-70, -100, 0.5f, Interpolation.fade), moveBy(-100, -50, 0.5f, Interpolation.fade), moveBy(-150, -300, 1.0f, Interpolation.elasticIn))); return layer; }
让menu和Option动起来:
添加MenuScreen:
private void showMenuButtons(boolean visible) { float moveDuration = 1.0f; Interpolation moveEasing = Interpolation.swing; float delayOptionsButton = 0.25f; float moveX = 300 * (visible ? -1 : 1); float moveY = 0 * (visible ? -1 : 1); final Touchable touchEnabled = visible ? Touchable.enabled : Touchable.disabled; btnMenuPlay.addAction(moveBy(moveX, moveY, moveDuration, moveEasing)); btnMenuOptions.addAction(sequence(delay(delayOptionsButton), moveBy(moveX, moveY, moveDuration, moveEasing))); SequenceAction seq = sequence(); if (visible) seq.addAction(delay(delayOptionsButton + moveDuration)); seq.addAction(run(new Runnable() { public void run() { btnMenuPlay.setTouchable(touchEnabled); btnMenuOptions.setTouchable(touchEnabled); } })); stage.addAction(seq); } private void showOptionsWindow(boolean visible, boolean animated) { float alphaTo = visible ? 0.8f : 0.0f; float duration = animated ? 1.0f : 0.0f; Touchable touchEnabled = visible ? Touchable.enabled : Touchable.disabled; winOptions.addAction(sequence(touchable(touchEnabled), alpha(alphaTo, duration))); }
修改:
private Table buildOptionsWindowLayer () { ... // Make options window slightly transparent winOptions.setColor(1, 1, 1, 0.8f); // Hide options window by default showOptionsWindow(false, false); if (debugEnabled) winOptions.debug(); // Let TableLayout recalculate widget sizes and positions winOptions.pack(); // Move options window to bottom right corner winOptions.setPosition(Constants.VIEWPORT_GUI_WIDTH - winOptions.getWidth() - 50, 50); return winOptions; } private void onOptionsClicked () { loadSettings(); showMenuButtons(false); showOptionsWindow(true, true); } private void onCancelClicked () { showMenuButtons(true); showOptionsWindow(false, true); AudioManager.instance.onSettingsUpdated(); }
使用的图像序列动画:
用texture packer把序列图片打包。
Libgdx播放动画的不同模式:
• NORMAL: This plays the animation once (first frame to last)
• REVERSED: This plays the animation once (last frame to first)
• LOOP: This plays the animation in a loop (first frame to last)
• LOOP_REVERSED: This plays the animation in a loop (last frame to first)
• LOOP_PINGPONG: This plays the animation in a loop (first frame, to last,to first)
• LOOP_RANDOM: This plays the animation in a loop (random frames)
给游戏场景增加动画:
这三个动画将分别使用不同的模式:
1. The first animation animCopterTransform plays all frames once (play mode:NORMAL; frame progression: 01, 02, 03, 04, 05).
2. The second animation animCopterRotate plays the last two frames in a ping-pong loop(play mode: LOOP_PINGPONG; frame progression: 04, 05, 05,04, [restart at first frame]).
3. Lastly, the third animation animCopterTransformBack is simply the reverse of the first animation (play mode: REVERSED; frame progression: 05, 04, 03,02, 01).
首先,在Assets添加素材:
public class AssetGoldCoin { public final AtlasRegion goldCoin; public final Animation animGoldCoin; public AssetGoldCoin(TextureAtlas atlas) { goldCoin = atlas.findRegion("item_gold_coin"); // Animation: Gold Coin Array<AtlasRegion> regions = atlas.findRegions("anim_gold_coin"); AtlasRegion region = regions.first(); for (int i = 0; i < 10; i++) regions.insert(0, region); animGoldCoin = new Animation(1.0f / 20.0f, regions, Animation.LOOP_PINGPONG); } } public class AssetBunny { public final AtlasRegion head; public final Animation animNormal; public final Animation animCopterTransform; public final Animation animCopterTransformBack; public final Animation animCopterRotate; public AssetBunny(TextureAtlas atlas) { head = atlas.findRegion("bunny_head"); Array<AtlasRegion> regions = null; AtlasRegion region = null; // Animation: Bunny Normal regions = atlas.findRegions("anim_bunny_normal"); animNormal = new Animation(1.0f / 10.0f, regions, Animation.LOOP_PINGPONG); // Animation: Bunny Copter - knot ears regions = atlas.findRegions("anim_bunny_copter"); animCopterTransform = new Animation(1.0f / 10.0f, regions); // Animation: Bunny Copter - unknot ears regions = atlas.findRegions("anim_bunny_copter"); animCopterTransformBack = new Animation(1.0f / 10.0f, regions, Animation.REVERSED); // Animation: Bunny Copter - rotate ears regions = new Array<AtlasRegion>(); regions.add(atlas.findRegion("anim_bunny_copter", 4)); regions.add(atlas.findRegion("anim_bunny_copter", 5)); animCopterRotate = new Animation(1.0f / 15.0f, regions); } }
修改AbstractGameObject:
public float stateTime; public Animation animation; public void setAnimation(Animation animation) { this.animation = animation; stateTime = 0; } public void update (float deltaTime) { stateTime += deltaTime; if (body == null) { updateMotionX(deltaTime); updateMotionY(deltaTime); // Move to new position position.x += velocity.x * deltaTime; position.y += velocity.y * deltaTime; } else { position.set(body.getPosition()); rotation = body.getAngle() * MathUtils.radiansToDegrees; } }
修改GoldCoin:
private void init() { dimension.set(0.5f, 0.5f); setAnimation(Assets.instance.goldCoin.animGoldCoin); stateTime = MathUtils.random(0.0f, 1.0f); // Set bounding box for collision detection bounds.set(0, 0, dimension.x, dimension.y); collected = false; } public void render(SpriteBatch batch) { if (collected) return; TextureRegion reg = null; reg = animation.getKeyFrame(stateTime, true); batch.draw(reg.getTexture(), position.x, 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); }
现在,给兔子头加动画,主要是螺旋桨飞行:
这里用到了状态机的概念,我们兔子头就是有限状态机,也就是说,它在任何时候一定处于有限状态的某种状态中,并且在合适的时候进行状态的切换。
我们来修改BunnyHead:
private Animation animNormal; private Animation animCopterTransform; private Animation animCopterTransformBack; private Animation animCopterRotate; public void init () { dimension.set(1, 1); animNormal = Assets.instance.bunny.animNormal; animCopterTransform = Assets.instance.bunny.animCopterTransform; animCopterTransformBack = Assets.instance.bunny.animCopterTransformBack; animCopterRotate = Assets.instance.bunny.animCopterRotate; setAnimation(animNormal); // Center image on game object origin.set(dimension.x / 2, dimension.y / 2); ... } @Override public void update(float deltaTime) { super.update(deltaTime); if (velocity.x != 0) { viewDirection = velocity.x < 0 ? VIEW_DIRECTION.LEFT : VIEW_DIRECTION.RIGHT; } if (timeLeftFeatherPowerup > 0) { if (animation == animCopterTransformBack) { // Restart "Transform" animation if another feather power-up // was picked up during "TransformBack" animation. Otherwise, // the "TransformBack" animation would be stuck while the // power-up is still active. setAnimation(animCopterTransform); } timeLeftFeatherPowerup -= deltaTime; if (timeLeftFeatherPowerup < 0) { // disable power-up timeLeftFeatherPowerup = 0; setFeatherPowerup(false); setAnimation(animCopterTransformBack); } } dustParticles.update(deltaTime); // Change animation state according to feather power-up if (hasFeatherPowerup) { if (animation == animNormal) { setAnimation(animCopterTransform); } else if (animation == animCopterTransform) { if (animation.isAnimationFinished(stateTime)) setAnimation(animCopterRotate); } } else { if (animation == animCopterRotate) { if (animation.isAnimationFinished(stateTime)) setAnimation(animCopterTransformBack); } else if (animation == animCopterTransformBack) { if (animation.isAnimationFinished(stateTime)) setAnimation(animNormal); } } } @Override public void render(SpriteBatch batch) { TextureRegion reg = null; // Draw Particles dustParticles.draw(batch); // Apply Skin Color batch.setColor(CharacterSkin.values()[GamePreferences.instance.charSkin] .getColor()); float dimCorrectionX = 0; float dimCorrectionY = 0; if (animation != animNormal) { dimCorrectionX = 0.05f; dimCorrectionY = 0.2f; } // Draw image reg = animation.getKeyFrame(stateTime, true); batch.draw(reg.getTexture(), position.x, position.y, origin.x, origin.y, dimension.x + dimCorrectionX, dimension.y + dimCorrectionY, scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(), reg.getRegionHeight(), viewDirection == VIEW_DIRECTION.LEFT, false); // Reset color to white batch.setColor(1, 1, 1, 1); }
呼,终于忙完了。
至此12章全文结束,所有章节的源码都在qq群文件中,大家知悉。