提要

有了前面的学习基础,我们就可以开始开发游戏了,当然,现阶段只是学习为主。

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

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

Android游戏开发十日通(3)-创建第一个Android游戏_Android


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

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

Android游戏开发十日通(3)-创建第一个Android游戏_Android_02


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


搭建工程

偷懒的话直接用上次创建的工程就ok,如果想重新创建的话,用gdx-setup-ui也很快。
在eclipse中导入三个工程。
看起来就像这样:
Android游戏开发十日通(3)-创建第一个Android游戏_Android_03

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

这里用到了一些MVC的知识,简单提一下:
MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部分分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组:
(控制器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<Block> blocks = new ArrayList(); 	/** Our player controlled hero **/ 	Bob bob;  	// Getters ----------- 	public ArrayList<Block>  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游戏开发十日通(3)-创建第一个Android游戏_Android_04Android游戏开发十日通(3)-创建第一个Android游戏_Android_05
放到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()可以用于不添加纹理的时候绘制,绘制的结果如下:

Android游戏开发十日通(3)-创建第一个Android游戏_Android_06

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,Boolean> keys = new HashMap<WorldController.Keys,Boolean>(); 	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

一起期待下一篇教程。