Skip to content

Skeleton Archer

ZarifHilman edited this page Oct 21, 2026 · 4 revisions

← Back to Enemies

Description

Skeleton Archer is the basic ranged enemy in Odysseus. It has a standard ranged arrow attack and its own ranged-type behaviour.

Unlike melee enemies, the Skeleton Archer attacks the player from a distance by firing arrows that follow a gravity-affected trajectory. Its arrows use the same underlying arrow physics as the player's standard arrows, but are configured to damage the player instead of other enemies.

The Skeleton Archer uses the shared enemy AI system for movement, chasing, repositioning and ranged attacks.

Enemy Stat

The following values are configured in configs/Enemies.json.

Stat Values
HP 30
Damage 2
Attack Type Range
Attack Range 10.0
Speed 2.5

Additional Configuration

Property Value
Behaviour chase
Chase Priority 10
View Distance 10.0
Maximum Chase Distance 12.5
Reposition Priority 25
Reposition Distance 2.0
Maximum Movement Speed 2.0
Gravity Enabled true

The configured speed and maxSpeed are separate properties. The shared enemy factory uses maxSpeed when constructing the PhysicsMovementComponent.

Components

EnemyFactory.java

The main class responsible for creating enemies.

The Skeleton Archer is created using createSkeletonArcher(Entity target), which loads its configuration and calls the shared createEnemy() method.

public static Entity createSkeletonArcher(Entity target) {
    EnemyConfig config = configs.skeletonArcher;
    Entity skeletonArcher = createEnemy(target, config);

    // Animation, sound, scaling and hitbox setup

    return skeletonArcher;
}

The shared factory provides the Archer with common enemy functionality, including:

  • Physics and movement components
  • Collision and hitbox components
  • Combat statistics and damage handling
  • Enemy death and item drop components
  • AI task management
  • Status effect components
  • Enemy health rendering

The Archer-specific factory additionally configures its sprite animations, animation controller, chase sound, entity scale and collision hitbox.

Animation

The Skeleton Archer uses the following texture atlas:

"images/enemies/skeleton_archer.atlas"

Two looping animations are registered:

animator.addAnimation(
    "walk",
    ARCHER_FRAME_TIME,
    Animation.PlayMode.LOOP,
    ARCHER_ANCHOR_X,
    ARCHER_ANCHOR_Y);

animator.addAnimation(
    "idle",
    ARCHER_FRAME_TIME,
    Animation.PlayMode.LOOP,
    ARCHER_ANCHOR_X,
    ARCHER_ANCHOR_Y);

The EnemyAnimationController manages the enemy's animations based on its behaviour.

The Archer's entity scale is calculated relative to the target's height, and its collision box is aligned to the bottom centre of the entity.

AI Behaviour

The Skeleton Archer uses the shared enemy AI system, which includes chasing, wandering, repositioning and ranged attacking.

Chasing

ChaseTask allows the enemy to pursue its target according to the configured behaviour, chase priority, view distance and maximum chase distance.

The Archer's configured chase priority is 10.

Wandering

WanderTask provides movement behaviour when the enemy is not actively chasing or attacking its target.

The Archer's configuration specifies a horizontal wander range of 4, vertical wander range of 0, and wander wait time of 1.

Repositioning

RepositionTask is used by the shared enemy AI system.

The Archer has a reposition priority of 25 and reposition distance of 2.0.

Ranged Attacking — RangedAttackTask.java

The Skeleton Archer uses RangedAttackTask to fire arrows at the player.

The shared enemy factory constructs its ranged attack task using the following parameters:

new RangedAttackTask(
    target,
    20,
    config.attackRange,
    2f,
    config.baseAttack,
    4.5f,
    5f,
    config.attackType
);
Parameter Value Purpose
Attack Priority 20 AI task priority
Attack Range 10.0 Maximum distance for attack eligibility
Cooldown 2 seconds Delay between attacks
Damage 2 Configured arrow damage
Projectile Speed 4.5 Launch speed
Projectile Lifetime 5 seconds Projectile lifetime limit
Attack Type range Selects the Skeleton Archer projectile

The task's getPriority() method checks whether the target is within attack range and whether the cooldown has elapsed.

When the attack executes, fireProjectile() calculates the enemy and target positions, chooses the firing side and registers an Archer arrow:

ServiceLocator.getEntityService()
    .register(
        EnemyProjectileFactory.createSkeletonArcherProjectile(
            enemy,
            spawnCenter,
            targetCenter,
            damage,
            projectileSpeed,
            projectileLifetime));

The arrow spawns on the side of the Archer facing the target, with a small vertical offset.

Projectile Implementation

EnemyProjectileFactory.java

The createSkeletonArcherProjectile() method constructs the Archer's standard arrow.

Unlike the magic projectiles used by other enemies, the Skeleton Archer's arrow follows a parabolic trajectory under gravity.

The initial launch direction is calculated using ArrowTrajectory.launchDirection():

float arcGravityY =
    PhysicsEngine.GRAVITY_Y
        * ArrowProjectileComponent.ARC_GRAVITY_SCALE;

Vector2 aim =
    ArrowTrajectory.launchDirection(
        spawnCenter,
        targetCenter,
        speed,
        arcGravityY);

This calculation aims the arrow so that its gravity-affected flight can reach the target.

The projectile is created with a dynamic physics body, an enemy projectile hitbox, combat statistics, an arrow movement component, a lifetime component and an arrow renderer.

