Android游戏开发(3):创建第一个Android游戏
发表于2017-09-13
提要
前面已经让大家学习安卓游戏开发的基础,那么下面我们就可以开始开发一个Android游戏了,当然,现阶段只是学习为主。
下面要做的一个游戏叫做 Star Guard,一款非常棒的独立游戏,画面非常有爱,难度不小,不过有无限生命可以玩。
键盘的上下左右控制小人,x开火,z跳跃。
今天我们要做的就是搭建舞台,创建项目的骨架。
最终的效果看起来是这样:
当然,可以同时部署在手机和PC上。
搭建工程
偷懒的话直接用上次创建的工程就ok,如果想重新创建的话,用gdx-setup-ui也很快。
在eclipse中导入三个工程。
看起来就像这样:
我们主要编写的工程是test-gdx-game.其他两个基本不东。
在工程下面创建相应的包,包括controller,model,screens,view.
这里用到了一些MVC的知识,简单提一下:
MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部分分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组:
(控制器Controller)- 负责转发请求,对请求进行处理。
(视图View) - 界面设计人员进行图形界面设计。
(模型Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
(控制器Controller)- 负责转发请求,对请求进行处理。
(视图View) - 界面设计人员进行图形界面设计。
(模型Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
编码
model
这里有三个model需要创建-Block(砖块),Bob(人物),World(世界)。
Block.java
- package com.me.testgdxgame.model;
- import com.badlogic.gdx.math.Rectangle;
- import com.badlogic.gdx.math.Vector2;
- public class Block {
- public static final float SIZE = 1f;
- Vector2 position = new Vector2();
- Rectangle bounds = new Rectangle();
- public Block(Vector2 pos) {
- this.position = pos;
- this.bounds.setX(pos.x);
- this.bounds.setY(pos.y);
- this.bounds.width = SIZE;
- this.bounds.height = SIZE;
- }
- public Vector2 getPosition() {
- return position;
- }
- public Rectangle getBounds() {
- return bounds;
- }
- }
Vector2是libgdx中的二维向量类。bounds用于后面的碰撞检测。
Bob.java
- package com.me.testgdxgame.model;
- import com.badlogic.gdx.math.Rectangle;
- import com.badlogic.gdx.math.Vector2;
- public class Bob {
- public enum State {
- IDLE, WALKING, JUMPING, DYING
- }
- public static final float SPEED = 4f; // unit per second
- static final float JUMP_VELOCITY = 1f;
- public static final float SIZE = 0.5f; // half a unit
- Vector2 position = new Vector2();
- Vector2 acceleration = new Vector2();
- Vector2 velocity = new Vector2();
- Rectangle bounds = new Rectangle();
- State state = State.IDLE;
- boolean facingLeft = true;
- float stateTime = 0;
- public Bob(Vector2 position) {
- this.position = position;
- this.bounds.height = SIZE;
- this.bounds.width = SIZE;
- }
- public boolean isFacingLeft() {
- return facingLeft;
- }
- public void setFacingLeft(boolean facingLeft) {
- this.facingLeft = facingLeft;
- }
- public Vector2 getPosition() {
- return position;
- }
- public Vector2 getAcceleration() {
- return acceleration;
- }
- public Vector2 getVelocity() {
- return velocity;
- }
- public Rectangle getBounds() {
- return bounds;
- }
- public State getState() {
- return state;
- }
- public void setState(State newState) {
- this.state = newState;
- }
- public float getStateTime() {
- return stateTime;
- }
- public void update(float delta) {
- //stateTime = delta;
- //position.add(velocity.tmp().mul(delta));
- position.add(velocity.cpy().mul(delta));
- }
- }
Bob就有很多属性了,速度,位置什么的,代码也很简单。
World.java
- package com.me.testgdxgame.model;
- import java.util.ArrayList;
- import com.badlogic.gdx.math.Vector2;
- import com.badlogic.gdx.utils.Array;
- public class World {
- /** The blocks making up the world **/
- ArrayList
blocks = new ArrayList(); - /** Our player controlled hero **/
- Bob bob;
- // Getters -----------
- public ArrayList
getBlocks() { - return blocks;
- }
- public Bob getBob() {
- return bob;
- }
- // --------------------
- public World() {
- createDemoWorld();
- }
- private void createDemoWorld() {
- bob = new Bob(new Vector2(7, 2));
- for (int i = 0; i < 10; i ) {
- blocks.add(new Block(new Vector2(i, 0)));
- blocks.add(new Block(new Vector2(i, 6)));
- if (i > 2)
- blocks.add(new Block(new Vector2(i, 1)));
- }
- blocks.add(new Block(new Vector2(9, 2)));
- blocks.add(new Block(new Vector2(9, 3)));
- blocks.add(new Block(new Vector2(9, 4)));
- blocks.add(new Block(new Vector2(9, 5)));
- blocks.add(new Block(new Vector2(6, 3)));
- blocks.add(new Block(new Vector2(6, 4)));
- blocks.add(new Block(new Vector2(6, 5)));
- }
- }
World中包括了地图和人物。
View
view中的类主要负责渲染。
首先在项目中添加两个纹理。
放到android工程的asset/data下面就可以了。
注意:纹理贴图的长宽像素一定是2的幂,比如64×64,128×128..否则无法加载。官方解释是opengl的一个bug,无法解决。
- package com.me.testgdxgame.view;
- import com.me.testgdxgame.*;
- import com.me.testgdxgame.model.Block;
- import com.me.testgdxgame.model.Bob;
- import com.me.testgdxgame.model.World;
- import com.badlogic.gdx.Gdx;
- import com.badlogic.gdx.graphics.Color;
- import com.badlogic.gdx.graphics.GL10;
- import com.badlogic.gdx.graphics.OrthographicCamera;
- import com.badlogic.gdx.graphics.Texture;
- import com.badlogic.gdx.graphics.g2d.SpriteBatch;
- import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
- import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
- import com.badlogic.gdx.math.Rectangle;
- public class WorldRenderer {
- private World world;
- private OrthographicCamera cam;
- private SpriteBatch spriteBatch;
- private boolean debug=false;
- private int width;
- private int height;
- private float ppuX; // pixels per unit on the X axis
- private float ppuY; // pixels per unit on the Y axis
- private static final float CAMERA_WIDTH = 10f;
- private static final float CAMERA_HEIGHT = 7f;
- /** Textures **/
- private Texture bobTexture;
- private Texture blockTexture;
- /** for debug rendering **/
- ShapeRenderer debugRenderer = new ShapeRenderer();
- public WorldRenderer(World world) {
- this.world = world;
- this.cam = new OrthographicCamera(10, 7);
- this.cam.position.set(5, 3.5f, 0);
- this.cam.update();
- spriteBatch=new SpriteBatch();
- loadTextures();
- }
- public void setSize (int w, int h) {
- this.width = w;
- this.height = h;
- ppuX = (float)width / CAMERA_WIDTH;
- ppuY = (float)height / CAMERA_HEIGHT;
- }
- private void loadTextures(){
- bobTexture=new Texture(Gdx.files.internal("data/bob_01.png"));
- blockTexture=new Texture(Gdx.files.internal("data/block.png"));
- }
- public void render() {
- spriteBatch.begin();
- drawBlocks();
- drawBob();
- spriteBatch.end();
- if(debug) drawDebug();
- }
- private void drawBlocks(){
- for (Block block : world.getBlocks()) {
- spriteBatch.draw(blockTexture, block.getPosition().x * ppuX, block.getPosition().y * ppuY, Block.SIZE * ppuX, Block.SIZE * ppuY);
- }
- }
- private void drawBob(){
- Bob bob = world.getBob();
- spriteBatch.draw(bobTexture, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);
- }
- private void drawDebug(){
- // render blocks
- debugRenderer.setProjectionMatrix(cam.combined);
- debugRenderer.begin(ShapeType.Rectangle);
- for (Block block : world.getBlocks()) {
- Rectangle rect = block.getBounds();
- float x1 = block.getPosition().x rect.x;
- float y1 = block.getPosition().y rect.y;
- debugRenderer.setColor(new Color(1, 0, 0, 1));
- debugRenderer.rect(x1, y1, rect.width, rect.height);
- }
- // render Bob
- Bob bob = world.getBob();
- Rectangle rect = bob.getBounds();
- float x1 = bob.getPosition().x rect.x;
- float y1 = bob.getPosition().y rect.y;
- debugRenderer.setColor(new Color(0, 1, 0, 1));
- debugRenderer.rect(x1, y1, rect.width, rect.height);
- debugRenderer.end();
- }
- }
有几个类简单解释一下:
OrthographicCamera:正交坐标系Camerta。用于设置摄像机。到后期需要将人固定在摄像机视口中间,跟随人移动。
ShapeRenderer:用于绘制,包括纹理,线条,点,矩形等等。
SpriteBatch:负责加载,管理,绘制纹理。
这个类里面有个drawDebug()可以用于不添加纹理的时候绘制,绘制的结果如下:
Controller
这个类用于处理输入。
WorldController.java
- package com.me.testgdxgame.controller;
- import java.util.HashMap;
- import java.util.Map;
- import com.me.testgdxgame.model.Bob;
- import com.me.testgdxgame.model.Bob.State;
- import com.me.testgdxgame.model.World;
- public class WorldController {
- enum Keys{
- LEFT,RIGHT,JUMP,FIRE
- }
- private World world;
- private Bob bob;
- static Map
keys = new HashMap(); - static {
- keys.put(Keys.LEFT, false);
- keys.put(Keys.RIGHT, false);
- keys.put(Keys.JUMP, false);
- keys.put(Keys.FIRE, false);
- };
- public WorldController(World w){
- world=w;
- bob=world.getBob();
- }
- //Key presses and touches
- public void leftPressed(){
- keys.get(keys.put(Keys.LEFT, true));
- }
- public void rightPressed() {
- keys.get(keys.put(Keys.RIGHT, true));
- }
- public void jumpPressed() {
- keys.get(keys.put(Keys.JUMP, true));
- }
- public void firePressed() {
- keys.get(keys.put(Keys.FIRE, false));
- }
- public void leftReleased() {
- keys.get(keys.put(Keys.LEFT, false));
- }
- public void rightReleased() {
- keys.get(keys.put(Keys.RIGHT, false));
- }
- public void jumpReleased() {
- keys.get(keys.put(Keys.JUMP, false));
- }
- public void fireReleased() {
- keys.get(keys.put(Keys.FIRE, false));
- }
- public void update(float delta){
- processInput();
- bob.update(delta);
- }
- private void processInput(){
- if(keys.get(Keys.LEFT)){
- bob.setFacingLeft(true);
- bob.setState(State.WALKING);
- bob.getVelocity().x=-Bob.SPEED;
- }
- if (keys.get(Keys.RIGHT)) {
- // left is pressed
- bob.setFacingLeft(false);
- bob.setState(State.WALKING);
- bob.getVelocity().x = Bob.SPEED;
- }
- // need to check if both or none direction are pressed, then Bob is idle
- if ((keys.get(Keys.LEFT) && keys.get(Keys.RIGHT)) ||
- (!keys.get(Keys.LEFT) && !(keys.get(Keys.RIGHT)))) {
- bob.setState(State.IDLE);
- // acceleration is 0 on the x
- bob.getAcceleration().x = 0;
- // horizontal speed is 0
- bob.getVelocity().x = 0;
- }
- }
- }
屏幕
mvc都有了,接下来就可以屏幕了。
一个游戏通常有很多屏幕,欢迎屏幕,游戏屏幕,参数设置屏幕,游戏结束屏幕等等。
今天我们之做一个游戏屏幕。
在screens包里添加GameScreen类。
- package com.me.testgdxgame.screens;
- import com.badlogic.gdx.Gdx;
- import com.badlogic.gdx.Input.Keys;
- import com.badlogic.gdx.InputProcessor;
- import com.badlogic.gdx.Screen;
- import com.badlogic.gdx.graphics.GL10;
- import com.me.testgdxgame.controller.WorldController;
- import com.me.testgdxgame.model.World;
- import com.me.testgdxgame.view.WorldRenderer;
- public class GameScreen implements Screen ,InputProcessor{
- private WorldRenderer renderer;
- private World world;
- private WorldController controller;
- private int width, height;
- @Override
- public void render(float delta) {
- // TODO Auto-generated method stub
- Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1);
- Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
- controller.update(delta);
- renderer.render();
- }
- @Override
- public void resize(int width, int height) {
- // TODO Auto-generated method stub
- renderer.setSize(width, height);
- this.width=width;
- this.height=height;
- }
- @Override
- public void show() {
- // TODO Auto-generated method stub
- world = new World();
- renderer = new WorldRenderer(world);
- controller=new WorldController(world);
- Gdx.input.setInputProcessor(this);
- }
- @Override
- public void hide() {
- // TODO Auto-generated method stub
- Gdx.input.setInputProcessor(null);
- }
- @Override
- public void pause() {
- // TODO Auto-generated method stub
- }
- @Override
- public void resume() {
- // TODO Auto-generated method stub
- }
- @Override
- public void dispose() {
- // TODO Auto-generated method stub
- Gdx.input.setInputProcessor(null);
- }
- @Override
- public boolean keyDown(int keycode) {
- // TODO Auto-generated method stub
- if (keycode == Keys.LEFT)
- controller.leftPressed();
- if (keycode == Keys.RIGHT)
- controller.rightPressed();
- if (keycode == Keys.Z)
- controller.jumpPressed();
- if (keycode == Keys.X)
- controller.firePressed();
- return true;
- }
- @Override
- public boolean keyUp(int keycode) {
- // TODO Auto-generated method stub
- if (keycode == Keys.LEFT)
- controller.leftReleased();
- if (keycode == Keys.RIGHT)
- controller.rightReleased();
- if (keycode == Keys.Z)
- controller.jumpReleased();
- if (keycode == Keys.X)
- controller.fireReleased();
- return true;
- }
- @Override
- public boolean keyTyped(char character) {
- // TODO Auto-generated method stub
- return false;
- }
- @Override
- public boolean touchDown(int x, int y, int pointer, int button) {
- // TODO Auto-generated method stub
- if (x < width / 2 && y > height / 2) {
- controller.leftPressed();
- }
- if (x > width / 2 && y > height / 2) {
- controller.rightPressed();
- }
- return false;
- }
- @Override
- public boolean touchUp(int x, int y, int pointer, int button) {
- // TODO Auto-generated method stub
- if (x < width / 2 && y > height / 2) {
- controller.leftReleased();
- }
- if (x > width / 2 && y > height / 2) {
- controller.rightReleased();
- }
- return true;
- }
- @Override
- public boolean touchDragged(int screenX, int screenY, int pointer) {
- // TODO Auto-generated method stub
- return false;
- }
- @Override
- public boolean mouseMoved(int screenX, int screenY) {
- // TODO Auto-generated method stub
- return false;
- }
- @Override
- public boolean scrolled(int amount) {
- // TODO Auto-generated method stub
- return false;
- }
- }
这个类实现了Screen ,InputProcessor接口。
InputProcessor用于接受触控事件和按键事件。
部署
desktop版本的项目基本不用修改。main函数如下:
- public class Main {
- public static void main(String[] args) {
- LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
- cfg.title = "test-gdx-game";
- cfg.useGL20 = false;
- cfg.width = 1024;
- cfg.height = 600;
- new LwjglApplication(new TestGdxGame(), cfg);
- }
- }
Main.java上右击->run as->java application.
android版本修改一下MainActiviy:
- public class MainActivity extends AndroidApplication {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
- cfg.useGL20 = false;
- initialize(new TestGdxGame(), cfg);
- }
- }
run as->android application.
总结
到现在,舞台和代码框架已经搭建好了,后面还可以添加下面的东西:
.地行碰撞
.动画
.高等级的camera(视角可以不断变化)
.音效
.改进的输入
.更多的GameScreen
一起期待下一篇教程。