Projectiles - UQcsse3200/2024-studio-1 GitHub Wiki

Overview of projectiles

The projectile in the game will be the main form of damage within the game, and will be launched from the players position and travel linearly until either a structure or entity is hit.

Projectile Generation

To generate a projectile it involves 2 separate functions; a projectile factory and projectile configuration.

Projectile Factory

The projectile factory creates the instance of the projectile by applying components as seen below:

public static Entity createProjectile(ProjectileConfig stats, Vector2 direction) {


    Entity projectile =
            new Entity()
                    .addComponent(new TextureRenderComponent(stats.projectileTexturePath))
                    .addComponent(new PhysicsComponent())
                    .addComponent(new PhysicsMovementComponent())
                    .addComponent(new ColliderComponent())
                    .addComponent(new HitboxComponent().setLayer(stats.Layer))
                    .addComponent(new CombatStatsComponent(stats.health, stats.baseAttack))
                    .addComponent(new ProjectileAttackComponent(stats.Layer, direction, stats.speed))
                    .addComponent(new ProjectileActions());



    PhysicsUtils.setScaledCollider(projectile, stats.scaleX, stats.scaleY);
    projectile.getComponent(ColliderComponent.class).setDensity(1.5f);
    projectile.getComponent(TextureRenderComponent.class).scaleEntity();


    return projectile;
}

For example the HitboxComponent generates the size in space that the projectile will occupy so if an entity enters this zone it registers a collision. Now this instantiates the projectile but it doesn't assign any values, that is what the projectile configuration function is designed to do.

Projectile Configuration

The projectile configuration is what assigns all values for any component associated with the projectile including the speed, position on the screen, texture rendered.

public class ProjectileConfig extends BaseEntityConfig{
    public Vector2 speed =  new Vector2(3f, 3f);
    public float scaleX = 0.6f;
    public float scaleY = 0.3f;
    public String projectileTexturePath = "image.png";
    public short Layer = PhysicsLayer.PLAYER;
    public int weaponID = 0;

}

The reason why the configuration and generation are separated this way is to but able to adjust all values because if there is an item that increases the hitbox of the projectile you would have to create a new factory to include this instead its possible to change the value in the configuration.

Projectile Attack Component

Once the Factory creates and config has been passed, it creates the entity that has the following life cycle:

1. Instantiation - creation of the projectile

public void create() {
    combatStats = entity.getComponent(CombatStatsComponent.class);
    hitboxComponent = entity.getComponent(HitboxComponent.class);
    entity.getEvents().addListener("collisionStart", this::onCollisionStart);
}

This method after creating the component listens for a collision event.

2. Movement - Traveling linearly

public ProjectileAttackComponent(Vector2 direction, Vector2 speed) {
    this.targetLayer = PhysicsLayer.NPC; // Default target layer, update as needed

    // Initialize the projectile movement component
    ProjectileMovementTask movementComponent = new ProjectileMovementTask(direction, speed);
    this.entity.addComponent(movementComponent);
}

This Method sets the layer for the projectile and additionally sets the direction and speed

3. Collision - When The projectile collides it sends an event registering the hit

private void onCollisionStart(Fixture me, Fixture other) {
    if (hitboxComponent.getFixture() != me) {
        return; // Not our hitbox
    }

    if (!PhysicsLayer.contains(targetLayer, other.getFilterData().categoryBits)) {
        return; // Not our target
    }

    Entity target = ((BodyUserData) other.getBody().getUserData()).entity;
    CombatStatsComponent targetStats = target.getComponent(CombatStatsComponent.class);

    if (targetStats != null) {
        targetStats.hit(combatStats);
    }

    entity.getComponent(TextureRenderComponent.class).dispose();
    entity.dispose();
}

Check if there are any collision events and if a collision event is heard is it relevant to the projectile if not ignore. Otherwise register a hit on the other entity and dispose of the projectile.

  1. Disposal - When the projectile collides it is disposed after the hit is registered As seen in the code block above the entity is disposed at the end.