Repository navigation
Skeleton Archer
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.
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 |
| 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.
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.
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.
The Skeleton Archer uses the shared enemy AI system, which includes chasing, wandering, repositioning and ranged attacking.
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.
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.
RepositionTask is used by the shared enemy AI system.
The Archer has a reposition priority of 25 and reposition distance of 2.0.
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.
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.
This component handles arrow movement, gravity, rotation, collisions, damage and maximum travel range.
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.
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.
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.
The arrow has two separate limits:
-
Maximum range:
ArrowProjectileComponentchecks the distance between the arrow and its range origin. -
Lifetime:
ProjectileComponentlimits 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.
Skeleton Archer projectile behaviour is covered by integration tests and related ranged attack tests.
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.
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.
| 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 |
Entities and Components
Input Handling
Arrow Selection Wheel Arrow Selection Wheel — Sprint 3
Game Screens and Areas
Enemies
-
Individual Enemy Pages:
-
Misc Enemy Pages: