[libGDX游戏开发教程]使用Libgdx进行游戏开发(5)-关卡加载_libgdx game development essentials-程序员宅基地

技术标签: libGDX  libgdx  游戏  

声明:《使用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();
    }

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

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chiyingluolei/article/details/53689334

智能推荐

[bzoj1190][HNOI2007]梦幻岛宝珠 分组背包_bzoj分组背包-程序员宅基地

文章浏览阅读554次。我读的书愈多,就愈亲近世界,愈明了生活的意义,愈觉得生活的重要。_bzoj分组背包

Centos7固定内网IP并允许访问外网_centos配置em1 ip-程序员宅基地

文章浏览阅读1.6k次。首先输入ip addr查看网卡名称因为有的服务器是有多个网卡的,比如我们公司的服务器就有四个网卡,所以要使用ip addr来查看使用的那个网卡这里可以看到使用的网卡是em1,网卡名称是不固定的,具体要看服务器上是什么名称编辑网卡配置使用vi或者vim编辑网卡配置文件一般文件名都是ifcfg-网卡名vi /etc/sysconfig/network-scripts/ifcfg-em1打开之后可以看到内容为将以上内容,修改为以下内容IPADDR="想要固定的内网ip"NE.._centos配置em1 ip

工资再高也要发展副业,这3种副业门槛低收入高,越早知道越好_高门槛高收入副业-程序员宅基地

文章浏览阅读399次。过,话说回来,副业真的很有必要。它不仅能够让你多一份收入,做得好了或许还能给你一份事业。再加上现在职场不好混,谁也不知道啥时候就被淘汰了,又或者实在不想伺候那些混账领导同事了,副业至少给你了一条退路,让你不至于一无所有。其实,发展副业并不难,很多都是门槛低收入高,尤其是以下这3种副业,非常适合上班族。_高门槛高收入副业

Exception in thread “main“ java.lang.UnsatisfiedLinkError:hadoop跑程序错误-程序员宅基地

文章浏览阅读1.4k次。写好Driver程序后运行出现Exception in thread “main“ java.lang.UnsatisfiedLinkError。将先前打开的NativeIO.class全选复制到新建的NativeIO.class中,并找到刚才的640行返回值。修改完之后 又报了一次同样的错:这次是在623行,同样的方法修改成 return true。查看本地版本配置无误后,依旧报错使用该方法。发现错误指向640行(显示源码注释后的)将return值改为true。

Asp.NetCore轻松学-部署到 Linux 进行托管-程序员宅基地

文章浏览阅读415次。前言上一篇文章介绍了如何将开发好的 Asp.Net Core 应用程序部署到 IIS,且学习了进程内托管和进程外托管的区别;接下来就要说说应用 Asp.Net Core ..._.netcore linux dotnet publish

Nginx下载与安装_下载nginx deb安装包-程序员宅基地

文章浏览阅读1.1k次。nginx 下载与安装_下载nginx deb安装包

随便推点

css自定义鼠标,设置光标位置_css设置光标初始位置-程序员宅基地

文章浏览阅读1.9k次。@TOCcss自定义鼠标,设置光标位置cursor:url("自定义图片路径/img.ico") x y,auto;//其中,默认的光标位置是在左上角,xy为距离左上角的距离,即光标位置注意:图片必须使用ico后缀名的图片,可以通过“程序图标获取编辑工具(Greenfish Icon Editor Pro) v3.25” 这一软件将png的图片转换为ico文件。..._css设置光标初始位置

iptables 原理概述_iptables内核架构-程序员宅基地

文章浏览阅读398次。Netfilter 包过滤框架和 iptables 防火墙是 Linux 服务器上大部分防火墙解决方案的基础。Netfilter 的内核 hook 和协议栈之间联系紧密,提供了对数据包经过系统时的强大控制功能。 iptables 基于这些功能提供了一个灵活的、可扩展的、将策略需求应用到内核的方案。理解了这些不同模块是如何联系到一起的,就可以更高效地对数据包进行控制。_iptables内核架构

认识KNX协议-程序员宅基地

文章浏览阅读2.9k次。一、简介KNX是Konnex的缩写。1999年5月,欧洲三大总线协议EIB、BatiBus和EHSA合并成立了Konnex协会,提出了KNX协议。该协议以EIB为基础,兼顾了BatiBus和EHSA的物理层规范,并吸收了BatiBus和EHSA中配置模式等优点,提供了家庭、楼宇自动化的完整解决方案。KNX总线是独立于制造商和应用领域的系统。 通过所有的总线设备连接到 KNX 介质上 ( ..._knx协议

Linux系统编程8-I2C通信_i2c8 linux 节点-程序员宅基地

文章浏览阅读2.9k次,点赞2次,收藏16次。序号内容链接1多进程点我访问2进程间通信点我访问3多线程点我访问4网络编程点我访问5shell点我访问6Makefile点我访问7串口通信点我访问7I2C通信点我访问一 I2C介绍IIC(IIC,inter-Integrated circuit),两线式串行总线,用于MCU和外设间的通信。IIC只需两根线:数据线SDA和时钟线SCL。以半双工方式实现MCU和外设之间数据传输,速度可达400kbps。二...._i2c8 linux 节点

android的组件开发教程,Android学习指南之三十三:自定义Android UI组件的方法-程序员宅基地

文章浏览阅读169次。我们为什么需要自定义Android UI组件呢?这是由于有很多Activity使用的View顶部的控件是差不多的,就像HTML页面里面我们有自己的header这样的东西,那我们可能应该将其统一到一块,但是view顶部又不是简单的统一的格式,里面有一些控件的样式或者文字还是有不同的,所以这里就需要有一个定制化的UI,然后我们可以设置这个UI的某一些自定义属性来满足不同的需求。这次我们自定义的这个组件..._自定义ui组件的3个重要方法

android电源管理PowerManager_powermanager怎么获得实例-程序员宅基地

文章浏览阅读1.4k次。PowerManager这个类提供了电源管理的一些功能,比如可以让屏幕或者键盘亮起来等。还有对设备的重启的api官网是这么解释的PowerManager Class Overview这个类提供了控制设备电源状态的管理功能。设备的电池的持续时间(寿命)会受到使用这个API的重要影响。在非必要的情况下不要使用WakeLock,即使必须使用,也要最低限度使用这个a_powermanager怎么获得实例