Compare commits

...

16 Commits

Author SHA1 Message Date
9850b52d84 Make afterburnerModel cyan 2020-02-02 02:05:30 +04:00
5c11df128f Change MovementSystem logic + others
- When acceleration results in a velocity vector in the opposite direction
of the current velocity, zero out velocity

- Moved maxVelocity from VelocityComponent to AccelerationComponent

-
2020-02-02 01:59:49 +04:00
3cf1d19c4d Use ComponentMappers everywhere else 2020-02-01 21:51:54 +04:00
84a353cfd2 Rename tag compontents to make it obvious they're for tagging 2020-02-01 21:15:57 +04:00
f4508fa0da Utilize a PLAYER tag and ASTEROIDS group
User PLAYER tag for access to the player entity
Use ASTEROIDS group to count how many asteroids there are
2020-02-01 20:40:39 +04:00
d8cabf4549 Support Entity tagging 2020-02-01 20:34:37 +04:00
f8f7868398 Add entity grouping. 2020-02-01 20:21:34 +04:00
749e378a03 Add constructors to Components, useful for more terse entity creation 2020-02-01 17:04:06 +04:00
69d5caf24d Move all ComponentMappers to Components as public static constants 2020-02-01 16:05:12 +04:00
6ae11ef2d3 ECS refactoring (breaks game code)
- Remove requirement of an Engine instance in ComponentMapper instances,
as the Engine from the entity passed to get() can be used. Allows for
static initialization of ComponentMappers.

- Store instances of ComponentMapper in a static Map within the
ComponentMapper class itself, rather that inside of Engine.

- Name bit/bits consistently. Some cases of bits were renamed to bit,
such as ComponentType getBits() is now getBit(). Change usage of Mask to
just Bits for consistency.

- Modify access modifiers and field finality where appropriate

- Add comments to public methods in Engine
2020-02-01 15:55:54 +04:00
cc53757a37 Don't activate/deactivate/remove entities when not needed 2020-01-31 21:31:33 +04:00
b389cb9c2b Add afterburner. 2020-01-31 21:30:33 +04:00
06d6a9630c Add new font 2020-01-31 20:27:53 +04:00
26049488ae Scale world units from banana units (800x600) to something smaller.
Let's just call them meters.

Get rid of Viewport and handle world scaling based on screen height:
- Keep world height at a constant 10 meters and scale width according
to screen dimensions.

Add uiSpriteBatch used for rendering UI elements at appropriate scale
2020-01-31 20:25:49 +04:00
5f846f00b0 Have entity.remove() set active = false
Removes the need to check if an entity hasn't been removed prior to
processing.
2020-01-31 13:00:24 +04:00
fa809148a7 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
2020-01-30 23:59:58 +04:00
42 changed files with 1191 additions and 456 deletions

View File

