libgdx开发指南
这是“使用LibGdx构建游戏”系列的第三部分。 请确保您阅读了之前的文章,以构建本文的上下文。
在上一篇文章中,我们对Bob的动作进行了动画处理,但是该动作非常机器人化。 在本文中,我将尝试使鲍勃跳起来,并以更自然的方式移动。 我将通过一些物理学来实现这一点。 我还将稍微清理一下代码,并修复一些在前几篇文章中提到的问题。
跳跃–物理学
跳跃是一个实体(在我们的例子中是鲍勃)执行的动作,该实体将自己推向空中并着陆回到地面(基板)上。 这是通过向地面施加足够大的力来抵抗地面(重力)所施加的力。 识别我们拥有的对象:
- 鲍勃 –实体
- 地面 –基材
- 重力(G) –作用在世界上所有实体上的恒定重力
为了实现逼真的跳跃,我们只需要应用牛顿运动定律即可 。 如果我们向鲍勃和世界添加必要的属性(质量,重力,摩擦力),我们将拥有实现跳跃所需的一切。 查看下图并检查其组件。 左侧是当我们按住“跳转”按钮时,右侧是Bob在跳跃中。
让我们研究鲍勃在不同状态下的力量。
1. Bob 闲置在地( 接地 )。 在这种情况下,只有重力作用于鲍勃。 这意味着鲍勃被恒力拉下。 计算将物体拉向地面的力的公式为 其中m是质量 (虽然不是重量,但应考虑重量), a是加速度 。 我们正在简化事情,并认为鲍勃的质量为1,因此力等于加速度。 如果我们对物体施加恒定的力,其速度将无限增大。 计算物体速度的公式为: 哪里
- v –是最终速度
- u –是初始速度( t秒钟前的速度)
- a –是加速度
- t –自施加加速度以来经过的时间
如果我们将鲍勃放在空中,这意味着起始速度为0。如果我们认为地球的重力加速度为9.8,鲍勃的重量(质量)为1,那么很容易计算一秒钟后他的下落速度。
因此,在一秒钟的自由落体运动后,鲍勃从0加速到9.8米/秒,即35.28公里/小时(21.92英里)。 那非常快。 如果我们想知道一秒钟后他的速度,我们将使用相同的公式。
那是70.56公里/小时或43.84英里/小时,非常快。 我们已经看到加速度是线性的,并且在恒定的力作用下,物体将无限加速。 这是在没有摩擦和阻力的理想环境中。 由于空气具有摩擦力,并且还会对下落的物体施加一定的力,因此下落的物体将在某个点达到最终速度 ,超过该速度则不会加速。 这取决于许多我们将忽略的因素。 一旦掉落的物体撞到地面,它将停止,重力不再影响它。 但是,这是不正确的,但是我们不是在构建完整的物理模拟器,而是在鲍勃以终极速度撞击地面时不会被杀死的游戏。 重新配置它,我们检查Bob是否撞到地面,如果是,那么我们将忽略重力。
让鲍勃跳
要使鲍勃跳起来,我们需要一个指向重力(向上)的力,该力不仅会抵消重力的作用,而且还会将鲍勃推向空中。 如果检查该图,该力( F )会更强(其大小或长度比重力矢量大得多)。 通过将两个向量(G和F)相加,我们将得出作用于Bob的最终力。
为简化起见,我们可以摆脱向量,仅使用其Y分量。
在地球上, 。 因为它指向下方,所以实际上 。 当鲍勃跳下时,他所做的不过是产生足够的力以产生足够的加速度,从而使他在重力( G )带回到地面之前达到高度( h )。 因为鲍勃是一个像我们一样的人,他一旦空降就无法保持加速度,至少没有喷气背包。 为了模拟这一点,我们可以在按下“跳转”键时产生巨大的力量。 通过应用上述公式,初始速度将足够高,因此即使重力作用在Bob上,他仍将爬升至开始自由下落序列的点。 如果实施此方法,我们将获得一个非常逼真的外观跳跃。
如果我们仔细检查原始的《星际守卫》游戏,英雄可以根据我们按下跳转按钮的时间而跳到不同的高度。 如果只要按住跳键并在一定时间后将其切断,就可以轻松解决此问题,请确保鲍勃不会开始飞行。
实施跳跃
我认为这已经足够了,让我们看看如何实现跳跃。 我们还将做一些整理工作,并重新组织代码。 我想隔离跳跃和运动,所以我将忽略世界其他地方。 要查看代码中已修改的内容,请向下滚动至“ 重构”部分。
打开BobController.java
。 这是旧的WorldController.java
但已重命名。 因为我们用它控制鲍勃,所以这很有意义。
public class BobController {
enum Keys {
LEFT, RIGHT, JUMP, FIRE
}
private static final long LONG_JUMP_PRESS = 150l;
private static final float ACCELERATION = 20f;
private static final float GRAVITY = -20f;
private static final float MAX_JUMP_SPEED = 7f;
private static final float DAMP = 0.90f;
private static final float MAX_VEL = 4f;
// these are temporary
private static final float WIDTH = 10f;
private World world;
private Bob bob;
private long jumpPressedTime;
private boolean jumpingPressed;
// ... code omitted ... //
public void jumpReleased() {
keys.get(keys.put(Keys.JUMP, false));
jumpingPressed = false;
}
// ... code omitted ... //
/** The main update method **/
public void update(float delta) {
processInput();
bob.getAcceleration().y = GRAVITY;
bob.getAcceleration().mul(delta);
bob.getVelocity().add(bob.getAcceleration().x, bob.getAcceleration().y);
if (bob.getAcceleration().x == 0) bob.getVelocity().x *= DAMP;
if (bob.getVelocity().x > MAX_VEL) {
bob.getVelocity().x = MAX_VEL;
}
if (bob.getVelocity().x < -MAX_VEL) {
bob.getVelocity().x = -MAX_VEL;
}
bob.update(delta);
if (bob.getPosition().y < 0) {
bob.getPosition().y = 0f;
bob.setPosition(bob.getPosition());
if (bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
}
if (bob.getPosition().x < 0) {
bob.getPosition().x = 0;
bob.setPosition(bob.getPosition());
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
}
if (bob.getPosition().x > WIDTH - bob.getBounds().width ) {
bob.getPosition().x = WIDTH - bob.getBounds().width;
bob.setPosition(bob.getPosition());
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
}
}
/** Change Bob's state and parameters based on input controls **/
private boolean processInput() {
if (keys.get(Keys.JUMP)) {
if (!bob.getState().equals(State.JUMPING)) {
jumpingPressed = true;
jumpPressedTime = System.currentTimeMillis();
bob.setState(State.JUMPING);
bob.getVelocity().y = MAX_JUMP_SPEED;
} else {
if (jumpingPressed && ((System.currentTimeMillis() - jumpPressedTime) >= LONG_JUMP_PRESS)) {
jumpingPressed = false;
} else {
if (jumpingPressed) {
bob.getVelocity().y = MAX_JUMP_SPEED;
}
}
}
}
if (keys.get(Keys.LEFT)) {
// left is pressed
bob.setFacingLeft(true);
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.WALKING);
}
bob.getAcceleration().x = -ACCELERATION;
} else if (keys.get(Keys.RIGHT)) {
// left is pressed
bob.setFacingLeft(false);
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.WALKING);
}
bob.getAcceleration().x = ACCELERATION;
} else {
if (!bob.getState().equals(State.JUMPING)) {
bob.setState(State.IDLE);
}
bob.getAcceleration().x = 0;
}
return false;
}
}
花一些时间来分析我们添加到该类中的内容。 解释了以下几行: #07 –#12 –包含影响世界和鲍勃的值的常量
- LONG_JUMP_PRESS –截断施加于跳跃的推力之前的时间(以毫秒为单位)。 请记住,我们正在跳高,并且玩家按下按钮的时间越长,鲍勃跳得越高。 为了防止飞行,我们将在150毫秒后切断跳跃推进。
- 加速 –实际上用于步行/跑步。 它与跳跃的原理完全相同,但在水平X轴上
- 重力 -这是重力加速度(G指向下方的图中)
- MAX_JUMP_SPEED –这是跳跃时我们永远不会超过的最终速度
- DAMP –当Bob停下时,这可以使运动平稳。 他不会突然停止的。 稍后再详细介绍,忽略它
- MAX_VEL –与MAX_JUMP_SPEED相同,但用于水平轴移动
#15 –这是一个临时常数,是世界单位的世界范围。 用于限制Bob在屏幕上的移动
#19 – jumpPressedTime是用于累积按下跳转按钮的时间的变量
#20 –一个布尔值,如果按下跳转按钮,则为true #26 – jumpReleased()
必须将jumpingReleased
变量设置为false。 它只是一个简单的状态变量。 按照主要为我们完成大部分工作的主要更新方法。 #32 –照常调用processInput
来检查是否按下了任何键 移至processInput
#71 –检查是否按下JUMP按钮 #72 –#76 –如果Bob不在JUMPING
状态(意味着他在地面上),则开始跳跃。 鲍勃已设置为跳跃状态,可以起飞了。 我们在此处作弊,而不是施加向上的力,而是将Bob的垂直速度设置为他可以跳跃的最大速度( 第76行)。 我们还存储启动跳转时的时间(以毫秒为单位)。 #77 –#85 –只要鲍勃在空中,就会执行此操作。 万一我们仍然按下跳转按钮,我们检查自跳转开始以来经过的时间是否大于我们设置的阈值,如果我们仍处于截止时间(当前为150ms),则我们保持Bob的垂直速度。 忽略#87-107行,因为它们是水平行走。 回到update
方法,我们有: #34 –鲍勃的加速度设置为“重力”。 这是因为重力是一个常数,我们从这里开始: #35 –我们计算出该周期所花费时间的加速度。 我们的初始值以单位/秒为单位,因此我们需要相应地调整值。 如果我们每秒更新60次,则增量将为1/60。 所有这些都由libgdx为您处理。 #36 – Bob的当前速度随着他在两个轴上的加速度而更新。 请记住,我们正在处理欧几里得空间中的向量。 #37 –这将使Bob的停顿变得顺畅。 如果我们在X轴上没有加速度,则每个循环将其速度降低10%。 一秒钟有很多周期,Bobo会很快但非常平稳地停止。 #38 –#43 –确保Bob不会超过其最大允许速度(终端速度)。 这再次捍卫了这样的规律:如果恒力作用在物体上,物体将无限加速。 #45 –调用Bob的update
方法,该方法除了根据Bob的速度来更新Bob的位置外没有其他作用。 #46 –#66 –这是非常基本的碰撞检测,可防止Bob离开屏幕。 我们只需检查Bob的位置是否在屏幕之外(使用世界坐标),如果是,则只需将Bob放回边缘即可。 值得注意的是,每当鲍勃跌落地面或到达世界边缘(屏幕)时,我们会将他的状态设置为Idle
。 这使我们能够再次跳跃。
如果我们通过上述更改运行该应用程序,则必须具有以下效果:
客房整理–重构
我们注意到,在生成的应用程序中,没有图块,并且Bob并不仅限于屏幕边缘。 当鲍勃在空中时,也有不同的图像。 他跳跃时一幅图像,跌倒时一幅图像。 我们做了以下工作:
- 更名
WorldController
到BobController
。 因为我们用它控制鲍勃,所以这很有意义。 - 在
WorldRenderer'
的render()
方法中注释了drawBlocks()
。 我们现在不渲染图块,因为我们忽略了它们。 - 加入
setDebug()
方法将WorldRendered
和在支撑肘节功能GameScreen.java
。 现在可以通过在桌面模式下按键盘上的D来切换调试渲染。 - WorldRenderer具有新的纹理区域,以表示跳跃和下降的鲍勃。 但是,我们仍然仅保持一种状态。 通过检查Bob的垂直速度(在Y轴上),世界渲染器如何知道何时显示。 如果为正,则鲍勃在跳;如果为负,则鲍勃在跌。
public class WorldRenderer { // ... omitted ... // private TextureRegion bobJumpLeft; private TextureRegion bobFallLeft; private TextureRegion bobJumpRight; private TextureRegion bobFallRight; private void loadTextures() { TextureAtlas atlas = new TextureAtlas(Gdx.files.internal('images/textures/textures.pack')); // ... omitted ... // bobJumpLeft = atlas.findRegion('bob-up'); bobJumpRight = new TextureRegion(bobJumpLeft); bobJumpRight.flip(true, false); bobFallLeft = atlas.findRegion('bob-down'); bobFallRight = new TextureRegion(bobFallLeft); bobFallRight.flip(true, false); } private void drawBob() { Bob bob = world.getBob(); bobFrame = bob.isFacingLeft() ? bobIdleLeft : bobIdleRight; if(bob.getState().equals(State.WALKING)) { bobFrame = bob.isFacingLeft() ? walkLeftAnimation.getKeyFrame(bob.getStateTime(), true) : walkRightAnimation.getKeyFrame(bob.getStateTime(), true); } else if (bob.getState().equals(State.JUMPING)) { if (bob.getVelocity().y > 0) { bobFrame = bob.isFacingLeft() ? bobJumpLeft : bobJumpRight; } else { bobFrame = bob.isFacingLeft() ? bobFallLeft : bobFallRight; } } spriteBatch.draw(bobFrame, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY); } }
上面的代码摘录显示了重要的补充。
#5-#8 –用于跳跃的新纹理区域。 我们需要一个左,一个右。
#15-#20 –准备资产。 我们需要向项目添加更多的png图像。 检查star-assault-android/images/
目录,在那里您将看到bob-down.png
和bob-up.png
。 这些已添加,并且还使用ImagePacker2
工具重新创建了纹理图集。 有关如何创建它,请参见第2部分 。 #28-#33 –是我们确定Bob在空中时要绘制哪个纹理区域的部分。 -
Bob.java
有一些错误修复。 现在,边界框的位置与bob相同,更新将解决此问题。 同样,setPosition
方法更新边界框的位置。 这对drawDebug()
内部的WorldRenderer
drawDebug()
方法产生了影响。 现在,我们不必担心根据图块的位置来计算边界框,因为这些框现在具有与实体相同的位置。 这是一个愚蠢的错误,让我溜进去。在进行碰撞检测时,这非常重要。
该列表几乎总结了所有更改,但是应该很容易理解。 该项目的源代码可以在这里找到: https : //github.com/obviam/star-assault您需要检出分支part3。 要使用git进行检查,请执行以下操作: git clone -b part3 git@github.com:obviam/star-assault.git
您还可以将其下载为zip文件 。
参考: 使用libgdx进行Android游戏开发-跳跃,引力和改进的动作,第3部分,来自我们的JCG合作伙伴 Impaler,网址为Against the Grain博客。
libgdx开发指南