Entity projectile =
    new Entity()
        .addComponent(
            new PhysicsComponent()
                .setBodyType(BodyType.DynamicBody))
        .addComponent(
            new HitboxComponent()
                .setLayer(PhysicsLayer.ENEMY_PROJECTILE))
        .addComponent(new CombatStatsComponent(1, damage))
        .addComponent(
            new ArrowProjectileComponent(
                shooter,
                aim,
                speed,
                ProjectileFactory.STANDARD_ARROW_RANGE,
                ArrowType.STANDARD)
                .hurtsPlayer())
        .addComponent(new ProjectileComponent(lifetime))
        .addComponent(new ArrowRenderComponent(ArrowType.STANDARD));

The Archer's arrow uses the standard arrow appearance and dimensions defined by the player projectile system.

ArrowProjectileComponent.java

This component handles arrow movement, gravity, rotation, collisions, damage and maximum travel range.

Gravity and Movement

The arrow uses 40% of the world's gravity.

public static final float ARC_GRAVITY_SCALE = 0.4f;

When created, the component configures its physics body:

body.setFixedRotation(true);
body.setGravityScale(ARC_GRAVITY_SCALE);
body.setLinearDamping(0f);
body.setBullet(true);
body.setLinearVelocity(direction.cpy().scl(speed));

As the arrow moves, its visual and physical orientation is updated to follow its current velocity.

This creates the curved arrow flight used by the Skeleton Archer.

Player Damage and Friendly Fire

The .hurtsPlayer() method changes the arrow's collision targets:

public ArrowProjectileComponent hurtsPlayer() {
    targetLayers = PhysicsLayer.PLAYER;
    ignoredLayers = PhysicsLayer.NPC;
    return this;
}

As a result:

  • Skeleton Archer arrows damage the player.
  • Arrows pass through other enemies without damaging them.
  • Arrows ignore collisions with their own shooter.

When a valid target is hit, the arrow triggers the target's damage event:

int healthBefore = targetStats.getHealth();
target.getEvents().trigger("takeDamage", combatStats);
boolean damaged = targetStats.getHealth() < healthBefore;

For a standard arrow, a successful damaging hit causes the projectile to expire.

Terrain Collision

Arrows react to collisions with ground and obstacles.

public static final short TERRAIN =
    (short) (PhysicsLayer.GROUND | PhysicsLayer.OBSTACLE);

The component includes a small spawn-clearance distance to prevent arrows from immediately disappearing when spawned close to terrain.

Once the arrow has cleared its spawn position, contact with terrain causes it to expire.

This prevents arrows from continuing to move along the floor after impact.

Maximum Range and Lifetime

The arrow has two separate limits:

  1. Maximum range: ArrowProjectileComponent checks the distance between the arrow and its range origin.
  2. Lifetime: ProjectileComponent limits how long the projectile can remain active.

For Archer arrows, the range origin is the shooter's current centre position.

An arrow can also be removed earlier when it hits terrain or successfully damages the player.

Testing

Skeleton Archer projectile behaviour is covered by integration tests and related ranged attack tests.

EnemyArrowFlightIntegrationTest.java

This integration test uses the real RangedAttackTask, physics simulation and collision handling to verify the Archer's arrows.

Test Verified Behaviour
shouldLandOnATargetOnTheSameLevelAtAnyDistanceOnEitherSide Arrows can hit targets at 3, 6, 9 and 12 units to either side
shouldReachATargetAtTheDistanceTheSkeletonArcherShootsFrom Arrows reach the configured attack distance
shouldLandOnATargetStandingHigherUp Arrows hit targets on higher platforms
shouldLandOnATargetOnTheFloorBelowALedge Arrows hit targets below the Archer
shouldArcUpAndOverLikeAPlayersArrowRatherThanFlyStraight Arrows follow an upward arc rather than a straight trajectory
shouldBreakOnTheFloorInsteadOfDraggingAlongIt Arrows expire after hitting the floor
shouldBreakOnTheFloorAfterAShortArcFromALedge Arrows expire after a short flight from a ledge into the floor
shouldNotHurtItsOwnArcherOrFlyIntoOtherEnemiesBeforeHittingThePlayer Arrows do not damage their shooter or other enemies
shouldNotCarryOnThroughThePlayerAfterHittingThem Arrows are removed after successfully hitting the player

Note: The integration tests use a test damage value of 5, while the Skeleton Archer's configured in-game damage is 2.

RangedAttackTaskTest.java

This test class verifies shared ranged attack functionality, including projectile spawning, targeting, damage, cooldown and lifetime.

Its inspected parameterized cases cover calypso and summon projectiles rather than Skeleton Archer arrows directly.

The Archer-specific flight and collision behaviour is tested in EnemyArrowFlightIntegrationTest.java.

Relevant Source Files

File Responsibility
EnemyFactory.java Creates and configures the Skeleton Archer
Enemies.json Defines Archer statistics and AI configuration
RangedAttackTask.java Handles ranged attack eligibility, cooldown and firing
EnemyProjectileFactory.java Creates the Archer's arrow entity
ArrowTrajectory.java Calculates the arrow's launch direction
ArrowProjectileComponent.java Handles arrow physics, damage and collisions
ProjectileComponent.java Controls projectile lifetime
ArrowRenderComponent.java Renders the standard arrow
EnemyArrowFlightIntegrationTest.java Tests Archer arrow flight and collisions

Sprite

skeleton_archer

Clone this wiki locally