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 542 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
public void create() {
graphics = new Graphics(Constants.WIDTH, Constants.HEIGHT);
graphics = new Graphics();
graphics.initialize();
updateViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
game = new Game();
game.setNextScreen(new GameScreen(graphics));
@ -33,7 +34,12 @@ public class Asteroids extends ApplicationAdapter {
@Override
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;
public class Constants {
public final class Constants {
public static final boolean DEBUG = false;
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 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.me.asteroids.components.AccelerationComponent;
import com.me.asteroids.components.AsteroidComponent;
import com.me.asteroids.components.BulletComponent;
import com.me.asteroids.components.ColliderComponent;
import com.me.asteroids.components.DebrisComponent;
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.ModelComponent;
import com.me.asteroids.components.PlayerComponent;
@ -21,6 +21,10 @@ import com.me.common.ecs.Entity;
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;
public class EntityFactory {
@ -30,7 +34,9 @@ public class EntityFactory {
private static final Vector2 tmpA = 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) {
Entity entity = engine.createEntity();
@ -41,35 +47,39 @@ public class EntityFactory {
}
public static Entity createPlayer(Engine engine) {
PositionComponent position = new PositionComponent();
position.position = new Vector2(Constants.HALF_WIDTH, Constants.HALF_HEIGHT);
position.rotation = 90;
VelocityComponent velocity = new VelocityComponent(0f, 0f, 0f);
VelocityComponent velocity = new VelocityComponent();
velocity.velocity = new Vector2(0f, 0f);
velocity.maxVelocity = 400f;
ModelComponent model = new ModelComponent();
model.model = new PolygonModel(Color.WHITE);
model.model.setVertices(new float[]{
0f, 4f, // tip
-2.5f, -4f, // bottom left
-1f, -2.5f, // indent
1f, -2.5f, // indent
2.5f, -4f, // bottom right
ModelComponent playerModel = new ModelComponent(new PolygonModel(Color.WHITE));
playerModel.setVertices(new float[]{
0f, 0.5f, // tip
-5/16f, -0.5f, // bottom left
-1/8f, -5/16f, // indent
1/8f, -5/16f, // indent
5/16f, -0.5f, // bottom right
});
model.model.setScale(5);
AccelerationComponent accel = new AccelerationComponent();
accel.acceleration = new Vector2(0, 1f);
ModelComponent afterburnerModel = new ModelComponent(new LineModel(Color.CYAN));
afterburnerModel.setVertices(new float[]{
-2/16f, -5/16f,
0f, -0.8f,
2/16f, -5/16f
});
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(model);
player.addComponent(accel);
player.addComponent(COLLIDER);
player.addComponent(new PlayerComponent());
player.addComponent(playerModel);
player.addComponent(new AccelerationComponent(10f));
player.addComponent(COLLIDER_TAG);
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;
}
@ -103,35 +113,31 @@ public class EntityFactory {
}
public static Entity[] createDebris(Engine engine, Entity entity) {
Vector2 playerVelocity = entity.getComponent(VelocityComponent.class).velocity;
PositionComponent playerPosition = entity.getComponent(PositionComponent.class);
LineModel[] models = getLineModels(entity.getComponent(ModelComponent.class).model);
Vector2 explosionCenter = tmp.set(playerPosition.position).sub(Utils.setUnitVectorAngle(tmp2, playerPosition.rotation).scl(5));
Vector2 playerVelocity = VELOCITY.get(entity).velocity;
PositionComponent playerPosition = POSITION.get(entity);
LineModel[] models = getLineModels(MODEL.get(entity).model);
Vector2 explosionCenter = tmp.set(playerPosition.position).sub(Utils.setUnitVectorAngle(tmp2, playerPosition.rotation).scl(0.125f));
Entity[] entities = new Entity[models.length];
for (int i = 0, n = models.length; i < n; i++) {
ModelComponent model = new ModelComponent();
model.model = models[i];
PositionComponent position = new PositionComponent();
position.position = new Vector2(model.model.getPosition());
position.rotation = 90;
ModelComponent model = new ModelComponent(models[i]);
VelocityComponent velocity = new VelocityComponent();
velocity.velocity = new Vector2(models[i].getPosition())
velocity.set(new Vector2(models[i].getPosition())
.sub(explosionCenter)
.nor() // Direction from explosion center to center of piece
.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
.add(tmp2.set(playerVelocity).scl(0.75f)); // Maintain 75% of the player's velocity at impact
.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
velocity.angularVelocity = rand.nextFloat(-60, 60); // Make each piece spin at a different rate
Entity debris = createEntity(engine);
debris.addComponent(position);
debris.addComponent(new PositionComponent(new Vector2(model.getPosition()), 90));
debris.addComponent(velocity);
debris.addComponent(model);
debris.addComponent(DEBRIS_TAG);
debris.addComponent(new DecayComponent(rand.nextFloat(0.5f, 2.5f)));
debris.addComponent(new DebrisComponent());
entities[i] = debris;
}
return entities;
@ -139,49 +145,37 @@ public class EntityFactory {
public static Entity createBullet(Engine engine, Entity player) {
float[] modelVertices = player.getComponent(ModelComponent.class).model.getVertices();
float rotation = player.getComponent(PositionComponent.class).rotation;
float[] modelVertices = MODEL.get(player).model.getVertices();
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);
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);
bullet.addComponent(position);
bullet.addComponent(velocity);
bullet.addComponent(new VelocityComponent(new Vector2(direction).scl(12.5f)));
bullet.addComponent(model);
bullet.addComponent(COLLIDER);
bullet.addComponent(new BulletComponent());
bullet.addComponent(COLLIDER_TAG);
bullet.addComponent(BULLET_TAG);
return bullet;
}
public static Entity createAsteroid(Engine engine) {
// Creates an asteroid entity with position and velocity unset - the AsteroidSpawningSystem
// is responsible for setting its position and velocity
PositionComponent position = new PositionComponent();
VelocityComponent velocity = new VelocityComponent();
ModelComponent model = new ModelComponent();
model.model = new PolygonModel(Color.WHITE);
int size = rand.nextInt(45, 75);
model.model.setVertices(new AsteroidFactory()
ModelComponent model = new ModelComponent(new PolygonModel(Color.WHITE));
float size = rand.nextFloat(1f, 1.75f);
model.setVertices(new AsteroidFactory()
.setVertexCount(32)
.setSize(size)
.setSizeVariation(size * 0.5f)
@ -189,11 +183,12 @@ public class EntityFactory {
.generate());
Entity asteroid = createEntity(engine);
asteroid.addComponent(position);
asteroid.addComponent(velocity);
asteroid.addComponent(new PositionComponent());
asteroid.addComponent(new VelocityComponent());
asteroid.addComponent(model);
asteroid.addComponent(COLLIDER);
asteroid.addComponent(COLLIDER_TAG);
asteroid.addComponent(new AsteroidComponent());
asteroid.addGroup("ASTEROIDS");
return asteroid;
}
@ -207,8 +202,8 @@ public class EntityFactory {
}
public static Entity[] splitAsteroidIntoChunks(Engine engine, Entity asteroid, int chunkCount, float chunkScale) {
Vector2 asteroidVelocity = asteroid.getComponent(VelocityComponent.class).velocity;
Model asteroidModel = asteroid.getComponent(ModelComponent.class).model;
Vector2 asteroidVelocity = VELOCITY.get(asteroid).velocity;
ModelComponent asteroidModel = MODEL.get(asteroid);
Vector2 asteroidPosition = asteroidModel.getPosition();
float[] scaledVertices = scaleAndRelativizeVertices(asteroidPosition, asteroidModel.getVertices(), chunkScale);
@ -218,123 +213,31 @@ public class EntityFactory {
Entity[] entities = new Entity[chunkCount];
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();
model.model = new PolygonModel(asteroidModel.getColor());
model.model.setVertices(Arrays.copyOf(scaledVertices, scaledVertices.length));
model.model.setPosition(chunkPosition);
PositionComponent position = new PositionComponent();
position.position = new Vector2(chunkPosition);
position.rotation = 90;
ModelComponent model = new ModelComponent(new PolygonModel(asteroidModel.getColor()));
model.setVertices(Arrays.copyOf(scaledVertices, scaledVertices.length));
model.setPosition(chunkPosition);
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);
AsteroidComponent asteroidComponent = new AsteroidComponent();
asteroidComponent.generation = asteroid.getComponent(AsteroidComponent.class).generation + 1;
angle += angleStep;
Utils.setUnitVectorAngleRad(tmp, angle);
Entity split = createEntity(engine);
split.addComponent(model);
split.addComponent(position);
split.addComponent(new PositionComponent(new Vector2(chunkPosition), 90));
split.addComponent(velocity);
split.addComponent(asteroidComponent);
split.addComponent(COLLIDER);
split.addComponent(new AsteroidComponent(ASTEROID.get(asteroid).generation + 1));
split.addComponent(COLLIDER_TAG);
split.addGroup("ASTEROIDS");
entities[i] = split;
}
return entities;
}
public static PolygonModel[] getPolygonShards(PolygonModel model, int shardCount) {
PolygonModel[] shards = new PolygonModel[shardCount];
Vector2 position = tmp.set(model.getPosition());
float[] vertices = model.getVertices();
int verticesPerShard = vertices.length / (shardCount * 2);
int remaining = vertices.length % (shardCount * 2);
int vertice = 0;
for (int i = 0; i < shardCount; i++) {
if (i + 1 == shardCount && remaining > 0) {
// Put the remaining vertices onto the last shard
verticesPerShard += remaining / 2;
}
float[] shardVertices = new float[(verticesPerShard + 1) * 2];
shardVertices[0] = 0;
shardVertices[1] = 0;
float minX = shardVertices[0], maxX = shardVertices[0];
float minY = shardVertices[1], maxY = shardVertices[1];
for (int j = 2, n = shardVertices.length; j < n; j += 2) {
float x = shardVertices[j] = vertices[vertice++] - position.x;
float y = shardVertices[j + 1] = vertices[vertice++] - position.y;
minX = x < minX ? x : minX;
maxX = x > maxX ? x : maxX;
minY = y < minY ? y : minY;
maxY = y > maxY ? y : maxY;
}
float centerX = minX + ((maxX - minX) / 2);
float centerY = minY + ((maxY - minY) / 2);
for (int j = 0, n = shardVertices.length; j < n; j += 2) {
shardVertices[j] -= centerX;
shardVertices[j + 1] -= centerY;
}
PolygonModel shard = new PolygonModel(model.getColor());
shard.setVertices(shardVertices);
shard.setPosition(tmp2.set(position).add(centerX, centerY));
shards[i] = shard;
}
return shards;
}
public static Entity[] splitAsteroidIntoShards(Engine engine, Entity asteroid, int shardCount) {
Entity[] entities = new Entity[shardCount];
PolygonModel asteroidModel = (PolygonModel) asteroid.getComponent(ModelComponent.class).model;
PolygonModel[] shards = getPolygonShards(asteroidModel, shardCount);
Vector2 asteroidPosition = asteroidModel.getPosition();
Vector2 asteroidVelocity = asteroid.getComponent(VelocityComponent.class).velocity;
int generation = asteroid.getComponent(AsteroidComponent.class).generation;
for (int i = 0; i < entities.length; i++) {
ModelComponent model = new ModelComponent();
model.model = shards[i];
PositionComponent position = new PositionComponent();
position.position = new Vector2(model.model.getPosition());
position.rotation = 90;
VelocityComponent velocity = new VelocityComponent();
velocity.velocity = new Vector2(shards[i].getPosition())
.sub(asteroidPosition)
.nor()
.rotate(rand.nextFloat(-15, 15)) // Slightly alter the direction each piece flies off in
.scl(asteroidVelocity.len() * 1f) // Set speed to asteroid's original velocity
.add(tmp2.set(asteroidVelocity).scl(0.25f));
velocity.angularVelocity = rand.nextFloat(-30, 30);
AsteroidComponent asteroidComponent = new AsteroidComponent();
asteroidComponent.generation = generation + 1;
Entity entity = createEntity(engine);
entity.addComponent(model);
entity.addComponent(position);
entity.addComponent(velocity);
entity.addComponent(asteroidComponent);
entity.addComponent(COLLIDER);
entities[i] = entity;
}
return entities;
}
}

View File

@ -1,22 +1,23 @@
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.CollisionEvent;
import com.me.asteroids.events.PlayerASteroidCollisionEvent;
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 static CollisionEvent getNewCollisionEvent(Entity a, Entity b) {
boolean isEntityAPlayer = a.hasComponent(PlayerComponent.class);
boolean isEntityBPlayer = b.hasComponent(PlayerComponent.class);
boolean isEntityABullet = !isEntityAPlayer && a.hasComponent(BulletComponent.class);
boolean isEntityBBullet = !isEntityBPlayer && b.hasComponent(BulletComponent.class);
boolean isEntityAAsteroid = !isEntityAPlayer && !isEntityABullet && a.hasComponent(AsteroidComponent.class);
boolean isEntityBAsteroid = !isEntityBPlayer && !isEntityBBullet && b.hasComponent(AsteroidComponent.class);
boolean isEntityAPlayer = PLAYER.has(a);
boolean isEntityBPlayer = PLAYER.has(b);
boolean isEntityABullet = !isEntityAPlayer && BULLET.has(a);
boolean isEntityBBullet = !isEntityBPlayer && BULLET.has(b);
boolean isEntityAAsteroid = !isEntityAPlayer && !isEntityABullet && ASTEROID.has(a);
boolean isEntityBAsteroid = !isEntityBPlayer && !isEntityBBullet && ASTEROID.has(b);
if (isEntityAAsteroid || 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;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
public class Graphics {
private int worldWidth, worldHeight;
private int screenWidth, screenHeight;
private Camera camera;
private Viewport viewport;
private OrthographicCamera camera;
private OrthographicCamera uiCamera;
private ShapeRenderer shapeRenderer;
private SpriteBatch spriteBatch;
private SpriteBatch uiSpriteBatch;
public Graphics(int worldWidth, int worldHeight) {
this.worldWidth = worldWidth;
this.worldHeight = worldHeight;
this.screenWidth = Gdx.graphics.getWidth();
this.screenHeight = Gdx.graphics.getHeight();
public Graphics() {
this.camera = new OrthographicCamera();
this.viewport = new FitViewport(worldWidth, worldHeight, camera);
this.uiCamera = new OrthographicCamera();
this.shapeRenderer = new ShapeRenderer();
this.spriteBatch = new SpriteBatch();
this.uiSpriteBatch = new SpriteBatch();
}
public void initialize() {
Gdx.gl.glClearColor(0, 0, 0, 1);
updateDimensions();
}
public void reset() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
public void setScreenSize(int width, int height) {
screenWidth = width;
screenHeight = height;
updateDimensions();
}
public void dispose() {
shapeRenderer.dispose();
spriteBatch.dispose();
uiSpriteBatch.dispose();
}
public ShapeRenderer getShapeRenderer() {
return shapeRenderer;
}
private void updateDimensions() {
viewport.setWorldSize(worldWidth, worldHeight);
viewport.update(screenWidth, screenHeight, true);
public SpriteBatch getSpriteBatch() {
return spriteBatch;
}
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);
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 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 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;
public class BulletComponent implements Component {
public class BulletTagComponent implements Component {
// 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.
*/
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;
public class DebrisComponent implements Component {
public class DebrisTagComponent implements Component {
// 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;
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.common.ecs.Component;
@ -7,4 +11,52 @@ public class ModelComponent implements Component {
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;
import com.me.common.ecs.Component;
import com.me.common.ecs.Entity;
public class PlayerComponent implements Component {
// 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 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 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.components.AccelerationComponent;
import com.me.asteroids.components.AsteroidComponent;
import com.me.asteroids.components.BulletComponent;
import com.me.asteroids.components.ColliderComponent;
import com.me.asteroids.components.DebrisComponent;
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.asteroids.components.model.PolygonModel;
import com.me.asteroids.events.AsteroidHitEvent;
import com.me.asteroids.events.BulletAsteroidCollisionEvent;
import com.me.asteroids.events.PlayerASteroidCollisionEvent;
import com.me.asteroids.events.PlayerDeathEvent;
import com.me.asteroids.events.ScreenWrapEvent;
import com.me.asteroids.systems.AsteroidSpawningSystem;
import com.me.asteroids.systems.CollisionSystem;
import com.me.asteroids.systems.DecaySystem;
import com.me.asteroids.systems.GameDataRenderSystem;
import com.me.asteroids.systems.GameDataSystem;
import com.me.asteroids.systems.ModelRenderSystem;
import com.me.asteroids.systems.MovementSystem;
import com.me.asteroids.systems.PlayerInputSystem;
import com.me.asteroids.systems.ScreenWrapSystem;
import com.me.common.Screen;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.event.EventHandler;
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 {
Engine engine;
@ -51,23 +62,27 @@ public class GameScreen extends Screen implements Listener {
@Override
public void setup() {
batch = new SpriteBatch();
font = new BitmapFont();
font.setColor(Color.RED);
batch = graphics.getUISpriteBatch();
font = new BitmapFont(Gdx.files.internal("Retro_Font.fnt"));
engine = new Engine();
engine.registerComponentClass(PlayerComponent.class);
engine.registerComponentClass(BulletComponent.class);
engine.registerComponentClass(BulletTagComponent.class);
engine.registerComponentClass(ColliderTagComponent.class);
engine.registerComponentClass(DebrisTagComponent.class);
engine.registerComponentClass(AccelerationComponent.class);
engine.registerComponentClass(AsteroidComponent.class);
engine.registerComponentClass(DebrisComponent.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(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 AsteroidSpawningSystem(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 ScreenWrapSystem(engine));
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);
player.activate();
@ -91,35 +110,22 @@ public class GameScreen extends Screen implements Listener {
if (Constants.DEBUG) {
batch.begin();
font.draw(batch, String.format("FPS: %d, Entities: %d", Gdx.graphics.getFramesPerSecond(), engine.getEntityCount()), 50, 50);
font.setColor(Color.RED);
font.draw(batch, String.format("FPS: %d, Entities: %d", Gdx.graphics.getFramesPerSecond(), engine.getEntities().size), 15, 15 + font.getCapHeight());
batch.end();
}
}
@Override
public void dispose() {
font.dispose();
}
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
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
event.setCancelled(true);
event.entity.remove();
@ -128,15 +134,16 @@ public class GameScreen extends Screen implements Listener {
@EventHandler
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)) {
// AABBs intersect but let's only consider it a hit if the bullet's position
// is actually inside the asteroid
AsteroidComponent asteroid = ASTEROID.get(event.getAsteroid());
engine.callEvent(new AsteroidHitEvent(asteroid));
event.getBullet().remove();
int generation = asteroidMapper.get(event.getAsteroid()).generation;
if (generation < 2) {
if (asteroid.generation < 2) {
for (Entity shard : EntityFactory.splitAsteroidIntoChunks(engine, event.getAsteroid(), 2, 2/3f)) {
shard.activate();
}
@ -146,16 +153,17 @@ public class GameScreen extends Screen implements Listener {
}
}
event.getAsteroid().remove();
}
}
@EventHandler
public void onPlayerAsteroidCollision(PlayerASteroidCollisionEvent event) {
PolygonModel asteroid = (PolygonModel) modelMapper.get(event.getAsteroid()).model;
PolygonModel player = (PolygonModel) modelMapper.get(event.getPlayer()).model;
PolygonModel asteroid = (PolygonModel) MODEL.get(event.getAsteroid()).model;
PolygonModel player = (PolygonModel) MODEL.get(event.getPlayer()).model;
if (asteroid.contains(player.getVertices()) || player.contains(asteroid.getVertices())) {
engine.callEvent(new PlayerDeathEvent(event.getPlayer()));
PLAYER.get(event.getPlayer()).afterBurner.deactivate();
event.getPlayer().deactivate();
for (Entity debris : EntityFactory.createDebris(engine, event.getPlayer())) {
debris.activate();

View File

@ -2,37 +2,26 @@ package com.me.asteroids.systems;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.me.asteroids.Constants;
import com.me.asteroids.EntityFactory;
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.model.Model;
import com.me.common.ecs.BaseSystem;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
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;
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;
public AsteroidSpawningSystem(Engine 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) {
@ -55,16 +44,16 @@ public class AsteroidSpawningSystem extends BaseSystem {
private void spawnAsteroid() {
Entity asteroid = EntityFactory.createAsteroid(engine);
Model model = modelMapper.get(asteroid).model;
Model model = MODEL.get(asteroid).model;
Rectangle aabb = model.getBoundingBox();
Vector2 position
= positionMapper.get(asteroid).position
= POSITION.get(asteroid).position
= getRandomSpawnLocation(aabb.getWidth(), aabb.getHeight());
model.setPosition(position);
VelocityComponent velocityComponent = velocityMapper.get(asteroid);
velocityComponent.velocity = new Vector2().setToRandomDirection().scl(rand.nextFloat(75, 125));
VelocityComponent velocityComponent = VELOCITY.get(asteroid);
velocityComponent.velocity = new Vector2().setToRandomDirection().scl(rand.nextFloat(1.875f, 3.125f));
velocityComponent.angularVelocity = rand.nextFloat(-30, 30);
asteroid.activate();
@ -72,16 +61,7 @@ public class AsteroidSpawningSystem extends BaseSystem {
@Override
public void process(float dt) {
Array<Entity> entities = engine.getEntities();
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++;
}
}
int asteroidCount = engine.getEntitiesInGroup("ASTEROIDS").size;
if (asteroidCount++ < Constants.ASTEROID_SPAWN_COUNT && (asteroidSpawnDelay -= dt) <= 0) {
spawnAsteroid();

View File

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

View File

@ -1,30 +1,27 @@
package com.me.asteroids.systems;
import com.me.asteroids.components.DecayComponent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem;
public class DecaySystem extends EntitySystem {
import static com.me.asteroids.Components.DECAY;
private ComponentMapper<DecayComponent> decayTimerMapper;
public class DecaySystem extends EntitySystem {
public DecaySystem(Engine engine) {
super(engine, DecayComponent.class);
decayTimerMapper = engine.getComponentMapper(DecayComponent.class);
}
@Override
public void processEntity(Entity entity, float dt) {
DecayComponent decayComponent = decayTimerMapper.get(entity);
DecayComponent decayComponent = DECAY.get(entity);
if ((decayComponent.decayTimer -= dt) <= 0) {
if (decayComponent.remove) {
entity.remove();
} else {
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.components.ModelComponent;
import com.me.asteroids.components.model.Model;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem;
public class ModelRenderSystem extends EntitySystem {
import static com.me.asteroids.Components.MODEL;
private ComponentMapper<ModelComponent> modelMapper;
public class ModelRenderSystem extends EntitySystem {
private ShapeRenderer renderer;
public ModelRenderSystem(Engine engine, Graphics graphics) {
super(engine, ModelComponent.class);
this.modelMapper = engine.getComponentMapper(ModelComponent.class);
this.renderer = graphics.getShapeRenderer();
}
@ -28,7 +26,7 @@ public class ModelRenderSystem extends EntitySystem {
@Override
public void processEntity(Entity entity, float dt) {
Model model = modelMapper.get(entity).model;
Model model = MODEL.get(entity).model;
renderer.setColor(model.getColor());
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.VelocityComponent;
import com.me.asteroids.components.model.Model;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem;
import 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 {
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) {
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
public void processEntity(Entity entity, float dt) {
PositionComponent positionComponent = positionMapper.get(entity);
VelocityComponent velocityComponent = velocityMapper.get(entity);
PositionComponent positionComponent = POSITION.get(entity);
VelocityComponent velocityComponent = VELOCITY.get(entity);
AccelerationComponent accelComponent = ACCELERATION.get(entity);
Vector2 velocity = velocityComponent.velocity;
AccelerationComponent accelComponent = accelMapper.get(entity);
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;
if (!velocity.isZero()) {
velocity.clamp(0, maxVelocity);
position.add(tmp.set(velocity).scl(dt));
positionComponent.position.add(tmp.set(velocity).scl(dt));
}
float angularVelocity = velocityComponent.angularVelocity;
@ -55,7 +59,7 @@ public class MovementSystem extends EntitySystem {
positionComponent.rotation = Utils.wrapAngle(positionComponent.rotation + (angularVelocity * dt));
}
ModelComponent modelComponent = modelMapper.get(entity);
ModelComponent modelComponent = MODEL.get(entity);
if (modelComponent != null) {
Model model = modelComponent.model;
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.PositionComponent;
import com.me.asteroids.components.VelocityComponent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem;
import 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 Vector2 tmp = new Vector2(0, 1);
private ComponentMapper<PositionComponent> positionMapper;
private ComponentMapper<VelocityComponent> velocityMapper;
private ComponentMapper<AccelerationComponent> accelMapper;
public PlayerInputSystem(Engine engine) {
super(
engine,
@ -30,16 +32,14 @@ public class PlayerInputSystem extends EntitySystem {
AccelerationComponent.class,
PlayerComponent.class
);
positionMapper = engine.getComponentMapper(PositionComponent.class);
velocityMapper = engine.getComponentMapper(VelocityComponent.class);
accelMapper = engine.getComponentMapper(AccelerationComponent.class);
}
@Override
public void processEntity(Entity entity, float dt) {
PositionComponent positionComponent = positionMapper.get(entity);
VelocityComponent velocityComponent = velocityMapper.get(entity);
AccelerationComponent accelComponent = accelMapper.get(entity);
PositionComponent positionComponent = POSITION.get(entity);
VelocityComponent velocityComponent = VELOCITY.get(entity);
AccelerationComponent accelComponent = ACCELERATION.get(entity);
PlayerComponent playerComponent = PLAYER.get(entity);
boolean isLeftPressed = Gdx.input.isKeyPressed(Input.Keys.A);
boolean isRightPressed = Gdx.input.isKeyPressed(Input.Keys.D);
@ -51,16 +51,27 @@ public class PlayerInputSystem extends EntitySystem {
velocityComponent.angularVelocity = 0;
}
Vector2 acceleration = accelComponent.acceleration;
Vector2 velocity = velocityComponent.velocity;
MODEL.get(playerComponent.afterBurner).setPosition(positionComponent.position);
if (Gdx.input.isKeyPressed(Input.Keys.W)) {
acceleration.set(Utils.setUnitVectorAngle(tmp, positionComponent.rotation).scl(500));
} else {
if (!velocity.isZero(1f)) {
acceleration.set(Utils.setUnitVectorAngleRad(tmp, velocity.angleRad()).scl(-100));
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 {
acceleration.set(0, 0);
playerComponent.afterBurner.deactivate();
}
} else {
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.PositionComponent;
import com.me.asteroids.events.ScreenWrapEvent;
import com.me.common.ecs.ComponentMapper;
import com.me.common.ecs.Engine;
import com.me.common.ecs.Entity;
import com.me.common.ecs.EntitySystem;
public class ScreenWrapSystem extends EntitySystem {
import static com.me.asteroids.Components.MODEL;
import static com.me.asteroids.Components.POSITION;
private ComponentMapper<PositionComponent> positionMapper;
private ComponentMapper<ModelComponent> modelMapper;
public class ScreenWrapSystem extends EntitySystem {
public ScreenWrapSystem(Engine engine) {
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) {
@ -33,8 +30,8 @@ public class ScreenWrapSystem extends EntitySystem {
@Override
public void processEntity(Entity entity, float dt) {
Vector2 position = positionMapper.get(entity).position;
Rectangle aabb = modelMapper.get(entity).model.getBoundingBox();
Vector2 position = POSITION.get(entity).position;
Rectangle aabb = MODEL.get(entity).model.getBoundingBox();
// Check top/bottom edges
float minY = aabb.y;

View File

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

View File

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

View File

@ -1,23 +1,36 @@
package com.me.common.ecs;
public class ComponentMapper<T extends Component> {
import java.util.HashMap;
import java.util.Map;
private Engine engine;
private ComponentType type;
private Class<T> typeClass;
public final class ComponentMapper<T extends Component> {
public ComponentMapper(Engine engine, Class<T> typeClass) {
this.engine = engine;
private static final Map<Class<? extends Component>, ComponentMapper> mappers = new HashMap<>();
private final ComponentType type;
private final Class<T> typeClass;
private ComponentMapper(Class<T> typeClass) {
this.type = ComponentType.getComponentType(typeClass);
this.typeClass = typeClass;
}
public T get(Entity entity) {
return typeClass.cast(engine.getEntityComponent(entity, type));
return typeClass.cast(entity.getComponent(type));
}
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 {
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 int nextId = 0;
protected long bits;
protected int id;
final long bit;
final int id;
private ComponentType() {
this.bits = nextBit;
this.bit = nextBit;
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;
}
protected int getId() {
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) {
static ComponentType registerComponentType(Class<? extends Component> component) {
ComponentType type = types.get(component);
if (type != null) {
throw new IllegalArgumentException(component.getName() + " has already been registered.");
}
type = new ComponentType();
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);
if (type == null) {
throw new IllegalArgumentException(component.getName() + " has not been registered.");
@ -71,8 +51,4 @@ final class ComponentType {
return type;
}
protected static int getRegisteredComponentTypeCount() {
return types.size();
}
}

View File

@ -1,49 +1,55 @@
package com.me.common.ecs;
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.Listener;
import java.util.HashMap;
import java.util.Map;
public class Engine {
private ComponentBag[] components;
protected Array<BaseSystem> systems;
private final EntityManager entityManager;
private final ComponentBag[] components;
private final Array<BaseSystem> systems;
private EntityManager entityManager;
private ListenerRegistry listenerRegistry;
private Map<Class<? extends Component>, ComponentMapper> componentMappers;
private final ListenerRegistry listenerRegistry;
public Engine() {
this.entityManager = new EntityManager(this);
this.components = new ComponentBag[Long.SIZE];
this.systems = new Array<>();
this.listenerRegistry = new ListenerRegistry();
this.componentMappers = new HashMap<>();
}
/**
* Register a Component class.
* @param 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) {
this.systems.add(system);
systems.add(system);
}
/**
* Register an event Listener.
* @param listener
*/
public void registerListener(Listener listener) {
listenerRegistry.registerListener(listener);
}
public void ready() {
this.components = new ComponentBag[ComponentType.getRegisteredComponentTypeCount()];
for (int i = 0; i < components.length; i++) {
components[i] = new ComponentBag();
}
}
/**
* Process all systems
* @param dt the time (in seconds) passed since the last update
*/
public void update(float dt) {
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() {
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() {
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) {
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);
}
protected void removeAllEntityComponents(int entityId) {
for (int i = 0; i < components.length; i++) {
components[i].insert(entityId, null);
void removeAllEntityComponents(int entityId) {
for (ComponentBag bag : components) {
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());
components[type.id].insert(entity.id, component);
entity.addComponentType(type);
}
protected void removeEntityComponent(Entity entity, Component component) {
void removeEntityComponent(Entity entity, Component component) {
ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.id].remove(entity.id);
entity.removeComponentType(type);
}
protected <T extends Component> T getEntityComponent(Entity entity, Class<T> clazz) {
return clazz.cast(components[ComponentType.getTypeId(clazz)].get(entity.id));
<T extends Component> T getEntityComponent(Entity entity, Class<T> clazz) {
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);
}
@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;
import com.badlogic.gdx.utils.ObjectSet;
public final class Entity {
private static int nextId = 0;
private static long nextUniqueId = 0;
private Engine engine;
protected final int id;
private final Engine engine;
final int id;
protected long componentBits;
protected long systemEnabledBits;
long componentBits;
long systemEnabledBits;
boolean pendingRefresh;
protected boolean active;
protected boolean removed;
protected boolean pendingRefresh;
boolean active;
boolean removed;
ObjectSet<String> groups;
String tag;
private long uniqueId;
protected Entity(Engine engine) {
this.engine = engine;
this.id = nextId++;
this.groups = new ObjectSet<>();
}
public int getId() {
@ -30,6 +36,10 @@ public final class Entity {
return uniqueId;
}
public Engine getEngine() {
return engine;
}
public boolean isActive() {
return active;
}
@ -39,30 +49,25 @@ public final class Entity {
}
public void activate() {
active = true;
refresh();
if (!active) {
active = true;
refresh();
}
}
public void deactivate() {
active = false;
refresh();
if (active) {
active = false;
refresh();
}
}
public void remove() {
removed = true;
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);
if (!removed) {
removed = true;
active = false;
refresh();
}
}
public void addComponent(Component component) {
@ -75,39 +80,81 @@ public final class Entity {
refresh();
}
protected void addComponentType(ComponentType type) {
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() {
private void refresh() {
if (!pendingRefresh) {
engine.refreshEntity(this);
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;
systemEnabledBits = 0;
active = false;
removed = false;
pendingRefresh = false;
}
protected void updateUniqueId() {
this.uniqueId = nextUniqueId++;
uniqueId = nextUniqueId++;
}
public String toString() {

View File

@ -1,14 +1,18 @@
package com.me.common.ecs;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
final class EntityManager {
private Engine engine;
private final Engine engine;
protected Array<Entity> entities;
protected Array<Entity> removedEntities;
protected Array<Entity> toRefresh;
private final Array<Entity> entities;
private final Array<Entity> removedEntities;
private final Array<Entity> toRefresh;
private final ObjectMap<String, Array<Entity>> groupEntities;
private final ObjectMap<String, Entity> taggedEntities;
EntityManager(Engine engine) {
this.engine = engine;
@ -16,9 +20,16 @@ final class EntityManager {
this.entities = new Array<>(false, 16);
this.removedEntities = 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;
if (!removedEntities.isEmpty()) {
entity = removedEntities.removeIndex(0);
@ -26,12 +37,11 @@ final class EntityManager {
entity = new Entity(engine);
}
entity.reset();
entity.updateUniqueId();
entities.add(entity);
return entity;
}
public void update() {
void update() {
if (toRefresh.isEmpty()) {
return;
}
@ -44,12 +54,52 @@ final class EntityManager {
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);
}
private void refreshEntity(Entity entity) {
for (BaseSystem system : engine.systems) {
for (BaseSystem system : engine.getSystems()) {
if (!(system instanceof EntitySystem)) {
continue;
}
@ -57,11 +107,20 @@ final class EntityManager {
((EntitySystem) system).refresh(entity);
}
if (entity.removed) {
if (entity.isRemoved()) {
engine.removeAllEntityComponents(entity.id);
entities.removeValue(entity, true);
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 long nextBit = 1l;
private long systemBit;
private long typeBits;
private final long systemBit;
private final long componentBits;
protected Array<Entity> entities;
private final Array<Entity> entities;
@SafeVarargs
public EntitySystem(Engine engine, Class<? extends Component>... components) {
super(engine);
this.systemBit = getBitFor(getClass());
this.typeBits = ComponentType.getMaskBits(components);
this.componentBits = ComponentType.getBitsFor(components);
this.entities = new Array<>(true, 16, Entity.class);
}
@ -29,7 +30,7 @@ public abstract class EntitySystem extends BaseSystem {
public void process(float dt) {
for (int i = 0, n = entities.size; i < n; i++) {
Entity entity = entities.items[i];
if (!entity.removed && entity.active) {
if (entity.isActive()) {
processEntity(entity, dt);
}
}
@ -37,13 +38,13 @@ public abstract class EntitySystem extends BaseSystem {
public abstract void processEntity(Entity entity, float dt);
protected void refresh(Entity entity) {
void refresh(Entity entity) {
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) {
add(entity);
} else if (enabled && (!interested || !entity.active || entity.removed)) {
} else if (enabled && (!interested || !entity.active) || entity.removed) {
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);
if (bits == null) {
bits = nextBit;

View File

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

View File

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

View File

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