提要
有了前面的学习基础,我们就可以开始开发游戏了,当然,现阶段只是学习为主。
下面要做的一个游戏叫做 Star Guard,一款非常棒的独立游戏,画面非常有爱,难度不小,不过有无限生命可以玩。
键盘的上下左右控制小人,x开火,z跳跃。
今天我们要做的就是搭建舞台,创建项目的骨架。
最终的效果看起来是这样:
当然,可以同时部署在手机和PC上。
搭建工程
(控制器Controller)- 负责转发请求,对请求进行处理。
(视图View) - 界面设计人员进行图形界面设计。
(模型Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
编码
model
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用于后面的碰撞检测。
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就有很多属性了,速度,位置什么的,代码也很简单。
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
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(); } }
有几个类简单解释一下:
Controller
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; } } }
屏幕
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接口。
部署
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.
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); } }