Add GameDataComponent which holds current game's data (score, lives, etc)

Now more like an actual game. Score, lives, and when you die, you get
respawned. If you die rnough times, you Game Over, and your lives and
score are reset. Each time you get 10000 points, you gain a life!

Add GameMode enum
Add GameDataSystem which manipulates the current game data based on events.
Add GameDataRenderSystem which renders the current game data.

Add AsteroidHitEvent and PlayerDeathEvent which are are fired when an
asteroid is destroyed or the player is killed, respectively.

Add SpriteBatch to Graphics
This commit is contained in:
Matt Low 2020-01-30 23:56:00 +04:00
parent c3b806f79f
commit fa809148a7
9 changed files with 274 additions and 5 deletions

View File

@ -17,4 +17,6 @@ public class Constants {
public static final float ASTEROID_SPAWN_DELAY = 1f;
public static final int ASTEROID_SPAWN_COUNT = 4;
public static final int NEW_LIFE_SCORE = 10000;
}

View File

@ -0,0 +1,24 @@
package com.me.asteroids;
public enum GameMode {
PLAYING,
DIED(3f),
GAME_OVER(4f),
;
private final float modeTimer;
GameMode() {
this(0f);
}
GameMode(float modeTimer) {
this.modeTimer = modeTimer;
}
public float getTimer() {
return modeTimer;
}
}

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
@ -17,6 +18,7 @@ public class Graphics {
private Viewport viewport;
private ShapeRenderer shapeRenderer;
private SpriteBatch spriteBatch;
public Graphics(int worldWidth, int worldHeight) {
this.worldWidth = worldWidth;
@ -28,6 +30,7 @@ public class Graphics {
this.viewport = new FitViewport(worldWidth, worldHeight, camera);
this.shapeRenderer = new ShapeRenderer();
this.spriteBatch = new SpriteBatch();
}
public void initialize() {
@ -53,6 +56,10 @@ public class Graphics {
return shapeRenderer;
}
public SpriteBatch getSpriteBatch() {
return spriteBatch;
}
private void updateDimensions() {
viewport.setWorldSize(worldWidth, worldHeight);
viewport.update(screenWidth, screenHeight, true);

View File

@ -0,0 +1,28 @@
package com.me.asteroids.components;
import com.me.asteroids.GameMode;
import com.me.common.ecs.Component;
public class GameDataComponent implements Component {
public int score;
public int lives;
public int newLifeScore;
public GameMode gameMode;
public float gameModeTimer;
public GameDataComponent() {
reset();
}
public void reset() {
this.score = 0;
this.lives = 3;
this.newLifeScore = 0;
this.gameMode = GameMode.PLAYING;
this.gameModeTimer = 0f;
}
}

View File

@ -0,0 +1,14 @@
package com.me.asteroids.events;
import com.me.asteroids.components.AsteroidComponent;
import com.me.common.ecs.event.Event;
public class AsteroidHitEvent extends Event {
public AsteroidComponent asteroid;
public AsteroidHitEvent(AsteroidComponent asteroid) {
this.asteroid = asteroid;
}
}

View File

@ -0,0 +1,14 @@
package com.me.asteroids.events;
import com.me.common.ecs.Entity;
import com.me.common.ecs.event.Event;
public class PlayerDeathEvent extends Event {
public Entity player;
public PlayerDeathEvent(Entity player) {
this.player = player;
}
}

View File

@ -14,17 +14,22 @@ import com.me.asteroids.components.BulletComponent;
import com.me.asteroids.components.ColliderComponent;
import com.me.asteroids.components.DebrisComponent;
import com.me.asteroids.components.DecayComponent;
import com.me.asteroids.components.GameDataComponent;
import com.me.asteroids.components.ModelComponent;
import com.me.asteroids.components.PlayerComponent;
import com.me.asteroids.components.PositionComponent;
import com.me.asteroids.components.VelocityComponent;
import com.me.asteroids.components.model.PolygonModel;
import com.me.asteroids.events.AsteroidHitEvent;
import com.me.asteroids.events.BulletAsteroidCollisionEvent;
import com.me.asteroids.events.PlayerASteroidCollisionEvent;
import com.me.asteroids.events.PlayerDeathEvent;
import com.me.asteroids.events.ScreenWrapEvent;
import com.me.asteroids.systems.AsteroidSpawningSystem;
import com.me.asteroids.systems.CollisionSystem;
import com.me.asteroids.systems.DecaySystem;
import com.me.asteroids.systems.GameDataRenderSystem;
import com.me.asteroids.systems.GameDataSystem;
import com.me.asteroids.systems.ModelRenderSystem;
import com.me.asteroids.systems.MovementSystem;
import com.me.asteroids.systems.PlayerInputSystem;
@ -53,10 +58,10 @@ public class GameScreen extends Screen implements Listener {
public void setup() {
batch = new SpriteBatch();
font = new BitmapFont();
font.setColor(Color.RED);
engine = new Engine();
engine.registerComponentClass(GameDataComponent.class);
engine.registerComponentClass(PlayerComponent.class);
engine.registerComponentClass(BulletComponent.class);
engine.registerComponentClass(AsteroidComponent.class);
@ -68,6 +73,9 @@ public class GameScreen extends Screen implements Listener {
engine.registerComponentClass(AccelerationComponent.class);
engine.registerComponentClass(ModelComponent.class);
GameDataSystem system = new GameDataSystem(engine);
engine.registerSystem(system);
engine.registerSystem(new PlayerInputSystem(engine));
engine.registerSystem(new AsteroidSpawningSystem(engine));
engine.registerSystem(new DecaySystem(engine));
@ -75,11 +83,17 @@ public class GameScreen extends Screen implements Listener {
engine.registerSystem(new CollisionSystem(engine));
engine.registerSystem(new ScreenWrapSystem(engine));
engine.registerSystem(new ModelRenderSystem(engine, graphics));
engine.registerSystem(new GameDataRenderSystem(engine, graphics, font));
engine.registerListener(system);
engine.registerListener(this.new EventListener(engine));
engine.ready();
Entity gameData = engine.createEntity();
gameData.addComponent(new GameDataComponent());
gameData.activate();
Entity player = EntityFactory.createPlayer(engine);
player.activate();
}
@ -91,7 +105,8 @@ public class GameScreen extends Screen implements Listener {
if (Constants.DEBUG) {
batch.begin();
font.draw(batch, String.format("FPS: %d, Entities: %d", Gdx.graphics.getFramesPerSecond(), engine.getEntityCount()), 50, 50);
font.setColor(Color.RED);
font.draw(batch, String.format("FPS: %d, Entities: %d", Gdx.graphics.getFramesPerSecond(), engine.getEntityCount()), 15, 15 + font.getCapHeight());
batch.end();
}
}
@ -134,9 +149,10 @@ public class GameScreen extends Screen implements Listener {
if (model.contains(bulletPosition)) {
// AABBs intersect but let's only consider it a hit if the bullet's position
// is actually inside the asteroid
AsteroidComponent asteroid = asteroidMapper.get(event.getAsteroid());
engine.callEvent(new AsteroidHitEvent(asteroid));
event.getBullet().remove();
int generation = asteroidMapper.get(event.getAsteroid()).generation;
if (generation < 2) {
if (asteroid.generation < 2) {
for (Entity shard : EntityFactory.splitAsteroidIntoChunks(engine, event.getAsteroid(), 2, 2/3f)) {
shard.activate();
}
@ -146,7 +162,6 @@ public class GameScreen extends Screen implements Listener {
}
}
event.getAsteroid().remove();
}
}
@ -156,6 +171,7 @@ public class GameScreen extends Screen implements Listener {
PolygonModel player = (PolygonModel) modelMapper.get(event.getPlayer()).model;
if (asteroid.contains(player.getVertices()) || player.contains(asteroid.getVertices())) {
engine.callEvent(new PlayerDeathEvent(event.getPlayer()));
event.getPlayer().deactivate();
for (Entity debris : EntityFactory.createDebris(engine, event.getPlayer())) {
debris.activate();

View File

@ -0,0 +1,62 @@
package com.me.asteroids.systems;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.me.asteroids.Constants;
import com.me.asteroids.Graphics;
import com.me.asteroids.components.GameDataComponent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem;
public class GameDataRenderSystem extends EntitySystem {
private ComponentMapper<GameDataComponent> gameDataMapper;
private SpriteBatch batch;
private BitmapFont font;
private GlyphLayout gameOverLayout;
private GameDataComponent gameData;
public GameDataRenderSystem(Engine engine, Graphics graphics, BitmapFont font) {
super(engine, GameDataComponent.class);
gameDataMapper = engine.getComponentMapper(GameDataComponent.class);
batch = graphics.getSpriteBatch();
this.font = font;
this.gameOverLayout = new GlyphLayout(font, "GAME OVER");
}
@Override
public void preProcess() {
batch.begin();
}
@Override
public void processEntity(Entity entity, float dt) {
if(gameData == null) {
gameData = gameDataMapper.get(entity);
}
switch (gameData.gameMode) {
case GAME_OVER:
font.setColor(Color.RED);
font.draw(batch, "GAME OVER", Constants.HALF_WIDTH - (gameOverLayout.width / 2), Constants.HALF_HEIGHT);
break;
default:
font.setColor(Color.CHARTREUSE);
font.draw(batch, "Score: " + gameData.score, 15, Constants.HEIGHT - 15);
font.draw(batch, "Lives: " + gameData.lives, 15, Constants.HEIGHT - 30);
break;
}
}
@Override
public void postProcess() {
batch.end();
}
}

View File

@ -0,0 +1,102 @@
package com.me.asteroids.systems;
import com.me.asteroids.Constants;
import com.me.asteroids.GameMode;
import com.me.asteroids.components.GameDataComponent;
import com.me.asteroids.components.PositionComponent;
import com.me.asteroids.components.VelocityComponent;
import com.me.asteroids.events.AsteroidHitEvent;
import com.me.asteroids.events.PlayerDeathEvent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem;
import com.me.common.ecs.event.EventHandler;
import com.me.common.ecs.event.Listener;
public class GameDataSystem extends EntitySystem implements Listener {
private ComponentMapper<GameDataComponent> gameDataMapper;
private GameDataComponent gameData;
public GameDataSystem(Engine engine) {
super(engine, GameDataComponent.class);
gameDataMapper = engine.getComponentMapper(GameDataComponent.class);
}
@Override
public void processEntity(Entity entity, float dt) {
if(gameData == null) {
gameData = gameDataMapper.get(entity);
}
if (gameData.gameModeTimer > 0 && (gameData.gameModeTimer -= dt) < 0) {
switch (gameData.gameMode) {
case DIED:
if (--gameData.lives >= 0) {
resetPlayer();
setGameMode(GameMode.PLAYING);
} else {
setGameMode(GameMode.GAME_OVER);
}
break;
case GAME_OVER:
gameData.reset();
resetPlayer();
setGameMode(GameMode.PLAYING);
}
}
}
@EventHandler
public void onAsteroidHit(AsteroidHitEvent event) {
if (gameData == null) {
return;
}
switch (event.asteroid.generation) {
case 0:
gameData.score += 20;
gameData.newLifeScore += 20;
break;
case 1:
gameData.score += 50;
gameData.newLifeScore += 50;
break;
default:
gameData.score += 100;
gameData.newLifeScore += 100;
}
if (gameData.newLifeScore >= Constants.NEW_LIFE_SCORE) {
gameData.newLifeScore -= Constants.NEW_LIFE_SCORE;
gameData.lives++;
}
}
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
if (gameData == null) {
return;
}
setGameMode(GameMode.DIED);
}
private void resetPlayer() {
Entity player = engine.getEntities().get(1);
PositionComponent position = player.getComponent(PositionComponent.class);
position.rotation = 90;
position.position.set(Constants.HALF_WIDTH, Constants.HALF_HEIGHT);
player.getComponent(VelocityComponent.class).velocity.set(0, 0);
player.activate();
}
private void setGameMode(GameMode mode) {
gameData.gameMode = mode;
gameData.gameModeTimer = mode.getTimer();
}
}