Quick recap
The second weekend have I focus on game play. I have a working AI that is targeting the player, it is very very simple so far. I have done input to a ship so I can steer it. There are physics so the ships can move and turn. I have a weapon system so I can shoot projectiles. I can detect hits and an particle system are running when a hit is accuring. The game look like this.
Moving from Entity to ECS
Going from an Entity that inheritance a general object to an Entity Component System have been very hard for me. I do like the simple entity for small games. It is very fast to get started with. The complexity are a lot smaller then an ECS. For small games I think I will stick with the entity setup. The problem is that I need to know almost the entire game before started because it is very inflexible to work with. But for fast small thing I do like it. It is very simple.
I have been working with Artemis now this week and the complexity is going up a lot compared to the simpler entity solution. But the more I work with ECSÂ the more I start to like it. The big question for me at the moment is how many components should I have and how many systems. I have read this article and he suggest that when you move from an entity inheritance to an ECS you have a one-to-one ratio between component and systems. Then when you get more used to it you can get it more to what you need. But I think when you start a project there are a good aim to have a one-to-one ratio.
Components
All the components can be found here for easier understanding.
The components I have created so far are a Transform that handle where the entity are, the z-value on what plane things should be running manly for rendering, rotation and the scale. I Texture that just hold a sprite. Input that tell that this entity should handle manual inputs. AI that tell that the entity should be handled by a computer. Physic that handle velocity (what direction to move), speed and rotation speed. Explosion that just handle a particle effect, maybe I should make a more general particle component. Bomb that is a ball that is flying in a straight line, it have how long it will live and how much damage it should do. I don’t know how I should be doing with the different weapons so I’m doing different component for every weapons. Last have I the Ship, this have the health, max speed, max rotation speed and if can stop when you don’t press the button any more.
This is all the component I’m using at the moment. I will need some more weapons components. I don’t know if I should go with many ships also, or if I can have just one ship.
Systems
Now to the fun part. All my systems and how I’m thinking with them.
In my GameLoopSystemInvocationStratey class have I only added so my none Logic system get the remaining delta. This is used in the render to get a smoother animation but with fewer logic ticks. This meen that I can have a logic calculation on 20 FPS but I run the graphics on 60 FPS.
My SortedIteratingSystem are not changed, but my SortedRenderSystem have some addition. I have added so the camera are moving with the player ship, this is made in the begin method. In process method have I added delta calculation if the graphics running faster then the logics to make it look smoother. I have added particles with an explosion. I have also change the ZComparator to order with the highest number will be the one furthest up.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | @Override protected void begin() { if (jamSpace.camera != null) { AspectSubscriptionManager asm = world.getAspectSubscriptionManager(); EntitySubscription es = asm.get(Aspect.all(InputComp.class, Transform2DComp.class)); if (es.getEntities().size() == 1) { Transform2DComp t2d = world.getEntity(es.getEntities().get(0)).getComponent(Transform2DComp.class); if (physic.has(es.getEntities().get(0))) { PhysicComp physicComp = physic.get(es.getEntities().get(0)); jamSpace.camera.position.set(t2d.position.x + (physicComp.velocity.x * physicComp.speed * world.getDelta()), t2d.position.y + (physicComp.velocity.y * physicComp.speed * world.getDelta()), 0); } else { jamSpace.camera.position.set(t2d.position.x, t2d.position.y, 0); } jamSpace.camera.update(); } if (jamSpace.batch != null) { jamSpace.camera.update(); jamSpace.batch.setProjectionMatrix(jamSpace.camera.combined); jamSpace.batch.begin(); } } } @Override protected void process(Entity entity) { if (jamSpace.batch != null) { Transform2DComp transformComp = transform2d.get(entity); if (texture.has(entity)) { TextureComp textureComp = texture.get(entity); if (textureComp.sprite != null) { if (physic.has(entity)) { PhysicComp physicComp = physic.get(entity); textureComp.sprite.setPosition(transformComp.position.x + (physicComp.velocity.x * physicComp.speed * world.getDelta()), transformComp.position.y + (physicComp.velocity.y * physicComp.speed * world.getDelta())); } else { textureComp.sprite.setPosition(transformComp.position.x, transformComp.position.y); } textureComp.sprite.setScale(transformComp.scale.x, transformComp.scale.y); textureComp.sprite.setRotation(transformComp.rotation); textureComp.sprite.draw(jamSpace.batch); } } if (explosion.has(entity)) { ExplosionComp explosionComp = explosion.get(entity); explosionComp.effect.setPosition(transformComp.position.x, transformComp.position.y); explosionComp.effect.draw(jamSpace.batch, world.getDelta()); if (explosionComp.effect.isComplete()) { world.delete(entity.getId()); } } } } |
My PhysicsSystem are calculating where moving object should move. Not much to think about here.
1 2 3 4 5 6 | protected void process(int entityId) { Transform2DComp transform2DComp = transform2d.get(entityId); PhysicComp physicComp = physics.get(entityId); transform2DComp.rotation = transform2DComp.rotation + (physicComp.rotationSpeed * delta); transform2DComp.position.add(physicComp.velocity.x * physicComp.speed * delta, physicComp.velocity.y * physicComp.speed * delta); } |
My InputSystem handle all the inputs a human player can make. At the moment have I hard coded it to only work with one weapon type. So this will be the next thing to look at.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | protected void process(int entityId) { PhysicComp physicComp = physic.get(entityId); Transform2DComp transform2DComp = transform2d.get(entityId); ShipComp shipComp = ship.get(entityId); if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) { if (physicComp.rotationSpeed < shipComp.maxRotationSpeed) { physicComp.rotationSpeed += shipComp.maxRotationSpeed * delta; } } else if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) { if (physicComp.rotationSpeed > -shipComp.maxRotationSpeed) { physicComp.rotationSpeed += -shipComp.maxRotationSpeed * delta; } } else { physicComp.rotationSpeed = 0; } if (Gdx.input.isKeyPressed(Input.Keys.UP)) { physicComp.speed = shipComp.maxSpeed; float x = MathUtils.cosDeg(transform2DComp.rotation); float y = MathUtils.sinDeg(transform2DComp.rotation); physicComp.velocity.set(x, y); } else { if (shipComp.directStop) { physicComp.velocity.set(0,0); } } if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { float x = MathUtils.cosDeg(transform2DComp.rotation); float y = MathUtils.sinDeg(transform2DComp.rotation); Vector2 position = new Vector2(transform2DComp.position); position.x += 36 + (x * 36); position.y += 55 + (y * 55); Entity fire = world.createEntity(); fire.edit().add(new TextureComp(new Sprite(new Texture("weapons/bomb.png")))) .add(new Transform2DComp(position, 2, new Vector2(1, 1), transform2DComp.rotation)) .add(new PhysicComp(new Vector2(x, y), 500, 0)) .add(new BombComp(2)); } } |
My AISystem just handle computer ships. It try to find the closest human and attack it. Need to make this more intelligent later on. So far it just turn to the player and go. I need to make it swing or slowly rotate against the player and not just jump to where the player is. This will give the computer an unfair advantage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | @Override protected void begin() { closestShip = -1f; } @Override protected void process(int entityId) { AspectSubscriptionManager asm = world.getAspectSubscriptionManager(); EntitySubscription es = asm.get(Aspect.all(ShipComp.class, Transform2DComp.class)); Entity ep = world.getEntity(entityId); Entity toShip = null; for (int i = es.getEntities().size() - 1; i >= 0; i--) { Entity e = world.getEntity(es.getEntities().get(i)); if (e.getId() != entityId) { float distance = e.getComponent(Transform2DComp.class).position.dst(ep.getComponent(Transform2DComp.class).position); if (closestShip == -1 || distance < closestShip) { closestShip = distance; toShip = e; } } } if (toShip != null) { float degree = toShip.getComponent(Transform2DComp.class).position.cpy().sub(ep.getComponent(Transform2DComp.class).position).angle(); ep.getComponent(Transform2DComp.class).rotation = degree; float x = MathUtils.cosDeg(degree); float y = MathUtils.sinDeg(degree); ep.getComponent(PhysicComp.class).velocity.set(x, y); } } |
The last system for now are the WeaponSystem. So far it look like I will be building one system that handle all the different weapons instead of one system for every type of weapon. This might be a dumb thing to do. But I think it’s not that hard to split it if I need to do that later. When I’m looking at it while writing I think this isn’t the best way to go. Might split it tomorrow.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | protected void process(int entityId) { if (bomb.has(entityId)) { BombComp bombComp = bomb.get(entityId); if (bombComp.livedTime > bombComp.maxLiveTime) { world.delete(entityId); } bombComp.livedTime += delta; TextureComp bombTexture = texture.get(entityId); AspectSubscriptionManager asm = world.getAspectSubscriptionManager(); EntitySubscription es = asm.get(Aspect.all(ShipComp.class, TextureComp.class, AIComp.class)); for (int i = es.getEntities().size() - 1; i >= 0; i--) { Entity shipE = world.getEntity(es.getEntities().get(i)); TextureComp shipTexture = texture.get(shipE); if (shipTexture.sprite.getBoundingRectangle().overlaps(bombTexture.sprite.getBoundingRectangle())) { ShipComp shipComp = ship.get(shipE); shipComp.health -= MathUtils.random(bombComp.minDmg, bombComp.maxDmg); world.delete(entityId); if (shipComp.health < 0) { world.delete(shipE.getId()); } Entity explosion = world.createEntity(); explosion.edit().add(new Transform2DComp(new Vector2(bombTexture.sprite.getX(), bombTexture.sprite.getY()), 10, new Vector2(1,1), 0)) .add(new ExplosionComp()); explosion.getComponent(ExplosionComp.class).effect.start(); System.out.println("health: " + shipComp.health); } } } } |
I do hope you find this post some what informative in the problem I have. There aren’t that many explanation how I have solved stuff. Just some feelings I have against ECS and moving from an Entity object. Hopefully things have sattle more to next weekend so I can give more information how I solve things. If you have any thoughts just give a comment and I will help as best as I can.
Happy Coding!