在上一章我们介绍了如何管理和利用素材,但是我们注意到,这些素材都是零散的,比如岩石的左部等,这一章,我们将利用这些零件拼合成完整的游戏对象。
回顾最开始的设计类图,注意Level类和所有Level中的Object,看看它们的继承关系。
首先第一步就是创建所有对象的基类AbstractGameObject.
它应该包含所有公共的属性和功能。
package com.packtpub.libgdx.canyonbunny.game.objects; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Vector2; public abstract class AbstractGameObject { public Vector2 position; public Vector2 dimension; public Vector2 origin; public Vector2 scale; public float rotation; public AbstractGameObject() { position = new Vector2(); dimension = new Vector2(1, 1); origin = new Vector2(); scale = new Vector2(1, 1); rotation = 0; } public void update(float deltaTime) { } public abstract void render(SpriteBatch batch); }
这个抽象类包含很多基本的属性,update和render。update更新自己,render画自己。很多人虽然知道OOP,但是并没有在思维中形成OO的观念。对象的划分以及对象的行为(或者说对象的权责)是否分明,都能看出你编程的功力。
render是abstract的,这就限定了所有的子类需要自己去实现它。
我们先看Rock,Rock是由3个部分组成的,左中右,中间的部分是能够重复的。像这样
那么它的实现类似于:
package com.packtpub.libgdx.canyonbunny.game.objects; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.packtpub.libgdx.canyonbunny.game.Assets; public class Rock extends AbstractGameObject { private TextureRegion regEdge; private TextureRegion regMiddle; private int length; public Rock() { init(); } private void init() { dimension.set(1, 1.5f); regEdge = Assets.instance.rock.edge; regMiddle = Assets.instance.rock.middle; // Start length of this rock setLength(1); } public void setLength(int length) { this.length = length; } public void increaseLength(int amount) { setLength(length + amount); } @Override public void render(SpriteBatch batch) { TextureRegion reg = null; float relX = 0; float relY = 0; // Draw left edge reg = regEdge; relX -= dimension.x / 4; batch.draw(reg.getTexture(), position.x + relX, position.y + relY, origin.x, origin.y, dimension.x / 4, dimension.y, scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(), reg.getRegionHeight(), false, false); // Draw middle relX = 0; reg = regMiddle; for (int i = 0; i < length; i++) { batch.draw(reg.getTexture(), position.x + relX, position.y + relY, origin.x, origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(), reg.getRegionHeight(), false, false); relX += dimension.x; } // Draw right edge reg = regEdge; batch.draw(reg.getTexture(), position.x + relX, position.y + relY, origin.x + dimension.x / 8, origin.y, dimension.x / 4, dimension.y, scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(), reg.getRegionHeight(), true, false); } }
我们使用了一个length来表示rock的长度,就是中间可以重复的部分。
接下来是山,有人可能会奇怪,为什么用白色的山呢?用白色是为了方便着色的。Mountains类似于:
package com.packtpub.libgdx.canyonbunny.game.objects; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.MathUtils; import com.packtpub.libgdx.canyonbunny.game.Assets; public class Mountains extends AbstractGameObject { private TextureRegion regMountainLeft; private TextureRegion regMountainRight; private int length; public Mountains(int length) { this.length = length; init(); } private void init() { dimension.set(10, 2); regMountainLeft = Assets.instance.levelDecoration.mountainLeft; regMountainRight = Assets.instance.levelDecoration.mountainRight; // shift mountain and extend length origin.x = -dimension.x * 2; length += dimension.x * 2; } private void drawMountain(SpriteBatch batch, float offsetX, float offsetY, float tintColor) { TextureRegion reg = null; batch.setColor(tintColor, tintColor, tintColor, 1); float xRel = dimension.x * offsetX; float yRel = dimension.y * offsetY; // mountains span the whole level int mountainLength = 0; mountainLength += MathUtils.ceil(length / (2 * dimension.x)); mountainLength += MathUtils.ceil(0.5f + offsetX); for (int i = 0; i < mountainLength; i++) { // mountain left reg = regMountainLeft; batch.draw(reg.getTexture(), origin.x + xRel, position.y + origin.y + yRel, 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 right reg = regMountainRight; batch.draw(reg.getTexture(), origin.x + xRel, position.y + origin.y + yRel, 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; } // reset color to white batch.setColor(1, 1, 1, 1); } @Override public void render(SpriteBatch batch) { // distant mountains (dark gray) drawMountain(batch, 0.5f, 0.5f, 0.5f); // distant mountains (gray) drawMountain(batch, 0.25f, 0.25f, 0.7f); // distant mountains (light gray) drawMountain(batch, 0.0f, 0.0f, 0.9f); } }
这个跟Rock很像,也用了一个length来存储需要重复的次数。在render里调用了3个不同的drawMountain,这样大大的简化了画3层山的代码。
接下来是水面,这个类要比前面的简单多了,它只需要沿着x轴拉伸造成一直存在的假象就行了。(还有很多其他的方法可以达到这个目的:比如用一个摄像机视口一样宽的图片,跟着摄像机一起移动。不过这样你需要小心摄像机可能垂直移动)
package com.packtpub.libgdx.canyonbunny.game.objects; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.packtpub.libgdx.canyonbunny.game.Assets; public class WaterOverlay extends AbstractGameObject { private TextureRegion regWaterOverlay; private float length; public WaterOverlay(float length) { this.length = length; init(); } private void init() { dimension.set(length * 10, 3); regWaterOverlay = Assets.instance.levelDecoration.waterOverlay; origin.x = -dimension.x / 2; } @Override public void render(SpriteBatch batch) { TextureRegion reg = null; reg = regWaterOverlay; batch.draw(reg.getTexture(), position.x + origin.x, position.y + origin.y, origin.x, origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(), reg.getRegionHeight(), false, false); } }
接下来是云彩,云彩的分布由长度和间距两个参数决定。
package com.packtpub.libgdx.canyonbunny.game.objects; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.packtpub.libgdx.canyonbunny.game.Assets; public class Clouds extends AbstractGameObject { private float length; private Array<TextureRegion> regClouds; private Array<Cloud> clouds; private class Cloud extends AbstractGameObject { private TextureRegion regCloud; public Cloud() { } public void setRegion(TextureRegion region) { regCloud = region; } @Override public void render(SpriteBatch batch) { TextureRegion reg = regCloud; batch.draw(reg.getTexture(), position.x + origin.x, position.y + origin.y, origin.x, origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation, reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(), reg.getRegionHeight(), false, false); } } public Clouds(float length) { this.length = length; init(); } private void init() { dimension.set(3.0f, 1.5f); regClouds = new Array<TextureRegion>(); regClouds.add(Assets.instance.levelDecoration.cloud01); regClouds.add(Assets.instance.levelDecoration.cloud02); regClouds.add(Assets.instance.levelDecoration.cloud03); int distFac = 5; int numClouds = (int) (length / distFac); clouds = new Array<Cloud>(2 * numClouds); for (int i = 0; i < numClouds; i++) { Cloud cloud = spawnCloud(); cloud.position.x = i * distFac; clouds.add(cloud); } } 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; // position after end of level pos.y += 1.75; // base position // random additional position pos.y += MathUtils.random(0.0f, 0.2f) * (MathUtils.randomBoolean() ? 1 : -1); cloud.position.set(pos); return cloud; } @Override public void render(SpriteBatch batch) { for (Cloud cloud : clouds) cloud.render(batch); } }