[libGDX游戏开发教程]使用Libgdx进行游戏开发(5)-关卡加载

来源:互联网 发布:ubuntu修改系统语言 编辑:程序博客网 时间:2024/06/08 04:16

声明:《使用Libgdx进行游戏开发》是一个系列,文章的英文原文是《LearningLibgdx Game Development》,大家请周知。所有文章连接在这里


[libgdx游戏开发教程]使用Libgdx进行游戏开发(1)-游戏设计

[libgdx游戏开发教程]使用Libgdx进行游戏开发(2)-游戏框架搭建

[libgdx游戏开发教程]使用Libgdx进行游戏开发(3)-给游戏添加一些控制功能

[libgdx游戏开发教程]使用Libgdx进行游戏开发(4)-素材管理

[libGDX游戏开发教程]使用Libgdx进行游戏开发(5)-关卡加载

[libgdx游戏开发教程]使用Libgdx进行游戏开发(6)-添加主角和道具

[libgdx游戏开发教程]使用Libgdx进行游戏开发(7)-屏幕布局的最佳实践

[libgdx游戏开发教程]使用Libgdx进行游戏开发(8)-没有美工的程序员,能够依赖的还有粒子系统

[libgdx游戏开发教程]使用Libgdx进行游戏开发(9)-场景过渡

[libgdx游戏开发教程]使用Libgdx进行游戏开发(10)-音乐音效不求人,程序员也可以DIY

[libgdx游戏开发教程]使用Libgdx进行游戏开发(11)-高级编程技巧

[libGDX游戏开发教程]使用libGDX进行游戏开发(12)-动画


本章素材:http://files.cnblogs.com/mignet/assets.zip

在上一章我们介绍了如何管理和利用素材,但是我们注意到,这些素材都是零散的,比如岩石的左部等,这一章,我们将利用这些零件拼合成完整的游戏对象。

回顾最开始的设计类图,注意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);    }}

Clouds定义了内部类Cloud,Clouds是包含云彩的容器。

关卡加载

我们使用png图片来保存关卡数据:1像素代表1个对象,每一种不同的对象都有一种唯一的RGBA颜色值。我们使用纯色,不用透明色,那么一个RGBA就是32位,就是4字节。刚好java的int也是32位,用来存颜色刚刚好。

我们需要读取并解析它们:

