Android游戏开发(3):创建第一个Android游戏

发表于2017-09-13
评论0 1.2k浏览

提要

前面已经让大家学习安卓游戏开发的基础,那么下面我们就可以开始开发一个Android游戏了,当然,现阶段只是学习为主。

下面要做的一个游戏叫做 Star Guard,一款非常棒的独立游戏,画面非常有爱,难度不小,不过有无限生命可以玩。

键盘的上下左右控制小人,x开火,z跳跃。



今天我们要做的就是搭建舞台,创建项目的骨架。

最终的效果看起来是这样:



当然,可以同时部署在手机和PC上。


搭建工程

偷懒的话直接用上次创建的工程就ok,如果想重新创建的话,用gdx-setup-ui也很快。
在eclipse中导入三个工程。
看起来就像这样:


我们主要编写的工程是test-gdx-game.其他两个基本不东。
在工程下面创建相应的包,包括controller,model,screens,view.

这里用到了一些MVC的知识,简单提一下:
MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部分分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组:
(控制器Controller)- 负责转发请求,对请求进行处理。
(视图View) - 界面设计人员进行图形界面设计。
(模型Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。


编码

model

这里有三个model需要创建-Block(砖块),Bob(人物),World(世界)。

Block.java
  1. package com.me.testgdxgame.model;  
  2.   
  3. import com.badlogic.gdx.math.Rectangle;  
  4. import com.badlogic.gdx.math.Vector2;  
  5.   
  6. public class Block {  
  7.   
  8.     public static final float SIZE = 1f;  
  9.       
  10.     Vector2     position = new Vector2();  
  11.     Rectangle   bounds = new Rectangle();  
  12.       
  13.     public Block(Vector2 pos) {  
  14.         this.position = pos;  
  15.         this.bounds.setX(pos.x);  
  16.         this.bounds.setY(pos.y);  
  17.         this.bounds.width = SIZE;  
  18.         this.bounds.height = SIZE;  
  19.     }  
  20.   
  21.     public Vector2 getPosition() {  
  22.         return position;  
  23.     }  
  24.   
  25.     public Rectangle getBounds() {  
  26.         return bounds;  
  27.     }  
  28. }  



Vector2是libgdx中的二维向量类。bounds用于后面的碰撞检测。

Bob.java
  1. package com.me.testgdxgame.model;  
  2.   
  3. import com.badlogic.gdx.math.Rectangle;  
  4. import com.badlogic.gdx.math.Vector2;  
  5.   
  6. public class Bob {  
  7.   
  8.     public enum State {  
  9.         IDLE, WALKING, JUMPING, DYING  
  10.     }  
  11.   
  12.     public static final float SPEED = 4f;   // unit per second  
  13.     static final float JUMP_VELOCITY = 1f;  
  14.     public static final float SIZE = 0.5f; // half a unit  
  15.   
  16.     Vector2     position = new Vector2();  
  17.     Vector2     acceleration = new Vector2();  
  18.     Vector2     velocity = new Vector2();  
  19.     Rectangle   bounds = new Rectangle();  
  20.     State       state = State.IDLE;  
  21.     boolean     facingLeft = true;  
  22.     float       stateTime = 0;  
  23.   
  24.     public Bob(Vector2 position) {  
  25.         this.position = position;  
  26.         this.bounds.height = SIZE;  
  27.         this.bounds.width = SIZE;  
  28.     }  
  29.     public boolean isFacingLeft() {  
  30.         return facingLeft;  
  31.     }  
  32.     public void setFacingLeft(boolean facingLeft) {  
  33.         this.facingLeft = facingLeft;  
  34.     }  
  35.   
  36.     public Vector2 getPosition() {  
  37.         return position;  
  38.     }  
  39.   
  40.     public Vector2 getAcceleration() {  
  41.         return acceleration;  
  42.     }  
  43.   
  44.     public Vector2 getVelocity() {  
  45.         return velocity;  
  46.     }  
  47.   
  48.     public Rectangle getBounds() {  
  49.         return bounds;  
  50.     }  
  51.   
  52.     public State getState() {  
  53.         return state;  
  54.     }  
  55.   
  56.     public void setState(State newState) {  
  57.         this.state = newState;  
  58.     }  
  59.   
  60.     public float getStateTime() {  
  61.         return stateTime;  
  62.     }  
  63.   
  64.   
  65.     public void update(float delta) {  
  66.         //stateTime  = delta;  
  67.         //position.add(velocity.tmp().mul(delta));   
  68.         position.add(velocity.cpy().mul(delta));  
  69.     }  
  70. }  

Bob就有很多属性了,速度,位置什么的,代码也很简单。

World.java
  1. package com.me.testgdxgame.model;  
  2.   
  3. import java.util.ArrayList;  
  4.   
  5. import com.badlogic.gdx.math.Vector2;  
  6. import com.badlogic.gdx.utils.Array;  
  7.   
  8. public class World {  
  9.   
  10.     /** The blocks making up the world **/  
  11.     ArrayList blocks = new ArrayList();  
  12.     /** Our player controlled hero **/  
  13.     Bob bob;  
  14.   
  15.     // Getters -----------  
  16.     public ArrayList  getBlocks() {  
  17.         return blocks;  
  18.     }  
  19.     public Bob getBob() {  
  20.         return bob;  
  21.     }  
  22.     // --------------------  
  23.   
  24.     public World() {  
  25.         createDemoWorld();  
  26.     }  
  27.   
  28.     private void createDemoWorld() {  
  29.         bob = new Bob(new Vector2(72));  
  30.   
  31.         for (int i = 0; i < 10; i ) {                 
  32.             blocks.add(new Block(new Vector2(i, 0)));   
  33.             blocks.add(new Block(new Vector2(i, 6)));     
  34.             if (i > 2)  
  35.                 blocks.add(new Block(new Vector2(i, 1)));  
  36.         }  
  37.         blocks.add(new Block(new Vector2(92)));  
  38.         blocks.add(new Block(new Vector2(93)));  
  39.         blocks.add(new Block(new Vector2(94)));  
  40.         blocks.add(new Block(new Vector2(95)));  
  41.   
  42.         blocks.add(new Block(new Vector2(63)));  
  43.         blocks.add(new Block(new Vector2(64)));  
  44.         blocks.add(new Block(new Vector2(65)));  
  45.     }  
  46. }  

World中包括了地图和人物。

View

view中的类主要负责渲染。
首先在项目中添加两个纹理。

放到android工程的asset/data下面就可以了。
注意:纹理贴图的长宽像素一定是2的幂,比如64×64,128×128..否则无法加载。官方解释是opengl的一个bug,无法解决。
  1. package com.me.testgdxgame.view;  
  2.   
  3.   
  4. import com.me.testgdxgame.*;  
  5. import com.me.testgdxgame.model.Block;  
  6. import com.me.testgdxgame.model.Bob;  
  7. import com.me.testgdxgame.model.World;  
  8. import com.badlogic.gdx.Gdx;  
  9. import com.badlogic.gdx.graphics.Color;  
  10. import com.badlogic.gdx.graphics.GL10;  
  11. import com.badlogic.gdx.graphics.OrthographicCamera;  
  12. import com.badlogic.gdx.graphics.Texture;  
  13. import com.badlogic.gdx.graphics.g2d.SpriteBatch;  
  14. import com.badlogic.gdx.graphics.glutils.ShapeRenderer;  
  15. import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;  
  16. import com.badlogic.gdx.math.Rectangle;  
  17.   
  18. public class WorldRenderer {  
  19.   
  20.     private World world;  
  21.     private OrthographicCamera cam;  
  22.   
  23.     private SpriteBatch spriteBatch;  
  24.     private boolean debug=false;  
  25.     private int width;  
  26.     private int height;  
  27.     private float ppuX; // pixels per unit on the X axis  
  28.     private float ppuY; // pixels per unit on the Y axis  
  29.     private static final float CAMERA_WIDTH = 10f;  
  30.     private static final float CAMERA_HEIGHT = 7f;  
  31.     /** Textures **/  
  32.     private Texture bobTexture;  
  33.     private Texture blockTexture;  
  34.   
  35.   
  36.   
  37.     /** for debug rendering **/  
  38.     ShapeRenderer debugRenderer = new ShapeRenderer();  
  39.   
  40.     public WorldRenderer(World world) {  
  41.         this.world = world;  
  42.         this.cam = new OrthographicCamera(107);  
  43.         this.cam.position.set(53.5f, 0);  
  44.         this.cam.update();  
  45.         spriteBatch=new SpriteBatch();  
  46.         loadTextures();  
  47.     }  
  48.     public void setSize (int w, int h) {  
  49.         this.width = w;  
  50.         this.height = h;  
  51.         ppuX = (float)width / CAMERA_WIDTH;  
  52.         ppuY = (float)height / CAMERA_HEIGHT;  
  53.     }  
  54.   
  55.     private void loadTextures(){  
  56.         bobTexture=new Texture(Gdx.files.internal("data/bob_01.png"));  
  57.         blockTexture=new Texture(Gdx.files.internal("data/block.png"));  
  58.     }  
  59.   
  60.   
  61.     public void render() {  
  62.         spriteBatch.begin();  
  63.         drawBlocks();  
  64.         drawBob();  
  65.         spriteBatch.end();  
  66.         if(debug) drawDebug();  
  67.     }  
  68.   
  69.     private void  drawBlocks(){  
  70.         for (Block block : world.getBlocks()) {  
  71.             spriteBatch.draw(blockTexture, block.getPosition().x * ppuX, block.getPosition().y * ppuY, Block.SIZE * ppuX, Block.SIZE * ppuY);  
  72.         }  
  73.     }  
  74.     private void drawBob(){  
  75.         Bob bob = world.getBob();  
  76.         spriteBatch.draw(bobTexture, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);  
  77.     }  
  78.   
  79.     private void drawDebug(){  
  80.         // render blocks  
  81.         debugRenderer.setProjectionMatrix(cam.combined);  
  82.         debugRenderer.begin(ShapeType.Rectangle);  
  83.         for (Block block : world.getBlocks()) {  
  84.             Rectangle rect = block.getBounds();  
  85.             float x1 = block.getPosition().x   rect.x;  
  86.             float y1 = block.getPosition().y   rect.y;  
  87.             debugRenderer.setColor(new Color(1001));  
  88.             debugRenderer.rect(x1, y1, rect.width, rect.height);  
  89.         }  
  90.         // render Bob  
  91.   
  92.         Bob bob = world.getBob();  
  93.         Rectangle rect = bob.getBounds();  
  94.         float x1 = bob.getPosition().x   rect.x;  
  95.         float y1 = bob.getPosition().y   rect.y;  
  96.         debugRenderer.setColor(new Color(0101));  
  97.         debugRenderer.rect(x1, y1, rect.width, rect.height);  
  98.         debugRenderer.end();  
  99.     }  
  100. }  

有几个类简单解释一下:
OrthographicCamera:正交坐标系Camerta。用于设置摄像机。到后期需要将人固定在摄像机视口中间,跟随人移动。
ShapeRenderer:用于绘制,包括纹理,线条,点,矩形等等。
SpriteBatch:负责加载,管理,绘制纹理。

这个类里面有个drawDebug()可以用于不添加纹理的时候绘制,绘制的结果如下:



Controller

这个类用于处理输入。
WorldController.java
  1. package com.me.testgdxgame.controller;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. import com.me.testgdxgame.model.Bob;  
  7. import com.me.testgdxgame.model.Bob.State;  
  8. import com.me.testgdxgame.model.World;  
  9.   
  10. public class WorldController {  
  11.       
  12.     enum Keys{  
  13.         LEFT,RIGHT,JUMP,FIRE  
  14.     }  
  15.     private World world;  
  16.     private Bob bob;  
  17.       
  18.     static Map keys = new HashMap();  
  19.     static {  
  20.         keys.put(Keys.LEFT, false);  
  21.         keys.put(Keys.RIGHT, false);  
  22.         keys.put(Keys.JUMP, false);  
  23.         keys.put(Keys.FIRE, false);  
  24.     };  
  25.       
  26.     public WorldController(World w){  
  27.         world=w;  
  28.         bob=world.getBob();  
  29.     }  
  30.       
  31.     //Key presses and touches  
  32.     public void leftPressed(){  
  33.         keys.get(keys.put(Keys.LEFT, true));  
  34.     }  
  35.     public void rightPressed() {  
  36.         keys.get(keys.put(Keys.RIGHT, true));  
  37.     }  
  38.   
  39.     public void jumpPressed() {  
  40.         keys.get(keys.put(Keys.JUMP, true));  
  41.     }  
  42.   
  43.     public void firePressed() {  
  44.         keys.get(keys.put(Keys.FIRE, false));  
  45.     }  
  46.   
  47.     public void leftReleased() {  
  48.         keys.get(keys.put(Keys.LEFT, false));  
  49.     }  
  50.   
  51.     public void rightReleased() {  
  52.         keys.get(keys.put(Keys.RIGHT, false));  
  53.     }  
  54.   
  55.     public void jumpReleased() {  
  56.         keys.get(keys.put(Keys.JUMP, false));  
  57.     }  
  58.   
  59.     public void fireReleased() {  
  60.         keys.get(keys.put(Keys.FIRE, false));  
  61.     }  
  62.       
  63.     public void update(float delta){  
  64.         processInput();  
  65.         bob.update(delta);  
  66.     }  
  67.       
  68.     private void processInput(){  
  69.         if(keys.get(Keys.LEFT)){  
  70.             bob.setFacingLeft(true);  
  71.             bob.setState(State.WALKING);  
  72.             bob.getVelocity().x=-Bob.SPEED;  
  73.         }  
  74.         if (keys.get(Keys.RIGHT)) {  
  75.             // left is pressed  
  76.             bob.setFacingLeft(false);  
  77.             bob.setState(State.WALKING);  
  78.             bob.getVelocity().x = Bob.SPEED;  
  79.         }  
  80.         // need to check if both or none direction are pressed, then Bob is idle  
  81.         if ((keys.get(Keys.LEFT) && keys.get(Keys.RIGHT)) ||  
  82.                 (!keys.get(Keys.LEFT) && !(keys.get(Keys.RIGHT)))) {  
  83.             bob.setState(State.IDLE);  
  84.             // acceleration is 0 on the x  
  85.             bob.getAcceleration().x = 0;  
  86.             // horizontal speed is 0  
  87.             bob.getVelocity().x = 0;  
  88.         }  
  89.     }  
  90. }  

屏幕

mvc都有了,接下来就可以屏幕了。
一个游戏通常有很多屏幕,欢迎屏幕,游戏屏幕,参数设置屏幕,游戏结束屏幕等等。
今天我们之做一个游戏屏幕。
在screens包里添加GameScreen类。
[java] view plain copy
  1. package com.me.testgdxgame.screens;  
  2.   
  3.   
  4. import com.badlogic.gdx.Gdx;  
  5. import com.badlogic.gdx.Input.Keys;  
  6. import com.badlogic.gdx.InputProcessor;  
  7. import com.badlogic.gdx.Screen;  
  8. import com.badlogic.gdx.graphics.GL10;  
  9. import com.me.testgdxgame.controller.WorldController;  
  10. import com.me.testgdxgame.model.World;  
  11. import com.me.testgdxgame.view.WorldRenderer;  
  12.   
  13. public class GameScreen implements Screen ,InputProcessor{  
  14.   
  15.     private WorldRenderer renderer;  
  16.     private World world;  
  17.     private WorldController controller;  
  18.       
  19.     private int width, height;  
  20.   
  21.     @Override  
  22.     public void render(float delta) {  
  23.         // TODO Auto-generated method stub  
  24.           
  25.         Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1);  
  26.         Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);  
  27.           
  28.         controller.update(delta);  
  29.         renderer.render();  
  30.   
  31.     }  
  32.   
  33.     @Override  
  34.     public void resize(int width, int height) {  
  35.         // TODO Auto-generated method stub  
  36.         renderer.setSize(width, height);  
  37.         this.width=width;  
  38.         this.height=height;  
  39.     }  
  40.   
  41.     @Override  
  42.     public void show() {  
  43.         // TODO Auto-generated method stub  
  44.         world = new World();  
  45.         renderer = new WorldRenderer(world);  
  46.         controller=new WorldController(world);  
  47.         Gdx.input.setInputProcessor(this);  
  48.     }  
  49.   
  50.     @Override  
  51.     public void hide() {  
  52.         // TODO Auto-generated method stub  
  53.         Gdx.input.setInputProcessor(null);  
  54.     }  
  55.   
  56.     @Override  
  57.     public void pause() {  
  58.         // TODO Auto-generated method stub  
  59.     }  
  60.   
  61.     @Override  
  62.     public void resume() {  
  63.         // TODO Auto-generated method stub  
  64.     }  
  65.   
  66.     @Override  
  67.     public void dispose() {  
  68.         // TODO Auto-generated method stub  
  69.         Gdx.input.setInputProcessor(null);  
  70.     }  
  71.   
  72.     @Override  
  73.     public boolean keyDown(int keycode) {  
  74.         // TODO Auto-generated method stub  
  75.         if (keycode == Keys.LEFT)  
  76.             controller.leftPressed();  
  77.         if (keycode == Keys.RIGHT)  
  78.             controller.rightPressed();  
  79.         if (keycode == Keys.Z)  
  80.             controller.jumpPressed();  
  81.         if (keycode == Keys.X)  
  82.             controller.firePressed();  
  83.         return true;  
  84.     }  
  85.   
  86.     @Override  
  87.     public boolean keyUp(int keycode) {  
  88.         // TODO Auto-generated method stub  
  89.         if (keycode == Keys.LEFT)  
  90.             controller.leftReleased();  
  91.         if (keycode == Keys.RIGHT)  
  92.             controller.rightReleased();  
  93.         if (keycode == Keys.Z)  
  94.             controller.jumpReleased();  
  95.         if (keycode == Keys.X)  
  96.             controller.fireReleased();  
  97.         return true;  
  98.     }  
  99.   
  100.     @Override  
  101.     public boolean keyTyped(char character) {  
  102.         // TODO Auto-generated method stub  
  103.         return false;  
  104.     }  
  105.   
  106.     @Override  
  107.     public boolean touchDown(int x, int y, int pointer, int button) {  
  108.         // TODO Auto-generated method stub  
  109.         if (x < width / 2 && y > height / 2) {  
  110.             controller.leftPressed();  
  111.         }  
  112.         if (x > width / 2 && y > height / 2) {  
  113.             controller.rightPressed();  
  114.         }  
  115.         return false;  
  116.     }  
  117.   
  118.     @Override  
  119.     public boolean touchUp(int x, int y, int pointer, int button) {  
  120.         // TODO Auto-generated method stub  
  121.         if (x < width / 2 && y > height / 2) {  
  122.             controller.leftReleased();  
  123.         }  
  124.         if (x > width / 2 && y > height / 2) {  
  125.             controller.rightReleased();  
  126.         }  
  127.         return true;  
  128.     }  
  129.   
  130.     @Override  
  131.     public boolean touchDragged(int screenX, int screenY, int pointer) {  
  132.         // TODO Auto-generated method stub  
  133.         return false;  
  134.     }  
  135.   
  136.     @Override  
  137.     public boolean mouseMoved(int screenX, int screenY) {  
  138.         // TODO Auto-generated method stub  
  139.         return false;  
  140.     }  
  141.   
  142.     @Override  
  143.     public boolean scrolled(int amount) {  
  144.         // TODO Auto-generated method stub  
  145.         return false;  
  146.     }  
  147. }  

这个类实现了Screen ,InputProcessor接口。
InputProcessor用于接受触控事件和按键事件。

部署

desktop版本的项目基本不用修改。main函数如下:
[java] view plain copy
  1. public class Main {  
  2.     public static void main(String[] args) {  
  3.         LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();  
  4.         cfg.title = "test-gdx-game";  
  5.         cfg.useGL20 = false;  
  6.         cfg.width = 1024;  
  7.         cfg.height = 600;  
  8.           
  9.         new LwjglApplication(new TestGdxGame(), cfg);  
  10.     }  
  11. }  

Main.java上右击->run as->java application.

android版本修改一下MainActiviy:
[java] view plain copy
  1. public class MainActivity extends AndroidApplication {  
  2.     @Override  
  3.     public void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.           
  6.         AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();  
  7.         cfg.useGL20 = false;  
  8.           
  9.         initialize(new TestGdxGame(), cfg);  
  10.     }  
  11. }  

run as->android application.

总结

到现在,舞台和代码框架已经搭建好了,后面还可以添加下面的东西:

.地行碰撞
.动画
.高等级的camera(视角可以不断变化)
.音效
.改进的输入
.更多的GameScreen

一起期待下一篇教程。


如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引