Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored Rubble to use ECS #593

Merged
merged 3 commits into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2020 The Terasology Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.destinationsol.rubble.components;

import org.destinationsol.body.events.GenerateBodyEvent;
import org.destinationsol.rubble.systems.RubbleBodyCreationSystem;
import org.terasology.gestalt.entitysystem.component.EmptyComponent;

/**
* This class directs the {@link GenerateBodyEvent} to the {@link RubbleBodyCreationSystem}
*/
public class RubbleMesh extends EmptyComponent<RubbleMesh> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2020 The Terasology Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.destinationsol.rubble.systems;

import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.World;
import org.destinationsol.Const;
import org.destinationsol.asteroids.components.AsteroidMesh;
import org.destinationsol.body.events.BodyCreatedEvent;
import org.destinationsol.body.events.GenerateBodyEvent;
import org.destinationsol.common.In;
import org.destinationsol.entitysystem.EntitySystemManager;
import org.destinationsol.entitysystem.EventReceiver;
import org.destinationsol.game.CollisionMeshLoader;
import org.destinationsol.game.UpdateAwareSystem;
import org.destinationsol.location.components.Angle;
import org.destinationsol.location.components.Position;
import org.destinationsol.rendering.RenderableElement;
import org.destinationsol.rendering.components.Renderable;
import org.destinationsol.rubble.components.RubbleMesh;
import org.destinationsol.size.components.Size;
import org.terasology.gestalt.entitysystem.entity.EntityRef;
import org.terasology.gestalt.entitysystem.event.EventResult;
import org.terasology.gestalt.entitysystem.event.ReceiveEvent;

import java.util.ArrayList;

