ibgdx的ui库可以实现一些动画效果,但是做游戏来说可能有些不足。Universal Tween Engine是一个纯java实现的动画库。
地址:http://code.google.com/p/java-universal-tween-engine/
只要能够用float表示的一切java对象它可以让它动画化,可以使用于Libgdx、Android、Swing等等。
使用Universal Tween Engine最重要的一个步骤就是实现TweenAccessor接口,这个接口定义了getValues和setValues方法。
然后Engine中注册对应的接口。然后定义一些动画效果并添加到管理器中。最后用update方法更新时间。
具体的可以参考一下Wiki:http://code.google.com/p/java-universal-tween-engine/wiki/GetStarted
我比较喜欢使用Stage,所以下面的例子都是Stage中的。
首先实现TweenAccessor接口,我没有区分对待,比如给Image写一个,再给Button写个啥的。我直接给Actor写了一个,这样都可以用。
getValues和setValues中我定义了3中操作:只修改X值;只修改Y值;修改X和Y值。
1 2 3 |
public static final int POSITION_X = 1; public static final int POSITION_Y = 2; public static final int POSITION_XY = 3; |
这里注意一下getValues的返回值,你修改或者操作了几个值就返回几。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package com.cnblogs.htynkn; import aurelienribon.tweenengine.TweenAccessor; import com.badlogic.gdx.scenes.scene2d.Actor; public class ActorAccessor implements TweenAccessor { public static final int POSITION_X = 1; public static final int POSITION_Y = 2; public static final int POSITION_XY = 3; @Override public int getValues(Actor target, int tweenType, float[] returnValues) { switch (tweenType) { case POSITION_X: returnValues[0] = target.x; return 1; case POSITION_Y: returnValues[0] = target.y; return 1; case POSITION_XY: returnValues[0] = target.x; returnValues[1] = target.y; return 2; default: assert false; return -1; } } @Override public void setValues(Actor target, int tweenType, float[] newValues) { switch (tweenType) { case POSITION_X: target.x = newValues[0]; break; case POSITION_Y: target.y = newValues[0]; break; case POSITION_XY: target.x = newValues[0]; target.y = newValues[1]; break; default: assert false; break; } } } |
然后来写具体的动画和绘制部分。为了方便演示我编写一个随着点击移动的小图标的例子。
我的图标是
。声明image和stage的绘制和原来一样。先声明一个动画管理器
1 |
private TweenManager tweenManager = new TweenManager(); |
然后将我们的Image注册一下
1 |
Tween.registerAccessor(Image.class, new ActorAccessor());
|
同时实现InputProcessor接口以接收触碰事件。
在touchDown方法中添加
1 2 3 4 5 6 7 8 9 |
@Override public boolean touchDown(int x, int y, int pointer, int button) { Vector3 vector3 = new Vector3(x, y, 0); stage.getCamera().unproject(vector3); Tween.to(image, ActorAccessor.POSITION_XY, 1.0f).ease(Bounce.OUT) .target(vector3.x, vector3.y).start(tweenManager); return false; } |
说明一下,因为Stage的坐标和默认的Input的坐标不一致,所以通过unproject转化一下。
Tween.to(image, ActorAccessor.POSITION_XY, 1.0f)代表操作image对象移动。target(vector3.x, vector3.y)代表移动的目标。
ease(Bounce.OUT)声明了缓冲效果,具体的效果可以参考http://robertpenner.com/easing/easing_demo.html
start(tweenManager)启动管理器。
在render方法中添加
1 |
tweenManager.update(Gdx.graphics.getDeltaTime()); |
让管理器的时间更新。
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
package com.cnblogs.htynkn; import aurelienribon.tweenengine.Tween; import aurelienribon.tweenengine.TweenManager; import aurelienribon.tweenengine.equations.Bounce; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Image; public class App implements ApplicationListener, InputProcessor { Stage stage; private TweenManager tweenManager = new TweenManager(); Image image; @Override public void create() { stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true); TextureAtlas atlas = new TextureAtlas("packer/test.pack"); image = new Image(atlas.findRegion("news")); image.x = 20; image.y = 20; stage.addActor(image); Tween.registerAccessor(Image.class, new ActorAccessor()); InputMultiplexer multiplexer = new InputMultiplexer(); multiplexer.addProcessor(this); multiplexer.addProcessor(stage); Gdx.input.setInputProcessor(multiplexer); } @Override public void dispose() { } @Override public void render() { tweenManager.update(Gdx.graphics.getDeltaTime()); Gdx.gl.glClearColor(1, 1, 1, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); stage.act(Gdx.graphics.getDeltaTime()); stage.draw(); } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } @Override public boolean keyDown(int keycode) { // TODO Auto-generated method stub return false; } @Override public boolean keyUp(int keycode) { // TODO Auto-generated method stub return false; } @Override public boolean keyTyped(char character) { // TODO Auto-generated method stub return false; } @Override public boolean touchDown(int x, int y, int pointer, int button) { Vector3 vector3 = new Vector3(x, y, 0); stage.getCamera().unproject(vector3); Tween.to(image, ActorAccessor.POSITION_XY, 1.0f).ease(Bounce.OUT) .target(vector3.x, vector3.y).start(tweenManager); return false; } @Override public boolean touchUp(int x, int y, int pointer, int button) { // TODO Auto-generated method stub return false; } @Override public boolean touchDragged(int x, int y, int pointer) { // TODO Auto-generated method stub return false; } @Override public boolean touchMoved(int x, int y) { // TODO Auto-generated method stub return false; } @Override public boolean scrolled(int amount) { // TODO Auto-generated method stub return false; } } |
因为是动画效果,这里就不贴出了,文章末尾会有一个小视频的。
上面只是一个简单的移动效果,但就动画而言这个显然是不够的。如果希望实现一个渐渐显示的效果怎么办?
还是想想TweenAccessor接口,只要float类型的值就行了。所以同样的我们可以实现修改透明程度、大小等等实现更多的效果。
我最终选用了六种效果:
1 2 3 4 5 6 |
public static final int POS_XY = 1; public static final int CPOS_XY = 2; public static final int SCALE_XY = 3; public static final int ROTATION = 4; public static final int OPACITY = 5; public static final int COLOR = 6; |
实现修改X和Y值,修改X和Y值(包括对象自身大小),修改缩放,修改旋转,修改透明,修改颜色。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
package com.cnblogs.htynkn; import aurelienribon.tweenengine.TweenAccessor; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.scenes.scene2d.Actor; public class ActorAccessor implements TweenAccessor { public static final int POS_XY = 1; public static final int CPOS_XY = 2; public static final int SCALE_XY = 3; public static final int ROTATION = 4; public static final int OPACITY = 5; public static final int TINT = 6; @Override public int getValues(Actor target, int tweenType, float[] returnValues) { switch (tweenType) { case POS_XY: returnValues[0] = target.x; returnValues[1] = target.y; return 2; case CPOS_XY: returnValues[0] = target.x + target.width / 2; returnValues[1] = target.y + target.height / 2; return 2; case SCALE_XY: returnValues[0] = target.scaleX; returnValues[1] = target.scaleY; return 2; case ROTATION: returnValues[0] = target.rotation; return 1; case OPACITY: returnValues[0] = target.color.a; return 1; case TINT: returnValues[0] = target.color.r; returnValues[1] = target.color.g; returnValues[2] = target.color.b; return 3; default: assert false; return -1; } } @Override public void setValues(Actor target, int tweenType, float[] newValues) { switch (tweenType) { case POS_XY: target.x = newValues[0]; target.y = newValues[1]; break; case CPOS_XY: target.x = newValues[0] - target.width / 2; target.y = newValues[1] - target.height / 2; break; case SCALE_XY: target.scaleX = newValues[0]; target.scaleY = newValues[1]; break; case ROTATION: target.rotation = newValues[0]; break; case OPACITY: Color c = target.color; c.set(c.r, c.g, c.b, newValues[0]); target.color = c; break; case TINT: c = target.color; c.set(newValues[0], newValues[1], newValues[2], c.a); target.color = c; break; default: assert false; } } } |
因为Actor中的color是final,所以不能修改,自己改一下源代码吧。
TimeLine是Universal Tween Engine中的一大利器,可以实现平行和顺序动画。
比如
1 2 3 4 5 6 |
Timeline.createSequence() .beginSequence() .push(Tween.to(image, ActorAccessor.POS_XY, 1.0f).target(100, 100)) .push(Tween.to(image, ActorAccessor.POS_XY, 1.0f).target(200, 20)).start(tweenManager); |
就表示先移动到100,100处在移动到200,20处。
再比如
1 2 3 4 5 6 7 |
Timeline.createParallel() .beginParallel() .push(Tween.to(image, ActorAccessor.CPOS_XY, 1.0f).target( vector3.x, vector3.y)) .push(Tween.to(image, ActorAccessor.ROTATION, 1.0f).target(360)) .push(Tween.to(image, ActorAccessor.SCALE_XY, 1.0f).target( 1.5f, 1.5f)).end().start(tweenManager); |
实现的就是一般移动一般旋转和放大的效果。