package com.packtpub.libgdx.canyonbunny.game;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.graphics.Pixmap;import com.badlogic.gdx.graphics.g2d.SpriteBatch;import com.badlogic.gdx.utils.Array;import com.packtpub.libgdx.canyonbunny.game.objects.AbstractGameObject;import com.packtpub.libgdx.canyonbunny.game.objects.Clouds;import com.packtpub.libgdx.canyonbunny.game.objects.Mountains;import com.packtpub.libgdx.canyonbunny.game.objects.Rock;import com.packtpub.libgdx.canyonbunny.game.objects.WaterOverlay;public class Level {    public static final String TAG = Level.class.getName();    public enum BLOCK_TYPE {        EMPTY(0, 0, 0), // black        ROCK(0, 255, 0), // green        PLAYER_SPAWNPOINT(255, 255, 255), // white        ITEM_FEATHER(255, 0, 255), // purple        ITEM_GOLD_COIN(255, 255, 0); // yellow        private int color;        private BLOCK_TYPE(int r, int g, int b) {            color = r << 24 | g << 16 | b << 8 | 0xff;        }        public boolean sameColor(int color) {            return this.color == color;        }        public int getColor() {            return color;        }    }    // objects    public Array<Rock> rocks;    // decoration    public Clouds clouds;    public Mountains mountains;    public WaterOverlay waterOverlay;    public Level(String filename) {        init(filename);    }    private void init(String filename) {    }    public void render(SpriteBatch batch) {    }}

在init中加入代码 读地图,然后解析:(分析过tiledmap的同学可能知道,这个步骤在使用tiledmap时也是一样的过程)

private void init(String filename) {        // objects        rocks = new Array<Rock>();        // load image file that represents the level data        Pixmap pixmap = new Pixmap(Gdx.files.internal(filename));        // scan pixels from top-left to bottom-right        int lastPixel = -1;        for (int pixelY = 0; pixelY < pixmap.getHeight(); pixelY++) {            for (int pixelX = 0; pixelX < pixmap.getWidth(); pixelX++) {                AbstractGameObject obj = null;                float offsetHeight = 0;                // height grows from bottom to top                float baseHeight = pixmap.getHeight() - pixelY;                // get color of current pixel as 32-bit RGBA value                int currentPixel = pixmap.getPixel(pixelX, pixelY);                // find matching color value to identify block type at (x,y)                // point and create the corresponding game object if there is                // a match                // empty space                if (BLOCK_TYPE.EMPTY.sameColor(currentPixel)) {                    // do nothing                }                // rock                else if (BLOCK_TYPE.ROCK.sameColor(currentPixel)) {                    if (lastPixel != currentPixel) {                        obj = new Rock();                        float heightIncreaseFactor = 0.25f;                        offsetHeight = -2.5f;                        obj.position.set(pixelX, baseHeight * obj.dimension.y                                * heightIncreaseFactor + offsetHeight);                        rocks.add((Rock) obj);                    } else {                        rocks.get(rocks.size - 1).increaseLength(1);                    }                }                // player spawn point                else if (BLOCK_TYPE.PLAYER_SPAWNPOINT.sameColor(currentPixel)) {                }                // feather                else if (BLOCK_TYPE.ITEM_FEATHER.sameColor(currentPixel)) {                }                // gold coin                else if (BLOCK_TYPE.ITEM_GOLD_COIN.sameColor(currentPixel)) {                }                // unknown object/pixel color                else {                    int r = 0xff & (currentPixel >>> 24); // red color channel                    int g = 0xff & (currentPixel >>> 16); // green color channel                    int b = 0xff & (currentPixel >>> 8); // blue color channel                    int a = 0xff & currentPixel; // alpha channel                    Gdx.app.error(TAG, "Unknown object at x<" + pixelX + "> y<"                            + pixelY + ">: r<" + r + "> g<" + g + "> b<" + b                            + "> a<" + a + ">");                }                lastPixel = currentPixel;            }        }        // decoration        clouds = new Clouds(pixmap.getWidth());        clouds.position.set(0, 2);        mountains = new Mountains(pixmap.getWidth());        mountains.position.set(-1, -1);        waterOverlay = new WaterOverlay(pixmap.getWidth());        waterOverlay.position.set(0, -3.75f);        // free memory        pixmap.dispose();        Gdx.app.debug(TAG, "level '" + filename + "' loaded");    }

以此遍历渲染:

public void render(SpriteBatch batch) {        // Draw Mountains        mountains.render(batch);        // Draw Rocks        for (Rock rock : rocks)            rock.render(batch);        // Draw Water Overlay        waterOverlay.render(batch);        // Draw Clouds        clouds.render(batch);    }

渲染的次序决定了相互覆盖的效果。你可以想象它们是不同的层(当然实际上它们没有分层画,这个跟Unity不是一样的,但你可以这么以为),从45°角来看是这样的。

first to last,越后画的越显示在前边。

接下来,开始整合:

在Constants里加上一些游戏常量

public class Constants {    // Visible game world is 5 meters wide    public static final float VIEWPORT_WIDTH = 5.0f;    // Visible game world is 5 meters tall    public static final float VIEWPORT_HEIGHT = 5.0f;    // GUI Width    public static final float VIEWPORT_GUI_WIDTH = 800.0f;    // GUI Height    public static final float VIEWPORT_GUI_HEIGHT = 480.0f;    // Location of description file for texture atlas    public static final String TEXTURE_ATLAS_OBJECTS = "images/canyonbunny.pack";    // Location of image file for level 01    public static final String LEVEL_01 = "levels/level-01.png";    // Amount of extra lives at level start    public static final int LIVES_START = 3;}

移除controller里的testSprites和selectedSprite;当然也要移除那些相应的方法initTestObjects(),updateTestObjects(),moveSelectedSprite()。

删除handleDebugInput()里WSAD的控制。KeyUp只保留R键。

package com.packtpub.libgdx.canyonbunny.game;import com.badlogic.gdx.Application.ApplicationType;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.Input.Keys;import com.badlogic.gdx.InputAdapter;import com.badlogic.gdx.graphics.Pixmap;import com.badlogic.gdx.graphics.Pixmap.Format;import com.badlogic.gdx.graphics.g2d.Sprite;import com.badlogic.gdx.graphics.g2d.TextureAtlas;import com.badlogic.gdx.graphics.g2d.TextureRegion;import com.badlogic.gdx.math.MathUtils;import com.badlogic.gdx.utils.Array;import com.packtpub.libgdx.canyonbunny.util.CameraHelper;import com.packtpub.libgdx.canyonbunny.util.Constants;public class WorldController extends InputAdapter {    private static final String TAG = WorldController.class.getName();    public CameraHelper cameraHelper;    public Level level;    public int lives;    public int score;    private void initLevel() {        score = 0;        level = new Level(Constants.LEVEL_01);    }    public WorldController() {        Gdx.input.setInputProcessor(this);        init();    }    private void handleDebugInput(float deltaTime) {        if (Gdx.app.getType() != ApplicationType.Desktop)            return;        // Camera Controls (move)        float camMoveSpeed = 5 * deltaTime;        float camMoveSpeedAccelerationFactor = 5;        if (Gdx.input.isKeyPressed(Keys.SHIFT_LEFT))            camMoveSpeed *= camMoveSpeedAccelerationFactor;        if (Gdx.input.isKeyPressed(Keys.LEFT))            moveCamera(-camMoveSpeed, 0);        if (Gdx.input.isKeyPressed(Keys.RIGHT))            moveCamera(camMoveSpeed, 0);        if (Gdx.input.isKeyPressed(Keys.UP))            moveCamera(0, camMoveSpeed);        if (Gdx.input.isKeyPressed(Keys.DOWN))            moveCamera(0, -camMoveSpeed);        if (Gdx.input.isKeyPressed(Keys.BACKSPACE))            cameraHelper.setPosition(0, 0);        // Camera Controls (zoom)        float camZoomSpeed = 1 * deltaTime;        float camZoomSpeedAccelerationFactor = 5;        if (Gdx.input.isKeyPressed(Keys.SHIFT_LEFT))            camZoomSpeed *= camZoomSpeedAccelerationFactor;        if (Gdx.input.isKeyPressed(Keys.COMMA))            cameraHelper.addZoom(camZoomSpeed);        if (Gdx.input.isKeyPressed(Keys.PERIOD))            cameraHelper.addZoom(-camZoomSpeed);        if (Gdx.input.isKeyPressed(Keys.SLASH))            cameraHelper.setZoom(1);    }    private void moveCamera(float x, float y) {        x += cameraHelper.getPosition().x;        y += cameraHelper.getPosition().y;        cameraHelper.setPosition(x, y);    }    @Override    public boolean keyUp(int keycode) {        if (keycode == Keys.R) {            init();            Gdx.app.debug(TAG, "Game World Resetted!");        }        return false;    }    public void init() {        Gdx.input.setInputProcessor(this);        cameraHelper = new CameraHelper();        lives = Constants.LIVES_START;        initLevel();    }    private Pixmap createProceduralPixmap(int width, int height) {        Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888);        // Fill square with red color at 50% opacity        pixmap.setColor(1, 0, 0, 0.5f);        pixmap.fill();        // Draw a yellow-colored X shape on square        pixmap.setColor(1, 1, 0, 1);        pixmap.drawLine(0, 0, width, height);        pixmap.drawLine(width, 0, 0, height);        // Draw a cyan-colored border around square        pixmap.setColor(0, 1, 1, 1);        pixmap.drawRectangle(0, 0, width, height);        return pixmap;    }    public void update(float deltaTime) {        handleDebugInput(deltaTime);        cameraHelper.update(deltaTime);    }}

修改CameraHelper:(主要是将target的类型由Sprite改为AbstractGameObject)

package com.packtpub.libgdx.canyonbunny.util;import com.badlogic.gdx.graphics.OrthographicCamera;import com.badlogic.gdx.graphics.g2d.Sprite;import com.badlogic.gdx.math.MathUtils;import com.badlogic.gdx.math.Vector2;import com.packtpub.libgdx.canyonbunny.game.objects.AbstractGameObject;public class CameraHelper {    private static final String TAG = CameraHelper.class.getName();    private final float MAX_ZOOM_IN = 0.25f;    private final float MAX_ZOOM_OUT = 10.0f;    private Vector2 position;    private float zoom;    private AbstractGameObject target;    public CameraHelper() {        position = new Vector2();        zoom = 1.0f;    }    public void update(float deltaTime) {        if (!hasTarget())            return;        position.x = target.position.x + target.origin.x;        position.y = target.position.y + target.origin.y;    }    public void setPosition(float x, float y) {        this.position.set(x, y);    }    public Vector2 getPosition() {        return position;    }    public void addZoom(float amount) {        setZoom(zoom + amount);    }    public void setZoom(float zoom) {        this.zoom = MathUtils.clamp(zoom, MAX_ZOOM_IN, MAX_ZOOM_OUT);    }    public float getZoom() {        return zoom;    }    public void setTarget(AbstractGameObject target) {        this.target = target;    }    public AbstractGameObject getTarget() {        return target;    }    public boolean hasTarget() {        return target != null;    }    public boolean hasTarget(AbstractGameObject target) {        return hasTarget() && this.target.equals(target);    }    public void applyTo(OrthographicCamera camera) {        camera.position.x = position.x;        camera.position.y = position.y;        camera.zoom = zoom;        camera.update();    }}

修改WorldRender的render():

public void render(){        renderWorld(batch);    }    private void renderWorld (SpriteBatch batch) {        worldController.cameraHelper.applyTo(camera);        batch.setProjectionMatrix(camera.combined);        batch.begin();        worldController.level.render(batch);        batch.end();    }

实现GUI

Libgdx提供了默认的bitmap字体文件,arial-15.fnt和arial-15.png。用的时候可以把它们copy到images下。

我们把要用的字体(内部类)加到Assets中:

   public class AssetFonts {
        public final BitmapFont defaultSmall;        public final BitmapFont defaultNormal;        public final BitmapFont defaultBig;        public AssetFonts() {            // create three fonts using Libgdx's 15px bitmap font            defaultSmall = new BitmapFont(                    Gdx.files.internal("images/arial-15.fnt"), true);            defaultNormal = new BitmapFont(                    Gdx.files.internal("images/arial-15.fnt"), true);            defaultBig = new BitmapFont(                    Gdx.files.internal("images/arial-15.fnt"), true);            // set font sizes            defaultSmall.setScale(0.75f);            defaultNormal.setScale(1.0f);            defaultBig.setScale(2.0f);            // enable linear texture filtering for smooth fonts            defaultSmall.getRegion().getTexture()                    .setFilter(TextureFilter.Linear, TextureFilter.Linear);            defaultNormal.getRegion().getTexture()                    .setFilter(TextureFilter.Linear, TextureFilter.Linear);            defaultBig.getRegion().getTexture()                    .setFilter(TextureFilter.Linear, TextureFilter.Linear);        }    }

在init里加上字体的初始化:fonts = new AssetFonts();

在dispose里释放:fonts.defaultSmall.dispose();fonts.defaultNormal.dispose();fonts.defaultBig.dispose();

准备就绪了,我们需要先构想我们将要做的GUI图:(金币分数,兔子的额外性命,FPS)

接下来,我们在WorldRenderer中增加下面的代码:

    
private OrthographicCamera cameraGUI;    private void init() {        batch = new SpriteBatch();        camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH,                Constants.VIEWPORT_HEIGHT);        camera.position.set(0, 0, 0);        camera.update();        cameraGUI = new OrthographicCamera(Constants.VIEWPORT_GUI_WIDTH,                Constants.VIEWPORT_GUI_HEIGHT);        cameraGUI.position.set(0, 0, 0);        cameraGUI.setToOrtho(true); // flip y-axis        cameraGUI.update();    }        public void resize(int width, int height) {        camera.viewportWidth = (Constants.VIEWPORT_HEIGHT / height) * width;        camera.update();        cameraGUI.viewportHeight = Constants.VIEWPORT_GUI_HEIGHT;        cameraGUI.viewportWidth = (Constants.VIEWPORT_GUI_HEIGHT/ (float)height) * (float)width;        cameraGUI.position.set(cameraGUI.viewportWidth / 2,        cameraGUI.viewportHeight / 2, 0);        cameraGUI.update();    }

第二个摄像机是专门用来做GUI投影渲染的。下面是每个GUI元素的具体实现方法:

private void renderGuiScore(SpriteBatch batch) {        float x = -15;        float y = -15;        batch.draw(Assets.instance.goldCoin.goldCoin, x, y, 50, 50, 100, 100,                0.35f, -0.35f, 0);        Assets.instance.fonts.defaultBig.draw(batch,                "" + worldController.score, x + 75, y + 37);    }    private void renderGuiExtraLive(SpriteBatch batch) {        float x = cameraGUI.viewportWidth - 50 - Constants.LIVES_START * 50;        float y = -15;        for (int i = 0; i < Constants.LIVES_START; i++) {            if (worldController.lives <= i)                batch.setColor(0.5f, 0.5f, 0.5f, 0.5f);            batch.draw(Assets.instance.bunny.head, x + i * 50, y, 50, 50, 120,                    100, 0.35f, -0.35f, 0);            batch.setColor(1, 1, 1, 1);        }    }    private void renderGuiFpsCounter(SpriteBatch batch) {        float x = cameraGUI.viewportWidth - 55;        float y = cameraGUI.viewportHeight - 15;        int fps = Gdx.graphics.getFramesPerSecond();        BitmapFont fpsFont = Assets.instance.fonts.defaultNormal;        if (fps >= 45) {            // 45 or more FPS show up in green            fpsFont.setColor(0, 1, 0, 1);        } else if (fps >= 30) {            // 30 or more FPS show up in yellow            fpsFont.setColor(1, 1, 0, 1);        } else {            // less than 30 FPS show up in red            fpsFont.setColor(1, 0, 0, 1);        }        fpsFont.draw(batch, "FPS: " + fps, x, y);        fpsFont.setColor(1, 1, 1, 1); // white    }

整合到WorldRenderer:

    public void render() {
        renderWorld(batch);        renderGui(batch);    }    private void renderGui(SpriteBatch batch) {        batch.setProjectionMatrix(cameraGUI.combined);        batch.begin();        // draw collected gold coins icon + text        // (anchored to top left edge)        renderGuiScore(batch);        // draw extra lives icon + text (anchored to top right edge)        renderGuiExtraLive(batch);        // draw FPS text (anchored to bottom right edge)        renderGuiFpsCounter(batch);        batch.end();    }

下一章我们继续完成剩下的部分:

比如增加主角(兔子头),关卡道具(羽毛,金币),控制主角移动,基本的碰撞检测(几乎所有的游戏都有的)等等。

1 0