@ -0,0 +1,102 @@
info face="Retro Gaming" size=32 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-1,-1
common lineHeight=42 base=32 scaleW=256 scaleH=256 pages=1 packed=0
page id=0 file="Retro_Font.png"
chars count=97
char id=0 x=0 y=0 width=15 height=43 xoffset=4 yoffset=-1 xadvance=20 page=0 chnl=0
char id=10 x=0 y=0 width=15 height=43 xoffset=4 yoffset=-1 xadvance=20 page=0 chnl=0
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=31 xadvance=13 page=0 chnl=0
char id=33 x=246 y=69 width=8 height=26 xoffset=2 yoffset=7 xadvance=11 page=0 chnl=0
char id=34 x=218 y=173 width=17 height=11 xoffset=2 yoffset=7 xadvance=20 page=0 chnl=0
char id=35 x=22 y=147 width=31 height=26 xoffset=2 yoffset=7 xadvance=34 page=0 chnl=0
char id=36 x=15 y=0 width=22 height=38 xoffset=2 yoffset=1 xadvance=25 page=0 chnl=0
char id=37 x=0 y=147 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=38 x=53 y=147 width=26 height=26 xoffset=2 yoffset=7 xadvance=29 page=0 chnl=0
char id=39 x=242 y=147 width=8 height=11 xoffset=2 yoffset=7 xadvance=11 page=0 chnl=0
char id=40 x=238 y=95 width=14 height=26 xoffset=2 yoffset=7 xadvance=17 page=0 chnl=0
char id=41 x=110 y=121 width=14 height=26 xoffset=2 yoffset=7 xadvance=17 page=0 chnl=0
char id=42 x=193 y=173 width=25 height=17 xoffset=2 yoffset=10 xadvance=28 page=0 chnl=0
char id=43 x=174 y=173 width=19 height=17 xoffset=2 yoffset=11 xadvance=22 page=0 chnl=0
char id=44 x=0 y=190 width=11 height=11 xoffset=-1 yoffset=25 xadvance=11 page=0 chnl=0
char id=45 x=77 y=190 width=14 height=5 xoffset=2 yoffset=17 xadvance=17 page=0 chnl=0
char id=46 x=69 y=190 width=8 height=8 xoffset=2 yoffset=25 xadvance=11 page=0 chnl=0
char id=47 x=180 y=121 width=23 height=26 xoffset=2 yoffset=7 xadvance=26 page=0 chnl=0
char id=48 x=66 y=121 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=49 x=114 y=95 width=14 height=26 xoffset=2 yoffset=7 xadvance=17 page=0 chnl=0
char id=50 x=128 y=95 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=51 x=150 y=95 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=52 x=172 y=95 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=53 x=194 y=95 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=54 x=216 y=95 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=55 x=0 y=121 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=56 x=22 y=121 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=57 x=44 y=121 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=58 x=124 y=147 width=8 height=20 xoffset=2 yoffset=13 xadvance=11 page=0 chnl=0
char id=59 x=79 y=147 width=11 height=23 xoffset=-1 yoffset=13 xadvance=11 page=0 chnl=0
char id=60 x=90 y=147 width=17 height=23 xoffset=2 yoffset=10 xadvance=20 page=0 chnl=0
char id=61 x=33 y=190 width=17 height=11 xoffset=2 yoffset=16 xadvance=20 page=0 chnl=0
char id=62 x=107 y=147 width=17 height=23 xoffset=2 yoffset=10 xadvance=20 page=0 chnl=0
char id=63 x=88 y=121 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=64 x=203 y=121 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=65 x=62 y=0 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=66 x=84 y=0 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=67 x=106 y=0 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=68 x=128 y=0 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=69 x=150 y=0 width=23 height=26 xoffset=2 yoffset=7 xadvance=26 page=0 chnl=0
char id=70 x=173 y=0 width=23 height=26 xoffset=2 yoffset=7 xadvance=26 page=0 chnl=0
char id=71 x=196 y=0 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=72 x=218 y=0 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=73 x=0 y=43 width=19 height=26 xoffset=2 yoffset=7 xadvance=22 page=0 chnl=0
char id=74 x=19 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=75 x=41 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=76 x=63 y=43 width=20 height=26 xoffset=2 yoffset=7 xadvance=23 page=0 chnl=0
char id=77 x=83 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=78 x=105 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=79 x=127 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=80 x=149 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=81 x=171 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=82 x=193 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=83 x=215 y=43 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=84 x=0 y=69 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=85 x=22 y=69 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=86 x=44 y=69 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=87 x=66 y=69 width=25 height=26 xoffset=2 yoffset=7 xadvance=28 page=0 chnl=0
char id=88 x=91 y=69 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=89 x=113 y=69 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=90 x=135 y=69 width=23 height=26 xoffset=2 yoffset=7 xadvance=26 page=0 chnl=0
char id=91 x=124 y=121 width=14 height=26 xoffset=2 yoffset=7 xadvance=17 page=0 chnl=0
char id=92 x=225 y=121 width=23 height=26 xoffset=2 yoffset=7 xadvance=26 page=0 chnl=0
char id=93 x=138 y=121 width=14 height=26 xoffset=2 yoffset=7 xadvance=17 page=0 chnl=0
char id=94 x=11 y=190 width=22 height=11 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=95 x=91 y=190 width=17 height=5 xoffset=2 yoffset=28 xadvance=20 page=0 chnl=0
char id=96 x=235 y=173 width=14 height=11 xoffset=2 yoffset=7 xadvance=17 page=0 chnl=0
char id=97 x=132 y=147 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=98 x=158 y=69 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=99 x=154 y=147 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=100 x=180 y=69 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=101 x=176 y=147 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=102 x=237 y=43 width=17 height=26 xoffset=2 yoffset=7 xadvance=20 page=0 chnl=0
char id=103 x=202 y=69 width=22 height=26 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=104 x=224 y=69 width=22 height=26 xoffset=2 yoffset=7 xadvance=25 page=0 chnl=0
char id=105 x=240 y=0 width=8 height=26 xoffset=2 yoffset=7 xadvance=11 page=0 chnl=0
char id=106 x=45 y=0 width=17 height=29 xoffset=2 yoffset=7 xadvance=20 page=0 chnl=0
char id=107 x=0 y=95 width=20 height=26 xoffset=2 yoffset=7 xadvance=23 page=0 chnl=0
char id=108 x=20 y=95 width=11 height=26 xoffset=2 yoffset=7 xadvance=14 page=0 chnl=0
char id=109 x=198 y=147 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=110 x=220 y=147 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=111 x=0 y=173 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=112 x=31 y=95 width=22 height=26 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=113 x=53 y=95 width=22 height=26 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=114 x=22 y=173 width=20 height=17 xoffset=2 yoffset=16 xadvance=23 page=0 chnl=0
char id=115 x=42 y=173 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=116 x=75 y=95 width=17 height=26 xoffset=2 yoffset=7 xadvance=20 page=0 chnl=0
char id=117 x=64 y=173 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=118 x=86 y=173 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=119 x=108 y=173 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=120 x=130 y=173 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=121 x=92 y=95 width=22 height=26 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=122 x=152 y=173 width=22 height=17 xoffset=2 yoffset=16 xadvance=25 page=0 chnl=0
char id=123 x=152 y=121 width=14 height=26 xoffset=2 yoffset=7 xadvance=17 page=0 chnl=0
char id=124 x=37 y=0 width=8 height=32 xoffset=2 yoffset=7 xadvance=11 page=0 chnl=0
char id=125 x=166 y=121 width=14 height=26 xoffset=2 yoffset=7 xadvance=17 page=0 chnl=0
char id=126 x=50 y=190 width=19 height=11 xoffset=2 yoffset=13 xadvance=22 page=0 chnl=0
kernings count=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -12,8 +12,9 @@ public class Asteroids extends ApplicationAdapter {
@Override @Override
public void create() { public void create() {
graphics = new Graphics(Constants.WIDTH, Constants.HEIGHT); graphics = new Graphics();
graphics.initialize(); graphics.initialize();
updateViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
game = new Game(); game = new Game();
game.setNextScreen(new GameScreen(graphics)); game.setNextScreen(new GameScreen(graphics));
@ -33,7 +34,12 @@ public class Asteroids extends ApplicationAdapter {
@Override @Override
public void resize(int width, int height) { public void resize(int width, int height) {
graphics.setScreenSize(width, height); updateViewport(width, height);
}
private void updateViewport(int width, int height) {
Constants.updateDimensions(width, height);
graphics.updateViewport(Constants.WIDTH, Constants.HEIGHT, Constants.getUIWidth(), Constants.getUIHeight());
} }
} }

View File

@ -0,0 +1,44 @@
package com.me.asteroids;
import com.me.asteroids.components.AccelerationComponent;
import com.me.asteroids.components.AsteroidComponent;
import com.me.asteroids.components.BulletTagComponent;
import com.me.asteroids.components.ColliderTagComponent;
import com.me.asteroids.components.DebrisTagComponent;
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.common.ecs.ComponentMapper;
public class Components {
public static final ComponentMapper<AccelerationComponent> ACCELERATION;
public static final ComponentMapper<AsteroidComponent> ASTEROID;
public static final ComponentMapper<BulletTagComponent> BULLET;
public static final ComponentMapper<ColliderTagComponent> COLLIDER;
public static final ComponentMapper<DebrisTagComponent> DEBRIS;
public static final ComponentMapper<DecayComponent> DECAY;
public static final ComponentMapper<GameDataComponent> GAME_DATA;
public static final ComponentMapper<ModelComponent> MODEL;
public static final ComponentMapper<PlayerComponent> PLAYER;
public static final ComponentMapper<PositionComponent> POSITION;
public static final ComponentMapper<VelocityComponent> VELOCITY;
static {
ACCELERATION = ComponentMapper.getFor(AccelerationComponent.class);
ASTEROID = ComponentMapper.getFor(AsteroidComponent.class);
BULLET = ComponentMapper.getFor(BulletTagComponent.class);
COLLIDER = ComponentMapper.getFor(ColliderTagComponent.class);
DEBRIS = ComponentMapper.getFor(DebrisTagComponent.class);
DECAY = ComponentMapper.getFor(DecayComponent.class);
GAME_DATA = ComponentMapper.getFor(GameDataComponent.class);
MODEL = ComponentMapper.getFor(ModelComponent.class);
PLAYER = ComponentMapper.getFor(PlayerComponent.class);
POSITION = ComponentMapper.getFor(PositionComponent.class);
VELOCITY = ComponentMapper.getFor(VelocityComponent.class);
}
}

View File

@ -2,19 +2,50 @@ package com.me.asteroids;
import com.me.common.Random; import com.me.common.Random;
public class Constants { public final class Constants {
public static final boolean DEBUG = false; public static final boolean DEBUG = false;
public static final Random rand = new Random(); public static final Random rand = new Random();
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static final int HALF_WIDTH = WIDTH / 2;
public static final int HALF_HEIGHT = HEIGHT / 2;
public static final float ASTEROID_SPAWN_DELAY = 1f; public static final float ASTEROID_SPAWN_DELAY = 1f;
public static final int ASTEROID_SPAWN_COUNT = 4; public static final int ASTEROID_SPAWN_COUNT = 4;
public static final int NEW_LIFE_SCORE = 10000;
public static int SCREEN_WIDTH;
public static int SCREEN_HEIGHT;
public static final float HEIGHT = 10f;
public static float WIDTH;
public static final float HALF_HEIGHT = HEIGHT / 2;
public static float HALF_WIDTH;
public static float getUIWidth() {
return SCREEN_WIDTH * getUIScale();
}
public static float getUIHeight() {
return SCREEN_HEIGHT * getUIScale();
}
public static float getHalfUIWidth() {
return getUIWidth() / 2;
}
public static float getHalfUIHeight() {
return getUIHeight() / 2;
}
public static float getUIScale() {
return 1.5f / (Math.min(SCREEN_HEIGHT, SCREEN_WIDTH) / 400f);
}
public static void updateDimensions(int screenWidth, int screenHeight) {
SCREEN_WIDTH = screenWidth;
SCREEN_HEIGHT = screenHeight;
WIDTH = (HEIGHT / SCREEN_HEIGHT) * SCREEN_WIDTH;
HALF_WIDTH = WIDTH / 2;
}
} }

View File

@ -5,9 +5,9 @@ import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.me.asteroids.components.AccelerationComponent; import com.me.asteroids.components.AccelerationComponent;
import com.me.asteroids.components.AsteroidComponent; import com.me.asteroids.components.AsteroidComponent;
import com.me.asteroids.components.BulletComponent; import com.me.asteroids.components.BulletTagComponent;
import com.me.asteroids.components.ColliderComponent; import com.me.asteroids.components.ColliderTagComponent;
import com.me.asteroids.components.DebrisComponent; import com.me.asteroids.components.DebrisTagComponent;
import com.me.asteroids.components.DecayComponent; import com.me.asteroids.components.DecayComponent;
import com.me.asteroids.components.ModelComponent; import com.me.asteroids.components.ModelComponent;
import com.me.asteroids.components.PlayerComponent; import com.me.asteroids.components.PlayerComponent;
@ -21,6 +21,10 @@ import com.me.common.ecs.Entity;
import java.util.Arrays; import java.util.Arrays;
import static com.me.asteroids.Components.ASTEROID;
import static com.me.asteroids.Components.MODEL;
import static com.me.asteroids.Components.POSITION;
import static com.me.asteroids.Components.VELOCITY;
import static com.me.asteroids.Constants.rand; import static com.me.asteroids.Constants.rand;
public class EntityFactory { public class EntityFactory {
@ -30,7 +34,9 @@ public class EntityFactory {
private static final Vector2 tmpA = new Vector2(); private static final Vector2 tmpA = new Vector2();
private static final Vector2 tmpB = new Vector2(); private static final Vector2 tmpB = new Vector2();
private static final ColliderComponent COLLIDER = new ColliderComponent(); private static final ColliderTagComponent COLLIDER_TAG = new ColliderTagComponent();
private static final DebrisTagComponent DEBRIS_TAG = new DebrisTagComponent();
private static final BulletTagComponent BULLET_TAG = new BulletTagComponent();
private static Entity createEntity(Engine engine) { private static Entity createEntity(Engine engine) {
Entity entity = engine.createEntity(); Entity entity = engine.createEntity();
@ -41,35 +47,39 @@ public class EntityFactory {
} }
public static Entity createPlayer(Engine engine) { public static Entity createPlayer(Engine engine) {
PositionComponent position = new PositionComponent(); VelocityComponent velocity = new VelocityComponent(0f, 0f, 0f);
position.position = new Vector2(Constants.HALF_WIDTH, Constants.HALF_HEIGHT);
position.rotation = 90;
VelocityComponent velocity = new VelocityComponent(); ModelComponent playerModel = new ModelComponent(new PolygonModel(Color.WHITE));
velocity.velocity = new Vector2(0f, 0f); playerModel.setVertices(new float[]{
velocity.maxVelocity = 400f; 0f, 0.5f, // tip
-5/16f, -0.5f, // bottom left
ModelComponent model = new ModelComponent(); -1/8f, -5/16f, // indent
model.model = new PolygonModel(Color.WHITE); 1/8f, -5/16f, // indent
model.model.setVertices(new float[]{ 5/16f, -0.5f, // bottom right
0f, 4f, // tip
-2.5f, -4f, // bottom left
-1f, -2.5f, // indent
1f, -2.5f, // indent
2.5f, -4f, // bottom right
}); });
model.model.setScale(5);
AccelerationComponent accel = new AccelerationComponent(); ModelComponent afterburnerModel = new ModelComponent(new LineModel(Color.CYAN));
accel.acceleration = new Vector2(0, 1f); afterburnerModel.setVertices(new float[]{
-2/16f, -5/16f,
0f, -0.8f,
2/16f, -5/16f
});
Entity player = createEntity(engine); Entity player = createEntity(engine);
player.addComponent(position); Entity afterBurner = createEntity(engine);
player.addComponent(new PositionComponent(Constants.HALF_WIDTH, Constants.HALF_HEIGHT, 90));
player.addComponent(velocity); player.addComponent(velocity);
player.addComponent(model); player.addComponent(playerModel);
player.addComponent(accel); player.addComponent(new AccelerationComponent(10f));
player.addComponent(COLLIDER); player.addComponent(COLLIDER_TAG);
player.addComponent(new PlayerComponent()); player.addComponent(new PlayerComponent(afterBurner));
player.setTag("PLAYER");
afterBurner.addComponent(new PositionComponent(Constants.HALF_WIDTH, Constants.HALF_HEIGHT, 90));
afterBurner.addComponent(velocity);
afterBurner.addComponent(afterburnerModel);
return player; return player;
} }
@ -103,35 +113,31 @@ public class EntityFactory {
} }
public static Entity[] createDebris(Engine engine, Entity entity) { public static Entity[] createDebris(Engine engine, Entity entity) {
Vector2 playerVelocity = entity.getComponent(VelocityComponent.class).velocity; Vector2 playerVelocity = VELOCITY.get(entity).velocity;
PositionComponent playerPosition = entity.getComponent(PositionComponent.class); PositionComponent playerPosition = POSITION.get(entity);
LineModel[] models = getLineModels(entity.getComponent(ModelComponent.class).model); LineModel[] models = getLineModels(MODEL.get(entity).model);
Vector2 explosionCenter = tmp.set(playerPosition.position).sub(Utils.setUnitVectorAngle(tmp2, playerPosition.rotation).scl(5));
Vector2 explosionCenter = tmp.set(playerPosition.position).sub(Utils.setUnitVectorAngle(tmp2, playerPosition.rotation).scl(0.125f));
Entity[] entities = new Entity[models.length]; Entity[] entities = new Entity[models.length];
for (int i = 0, n = models.length; i < n; i++) { for (int i = 0, n = models.length; i < n; i++) {
ModelComponent model = new ModelComponent(); ModelComponent model = new ModelComponent(models[i]);
model.model = models[i];
PositionComponent position = new PositionComponent();
position.position = new Vector2(model.model.getPosition());
position.rotation = 90;
VelocityComponent velocity = new VelocityComponent(); VelocityComponent velocity = new VelocityComponent();
velocity.velocity = new Vector2(models[i].getPosition()) velocity.set(new Vector2(models[i].getPosition())
.sub(explosionCenter) .sub(explosionCenter)
.nor() // Direction from explosion center to center of piece .nor() // Direction from explosion center to center of piece
.rotate(rand.nextFloat(-15, 15)) // Slightly alter the direction each piece flies off in .rotate(rand.nextFloat(-15, 15)) // Slightly alter the direction each piece flies off in
.scl(rand.nextFloat(75, 100)) // Give each piece a slightly different speed .scl(rand.nextFloat(2f, 2.5f)) // Give each piece a slightly different speed
.add(tmp2.set(playerVelocity).scl(0.75f)); // Maintain 75% of the player's velocity at impact .add(tmp2.set(playerVelocity).scl(0.75f))); // Maintain 75% of the player's velocity at impact
velocity.angularVelocity = rand.nextFloat(-60, 60); // Make each piece spin at a different rate velocity.angularVelocity = rand.nextFloat(-60, 60); // Make each piece spin at a different rate
Entity debris = createEntity(engine); Entity debris = createEntity(engine);
debris.addComponent(position); debris.addComponent(new PositionComponent(new Vector2(model.getPosition()), 90));
debris.addComponent(velocity); debris.addComponent(velocity);
debris.addComponent(model); debris.addComponent(model);
debris.addComponent(DEBRIS_TAG);
debris.addComponent(new DecayComponent(rand.nextFloat(0.5f, 2.5f))); debris.addComponent(new DecayComponent(rand.nextFloat(0.5f, 2.5f)));
debris.addComponent(new DebrisComponent());
entities[i] = debris; entities[i] = debris;
} }
return entities; return entities;
@ -139,49 +145,37 @@ public class EntityFactory {
public static Entity createBullet(Engine engine, Entity player) { public static Entity createBullet(Engine engine, Entity player) {
float[] modelVertices = player.getComponent(ModelComponent.class).model.getVertices(); float[] modelVertices = MODEL.get(player).model.getVertices();
float rotation = player.getComponent(PositionComponent.class).rotation; float rotation = POSITION.get(player).rotation;
PositionComponent position = new PositionComponent(modelVertices[0], modelVertices[1], rotation);
ModelComponent model = new ModelComponent(new PolygonModel(Color.YELLOW));
model.setVertices(new float[]{
1/40f, 0f,
-1/40f, 0f,
-1/40f, -4/40f,
1/40f, -4/40f,
});
model.setRotation(position.rotation);
model.setPosition(position.position);
Vector2 direction = Utils.setUnitVectorAngle(tmp, rotation); Vector2 direction = Utils.setUnitVectorAngle(tmp, rotation);
VelocityComponent velocity = new VelocityComponent();
velocity.velocity = new Vector2(direction).scl(500);
PositionComponent position = new PositionComponent();
position.position = new Vector2(modelVertices[0], modelVertices[1]).add(direction.scl(4));
position.rotation = rotation;
ModelComponent model = new ModelComponent();
model.model = new PolygonModel(Color.YELLOW);
model.model.setVertices(new float[]{
1f, 0f,
-1f, 0f,
-1f, -4f,
1f, -4f,
});
model.model.setRotation(position.rotation);
model.model.setPosition(position.position);
Entity bullet = createEntity(engine); Entity bullet = createEntity(engine);
bullet.addComponent(position); bullet.addComponent(position);
bullet.addComponent(velocity); bullet.addComponent(new VelocityComponent(new Vector2(direction).scl(12.5f)));
bullet.addComponent(model); bullet.addComponent(model);
bullet.addComponent(COLLIDER); bullet.addComponent(COLLIDER_TAG);
bullet.addComponent(new BulletComponent()); bullet.addComponent(BULLET_TAG);
return bullet; return bullet;
} }
public static Entity createAsteroid(Engine engine) { public static Entity createAsteroid(Engine engine) {
// Creates an asteroid entity with position and velocity unset - the AsteroidSpawningSystem // Creates an asteroid entity with position and velocity unset - the AsteroidSpawningSystem
// is responsible for setting its position and velocity // is responsible for setting its position and velocity
PositionComponent position = new PositionComponent(); ModelComponent model = new ModelComponent(new PolygonModel(Color.WHITE));
VelocityComponent velocity = new VelocityComponent(); float size = rand.nextFloat(1f, 1.75f);
model.setVertices(new AsteroidFactory()
ModelComponent model = new ModelComponent();
model.model = new PolygonModel(Color.WHITE);
int size = rand.nextInt(45, 75);
model.model.setVertices(new AsteroidFactory()
.setVertexCount(32) .setVertexCount(32)
.setSize(size) .setSize(size)
.setSizeVariation(size * 0.5f) .setSizeVariation(size * 0.5f)
@ -189,11 +183,12 @@ public class EntityFactory {
.generate()); .generate());
Entity asteroid = createEntity(engine); Entity asteroid = createEntity(engine);
asteroid.addComponent(position); asteroid.addComponent(new PositionComponent());
asteroid.addComponent(velocity); asteroid.addComponent(new VelocityComponent());
asteroid.addComponent(model); asteroid.addComponent(model);
asteroid.addComponent(COLLIDER); asteroid.addComponent(COLLIDER_TAG);
asteroid.addComponent(new AsteroidComponent()); asteroid.addComponent(new AsteroidComponent());
asteroid.addGroup("ASTEROIDS");
return asteroid; return asteroid;
} }
@ -207,8 +202,8 @@ public class EntityFactory {
} }
public static Entity[] splitAsteroidIntoChunks(Engine engine, Entity asteroid, int chunkCount, float chunkScale) { public static Entity[] splitAsteroidIntoChunks(Engine engine, Entity asteroid, int chunkCount, float chunkScale) {
Vector2 asteroidVelocity = asteroid.getComponent(VelocityComponent.class).velocity; Vector2 asteroidVelocity = VELOCITY.get(asteroid).velocity;
Model asteroidModel = asteroid.getComponent(ModelComponent.class).model; ModelComponent asteroidModel = MODEL.get(asteroid);
Vector2 asteroidPosition = asteroidModel.getPosition(); Vector2 asteroidPosition = asteroidModel.getPosition();
float[] scaledVertices = scaleAndRelativizeVertices(asteroidPosition, asteroidModel.getVertices(), chunkScale); float[] scaledVertices = scaleAndRelativizeVertices(asteroidPosition, asteroidModel.getVertices(), chunkScale);
@ -218,33 +213,27 @@ public class EntityFactory {
Entity[] entities = new Entity[chunkCount]; Entity[] entities = new Entity[chunkCount];
for (int i = 0; i < chunkCount; i++) { for (int i = 0; i < chunkCount; i++) {
Vector2 chunkPosition = tmp2.set(asteroidPosition).add(tmp.scl(25)); Vector2 chunkPosition = tmp2.set(asteroidPosition).add(tmp.scl(0.5f));
ModelComponent model = new ModelComponent(); ModelComponent model = new ModelComponent(new PolygonModel(asteroidModel.getColor()));
model.model = new PolygonModel(asteroidModel.getColor()); model.setVertices(Arrays.copyOf(scaledVertices, scaledVertices.length));
model.model.setVertices(Arrays.copyOf(scaledVertices, scaledVertices.length)); model.setPosition(chunkPosition);
model.model.setPosition(chunkPosition);
PositionComponent position = new PositionComponent();
position.position = new Vector2(chunkPosition);
position.rotation = 90;
VelocityComponent velocity = new VelocityComponent(); VelocityComponent velocity = new VelocityComponent();
velocity.velocity = new Vector2(tmp).nor().rotate(rand.nextFloat(-45, 45)).scl(asteroidVelocity.len() * 1.10f); velocity.set(tmp);
velocity.velocity.nor().rotate(rand.nextFloat(-45, 45)).scl(asteroidVelocity.len() * 1.10f);
velocity.angularVelocity = rand.nextFloat(-30, 30); velocity.angularVelocity = rand.nextFloat(-30, 30);
AsteroidComponent asteroidComponent = new AsteroidComponent();
asteroidComponent.generation = asteroid.getComponent(AsteroidComponent.class).generation + 1;
angle += angleStep; angle += angleStep;
Utils.setUnitVectorAngleRad(tmp, angle); Utils.setUnitVectorAngleRad(tmp, angle);
Entity split = createEntity(engine); Entity split = createEntity(engine);
split.addComponent(model); split.addComponent(model);
split.addComponent(position); split.addComponent(new PositionComponent(new Vector2(chunkPosition), 90));
split.addComponent(velocity); split.addComponent(velocity);
split.addComponent(asteroidComponent); split.addComponent(new AsteroidComponent(ASTEROID.get(asteroid).generation + 1));
split.addComponent(COLLIDER); split.addComponent(COLLIDER_TAG);
split.addGroup("ASTEROIDS");
entities[i] = split; entities[i] = split;
} }

View File

@ -1,22 +1,23 @@
package com.me.asteroids; package com.me.asteroids;
import com.me.asteroids.components.AsteroidComponent;
import com.me.asteroids.components.BulletComponent;
import com.me.asteroids.components.PlayerComponent;
import com.me.asteroids.events.BulletAsteroidCollisionEvent; import com.me.asteroids.events.BulletAsteroidCollisionEvent;
import com.me.asteroids.events.CollisionEvent; import com.me.asteroids.events.CollisionEvent;
import com.me.asteroids.events.PlayerASteroidCollisionEvent; import com.me.asteroids.events.PlayerASteroidCollisionEvent;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import static com.me.asteroids.Components.ASTEROID;
import static com.me.asteroids.Components.BULLET;
import static com.me.asteroids.Components.PLAYER;
public class EventFactory { public class EventFactory {
public static CollisionEvent getNewCollisionEvent(Entity a, Entity b) { public static CollisionEvent getNewCollisionEvent(Entity a, Entity b) {
boolean isEntityAPlayer = a.hasComponent(PlayerComponent.class); boolean isEntityAPlayer = PLAYER.has(a);
boolean isEntityBPlayer = b.hasComponent(PlayerComponent.class); boolean isEntityBPlayer = PLAYER.has(b);
boolean isEntityABullet = !isEntityAPlayer && a.hasComponent(BulletComponent.class); boolean isEntityABullet = !isEntityAPlayer && BULLET.has(a);
boolean isEntityBBullet = !isEntityBPlayer && b.hasComponent(BulletComponent.class); boolean isEntityBBullet = !isEntityBPlayer && BULLET.has(b);
boolean isEntityAAsteroid = !isEntityAPlayer && !isEntityABullet && a.hasComponent(AsteroidComponent.class); boolean isEntityAAsteroid = !isEntityAPlayer && !isEntityABullet && ASTEROID.has(a);
boolean isEntityBAsteroid = !isEntityBPlayer && !isEntityBBullet && b.hasComponent(AsteroidComponent.class); boolean isEntityBAsteroid = !isEntityBPlayer && !isEntityBBullet && ASTEROID.has(b);
if (isEntityAAsteroid || isEntityBAsteroid) { if (isEntityAAsteroid || isEntityBAsteroid) {
if (isEntityAPlayer && isEntityBAsteroid) { if (isEntityAPlayer && isEntityBAsteroid) {

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

@ -1,63 +1,62 @@
package com.me.asteroids; package com.me.asteroids;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
public class Graphics { public class Graphics {
private int worldWidth, worldHeight; private OrthographicCamera camera;
private int screenWidth, screenHeight; private OrthographicCamera uiCamera;
private Camera camera;
private Viewport viewport;
private ShapeRenderer shapeRenderer; private ShapeRenderer shapeRenderer;
private SpriteBatch spriteBatch;
private SpriteBatch uiSpriteBatch;
public Graphics(int worldWidth, int worldHeight) { public Graphics() {
this.worldWidth = worldWidth;
this.worldHeight = worldHeight;
this.screenWidth = Gdx.graphics.getWidth();
this.screenHeight = Gdx.graphics.getHeight();
this.camera = new OrthographicCamera(); this.camera = new OrthographicCamera();
this.viewport = new FitViewport(worldWidth, worldHeight, camera); this.uiCamera = new OrthographicCamera();
this.shapeRenderer = new ShapeRenderer(); this.shapeRenderer = new ShapeRenderer();
this.spriteBatch = new SpriteBatch();
this.uiSpriteBatch = new SpriteBatch();
} }
public void initialize() { public void initialize() {
Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClearColor(0, 0, 0, 1);
updateDimensions();
} }
public void reset() { public void reset() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
} }
public void setScreenSize(int width, int height) {
screenWidth = width;
screenHeight = height;
updateDimensions();
}
public void dispose() { public void dispose() {
shapeRenderer.dispose(); shapeRenderer.dispose();
spriteBatch.dispose();
uiSpriteBatch.dispose();
} }
public ShapeRenderer getShapeRenderer() { public ShapeRenderer getShapeRenderer() {
return shapeRenderer; return shapeRenderer;
} }
private void updateDimensions() { public SpriteBatch getSpriteBatch() {
viewport.setWorldSize(worldWidth, worldHeight); return spriteBatch;
viewport.update(screenWidth, screenHeight, true); }
public SpriteBatch getUISpriteBatch() {
return uiSpriteBatch;
}
public void updateViewport(float worldWidth, float worldHeight, float uiWidth, float uiHeight) {
camera.setToOrtho(false, worldWidth, worldHeight);
uiCamera.setToOrtho(false, uiWidth, uiHeight);
shapeRenderer.setProjectionMatrix(camera.combined); shapeRenderer.setProjectionMatrix(camera.combined);
spriteBatch.setProjectionMatrix(camera.combined);
uiSpriteBatch.setProjectionMatrix(uiCamera.combined);
} }
} }

View File

@ -6,5 +6,48 @@ import com.me.common.ecs.Component;
public class AccelerationComponent implements Component { public class AccelerationComponent implements Component {
public Vector2 acceleration; public Vector2 acceleration;
public float maxVelocity;
public AccelerationComponent() {
this(0f, 0f, Float.MAX_VALUE);
}
public AccelerationComponent(float maxVelocity) {
this(0f, 0f, maxVelocity);
}
public AccelerationComponent(float x, float y, float maxVelocity) {
this(new Vector2(x, y), maxVelocity);
}
public AccelerationComponent(Vector2 acceleration, float maxVelocity) {
this.acceleration = acceleration;
this.maxVelocity = maxVelocity;
}
public float getX() {
return acceleration.x;
}
public float getY() {
return acceleration.y;
}
public void setX(float x) {
acceleration.x = x;
}
public void setY(float y) {
acceleration.y = y;
}
public void set(Vector2 vector) {
set(vector.x, vector.y);
}
public void set(float x, float y) {
acceleration.x = x;
acceleration.y = y;
}
} }

View File

@ -6,4 +6,12 @@ public class AsteroidComponent implements Component {
public int generation; public int generation;
public AsteroidComponent() {
this(0);
}
public AsteroidComponent(int generation) {
this.generation = generation;
}
} }

View File

@ -2,6 +2,6 @@ package com.me.asteroids.components;
import com.me.common.ecs.Component; import com.me.common.ecs.Component;
public class BulletComponent implements Component { public class BulletTagComponent implements Component {
// TODO: See PlayerComponent's TODO // TODO: See PlayerComponent's TODO
} }

View File

@ -5,5 +5,5 @@ import com.me.common.ecs.Component;
/** /**
* Add this to Entities that should be checked for collisions with other entities. * Add this to Entities that should be checked for collisions with other entities.
*/ */
public class ColliderComponent implements Component { public class ColliderTagComponent implements Component {
} }

View File

@ -2,6 +2,6 @@ package com.me.asteroids.components;
import com.me.common.ecs.Component; import com.me.common.ecs.Component;
public class DebrisComponent implements Component { public class DebrisTagComponent implements Component {
// TODO: See PlayerComponent's TODO // TODO: See PlayerComponent's TODO
} }

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

@ -1,5 +1,9 @@
package com.me.asteroids.components; package com.me.asteroids.components;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.me.asteroids.components.model.Model; import com.me.asteroids.components.model.Model;
import com.me.common.ecs.Component; import com.me.common.ecs.Component;
@ -7,4 +11,52 @@ public class ModelComponent implements Component {
public Model model; public Model model;
public ModelComponent(Model model) {
this.model = model;
}
public Color getColor() {
return model.getColor();
}
public ModelComponent setVertices(float[] vertices) {
model.setVertices(vertices);
return this;
}
public float[] getVertices() {
return model.getVertices();
}
public ModelComponent setPosition(Vector2 position) {
model.setPosition(position);
return this;
}
public Vector2 getPosition() {
return model.getPosition();
}
public ModelComponent setRotation(float degrees) {
model.setRotation(degrees);
return this;
}
public float getRotation() {
return model.getRotation();
}
public ModelComponent setScale(float scale) {
model.setScale(scale);
return this;
}
public float getScale() {
return model.getScale();
}
public Rectangle getBoundingBox() {
return model.getBoundingBox();
}
} }

View File

@ -1,7 +1,14 @@
package com.me.asteroids.components; package com.me.asteroids.components;
import com.me.common.ecs.Component; import com.me.common.ecs.Component;
import com.me.common.ecs.Entity;
public class PlayerComponent implements Component { public class PlayerComponent implements Component {
// TODO: implement engine feature for tagging entities (as player, for e.g.) // TODO: implement engine feature for tagging entities (as player, for e.g.)
public Entity afterBurner;
public PlayerComponent(Entity afterBurner) {
this.afterBurner = afterBurner;
}
} }

View File

@ -8,4 +8,42 @@ public class PositionComponent implements Component {
public Vector2 position; public Vector2 position;
public float rotation; public float rotation;
public PositionComponent() {
this(0f, 0f, 0f);
}
public PositionComponent(float x, float y, float rotation) {
this(new Vector2(x, y), rotation);
}
public PositionComponent(Vector2 position, float rotation) {
this.position = position;
this.rotation = rotation;
}
public float getX() {
return position.x;
}
public float getY() {
return position.y;
}
public void setX(float x) {
position.x = x;
}
public void setY(float y) {
position.y = y;
}
public void set(Vector2 vector) {
set(vector.x, vector.y);
}
public void set(float x, float y) {
position.x = x;
position.y = y;
}
} }

View File

@ -8,6 +8,50 @@ public class VelocityComponent implements Component {
public Vector2 velocity; public Vector2 velocity;
public float angularVelocity; public float angularVelocity;
public float maxVelocity = Float.MAX_VALUE; public VelocityComponent() {
this(0f, 0f, 0f);
}
public VelocityComponent(Vector2 vector) {
this(vector, 0f);
}
public VelocityComponent(float x, float y) {
this(x, y, 0f);
}
public VelocityComponent(float x, float y, float angularVelocity) {
this(new Vector2(x, y), angularVelocity);
}
public VelocityComponent(Vector2 velocity, float angularVelocity) {
this.velocity = velocity;
this.angularVelocity = angularVelocity;
}
public float getX() {
return velocity.x;
}
public float getY() {
return velocity.y;
}
public void setX(float x) {
velocity.x = x;
}
public void setY(float y) {
velocity.y = y;
}
public void set(Vector2 vector) {
set(vector.x, vector.y);
}
public void set(float x, float y) {
velocity.x = x;
velocity.y = y;
}
} }

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

@ -10,32 +10,43 @@ import com.me.asteroids.EntityFactory;
import com.me.asteroids.Graphics; import com.me.asteroids.Graphics;
import com.me.asteroids.components.AccelerationComponent; import com.me.asteroids.components.AccelerationComponent;
import com.me.asteroids.components.AsteroidComponent; import com.me.asteroids.components.AsteroidComponent;
import com.me.asteroids.components.BulletComponent; import com.me.asteroids.components.BulletTagComponent;
import com.me.asteroids.components.ColliderComponent; import com.me.asteroids.components.ColliderTagComponent;
import com.me.asteroids.components.DebrisComponent; import com.me.asteroids.components.DebrisTagComponent;
import com.me.asteroids.components.DecayComponent; import com.me.asteroids.components.DecayComponent;
import com.me.asteroids.components.GameDataComponent;
import com.me.asteroids.components.ModelComponent; import com.me.asteroids.components.ModelComponent;
import com.me.asteroids.components.PlayerComponent; import com.me.asteroids.components.PlayerComponent;
import com.me.asteroids.components.PositionComponent; import com.me.asteroids.components.PositionComponent;
import com.me.asteroids.components.VelocityComponent; import com.me.asteroids.components.VelocityComponent;
import com.me.asteroids.components.model.PolygonModel; import com.me.asteroids.components.model.PolygonModel;
import com.me.asteroids.events.AsteroidHitEvent;
import com.me.asteroids.events.BulletAsteroidCollisionEvent; import com.me.asteroids.events.BulletAsteroidCollisionEvent;
import com.me.asteroids.events.PlayerASteroidCollisionEvent; import com.me.asteroids.events.PlayerASteroidCollisionEvent;
import com.me.asteroids.events.PlayerDeathEvent;
import com.me.asteroids.events.ScreenWrapEvent; import com.me.asteroids.events.ScreenWrapEvent;
import com.me.asteroids.systems.AsteroidSpawningSystem; import com.me.asteroids.systems.AsteroidSpawningSystem;
import com.me.asteroids.systems.CollisionSystem; import com.me.asteroids.systems.CollisionSystem;
import com.me.asteroids.systems.DecaySystem; 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.ModelRenderSystem;
import com.me.asteroids.systems.MovementSystem; import com.me.asteroids.systems.MovementSystem;
import com.me.asteroids.systems.PlayerInputSystem; import com.me.asteroids.systems.PlayerInputSystem;
import com.me.asteroids.systems.ScreenWrapSystem; import com.me.asteroids.systems.ScreenWrapSystem;
import com.me.common.Screen; import com.me.common.Screen;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine; import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import com.me.common.ecs.event.EventHandler; import com.me.common.ecs.event.EventHandler;
import com.me.common.ecs.event.Listener; import com.me.common.ecs.event.Listener;
import static com.me.asteroids.Components.ASTEROID;
import static com.me.asteroids.Components.BULLET;
import static com.me.asteroids.Components.DEBRIS;
import static com.me.asteroids.Components.MODEL;
import static com.me.asteroids.Components.PLAYER;
import static com.me.asteroids.Components.POSITION;
public class GameScreen extends Screen implements Listener { public class GameScreen extends Screen implements Listener {
Engine engine; Engine engine;
@ -51,23 +62,27 @@ public class GameScreen extends Screen implements Listener {
@Override @Override
public void setup() { public void setup() {
batch = new SpriteBatch(); batch = graphics.getUISpriteBatch();
font = new BitmapFont(); font = new BitmapFont(Gdx.files.internal("Retro_Font.fnt"));
font.setColor(Color.RED);
engine = new Engine(); engine = new Engine();
engine.registerComponentClass(PlayerComponent.class); engine.registerComponentClass(BulletTagComponent.class);
engine.registerComponentClass(BulletComponent.class); engine.registerComponentClass(ColliderTagComponent.class);
engine.registerComponentClass(DebrisTagComponent.class);
engine.registerComponentClass(AccelerationComponent.class);
engine.registerComponentClass(AsteroidComponent.class); engine.registerComponentClass(AsteroidComponent.class);
engine.registerComponentClass(DebrisComponent.class);
engine.registerComponentClass(DecayComponent.class); engine.registerComponentClass(DecayComponent.class);
engine.registerComponentClass(ColliderComponent.class); engine.registerComponentClass(GameDataComponent.class);
engine.registerComponentClass(ModelComponent.class);
engine.registerComponentClass(PlayerComponent.class);
engine.registerComponentClass(PositionComponent.class); engine.registerComponentClass(PositionComponent.class);
engine.registerComponentClass(VelocityComponent.class); engine.registerComponentClass(VelocityComponent.class);
engine.registerComponentClass(AccelerationComponent.class);
engine.registerComponentClass(ModelComponent.class);
GameDataSystem system = new GameDataSystem(engine);
engine.registerSystem(system);
engine.registerSystem(new PlayerInputSystem(engine)); engine.registerSystem(new PlayerInputSystem(engine));
engine.registerSystem(new AsteroidSpawningSystem(engine)); engine.registerSystem(new AsteroidSpawningSystem(engine));
engine.registerSystem(new DecaySystem(engine)); engine.registerSystem(new DecaySystem(engine));
@ -75,10 +90,14 @@ public class GameScreen extends Screen implements Listener {
engine.registerSystem(new CollisionSystem(engine)); engine.registerSystem(new CollisionSystem(engine));
engine.registerSystem(new ScreenWrapSystem(engine)); engine.registerSystem(new ScreenWrapSystem(engine));
engine.registerSystem(new ModelRenderSystem(engine, graphics)); engine.registerSystem(new ModelRenderSystem(engine, graphics));
engine.registerSystem(new GameDataRenderSystem(engine, graphics, font));
engine.registerListener(this.new EventListener(engine)); engine.registerListener(system);
engine.registerListener(this.new EventListener());
engine.ready(); Entity gameData = engine.createEntity();
gameData.addComponent(new GameDataComponent());
gameData.activate();
Entity player = EntityFactory.createPlayer(engine); Entity player = EntityFactory.createPlayer(engine);
player.activate(); player.activate();
@ -91,35 +110,22 @@ public class GameScreen extends Screen implements Listener {
if (Constants.DEBUG) { if (Constants.DEBUG) {
batch.begin(); 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.getEntities().size), 15, 15 + font.getCapHeight());
batch.end(); batch.end();
} }
} }
@Override @Override
public void dispose() { public void dispose() {
font.dispose();
} }
private class EventListener implements Listener { private class EventListener implements Listener {
ComponentMapper<PositionComponent> positionMapper;
ComponentMapper<ModelComponent> modelMapper;
ComponentMapper<BulletComponent> bulletMapper;
ComponentMapper<DebrisComponent> debrisMapper;
ComponentMapper<AsteroidComponent> asteroidMapper;
EventListener(Engine engine) {
this.positionMapper = engine.getComponentMapper(PositionComponent.class);
this.modelMapper = engine.getComponentMapper(ModelComponent.class);
this.bulletMapper = engine.getComponentMapper(BulletComponent.class);
this.debrisMapper = engine.getComponentMapper(DebrisComponent.class);
this.asteroidMapper = engine.getComponentMapper(AsteroidComponent.class);
}
@EventHandler @EventHandler
public void onScreenWrap(ScreenWrapEvent event) { public void onScreenWrap(ScreenWrapEvent event) {
if (bulletMapper.has(event.entity) || debrisMapper.has(event.entity)) { if (BULLET.has(event.entity) || DEBRIS.has(event.entity)) {
// Remove bullets when they leave the screen // Remove bullets when they leave the screen
event.setCancelled(true); event.setCancelled(true);
event.entity.remove(); event.entity.remove();
@ -128,15 +134,16 @@ public class GameScreen extends Screen implements Listener {
@EventHandler @EventHandler
public void onBulletAsteroidCollision(BulletAsteroidCollisionEvent event) { public void onBulletAsteroidCollision(BulletAsteroidCollisionEvent event) {
Vector2 bulletPosition = positionMapper.get(event.getBullet()).position; Vector2 bulletPosition = POSITION.get(event.getBullet()).position;
PolygonModel model = (PolygonModel) modelMapper.get(event.getAsteroid()).model; PolygonModel model = (PolygonModel) MODEL.get(event.getAsteroid()).model;
if (model.contains(bulletPosition)) { if (model.contains(bulletPosition)) {
// AABBs intersect but let's only consider it a hit if the bullet's position // AABBs intersect but let's only consider it a hit if the bullet's position
// is actually inside the asteroid // is actually inside the asteroid
AsteroidComponent asteroid = ASTEROID.get(event.getAsteroid());
engine.callEvent(new AsteroidHitEvent(asteroid));
event.getBullet().remove(); event.getBullet().remove();
int generation = asteroidMapper.get(event.getAsteroid()).generation; if (asteroid.generation < 2) {
if (generation < 2) {
for (Entity shard : EntityFactory.splitAsteroidIntoChunks(engine, event.getAsteroid(), 2, 2/3f)) { for (Entity shard : EntityFactory.splitAsteroidIntoChunks(engine, event.getAsteroid(), 2, 2/3f)) {
shard.activate(); shard.activate();
} }
@ -146,16 +153,17 @@ public class GameScreen extends Screen implements Listener {
} }
} }
event.getAsteroid().remove(); event.getAsteroid().remove();
} }
} }
@EventHandler @EventHandler
public void onPlayerAsteroidCollision(PlayerASteroidCollisionEvent event) { public void onPlayerAsteroidCollision(PlayerASteroidCollisionEvent event) {
PolygonModel asteroid = (PolygonModel) modelMapper.get(event.getAsteroid()).model; PolygonModel asteroid = (PolygonModel) MODEL.get(event.getAsteroid()).model;
PolygonModel player = (PolygonModel) modelMapper.get(event.getPlayer()).model; PolygonModel player = (PolygonModel) MODEL.get(event.getPlayer()).model;
if (asteroid.contains(player.getVertices()) || player.contains(asteroid.getVertices())) { if (asteroid.contains(player.getVertices()) || player.contains(asteroid.getVertices())) {
engine.callEvent(new PlayerDeathEvent(event.getPlayer()));
PLAYER.get(event.getPlayer()).afterBurner.deactivate();
event.getPlayer().deactivate(); event.getPlayer().deactivate();
for (Entity debris : EntityFactory.createDebris(engine, event.getPlayer())) { for (Entity debris : EntityFactory.createDebris(engine, event.getPlayer())) {
debris.activate(); debris.activate();

View File

@ -2,37 +2,26 @@ package com.me.asteroids.systems;
import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.me.asteroids.Constants; import com.me.asteroids.Constants;
import com.me.asteroids.EntityFactory; import com.me.asteroids.EntityFactory;
import com.me.asteroids.Utils; import com.me.asteroids.Utils;
import com.me.asteroids.components.AsteroidComponent;
import com.me.asteroids.components.ModelComponent;
import com.me.asteroids.components.PositionComponent;
import com.me.asteroids.components.VelocityComponent; import com.me.asteroids.components.VelocityComponent;
import com.me.asteroids.components.model.Model; import com.me.asteroids.components.model.Model;
import com.me.common.ecs.BaseSystem; import com.me.common.ecs.BaseSystem;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine; import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import static com.me.asteroids.Components.MODEL;
import static com.me.asteroids.Components.POSITION;
import static com.me.asteroids.Components.VELOCITY;
import static com.me.asteroids.Constants.rand; import static com.me.asteroids.Constants.rand;
public class AsteroidSpawningSystem extends BaseSystem { public class AsteroidSpawningSystem extends BaseSystem {
private ComponentMapper<AsteroidComponent> asteroidMapper;
private ComponentMapper<PositionComponent> positionMapper;
private ComponentMapper<VelocityComponent> velocityMapper;
private ComponentMapper<ModelComponent> modelMapper;
private float asteroidSpawnDelay = 0f; private float asteroidSpawnDelay = 0f;
public AsteroidSpawningSystem(Engine engine) { public AsteroidSpawningSystem(Engine engine) {
super(engine); super(engine);
asteroidMapper = engine.getComponentMapper(AsteroidComponent.class);
positionMapper = engine.getComponentMapper(PositionComponent.class);
velocityMapper = engine.getComponentMapper(VelocityComponent.class);
modelMapper = engine.getComponentMapper(ModelComponent.class);
} }
private Vector2 getRandomSpawnLocation(float asteroidWidth, float asteroidHeight) { private Vector2 getRandomSpawnLocation(float asteroidWidth, float asteroidHeight) {
@ -55,16 +44,16 @@ public class AsteroidSpawningSystem extends BaseSystem {
private void spawnAsteroid() { private void spawnAsteroid() {
Entity asteroid = EntityFactory.createAsteroid(engine); Entity asteroid = EntityFactory.createAsteroid(engine);
Model model = modelMapper.get(asteroid).model; Model model = MODEL.get(asteroid).model;
Rectangle aabb = model.getBoundingBox(); Rectangle aabb = model.getBoundingBox();
Vector2 position Vector2 position
= positionMapper.get(asteroid).position = POSITION.get(asteroid).position
= getRandomSpawnLocation(aabb.getWidth(), aabb.getHeight()); = getRandomSpawnLocation(aabb.getWidth(), aabb.getHeight());
model.setPosition(position); model.setPosition(position);
VelocityComponent velocityComponent = velocityMapper.get(asteroid); VelocityComponent velocityComponent = VELOCITY.get(asteroid);
velocityComponent.velocity = new Vector2().setToRandomDirection().scl(rand.nextFloat(75, 125)); velocityComponent.velocity = new Vector2().setToRandomDirection().scl(rand.nextFloat(1.875f, 3.125f));
velocityComponent.angularVelocity = rand.nextFloat(-30, 30); velocityComponent.angularVelocity = rand.nextFloat(-30, 30);
asteroid.activate(); asteroid.activate();
@ -72,16 +61,7 @@ public class AsteroidSpawningSystem extends BaseSystem {
@Override @Override
public void process(float dt) { public void process(float dt) {
Array<Entity> entities = engine.getEntities(); int asteroidCount = engine.getEntitiesInGroup("ASTEROIDS").size;
int asteroidCount = 0;
for (Entity entity : entities) {
// It's rather inefficient to have to check our entire entity list every frame
// to count how many entities have a specific component. Maybe we should keep a count of
// how many entites a given component?
if (asteroidMapper.has(entity)) {
asteroidCount++;
}
}
if (asteroidCount++ < Constants.ASTEROID_SPAWN_COUNT && (asteroidSpawnDelay -= dt) <= 0) { if (asteroidCount++ < Constants.ASTEROID_SPAWN_COUNT && (asteroidSpawnDelay -= dt) <= 0) {
spawnAsteroid(); spawnAsteroid();

View File

@ -2,20 +2,18 @@ package com.me.asteroids.systems;
import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Rectangle;
import com.me.asteroids.EventFactory; import com.me.asteroids.EventFactory;
import com.me.asteroids.components.ColliderComponent; import com.me.asteroids.components.ColliderTagComponent;
import com.me.asteroids.components.ModelComponent; import com.me.asteroids.components.ModelComponent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine; import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem; import com.me.common.ecs.EntitySystem;
import static com.me.asteroids.Components.MODEL;
public class CollisionSystem extends EntitySystem { public class CollisionSystem extends EntitySystem {
private ComponentMapper<ModelComponent> modelMapper;
public CollisionSystem(Engine engine) { public CollisionSystem(Engine engine) {
super(engine, ColliderComponent.class, ModelComponent.class); super(engine, ColliderTagComponent.class, ModelComponent.class);
modelMapper = engine.getComponentMapper(ModelComponent.class);
} }
@Override @Override
@ -23,19 +21,18 @@ public class CollisionSystem extends EntitySystem {
Entity[] entities = getEntities().items; Entity[] entities = getEntities().items;
for (int i = 0, n = getEntities().size; i < n-1; i++) { for (int i = 0, n = getEntities().size; i < n-1; i++) {
Entity entityA = entities[i]; Entity entityA = entities[i];
if (!entityA.isActive() || entityA.isRemoved()) { if (!entityA.isActive()) {
continue; continue;
} }
Rectangle aabbA = modelMapper.get(entityA).model.getBoundingBox(); Rectangle aabbA = MODEL.get(entityA).model.getBoundingBox();
for (int j = i + 1; j < n; j++) { for (int j = i + 1; j < n; j++) {
Entity entityB = entities[j]; Entity entityB = entities[j];
if (!entityB.isActive() || entityB.isRemoved()) { if (!entityB.isActive()) {
continue; continue;
} }
Rectangle aabbB = modelMapper.get(entityB).model.getBoundingBox(); Rectangle aabbB = MODEL.get(entityB).model.getBoundingBox();
if (aabbA.overlaps(aabbB)) { if (aabbA.overlaps(aabbB)) {
engine.callEvent(EventFactory.getNewCollisionEvent(entityA, entityB)); engine.callEvent(EventFactory.getNewCollisionEvent(entityA, entityB));
} }

View File

@ -1,30 +1,27 @@
package com.me.asteroids.systems; package com.me.asteroids.systems;
import com.me.asteroids.components.DecayComponent; import com.me.asteroids.components.DecayComponent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine; import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem; import com.me.common.ecs.EntitySystem;
public class DecaySystem extends EntitySystem { import static com.me.asteroids.Components.DECAY;
private ComponentMapper<DecayComponent> decayTimerMapper; public class DecaySystem extends EntitySystem {
public DecaySystem(Engine engine) { public DecaySystem(Engine engine) {
super(engine, DecayComponent.class); super(engine, DecayComponent.class);
decayTimerMapper = engine.getComponentMapper(DecayComponent.class);
} }
@Override @Override
public void processEntity(Entity entity, float dt) { public void processEntity(Entity entity, float dt) {
DecayComponent decayComponent = decayTimerMapper.get(entity); DecayComponent decayComponent = DECAY.get(entity);
if ((decayComponent.decayTimer -= dt) <= 0) { if ((decayComponent.decayTimer -= dt) <= 0) {
if (decayComponent.remove) { if (decayComponent.remove) {
entity.remove(); entity.remove();
} else { } else {
entity.deactivate(); entity.deactivate();
} }
} }
} }

View File

@ -0,0 +1,60 @@
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.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem;
import static com.me.asteroids.Components.GAME_DATA;
public class GameDataRenderSystem extends EntitySystem {
private SpriteBatch batch;
private BitmapFont font;
private GlyphLayout gameOverLayout;
private GameDataComponent gameData;
public GameDataRenderSystem(Engine engine, Graphics graphics, BitmapFont font) {
super(engine, GameDataComponent.class);
this.batch = graphics.getUISpriteBatch();
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 = GAME_DATA.get(entity);
}
switch (gameData.gameMode) {
case GAME_OVER:
font.setColor(Color.RED);
font.draw(batch, "GAME OVER", Constants.getHalfUIWidth() - (gameOverLayout.width / 2), Constants.getHalfUIHeight() + (font.getCapHeight() / 2));
break;
default:
font.setColor(Color.CHARTREUSE);
font.draw(batch, "Score: " + gameData.score, 15, Constants.getUIHeight() - 15);
font.draw(batch, "Lives: " + gameData.lives, 15, Constants.getUIHeight() - font.getCapHeight() - 30);
break;
}
}
@Override
public void postProcess() {
batch.end();
}
}

View File

@ -0,0 +1,101 @@
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.events.AsteroidHitEvent;
import com.me.asteroids.events.PlayerDeathEvent;
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;
import static com.me.asteroids.Components.GAME_DATA;
import static com.me.asteroids.Components.POSITION;
import static com.me.asteroids.Components.VELOCITY;
public class GameDataSystem extends EntitySystem implements Listener {
private GameDataComponent gameData;
public GameDataSystem(Engine engine) {
super(engine, GameDataComponent.class);
}
@Override
public void processEntity(Entity entity, float dt) {
if(gameData == null) {
gameData = GAME_DATA.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.getEntityByTag("PLAYER");
PositionComponent position = POSITION.get(player);
position.set(Constants.HALF_WIDTH, Constants.HALF_HEIGHT);
position.rotation = 90;
VELOCITY.get(player).set(0, 0);
player.activate();
}
private void setGameMode(GameMode mode) {
gameData.gameMode = mode;
gameData.gameModeTimer = mode.getTimer();
}
}

View File

@ -4,20 +4,18 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.me.asteroids.Graphics; import com.me.asteroids.Graphics;
import com.me.asteroids.components.ModelComponent; import com.me.asteroids.components.ModelComponent;
import com.me.asteroids.components.model.Model; import com.me.asteroids.components.model.Model;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine; import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem; import com.me.common.ecs.EntitySystem;
public class ModelRenderSystem extends EntitySystem { import static com.me.asteroids.Components.MODEL;
private ComponentMapper<ModelComponent> modelMapper; public class ModelRenderSystem extends EntitySystem {
private ShapeRenderer renderer; private ShapeRenderer renderer;
public ModelRenderSystem(Engine engine, Graphics graphics) { public ModelRenderSystem(Engine engine, Graphics graphics) {
super(engine, ModelComponent.class); super(engine, ModelComponent.class);
this.modelMapper = engine.getComponentMapper(ModelComponent.class);
this.renderer = graphics.getShapeRenderer(); this.renderer = graphics.getShapeRenderer();
} }
@ -28,7 +26,7 @@ public class ModelRenderSystem extends EntitySystem {
@Override @Override
public void processEntity(Entity entity, float dt) { public void processEntity(Entity entity, float dt) {
Model model = modelMapper.get(entity).model; Model model = MODEL.get(entity).model;
renderer.setColor(model.getColor()); renderer.setColor(model.getColor());
model.render(renderer); model.render(renderer);

View File

@ -8,46 +8,50 @@ import com.me.asteroids.components.ModelComponent;
import com.me.asteroids.components.PositionComponent; import com.me.asteroids.components.PositionComponent;
import com.me.asteroids.components.VelocityComponent; import com.me.asteroids.components.VelocityComponent;
import com.me.asteroids.components.model.Model; import com.me.asteroids.components.model.Model;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine; import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem; import com.me.common.ecs.EntitySystem;
import static com.me.asteroids.Components.ACCELERATION;
import static com.me.asteroids.Components.MODEL;
import static com.me.asteroids.Components.POSITION;
import static com.me.asteroids.Components.VELOCITY;
public class MovementSystem extends EntitySystem { public class MovementSystem extends EntitySystem {
private Vector2 tmp = new Vector2(); private Vector2 tmp = new Vector2();
private ComponentMapper<PositionComponent> positionMapper;
private ComponentMapper<VelocityComponent> velocityMapper;
private ComponentMapper<AccelerationComponent> accelMapper;
private ComponentMapper<ModelComponent> modelMapper;
public MovementSystem(Engine engine) { public MovementSystem(Engine engine) {
super(engine, PositionComponent.class, VelocityComponent.class); super(engine, PositionComponent.class, VelocityComponent.class);
positionMapper = engine.getComponentMapper(PositionComponent.class);
velocityMapper = engine.getComponentMapper(VelocityComponent.class);
accelMapper = engine.getComponentMapper(AccelerationComponent.class);
modelMapper = engine.getComponentMapper(ModelComponent.class);
} }
@Override @Override
public void processEntity(Entity entity, float dt) { public void processEntity(Entity entity, float dt) {
PositionComponent positionComponent = positionMapper.get(entity); PositionComponent positionComponent = POSITION.get(entity);
VelocityComponent velocityComponent = velocityMapper.get(entity); VelocityComponent velocityComponent = VELOCITY.get(entity);
AccelerationComponent accelComponent = ACCELERATION.get(entity);
Vector2 velocity = velocityComponent.velocity; Vector2 velocity = velocityComponent.velocity;
AccelerationComponent accelComponent = accelMapper.get(entity);
if (accelComponent != null && !accelComponent.acceleration.isZero()) { if (accelComponent != null && !accelComponent.acceleration.isZero()) {
velocity.add(tmp.set(accelComponent.acceleration).scl(dt)); tmp.set(accelComponent.acceleration).scl(dt).add(velocity);
if (velocity.dot(tmp) < 0) {
// current velocity is opposite of velocity with acceleration applied, which means
// we're coming to a stop. Set position correctly and velocity to zero
velocity.set(0, 0);
positionComponent.position.add(tmp.scl(dt));
} else {
// Else just use the accelerated velocity
velocity.set(tmp);
velocity.clamp(0, accelComponent.maxVelocity);
}
} }
float maxVelocity = velocityComponent.maxVelocity;
Vector2 position = positionComponent.position; Vector2 position = positionComponent.position;
if (!velocity.isZero()) { if (!velocity.isZero()) {
velocity.clamp(0, maxVelocity); positionComponent.position.add(tmp.set(velocity).scl(dt));
position.add(tmp.set(velocity).scl(dt));
} }
float angularVelocity = velocityComponent.angularVelocity; float angularVelocity = velocityComponent.angularVelocity;
@ -55,7 +59,7 @@ public class MovementSystem extends EntitySystem {
positionComponent.rotation = Utils.wrapAngle(positionComponent.rotation + (angularVelocity * dt)); positionComponent.rotation = Utils.wrapAngle(positionComponent.rotation + (angularVelocity * dt));
} }
ModelComponent modelComponent = modelMapper.get(entity); ModelComponent modelComponent = MODEL.get(entity);
if (modelComponent != null) { if (modelComponent != null) {
Model model = modelComponent.model; Model model = modelComponent.model;
model.setPosition(position); model.setPosition(position);

View File

@ -9,19 +9,21 @@ import com.me.asteroids.components.AccelerationComponent;
import com.me.asteroids.components.PlayerComponent; import com.me.asteroids.components.PlayerComponent;
import com.me.asteroids.components.PositionComponent; import com.me.asteroids.components.PositionComponent;
import com.me.asteroids.components.VelocityComponent; import com.me.asteroids.components.VelocityComponent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine; import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem; import com.me.common.ecs.EntitySystem;
import static com.me.asteroids.Constants.rand;
import static com.me.asteroids.Components.ACCELERATION;
import static com.me.asteroids.Components.MODEL;
import static com.me.asteroids.Components.PLAYER;
import static com.me.asteroids.Components.POSITION;
import static com.me.asteroids.Components.VELOCITY;
public class PlayerInputSystem extends EntitySystem { public class PlayerInputSystem extends EntitySystem {
public Vector2 tmp = new Vector2(0, 1); public Vector2 tmp = new Vector2(0, 1);
private ComponentMapper<PositionComponent> positionMapper;
private ComponentMapper<VelocityComponent> velocityMapper;
private ComponentMapper<AccelerationComponent> accelMapper;
public PlayerInputSystem(Engine engine) { public PlayerInputSystem(Engine engine) {
super( super(
engine, engine,
@ -30,16 +32,14 @@ public class PlayerInputSystem extends EntitySystem {
AccelerationComponent.class, AccelerationComponent.class,
PlayerComponent.class PlayerComponent.class
); );
positionMapper = engine.getComponentMapper(PositionComponent.class);
velocityMapper = engine.getComponentMapper(VelocityComponent.class);
accelMapper = engine.getComponentMapper(AccelerationComponent.class);
} }
@Override @Override
public void processEntity(Entity entity, float dt) { public void processEntity(Entity entity, float dt) {
PositionComponent positionComponent = positionMapper.get(entity); PositionComponent positionComponent = POSITION.get(entity);
VelocityComponent velocityComponent = velocityMapper.get(entity); VelocityComponent velocityComponent = VELOCITY.get(entity);
AccelerationComponent accelComponent = accelMapper.get(entity); AccelerationComponent accelComponent = ACCELERATION.get(entity);
PlayerComponent playerComponent = PLAYER.get(entity);
boolean isLeftPressed = Gdx.input.isKeyPressed(Input.Keys.A); boolean isLeftPressed = Gdx.input.isKeyPressed(Input.Keys.A);
boolean isRightPressed = Gdx.input.isKeyPressed(Input.Keys.D); boolean isRightPressed = Gdx.input.isKeyPressed(Input.Keys.D);
@ -51,16 +51,27 @@ public class PlayerInputSystem extends EntitySystem {
velocityComponent.angularVelocity = 0; velocityComponent.angularVelocity = 0;
} }
Vector2 acceleration = accelComponent.acceleration;
Vector2 velocity = velocityComponent.velocity; Vector2 velocity = velocityComponent.velocity;
MODEL.get(playerComponent.afterBurner).setPosition(positionComponent.position);
if (Gdx.input.isKeyPressed(Input.Keys.W)) { if (Gdx.input.isKeyPressed(Input.Keys.W)) {
acceleration.set(Utils.setUnitVectorAngle(tmp, positionComponent.rotation).scl(500)); accelComponent.set(Utils.setUnitVectorAngle(tmp, positionComponent.rotation).scl(12.5f));
if (rand.nextFloat() < 0.85) {
PositionComponent afterBurnerPosition = POSITION.get(playerComponent.afterBurner);
afterBurnerPosition.position.set(positionComponent.position);
afterBurnerPosition.rotation = positionComponent.rotation;
playerComponent.afterBurner.activate();
} else { } else {
if (!velocity.isZero(1f)) { playerComponent.afterBurner.deactivate();
acceleration.set(Utils.setUnitVectorAngleRad(tmp, velocity.angleRad()).scl(-100)); }
} else { } else {
acceleration.set(0, 0); playerComponent.afterBurner.deactivate();
if (velocity.isZero(0f)) {
accelComponent.set(0, 0);
} else {
accelComponent.set(Utils.setUnitVectorAngleRad(tmp, velocity.angleRad()).scl(-2.5f));
} }
} }

View File

@ -6,20 +6,17 @@ import com.me.asteroids.Constants;
import com.me.asteroids.components.ModelComponent; import com.me.asteroids.components.ModelComponent;
import com.me.asteroids.components.PositionComponent; import com.me.asteroids.components.PositionComponent;
import com.me.asteroids.events.ScreenWrapEvent; import com.me.asteroids.events.ScreenWrapEvent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine; import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity; import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem; import com.me.common.ecs.EntitySystem;
public class ScreenWrapSystem extends EntitySystem { import static com.me.asteroids.Components.MODEL;
import static com.me.asteroids.Components.POSITION;
private ComponentMapper<PositionComponent> positionMapper; public class ScreenWrapSystem extends EntitySystem {
private ComponentMapper<ModelComponent> modelMapper;
public ScreenWrapSystem(Engine engine) { public ScreenWrapSystem(Engine engine) {
super(engine, PositionComponent.class, ModelComponent.class); super(engine, PositionComponent.class, ModelComponent.class);
positionMapper = engine.getComponentMapper(PositionComponent.class);
modelMapper = engine.getComponentMapper(ModelComponent.class);
} }
private void updatePosition(Entity entity, Vector2 position, float newX, float newY) { private void updatePosition(Entity entity, Vector2 position, float newX, float newY) {
@ -33,8 +30,8 @@ public class ScreenWrapSystem extends EntitySystem {
@Override @Override
public void processEntity(Entity entity, float dt) { public void processEntity(Entity entity, float dt) {
Vector2 position = positionMapper.get(entity).position; Vector2 position = POSITION.get(entity).position;
Rectangle aabb = modelMapper.get(entity).model.getBoundingBox(); Rectangle aabb = MODEL.get(entity).model.getBoundingBox();
// Check top/bottom edges // Check top/bottom edges
float minY = aabb.y; float minY = aabb.y;

View File

@ -2,7 +2,7 @@ package com.me.common.ecs;
public abstract class BaseSystem { public abstract class BaseSystem {
protected Engine engine; protected final Engine engine;
public BaseSystem(Engine engine) { public BaseSystem(Engine engine) {
this.engine = engine; this.engine = engine;

View File

@ -1,39 +1,34 @@
package com.me.common.ecs; package com.me.common.ecs;
public class ComponentBag { class ComponentBag {
private Component[] items; private Component[] items;
private int size; private int size;
public ComponentBag() { ComponentBag() {
this.items = new Component[16]; this.items = new Component[16];
this.size = items.length; this.size = items.length;
} }
public Component get(int index) { Component get(int index) {
if (index >= size) { if (index >= size) {
return null; return null;
} }
return items[index]; return items[index];
} }
public boolean contains(int index) { void insert(int index, Component item) {
return index < size && items[index] != null;
}
public void insert(int index, Component item) {
if (index >= size) { if (index >= size) {
grow((int) (index * 1.5f)); grow((int) (index * 1.5f));
} }
items[index] = item; items[index] = item;
} }
public void remove(int index) { void remove(int index) {
if (index >= size) { if (index < size) {
throw new IndexOutOfBoundsException("index must be < size");
}
items[index] = null; items[index] = null;
} }
}
private void grow(int newSize) { private void grow(int newSize) {
Component[] newItems = new Component[newSize]; Component[] newItems = new Component[newSize];

View File

@ -1,23 +1,36 @@
package com.me.common.ecs; package com.me.common.ecs;
public class ComponentMapper<T extends Component> { import java.util.HashMap;
import java.util.Map;
private Engine engine; public final class ComponentMapper<T extends Component> {
private ComponentType type;
private Class<T> typeClass;
public ComponentMapper(Engine engine, Class<T> typeClass) { private static final Map<Class<? extends Component>, ComponentMapper> mappers = new HashMap<>();
this.engine = engine;
private final ComponentType type;
private final Class<T> typeClass;
private ComponentMapper(Class<T> typeClass) {
this.type = ComponentType.getComponentType(typeClass); this.type = ComponentType.getComponentType(typeClass);
this.typeClass = typeClass; this.typeClass = typeClass;
} }
public T get(Entity entity) { public T get(Entity entity) {
return typeClass.cast(engine.getEntityComponent(entity, type)); return typeClass.cast(entity.getComponent(type));
} }
public boolean has(Entity entity) { public boolean has(Entity entity) {
return type.isTypeInMask(entity.componentBits); return type.isInBits(entity.componentBits);
}
@SuppressWarnings("unchecked")
public static <T extends Component> ComponentMapper<T> getFor(Class<T> typeClass) {
ComponentMapper<T> mapper = mappers.get(typeClass);
if (mapper == null) {
mapper = new ComponentMapper<>(typeClass);
mappers.put(typeClass, mapper);
}
return mapper;
} }
} }

View File

@ -6,64 +6,44 @@ import java.util.Map;
final class ComponentType { final class ComponentType {
private static final Map<Class<? extends Component>, ComponentType> types = new HashMap<>(); private static final Map<Class<? extends Component>, ComponentType> types = new HashMap<>();
private static final ComponentType[] typeById = new ComponentType[Long.SIZE];
private static long nextBit = 1l; private static long nextBit = 1l;
private static int nextId = 0; private static int nextId = 0;
protected long bits; final long bit;
protected int id; final int id;
private ComponentType() { private ComponentType() {
this.bits = nextBit; this.bit = nextBit;
this.id = nextId++; this.id = nextId++;
this.nextBit <<= 1; nextBit <<= 1;
} }
protected long getBits() { boolean isInBits(long bits) {
return (bits & bit) == bit;
}
@SafeVarargs
static long getBitsFor(Class<? extends Component>... components) {
long bits = 0l;
for (Class<? extends Component> clazz : components) {
bits |= getComponentType(clazz).bit;
}
return bits; return bits;
} }
protected int getId() { static ComponentType registerComponentType(Class<? extends Component> component) {
return id;
}
protected static long getMaskBits(Class<? extends Component>... components) {
long mask = 0l;
for (Class<? extends Component> clazz : components) {
mask |= getTypeBits(clazz);
}
return mask;
}
protected static long getTypeBits(Class<? extends Component> component) {
return getComponentType(component).bits;
}
protected static int getTypeId(Class<? extends Component> component) {
return getComponentType(component).id;
}
protected static ComponentType getById(int id) {
return typeById[id];
}
protected boolean isTypeInMask(long mask) {
return (bits & mask) == bits;
}
protected static void registerComponentType(Class<? extends Component> component) {
ComponentType type = types.get(component); ComponentType type = types.get(component);
if (type != null) { if (type != null) {
throw new IllegalArgumentException(component.getName() + " has already been registered."); throw new IllegalArgumentException(component.getName() + " has already been registered.");
} }
type = new ComponentType(); type = new ComponentType();
types.put(component, type); types.put(component, type);
typeById[type.id] = type; return type;
} }
protected static ComponentType getComponentType(Class<? extends Component> component) { static ComponentType getComponentType(Class<? extends Component> component) {
ComponentType type = types.get(component); ComponentType type = types.get(component);
if (type == null) { if (type == null) {
throw new IllegalArgumentException(component.getName() + " has not been registered."); throw new IllegalArgumentException(component.getName() + " has not been registered.");
@ -71,8 +51,4 @@ final class ComponentType {
return type; return type;
} }
protected static int getRegisteredComponentTypeCount() {
return types.size();
}
} }

View File

@ -1,49 +1,55 @@
package com.me.common.ecs; package com.me.common.ecs;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectSet;
import com.me.common.ecs.event.Event; import com.me.common.ecs.event.Event;
import com.me.common.ecs.event.Listener; import com.me.common.ecs.event.Listener;
import java.util.HashMap;
import java.util.Map;
public class Engine { public class Engine {
private ComponentBag[] components; private final EntityManager entityManager;
protected Array<BaseSystem> systems; private final ComponentBag[] components;
private final Array<BaseSystem> systems;
private EntityManager entityManager; private final ListenerRegistry listenerRegistry;
private ListenerRegistry listenerRegistry;
private Map<Class<? extends Component>, ComponentMapper> componentMappers;
public Engine() { public Engine() {
this.entityManager = new EntityManager(this); this.entityManager = new EntityManager(this);
this.components = new ComponentBag[Long.SIZE];
this.systems = new Array<>(); this.systems = new Array<>();
this.listenerRegistry = new ListenerRegistry(); this.listenerRegistry = new ListenerRegistry();
this.componentMappers = new HashMap<>();
} }
/**
* Register a Component class.
* @param clazz
*/
public void registerComponentClass(Class<? extends Component> clazz) { public void registerComponentClass(Class<? extends Component> clazz) {
ComponentType.registerComponentType(clazz); ComponentType type = ComponentType.registerComponentType(clazz);
components[type.id] = new ComponentBag();
} }
/**
* Register a BaseSystem.
* @param system
*/
public void registerSystem(BaseSystem system) { public void registerSystem(BaseSystem system) {
this.systems.add(system); systems.add(system);
} }
/**
* Register an event Listener.
* @param listener
*/
public void registerListener(Listener listener) { public void registerListener(Listener listener) {
listenerRegistry.registerListener(listener); listenerRegistry.registerListener(listener);
} }
public void ready() { /**
this.components = new ComponentBag[ComponentType.getRegisteredComponentTypeCount()]; * Process all systems
for (int i = 0; i < components.length; i++) { * @param dt the time (in seconds) passed since the last update
components[i] = new ComponentBag(); */
}
}
public void update(float dt) { public void update(float dt) {
entityManager.update(); entityManager.update();
@ -54,61 +60,103 @@ public class Engine {
} }
} }
/**
* @return all entities known by the engine - both those active and inactive.
*/
public Array<Entity> getEntities() { public Array<Entity> getEntities() {
return entityManager.entities; return entityManager.getEntities();
} }
public int getEntityCount() { /**
return entityManager.entities.size; * @return all currently registered systems.
*/
public Array<BaseSystem> getSystems() {
return systems;
} }
/**
* @return a new Entity object which can have fresh components added to it
*/
public Entity createEntity() { public Entity createEntity() {
return entityManager.create(); return entityManager.create();
} }
/**
* Calls the given event, dispatching it to all registered Listeners that have an EventHandler
* for this particular event.
* @param event the event to call
*/
public void callEvent(Event event) { public void callEvent(Event event) {
listenerRegistry.callEvent(event); listenerRegistry.callEvent(event);
} }
protected void refreshEntity(Entity entity) { /**
* @param group the group to look up
* @return the list of entities that are a part of the given group.
*/
public Array<Entity> getEntitiesInGroup(String group) {
return entityManager.getGroupEntities(group);
}
/**
* @param tag the tag to look up
* @return the entity with the given tag, or null
*/
public Entity getEntityByTag(String tag) {
return entityManager.getEntityByTag(tag);
}
void addEntityGroup(Entity entity, String group) {
entityManager.addEntityToGroup(entity, group);
}
void removeEntityGroup(Entity entity, String group) {
entityManager.removeEntityFromGroup(entity, group);
}
void tagEntity(Entity entity, String tag) {
entityManager.tagEntity(entity, tag);
}
void removeEntityTag(Entity entity) {
entityManager.removeEntityTag(entity);
}
void refreshEntity(Entity entity) {
entityManager.queueRefresh(entity); entityManager.queueRefresh(entity);
} }
protected void removeAllEntityComponents(int entityId) { void removeAllEntityComponents(int entityId) {
for (int i = 0; i < components.length; i++) { for (ComponentBag bag : components) {
components[i].insert(entityId, null); if (bag == null) {
// The rest of the array should also be null given that we never nullify
// an index after it has been set, and they are set sequentially. So, break.
break;
}
bag.remove(entityId);
} }
} }
protected void addEntityComponent(Entity entity, Component component) { void addEntityComponent(Entity entity, Component component) {
ComponentType type = ComponentType.getComponentType(component.getClass()); ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.id].insert(entity.id, component); components[type.id].insert(entity.id, component);
entity.addComponentType(type); entity.addComponentType(type);
} }
protected void removeEntityComponent(Entity entity, Component component) { void removeEntityComponent(Entity entity, Component component) {
ComponentType type = ComponentType.getComponentType(component.getClass()); ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.id].remove(entity.id); components[type.id].remove(entity.id);
entity.removeComponentType(type); entity.removeComponentType(type);
} }
protected <T extends Component> T getEntityComponent(Entity entity, Class<T> clazz) { <T extends Component> T getEntityComponent(Entity entity, Class<T> clazz) {
return clazz.cast(components[ComponentType.getTypeId(clazz)].get(entity.id)); return clazz.cast(getEntityComponent(entity, ComponentType.getComponentType(clazz)));
} }
protected Component getEntityComponent(Entity entity, ComponentType type) { Component getEntityComponent(Entity entity, ComponentType type) {
return components[type.id].get(entity.id); return components[type.id].get(entity.id);
} }
@SuppressWarnings("unchecked")
public <T extends Component> ComponentMapper<T> getComponentMapper(Class<T> typeClass) {
ComponentMapper<T> mapper = componentMappers.get(typeClass);
if (mapper == null) {
mapper = new ComponentMapper<>(this, typeClass);
componentMappers.put(typeClass, mapper);
}
return mapper;
}
} }

View File

@ -1,25 +1,31 @@
package com.me.common.ecs; package com.me.common.ecs;
import com.badlogic.gdx.utils.ObjectSet;
public final class Entity { public final class Entity {
private static int nextId = 0; private static int nextId = 0;
private static long nextUniqueId = 0; private static long nextUniqueId = 0;
private Engine engine; private final Engine engine;
protected final int id; final int id;
protected long componentBits; long componentBits;
protected long systemEnabledBits; long systemEnabledBits;
boolean pendingRefresh;
protected boolean active; boolean active;
protected boolean removed; boolean removed;
protected boolean pendingRefresh;
ObjectSet<String> groups;
String tag;
private long uniqueId; private long uniqueId;
protected Entity(Engine engine) { protected Entity(Engine engine) {
this.engine = engine; this.engine = engine;
this.id = nextId++; this.id = nextId++;
this.groups = new ObjectSet<>();
} }
public int getId() { public int getId() {
@ -30,6 +36,10 @@ public final class Entity {
return uniqueId; return uniqueId;
} }
public Engine getEngine() {
return engine;
}
public boolean isActive() { public boolean isActive() {
return active; return active;
} }
@ -39,30 +49,25 @@ public final class Entity {
} }
public void activate() { public void activate() {
if (!active) {
active = true; active = true;
refresh(); refresh();
} }
}
public void deactivate() { public void deactivate() {
if (active) {
active = false; active = false;
refresh(); refresh();
} }
}
public void remove() { public void remove() {
if (!removed) {
removed = true; removed = true;
active = false;
refresh(); refresh();
} }
public Engine getEngine() {
return this.engine;
}
public boolean hasComponent(Class<? extends Component> clazz) {
return ComponentType.getComponentType(clazz).isTypeInMask(componentBits);
}
public <T extends Component> T getComponent(Class<T> clazz) {
return engine.getEntityComponent(this, clazz);
} }
public void addComponent(Component component) { public void addComponent(Component component) {
@ -75,39 +80,81 @@ public final class Entity {
refresh(); refresh();
} }
protected void addComponentType(ComponentType type) { private void refresh() {
componentBits |= type.bits;
}
protected void removeComponentType(ComponentType type) {
componentBits &= ~type.bits;
}
protected void addSystemEnabledBit(long bit) {
systemEnabledBits |= bit;
}
protected void removeSystemEnabledBit(long bit) {
systemEnabledBits &= ~bit;
}
public void refresh() {
if (!pendingRefresh) { if (!pendingRefresh) {
engine.refreshEntity(this); engine.refreshEntity(this);
pendingRefresh = true; pendingRefresh = true;
} }
} }
protected void reset() { public boolean hasComponent(Class<? extends Component> clazz) {
return ComponentType.getComponentType(clazz).isInBits(componentBits);
}
public <T extends Component> T getComponent(Class<T> clazz) {
return engine.getEntityComponent(this, clazz);
}
public ObjectSet<String> getGroups() {
return groups;
}
public void addGroup(String group) {
engine.addEntityGroup(this, group);
}
public void removeGroup(String group) {
engine.removeEntityGroup(this, group);
}
public boolean inGroup(String group) {
return groups.contains(group);
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
engine.tagEntity(this, tag);
}
public void removeTag() {
engine.removeEntityTag(this);
}
public boolean hasTag(String tag) {
return tag.equals(this.tag);
}
// Internal getComponent method accessed only by ComponentMapper
Component getComponent(ComponentType type) {
return engine.getEntityComponent(this, type);
}
void addComponentType(ComponentType type) {
componentBits |= type.bit;
}
void removeComponentType(ComponentType type) {
componentBits &= ~type.bit;
}
void addSystemEnabledBit(long systemBit) {
systemEnabledBits |= systemBit;
}
void removeSystemEnabledBit(long systemBit) {
systemEnabledBits &= ~systemBit;
}
void reset() {
componentBits = 0; componentBits = 0;
systemEnabledBits = 0; systemEnabledBits = 0;
active = false; active = false;
removed = false; removed = false;
pendingRefresh = false; pendingRefresh = false;
} uniqueId = nextUniqueId++;
protected void updateUniqueId() {
this.uniqueId = nextUniqueId++;
} }
public String toString() { public String toString() {

View File

@ -1,14 +1,18 @@
package com.me.common.ecs; package com.me.common.ecs;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
final class EntityManager { final class EntityManager {
private Engine engine; private final Engine engine;
protected Array<Entity> entities; private final Array<Entity> entities;
protected Array<Entity> removedEntities; private final Array<Entity> removedEntities;
protected Array<Entity> toRefresh; private final Array<Entity> toRefresh;
private final ObjectMap<String, Array<Entity>> groupEntities;
private final ObjectMap<String, Entity> taggedEntities;
EntityManager(Engine engine) { EntityManager(Engine engine) {
this.engine = engine; this.engine = engine;
@ -16,9 +20,16 @@ final class EntityManager {
this.entities = new Array<>(false, 16); this.entities = new Array<>(false, 16);
this.removedEntities = new Array<>(false, 16); this.removedEntities = new Array<>(false, 16);
this.toRefresh = new Array<>(false, 16); this.toRefresh = new Array<>(false, 16);
this.groupEntities = new ObjectMap<>();
this.taggedEntities = new ObjectMap<>();
} }
public Entity create() { Array<Entity> getEntities() {
return entities;
}
Entity create() {
Entity entity; Entity entity;
if (!removedEntities.isEmpty()) { if (!removedEntities.isEmpty()) {
entity = removedEntities.removeIndex(0); entity = removedEntities.removeIndex(0);
@ -26,12 +37,11 @@ final class EntityManager {
entity = new Entity(engine); entity = new Entity(engine);
} }
entity.reset(); entity.reset();
entity.updateUniqueId();
entities.add(entity); entities.add(entity);
return entity; return entity;
} }
public void update() { void update() {
if (toRefresh.isEmpty()) { if (toRefresh.isEmpty()) {
return; return;
} }
@ -44,12 +54,52 @@ final class EntityManager {
toRefresh.clear(); toRefresh.clear();
} }
public void queueRefresh(Entity entity) { Array<Entity> getGroupEntities(String group) {
Array<Entity> entities = groupEntities.get(group);
if (entities == null) {
entities = new Array<>();
groupEntities.put(group, entities);
}
return entities;
}
void addEntityToGroup(Entity entity, String group) {
if (!entity.groups.contains(group)) {
getGroupEntities(group).add(entity);
entity.groups.add(group);
}
}
void removeEntityFromGroup(Entity entity, String group) {
getGroupEntities(group).removeValue(entity, true);
entity.groups.remove(group);
}
Entity getEntityByTag(String tag) {
return taggedEntities.get(tag);
}
void tagEntity(Entity entity, String tag) {
Entity previouslyTagged = taggedEntities.put(tag, entity);
if (previouslyTagged != null) {
previouslyTagged.tag = null;
}
entity.tag = tag;
}
void removeEntityTag(Entity entity) {
if (entity.tag != null) {
taggedEntities.remove(entity.tag);
entity.tag = null;
}
}
void queueRefresh(Entity entity) {
toRefresh.add(entity); toRefresh.add(entity);
} }
private void refreshEntity(Entity entity) { private void refreshEntity(Entity entity) {
for (BaseSystem system : engine.systems) { for (BaseSystem system : engine.getSystems()) {
if (!(system instanceof EntitySystem)) { if (!(system instanceof EntitySystem)) {
continue; continue;
} }
@ -57,11 +107,20 @@ final class EntityManager {
((EntitySystem) system).refresh(entity); ((EntitySystem) system).refresh(entity);
} }
if (entity.removed) { if (entity.isRemoved()) {
engine.removeAllEntityComponents(entity.id); engine.removeAllEntityComponents(entity.id);
entities.removeValue(entity, true); entities.removeValue(entity, true);
removedEntities.add(entity); removedEntities.add(entity);
// remove the entity from any groups it is in
for (String group : entity.groups) {
getGroupEntities(group).removeValue(entity, true);
}
entity.groups.clear();
removeEntityTag(entity);
} }
} }
} }

View File

@ -10,15 +10,16 @@ public abstract class EntitySystem extends BaseSystem {
private static final Map<Class<? extends EntitySystem>, Long> systemBits = new HashMap<>(); private static final Map<Class<? extends EntitySystem>, Long> systemBits = new HashMap<>();
private static long nextBit = 1l; private static long nextBit = 1l;
private long systemBit; private final long systemBit;
private long typeBits; private final long componentBits;
protected Array<Entity> entities; private final Array<Entity> entities;
@SafeVarargs
public EntitySystem(Engine engine, Class<? extends Component>... components) { public EntitySystem(Engine engine, Class<? extends Component>... components) {
super(engine); super(engine);
this.systemBit = getBitFor(getClass()); this.systemBit = getBitFor(getClass());
this.typeBits = ComponentType.getMaskBits(components); this.componentBits = ComponentType.getBitsFor(components);
this.entities = new Array<>(true, 16, Entity.class); this.entities = new Array<>(true, 16, Entity.class);
} }
@ -29,7 +30,7 @@ public abstract class EntitySystem extends BaseSystem {
public void process(float dt) { public void process(float dt) {
for (int i = 0, n = entities.size; i < n; i++) { for (int i = 0, n = entities.size; i < n; i++) {
Entity entity = entities.items[i]; Entity entity = entities.items[i];
if (!entity.removed && entity.active) { if (entity.isActive()) {
processEntity(entity, dt); processEntity(entity, dt);
} }
} }
@ -37,13 +38,13 @@ public abstract class EntitySystem extends BaseSystem {
public abstract void processEntity(Entity entity, float dt); public abstract void processEntity(Entity entity, float dt);
protected void refresh(Entity entity) { void refresh(Entity entity) {
boolean enabled = (entity.systemEnabledBits & systemBit) == systemBit; boolean enabled = (entity.systemEnabledBits & systemBit) == systemBit;
boolean interested = (entity.componentBits & typeBits) == typeBits; boolean interested = (entity.componentBits & componentBits) == componentBits;
if (interested && !enabled && entity.active && !entity.removed) { if (interested && !enabled && entity.active && !entity.removed) {
add(entity); add(entity);
} else if (enabled && (!interested || !entity.active || entity.removed)) { } else if (enabled && (!interested || !entity.active) || entity.removed) {
remove(entity); remove(entity);
} }
} }
@ -59,7 +60,7 @@ public abstract class EntitySystem extends BaseSystem {
} }
static long getBitFor(Class<? extends EntitySystem> es) { private static long getBitFor(Class<? extends EntitySystem> es) {
Long bits = systemBits.get(es); Long bits = systemBits.get(es);
if (bits == null) { if (bits == null) {
bits = nextBit; bits = nextBit;

View File

@ -11,15 +11,15 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class ListenerRegistry { class ListenerRegistry {
private final Map<Class<? extends Event>, List<RegisteredListener>> registeredListeners; private final Map<Class<? extends Event>, List<RegisteredListener>> registeredListeners;
protected ListenerRegistry() { ListenerRegistry() {
this.registeredListeners = new HashMap<>(); this.registeredListeners = new HashMap<>();
} }
protected void registerListener(Listener listener) { void registerListener(Listener listener) {
for (Method method : listener.getClass().getMethods()) { for (Method method : listener.getClass().getMethods()) {
EventHandler eh = method.getAnnotation(EventHandler.class); EventHandler eh = method.getAnnotation(EventHandler.class);
if (eh == null) { if (eh == null) {
@ -37,10 +37,9 @@ public class ListenerRegistry {
List<RegisteredListener> executors = getRegisteredListeners(eventClass); List<RegisteredListener> executors = getRegisteredListeners(eventClass);
executors.add(new RegisteredListener(listener, method, eventClass, eh)); executors.add(new RegisteredListener(listener, method, eventClass, eh));
} }
} }
protected void callEvent(Event event) { void callEvent(Event event) {
List<RegisteredListener> listeners = getRegisteredListeners(event.getClass()); List<RegisteredListener> listeners = getRegisteredListeners(event.getClass());
if (listeners.isEmpty()) { if (listeners.isEmpty()) {
return; return;

View File

@ -4,11 +4,10 @@ import java.lang.reflect.Method;
public class RegisteredListener { public class RegisteredListener {
private Listener listener; private final Listener listener;
private Method method; private final Method method;
private Class<? extends Event> eventType; private final Class<? extends Event> eventType;
private final EventHandler eh;
public EventHandler eh;
public RegisteredListener(Listener listener, Method method, Class<? extends Event> eventType, EventHandler eh) { public RegisteredListener(Listener listener, Method method, Class<? extends Event> eventType, EventHandler eh) {
this.listener = listener; this.listener = listener;

View File

@ -8,9 +8,10 @@ public class DesktopLauncher {
public static void main (String[] arg) { public static void main (String[] arg) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = "Asteroids"; config.title = "Asteroids";
config.resizable = false; config.resizable = true;
// config.fullscreen = false;
config.width = 800; config.width = 800;
config.height = 600; config.height = 400;
new LwjglApplication(new Asteroids(), config); new LwjglApplication(new Asteroids(), config);
} }
} }