87291547 2014-03-26
昨天在编译以前的代码的时候发现Stage类发生了变化,编译出错。Stage开始使用viewpoint来初始化。
viewpoint是什么?
现在用个简单的方法来理解。
举起双手,两手食指和拇指圈一个类似的矩形,然后我们通过手指的这个矩形看我们的电脑屏幕。看到一小块地方是吗。
眼睛-->手指组成的矩形-->屏幕
眼睛是一个点,手指组成的矩形稍微大点,屏幕最大,这是不是一个类似金字塔样的锥形视角。
我们把手指组成的这个小矩形称为viewpoint,实际上屏幕也是个viewpoint。
通过手指的矩形看到的图像实际大小肯定远大于手指组成的矩形的大小。距离越远看到的越广,物体也越小。距离越近看到的越少,物体也就越大。我们把手指组成的矩阵看到的这部分投影到手机屏幕上,这就是手机屏幕显示的内容了。
然后介绍下正交相机,它就是我们上面说的那种情况的一种特殊情况,眼睛的视线不是随着距离增加不断变大的一个锥形视野,而是平行的。
可以想象一下我们的太阳,无限远。它发出的光我们近似认为是平行的。也就是说。距离再远视野也不会变大,物体也不会变小。所以正交相机一般用在二维游戏中。
想想我前面做的那些工作,把屏幕划分成10*6的方格,然后计算每个方格的像素大小。我突然发现,使用正交相机也是可以实现的
我们直接把10*6的正交相机的viewpoint投射到屏幕上不就是可以了。还省的计算了。
修改前面的GirlView代码:
public GirlView(World<GirlActor> world) { assetManager = new AssetManager(); assetManager.load("data/image/girlRun.atlas", TextureAtlas.class); this.world = world; girlsActor = world.getActors("girl"); spriteBatch = new SpriteBatch(); currentFrame = new TextureRegion(); girlRunFrameLeft = new TextureRegion[16]; girlRunFrameRight = new TextureRegion[16]; this.camera = new OrthographicCamera(world.getWorldWidth(), world.getWorldHeight()); this.camera.position.set(world.getWorldWidth() / 2, world.getWorldHeight() / 2, 0); this.camera.update(); isload = false; }
很简单,定义一个正交相机,然后初始化。投影到屏幕的矩阵。
设置为10*6大小,位置在中间。最后那个0其实是三维中z轴。咱是二维的。
然后在spriteBatchbegin之前插入
spriteBatch.setProjectionMatrix(camera.combined);
Ok,搞定。渲染方法也不用根据像素进行相乘计算了。好happy。
spriteBatch.draw(currentFrame , girlsActor.getPosition().x , girlsActor.getPosition().y , girlsActor.getBounds().getWidth() , girlsActor.getBounds().getHeight());
其实这只是个简单的相机的应用,例如天天酷跑的背景的移动,人物的放大缩小,都可以使用相机实现。具体可参考官方API和这篇介绍相机的文章。还不理解的可以去看看。
http://blog.sina.com.cn/s/blog_940dd50a0101cpnb.html
在正式开始之前先复习下中学物理知识,真是苦逼,都忘光了。我发现除了基本的加减乘除,和一次元运算,别的基本都还给老师了。物理数学完全没印象了。这对游戏开发来说无疑是个大坑啊。和我一样的同学来以前复习中学知识。这都是为了模拟出比较真实的游戏感觉。
游戏中的匀加速运动:
初速度不为0的情况:Vt=Vo+a*t(Vt为末速度,Vo为初速度,*为乘号,a为加速度)
初速度为0:Vt=a*t
再看看游戏中的跳跃动作。起跳的时候我们可以给它一个初速度Vo,受重力影响,达到最高点后开始回落。这里我们只需要第一个公式就可以了,具体后边要用到的物理知识和向量几何知识只能边学边写了。
先修改GirlActor类。有两个地方需要修改。我们要增加一个加速度的属性。
还要修改下update方法
//演员加速度向量 private Vector2 acceleration = new Vector2();修改update
public void update(float deltaTime) { stateTime += deltaTime; //根据时间和速度来运算当前的演员位置信息。 //这里进行一次拷贝操作,不改变原有的velocity值 //因为在匀加速运动中,我们需要它作为初始速度值进行计算 position.add(velocity.cpy().scl(deltaTime)); }
多了个cpy,其实就是返回一个新的对象,不对原来对象进行修改。
原来我么直接使用velocity.scl(deltatime)
这会改变velocity的值。上面的公式中,我们每刷新一次屏幕,就要获取现在的速度做为初始速度Vo计算下次的Vt,下次的Vt作为下下次的Vo,所以为了不修改这个变量的值,使用cpy方法new个新的。
上篇我们使用枚举类型定义按键状态,后来我发现这么做不怎么合适。
因为普遍我们的输入不是单点输入的,可能会同时按下几个按键,所以我们略微变通下,让控制可以支持多按键。
修改GirlControl
package com.me.mygdxgame.control; import com.badlogic.gdx.Gdx; import com.me.mygdxgame.actor.GirlActor; import com.me.mygdxgame.actor.World; import java.util.HashMap; import java.util.Map; import static com.me.mygdxgame.actor.World.GRAVITY; /** * control层,演员的控制 */ public class GirlControl { private World<GirlActor> world; private GirlActor girl; /** * 枚举类型的按键 */ public enum KEY { LEFT, RIGTH, JUMP } /** * 定义个map来存放按键状态。这样就可以支持多按键了。 * 并且每个按键的状态只能存在一种,这样比较符合实际需要 */ private static Map<KEY, Boolean> keys = new HashMap<>(); static { keys.put(KEY.LEFT, false); keys.put(KEY.RIGTH, false); keys.put(KEY.JUMP, false); } //定义一组改变按键状态的方法 public void leftDown() { keys.put(KEY.LEFT, true); } public void leftUp() { keys.put(KEY.LEFT, false); } public void rightDown() { keys.put(KEY.RIGTH, true); } public void rigthUp() { keys.put(KEY.RIGTH, false); } public void jumpDown() { keys.put(KEY.JUMP, true); } public void jumpUp() { keys.put(KEY.JUMP, false); } /** * 构造器 * * @param world 游戏世界 */ public GirlControl(World<GirlActor> world) { this.world = world; girl = world.getActors("girl"); //设置演员受到的世界中的重力加速度 girl.getAcceleration().y = GRAVITY; } /** * 不断执行的方法 * 在MyGame的render方法中调用来不断判断和更新演员状态 */ public void update() { state(); if (girl.getState().equals(GirlActor.State.JUMPING)) { girl.getVelocity().add(girl.getAcceleration().cpy().scl(Gdx.graphics.getDeltaTime())); } //边界检查,是否超出屏幕右边。世界的宽度-人物纹理的宽度 //这里就体现出我们定义世界网格的好处了。我根本不需要知道屏幕纹理的具体大小。 if (girl.getPosition().x > world.getWorldWidth() - girl.getBounds().getWidth()) { girl.getPosition().x = world.getWorldWidth() - girl.getBounds().getWidth(); } //左边界判断,这个更简单些可以直接判断x是否小于0,也就是是否移动到原点的左边 if (girl.getPosition().x < girl.getBounds().x) { girl.getPosition().x = 0; } if (girl.getPosition().y < 0) { girl.getPosition().y = 0; if (girl.getState().equals(GirlActor.State.JUMPING)) { girl.getVelocity().y = 0; girl.setState(GirlActor.State.IDLE); } } if (girl.getPosition().y > world.getWorldHeight() - girl.getBounds().getHeight()) { girl.getPosition().y = world.getWorldHeight() - girl.getBounds().getHeight(); } } /** * 判断当前按下的按键状态 * 跳跃和左右方向是可以同时按下的 */ private void state() { if (keys.get(KEY.JUMP)) { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.setState(GirlActor.State.JUMPING); girl.getVelocity().y = girl.getSpeed_y(); } } if (keys.get(KEY.LEFT)) { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.setState(GirlActor.State.RUNING); girl.setFacingRight(false); girl.getVelocity().x = -girl.getSpeed_x(); } else { girl.setFacingRight(false); girl.getVelocity().x = -girl.getSpeed_x(); } } else if (keys.get(KEY.RIGTH)) { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.setState(GirlActor.State.RUNING); girl.setFacingRight(true); girl.getVelocity().x = girl.getSpeed_x(); } else { girl.setFacingRight(true); girl.getVelocity().x = girl.getSpeed_x(); } } else { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.setState(GirlActor.State.IDLE); girl.getVelocity().x = 0; } } } }
我在world中定义了一个静态的重力变量GRAVITY,设置为-18。
这样在上升中girlActor的初始速度privatefloatspeed_y=10f;会不断减少,然后变成负值,直到落回地面。然后我们判断演员的位置的Y的值是否小于0来判断是否落地。并改变演员状态为站立状态。
重点代码的含义。
girl.getVelocity().add(girl.getAcceleration().cpy().scl(Gdx.graphics.getDeltaTime()));
这里我们为了理解,我把这段代码拆分开
//设置演员受到的世界中的重力加速度,下面那段代码会改变y的值,所以每次必须重置为重力的值 girl.getAcceleration().y = GRAVITY; //求出每刷新一次的加速度,也就是每帧的速度相当于a*t girl.getAcceleration().scl(Gdx.graphics.getDeltaTime()); //把这个速度和人物的初始速度相加,也就是当前的末速度Vt=Vo+a*t //这个末速度会在下次屏幕刷新时当作初始速度进行计算,所以需要修改演员的update方法 girl.getVelocity().add(girl.getAcceleration().x, girl.getAcceleration().y);
这样就好理解了。我们这里也使用了cpy,所以acceleration的值不会因为运算发生变化,那么我们每次也不用重新赋值给它。这和演员类中的update方法中的修改是一样的道理。
处理下mygame的按键的操作。
@Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { //点击屏幕左半边就左移,右半边就右移 if (screenX < world.getWidth() / 2 && screenY > world.getHeight() / 2) { control.leftDown(); return false; } if (screenX > world.getWidth() / 2 && screenY > world.getHeight() / 2) { control.rightDown(); return false; } if (screenY < world.getHeight() / 2) { control.jumpDown(); return false; } return false; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { if (screenX < world.getWidth() / 2 && screenY > world.getHeight() / 2) { control.leftUp(); return false; } if (screenX > world.getWidth() / 2 && screenY > world.getHeight() / 2) { control.rigthUp(); return false; } if (screenY < world.getHeight() / 2) { control.jumpUp(); return false; } return false; }
点击屏幕上半部分就是跳起操作。
需要注意的就一点。screenX,screenY是以屏幕左上角为原点的,这和android的坐标系相同。
源码/** Called when the screen was touched or a mouse button was pressed. The button parameter will be {@link Buttons#LEFT} on * Android and iOS. * @param screenX The x coordinate, origin is in the upper left corner * @param screenY The y coordinate, origin is in the upper left corner * @param pointer the pointer for the event. * @param button the button * @return whether the input was processed */ public boolean touchDown (int screenX, int screenY, int pointer, int button);
指明左上角开始的。
接下来实现下演员的飞行,其实这里不能算飞行,相当于扔东西的感觉,把演员抛出去。抛物线运动,实现非常简单。
前几篇有提到使用屏幕的手势操作,因为当时用不到所以现在我们实现下手势操作。
修改Mygame实现GestureDetector.GestureListener
public class MyGame implements ApplicationListener, InputProcessor, GestureDetector.GestureListener {
修改下输入监听的注册
@Override public void create() { world = new World<>(); GirlActor girlsActor = new GirlActor(new Vector2(0, 0), new Vector2(2, 2)); world.addActors("girl", girlsActor); girlView = new GirlView(world); control = new GirlControl(world); //游戏输入进程注册当前界面 InputMultiplexer multiplexer = new InputMultiplexer(); multiplexer.addProcessor(this); multiplexer.addProcessor(new GestureDetector(this)); Gdx.input.setInputProcessor(multiplexer); }
这里使用到了InputMultiplexer,很好理解,可以监听多个进程的输入事件。
因为GestureDetector没有touchUp这些操作,所以我们保留InputProcessor
并且同时监听这两个输入进程。
接下来实现GestureDetector.GestureListener的方法,我们现在只实现它的fling方法
@Override public boolean fling(float velocityX, float velocityY, int button) { //反转下Y坐标以左下角开始 Vector2 vector2_fly = new Vector2(velocityX / world.getPixelsX() , (world.getHeight() - velocityY) / world.getPixelsY()); control.flyDown(vector2_fly); return false; }
顾名思义:滑动操作,它实际上返回的是一个速度向量,也就是我们滑动的速度和方向的这样一个向量。想一想,把它和演员的位置信息的向量进行加运算就可以了。
修改下控制类
package com.me.mygdxgame.control; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Vector2; import com.me.mygdxgame.actor.GirlActor; import com.me.mygdxgame.actor.World; import java.util.HashMap; import java.util.Map; import static com.me.mygdxgame.actor.World.GRAVITY; /** * control层,演员的控制 */ public class GirlControl { private World<GirlActor> world; private GirlActor girl; private Vector2 vector2_fly; /** * 枚举类型的按键 */ public enum KEY { LEFT, RIGTH, JUMP, FLY } /** * 定义个map来存放按键状态。这样就可以支持多按键了。 * 并且每个按键的状态只能存在一种,这样比较符合实际需要 */ private static Map<KEY, Boolean> keys = new HashMap<>(); static { keys.put(KEY.LEFT, false); keys.put(KEY.RIGTH, false); keys.put(KEY.JUMP, false); keys.put(KEY.FLY, false); } //定义一组改变按键状态的方法 public void leftDown() { keys.put(KEY.LEFT, true); } public void leftUp() { keys.put(KEY.LEFT, false); } public void rightDown() { keys.put(KEY.RIGTH, true); } public void rigthUp() { keys.put(KEY.RIGTH, false); } public void jumpDown() { keys.put(KEY.JUMP, true); } public void jumpUp() { keys.put(KEY.JUMP, false); } public void flyDown(Vector2 vector2_fly) { this.vector2_fly = vector2_fly; keys.put(KEY.FLY, true); } public void flyUp() { keys.put(KEY.FLY, false); } /** * 构造器 * * @param world 游戏世界 */ public GirlControl(World<GirlActor> world) { this.world = world; girl = world.getActors("girl"); //设置演员受到的世界中的重力加速度 girl.getAcceleration().y = GRAVITY; } /** * 不断执行的方法 * 在MyGame的render方法中调用来不断判断和更新演员状态 */ public void update() { state(); if (girl.getState().equals(GirlActor.State.JUMPING)) { girl.getVelocity().add(girl.getAcceleration().cpy().scl(Gdx.graphics.getDeltaTime())); } //边界检查,是否超出屏幕右边。世界的宽度-人物纹理的宽度 //这里就体现出我们定义世界网格的好处了。我根本不需要知道屏幕纹理的具体大小。 if (girl.getPosition().x > world.getWorldWidth() - girl.getBounds().getWidth()) { girl.getPosition().x = world.getWorldWidth() - girl.getBounds().getWidth(); } //左边界判断,这个更简单些可以直接判断x是否小于0,也就是是否移动到原点的左边 if (girl.getPosition().x < girl.getBounds().x) { girl.getPosition().x = 0; } if (girl.getPosition().y < 0) { girl.getPosition().y = 0; if (girl.getState().equals(GirlActor.State.JUMPING)) { girl.getVelocity().y = 0; girl.setState(GirlActor.State.IDLE); } } if (girl.getPosition().y > world.getWorldHeight() - girl.getBounds().getHeight()) { girl.getPosition().y = world.getWorldHeight() - girl.getBounds().getHeight(); } } /** * 判断当前按下的按键状态 * 跳跃和左右方向是可以同时按下的 */ private void state() { if (keys.get(KEY.JUMP)) { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.setState(GirlActor.State.JUMPING); girl.getVelocity().y = girl.getSpeed_y(); } } if (keys.get(KEY.FLY)) { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.getVelocity().add(vector2_fly); girl.setState(GirlActor.State.JUMPING); //直接释放按钮 flyUp(); } } if (keys.get(KEY.LEFT)) { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.setState(GirlActor.State.RUNING); girl.setFacingRight(false); girl.getVelocity().x = -girl.getSpeed_x(); } else { girl.setFacingRight(false); girl.getVelocity().x = -girl.getSpeed_x(); } } else if (keys.get(KEY.RIGTH)) { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.setState(GirlActor.State.RUNING); girl.setFacingRight(true); girl.getVelocity().x = girl.getSpeed_x(); } else { girl.setFacingRight(true); girl.getVelocity().x = girl.getSpeed_x(); } } else { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.setState(GirlActor.State.IDLE); girl.getVelocity().x = 0; } } } }
增加了滑动的按键状态。
if (keys.get(KEY.FLY)) { if (!girl.getState().equals(GirlActor.State.JUMPING)) { girl.getVelocity().add(vector2_fly); girl.setState(GirlActor.State.JUMPING); //直接释放按钮 flyUp(); } }
这里为了方便简单,直接设置为跳起状态,让它受跳动作重力影响。当然可以自己定义一个飞行的状态。
好了,轻轻滑动下屏幕,演员被扔飞了。别滑动太大,因为我们判断按键状态是把屏幕分成了左右和上下几部分,小范围的滑动就可以了。如果从左边滑到右边演员状态会出现异常。
源码地址:http://pan.baidu.com/s/1mgM9QdU