/**
* This system creates a {@link Body} for an entity with a {@link RubbleMesh} component.
*/
public class RubbleBodyCreationSystem implements EventReceiver {

private static final float DENSITY = 3f;

@In
private EntitySystemManager entitySystemManager;

@In
private World world;

private final CollisionMeshLoader collisionMeshLoader = new CollisionMeshLoader("engine:miscCollisionMeshes");

@ReceiveEvent(components = {RubbleMesh.class, Size.class, Position.class, Angle.class, Renderable.class})
public EventResult onGenerateBody(GenerateBodyEvent event, EntityRef entity) {

float size = entity.getComponent(Size.class).get().size;
Vector2 position = entity.getComponent(Position.class).get().position;
float angle = entity.getComponent(Angle.class).get().getAngle();
ArrayList<RenderableElement> renderableElements = entity.getComponent(Renderable.class).get().elements;

//This creates an entity with a generic Body. The fixtures, which provide the collision meshes, are attached later.
BodyDef bd = new BodyDef();
bd.type = BodyDef.BodyType.DynamicBody;
bd.angle = angle * MathUtils.degRad;
bd.angularDamping = 0;
bd.position.set(position);
bd.linearDamping = 0;
Body body = world.createBody(bd);

//This sets a reference to an entity in the Body, so that the entity can be retrieved from the body during collision handling.
body.setUserData(entity);

//This attaches collision meshes to the Body of an entity, based on its graphics.
for (RenderableElement element : renderableElements) {
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.density = DENSITY;
fixtureDef.friction = Const.FRICTION;
collisionMeshLoader.attachFixture(body, element.texture.name, fixtureDef, size);

calculateGraphicsOffset(element);
}

entitySystemManager.sendEvent(new BodyCreatedEvent(body), entity);

return EventResult.CONTINUE;
}

/**
* This calculates the offset of the renderable element from "the origin" (as defined in the JSON that the
* CollisionMeshLoader reads from).
* The origin is where the center of the object should be, which is relevant for physics handling. The
* CollisionMeshLoader creates Fixtures (collision meshes) using that information, so the sprites need to be
* adjusted to overlay the mesh properly.
* LibGDX draws sprites from the bottom left corner. Since the position information is from the center, it
* needs to be adjusted to be at the bottom left of the sprite. To do so, (.5, .5) is subtracted from the origin.
* (The coordinates are scaled to range from zero to one, so (.5, .5) represents the center.)
* The originInformation is the information that was read from the JSON, which is used to calculate the graphics
* offset information.
*/
//TODO separate this method into a separate system once CollisionMeshLoader is modular
private void calculateGraphicsOffset(RenderableElement element) {
Vector2 originInformation = collisionMeshLoader.getOrigin(element.texture.name, 1);
element.graphicsOffset = new Vector2(originInformation.x - .5f, originInformation.y - .5f);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,29 @@
*/
package org.destinationsol.rubble.systems;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import org.destinationsol.assets.Assets;
import org.destinationsol.body.components.BodyLinked;
import org.destinationsol.common.In;
import org.destinationsol.common.SolMath;
import org.destinationsol.common.SolRandom;
import org.destinationsol.entitysystem.EntitySystemManager;
import org.destinationsol.entitysystem.EventReceiver;
import org.destinationsol.game.Rubble;
import org.destinationsol.game.RubbleBuilder;
import org.destinationsol.game.SolGame;
import org.destinationsol.game.drawables.DrawableLevel;
import org.destinationsol.health.components.Health;
import org.destinationsol.location.components.Angle;
import org.destinationsol.location.components.Position;
import org.destinationsol.location.components.Velocity;
import org.destinationsol.removal.events.DeletionEvent;
import org.destinationsol.removal.systems.DestructionSystem;
import org.destinationsol.rendering.RenderableElement;
import org.destinationsol.rendering.components.Renderable;
import org.destinationsol.rubble.components.CreatesRubbleOnDestruction;
import org.destinationsol.rubble.components.RubbleMesh;
import org.destinationsol.size.components.Size;
import org.destinationsol.stasis.components.Stasis;
import org.terasology.gestalt.entitysystem.entity.EntityRef;
Expand All @@ -39,32 +51,103 @@
*/
public class RubbleCreationSystem implements EventReceiver {

public static final float SIZE_TO_SHARD_COUNT = 13f;
public static final float MIN_SCALE = .07f;
public static final float MAX_SCALE = .12f;
private static final float MAX_SPD = 40f;

@In
private RubbleBuilder rubbleBuilder;

@In
private SolGame solGame;

//TODO once Shards are entities, this needs to be refactored to replace ShardBuilder
@In
private EntitySystemManager entitySystemManager;

/**
* When an entity with a {@link CreatesRubbleOnDestruction} component is destroyed, this creates {@link Rubble}s where
* When an entity with a {@link CreatesRubbleOnDestruction} component is destroyed, this creates {@link Rubble} where
* the entity was, unless the entity is in {@link Stasis}.
*/
@ReceiveEvent(components = {CreatesRubbleOnDestruction.class, Position.class, Size.class})
@Before(DestructionSystem.class)
public EventResult onDeletion(DeletionEvent event, EntityRef entity) {
if (!entity.hasComponent(Stasis.class)) {
Vector2 position = entity.getComponent(Position.class).get().position;
Vector2 velocity;
Velocity velocityComponent;
Angle angleComponent;

if (entity.hasComponent(Velocity.class)) {
velocity = entity.getComponent(Velocity.class).get().velocity;
velocityComponent = entity.getComponent(Velocity.class).get();
} else {
velocityComponent = new Velocity();
velocityComponent.velocity = new Vector2();
}

if (entity.hasComponent(Angle.class)) {
angleComponent = entity.getComponent(Angle.class).get();
} else {
velocity = new Vector2();
angleComponent = new Angle();
angleComponent.setAngle(0f);
}
float size = entity.getComponent(Size.class).get().size;
rubbleBuilder.buildExplosionShards(solGame, position, velocity, size);

buildRubblePieces(entity.getComponent(Position.class).get(), velocityComponent, angleComponent, entity.getComponent(Size.class).get());
}
return EventResult.CONTINUE;
}

/**
* This method creates pieces of rubble using values from the object that they are being creating from. It
* initializes entities for each piece of rubble with the relevant component
* @param pos Position component of parent entity
* @param vel Velocity component of parent entity
* @param angle Angle component of parent entity
* @param size Size component of parent entity, used to determine amount of rubble to generate
*/
private void buildRubblePieces(Position pos, Velocity vel, Angle angle, Size size) {
int count = (int) (size.size * SIZE_TO_SHARD_COUNT);
Vector2 basePos = pos.position;
for (int i = 0; i < count; i++) {

//Create graphics component
RenderableElement element = new RenderableElement();
element.texture = SolRandom.randomElement(Assets.listTexturesMatching("engine:shard_.*"));
element.drawableLevel = DrawableLevel.PROJECTILES;
element.graphicsOffset = new Vector2();

float scale = SolRandom.randomFloat(MIN_SCALE, MAX_SCALE);
element.setSize(scale);

element.relativePosition = new Vector2();
element.tint = Color.WHITE;
Renderable graphicsComponent = new Renderable();
graphicsComponent.elements.add(element);

//Create position component
float velocityAngle = SolRandom.randomFloat(180);
Vector2 position = new Vector2();
SolMath.fromAl(position, velocityAngle, SolRandom.randomFloat(size.size));
position.add(basePos);
Position positionComponent = new Position();
positionComponent.position = position;

//Create health component
Health health = new Health();
health.currentHealth = 1;

//Create size component
Size sizeComponent = new Size();
sizeComponent.size = scale;

//Create velocity component
Velocity velocityComponent = new Velocity();
Vector2 velocity = SolMath.fromAl(velocityAngle, SolRandom.randomFloat(MAX_SPD));
velocity.add(vel.velocity);
velocityComponent.velocity = velocity;

EntityRef entityRef = entitySystemManager.getEntityManager().createEntity(graphicsComponent, positionComponent,
velocityComponent, angle, sizeComponent, new RubbleMesh());
SolMath.free(velocity);
entityRef.setComponent(new BodyLinked());
}
}
}