Projectile Entities - UQcsse3200/2024-studio-2 GitHub Wiki
Overview
Projectiles are entities that have single purpose of moving towards a target entity. Currently, the only implementation is the Banana entity.
Implementation
Projectile entities consist of 3 parts: the ProjectileFactory
class, which handles the creation of projectile entities; the ProjectileMovementTask
class, which handles the entity AI; and the ProjectileAttackComponent
class, which handles collisions. They are created in the ProjectileFactory
.
ProjectileFactory
The ProjectileFactory
class is responsible for creating projectile entities with predefined components and configurations. It provides methods for creating various types of projectiles, such as the banana projectile, by combining different components that define the behavior, physics, and rendering of the projectiles. The main method for this which all projectiles must use is:
private static Entity createBaseProjectile(Entity target, BaseEnemyEntityConfig config, float scale,
AnimationRenderComponent animator, Component controller) {
Entity projectile =
new Entity()
.addComponent(new PhysicsComponent())
.addComponent(new PhysicsMovementComponent())
.addComponent(new ColliderComponent())
.addComponent(new HitboxComponent().setLayer(PhysicsLayer.PROJECTILE))
.addComponent(new ProjectileAttackComponent((short)(PhysicsLayer.PLAYER + PhysicsLayer.OBSTACLE), config.getBaseAttack(), target));
PhysicsUtils.setScaledCollider(projectile, 0.9f, 0.4f);
AITaskComponent aiTaskComponent = new AITaskComponent();
aiTaskComponent.addTask(new ProjectileMovementTask(target, 10));
projectile.addComponent(aiTaskComponent);
projectile
.addComponent(animator)
.addComponent(controller);
projectile.setScale(scale, scale);
projectile.getComponent(PhysicsMovementComponent.class).changeMaxSpeed(new Vector2(config.getSpeed(), config.getSpeed()));
return projectile;
}
Key Responsibilities:
- Centralised Creation of Projectiles: The factory provides a method for each type of projectile (e.g.,
createBanana
) that initialises the necessary components and sets up the properties and behaviour. - Configuration Management: Projectile properties like speed, damage, and animations are loaded from JSON configuration files, making it easy to adjust these properties without altering the core code.
- Component Composition: Each projectile is built with a combination of components for physics (
PhysicsComponent
), AI (AITaskComponent
), collisions (ProjectileAttackComponent
), and rendering (AnimationRenderComponent
).
createBanana(Entity target)
Example Method: public static Entity createBanana(Entity target) {
String path = configs.banana.getSpritePath();
TextureAtlas atlas = ServiceLocator.getResourceService().getAsset(path, TextureAtlas.class);
AnimationRenderComponent animator = new AnimationRenderComponent(atlas);
animator.addAnimation("fire", 0.25f, Animation.PlayMode.LOOP);
Entity banana = createBaseProjectile(target, configs.banana, 0.5f, animator, new BananaAnimationController());
banana.setEnemyType(Entity.EnemyType.BANANA);
return banana;
}
This method creates a banana projectile entity that chases a specified target (such as a player), with the help of the createBaseProjectile() method.
ProjectileMovementTask
This task is the only task that projectile entities should have. It piggybacks on the MovementTask, which provides most of the implementation of moving the entity and updating its status (if it has reached its destination).
start()
public void start() {
super.start();
System.out.println("firing banana");
movementTask = new MovementTask(targetPosition);
movementTask.create(owner);
movementTask.start();
this.owner.getEntity().getEvents().trigger("ProjectileMove");
}
Initialises the MovementTask to go towards the position of the target entity, and starts the animation that the entity has associated with the ProjectileMove name, hence all projectile entities will need an animation of this name.
update()
public void update() {
if (movementTask.getStatus() != Status.ACTIVE) {
owner.getEntity().setEnabled(false);
AnimationRenderComponent animationRenderComponent = owner.getEntity().getComponent(AnimationRenderComponent.class);
animationRenderComponent.stopAnimation();
owner.getEntity().dispose();
}
movementTask.update();
}
Every frame the task gets update, which either moves the entity further towards its target position, or if it has reached its target position, disposes of the entity. If dispose is called here, it means that the entity has NOT collided with any other entities.
Note that with this current implementation, projectiles do not track their target, they get the position of the target entity when created, and move towards that position with constant velocity.
ProjectileAttackComponent
This Component provides the logic for when a projectile entity collides with any other entity (Excluding other projectiles). This logic is to dispose of the projectile entity, and if the entity has the CombatStatsComponent, to deal damage (currently 2 health points for all projectiles).
onCollisionStart()
protected void onCollisionStart(Fixture me, Fixture other) {
if (checkHitboxAndLayer(me, other)) return;
// does damage if player
if (((BodyUserData) other.getBody().getUserData()).entity.isPlayer()) { //dont do damage if not player
if (getEntity().getEnemyType() == ELECTRICORB) {
target.getComponent(KeyboardPlayerInputComponent.class).paralyze();
} else {
target.getComponent(CombatStatsComponent.class).addHealth(-damage);
}
}
// disposes of projectile
Entity owner = getEntity();
if (owner.getEnemyType() == HIVE) {
HiveTask task = (HiveTask) owner.getComponent(AITaskComponent.class).getCurrentTask();
if (task != null) {
task.stop();
}
} else {
ProjectileMovementTask task = (ProjectileMovementTask) owner.getComponent(AITaskComponent.class).getCurrentTask();
if (task != null) task.getMovementTask().stop();
}
}
When a collision is detected, first it is checked if the collision was triggered by the hitbox of the entity, and was on the right physics layer. If one is false then the collision is ignored. Otherwise, the entity it collides with takes damage if it can, and the projectile entity is disposed of. Electric Orb and Hive projectiles have different behaviour.