Skip to content

Commit

Permalink
enemies actually fire bullets
Browse files Browse the repository at this point in the history
  • Loading branch information
Acepie committed Jun 29, 2024
1 parent 72a6f38 commit 7d55440
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 14 deletions.
62 changes: 61 additions & 1 deletion src/enemy.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import dungeon.{type Dungeon}
import gleam/bool
import gleam/float
import gleam/int
import gleam/io
import gleam/list
import gleam_community/maths/elementary
import obstacle
Expand Down Expand Up @@ -321,6 +320,58 @@ pub fn move_in_air_behavior(inputs: BehaviorInput) -> BehaviorResult {
)
}

// Simple behavior that checks if the enemy is looking at the player.
pub fn is_facing_player_behavior(inputs: BehaviorInput) -> BehaviorResult {
let behavior_tree.BehaviorInput(
entity: enemy,
additional_inputs: Inputs(_, _, player),
) = inputs

let player2d = vector.vector_2d(player.position)
let enemy2d = vector.vector_2d(enemy.position)
// Find vector from enemy to player
let target = vector.subtract(player2d, enemy2d)
// Extend vector based on the projection from the enemies facing direction
let target =
vector.multiply(
target,
vector.dot(vector.from_angle2d(enemy.rotation), target)
/. vector.magnitude(target),
)
// Find point along vector from enemy to player
let target = vector.add(target, enemy2d)

behavior_tree.BehaviorResult(
vector.distance(target, player2d) <. player.player_size /. 2.0,
enemy,
AdditionalOutputs([]),
)
}

const bullet_wait_time = 200

// Fires a bullet from the enemy
pub fn fire_bullet_behavior(inputs: BehaviorInput) -> BehaviorResult {
let behavior_tree.BehaviorInput(entity: enemy, additional_inputs: _) = inputs

let now = utils.now_in_milliseconds()
case enemy.last_bullet_fired + bullet_wait_time > now {
True -> behavior_tree.BehaviorResult(False, enemy, AdditionalOutputs([]))
False ->
behavior_tree.BehaviorResult(
True,
Enemy(..enemy, last_bullet_fired: now),
AdditionalOutputs([
bullet.spawn_bullet(
enemy.position,
vector.from_angle2d(enemy.rotation),
False,
),
]),
)
}
}

/// Represents an enemy to defeat.
pub type Enemy {
/// Represents an enemy to defeat.
Expand All @@ -343,6 +394,8 @@ pub type Enemy {
last_path_updated: Int,
/// Can the enemy currently see the player.
spotted_player: Bool,
/// The last time the enemy fired a bullet.
last_bullet_fired: Int,
/// The behavior that an enemy will follow on each tick.
btree: BehaviorTree,
)
Expand Down Expand Up @@ -379,6 +432,7 @@ pub fn new_enemy(initial_position: Vector) -> Enemy {
path: [],
last_path_updated: 0,
spotted_player: False,
last_bullet_fired: 0,
btree: behavior_tree.all(
[
// Look for player
Expand All @@ -400,6 +454,12 @@ pub fn new_enemy(initial_position: Vector) -> Enemy {
sequence([has_path_behavior, follow_path_behavior]),
sequence([spotted_player_behavior, follow_player_behavior]),
]),
// Fire bullets
sequence([
in_range_of_player_behavior,
is_facing_player_behavior,
fire_bullet_behavior,
]),
],
default_output,
output_to_input,
Expand Down
5 changes: 5 additions & 0 deletions src/vector.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,8 @@ pub fn rotate2d(v: Vector, rotation: Float) -> Vector {
let mag = magnitude(vector_2d(v))
Vector(elementary.cos(heading) *. mag, elementary.sin(heading) *. mag, v.z)
}

/// Create a vector around the z axis by the given amount in radians.
pub fn from_angle2d(rotation: Float) -> Vector {
rotate2d(Vector(1.0, 0.0, 0.0), rotation)
}
28 changes: 28 additions & 0 deletions test/enemy_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub fn enemy_tests() {
path: [],
last_path_updated: 0,
spotted_player: False,
last_bullet_fired: 0,
btree: dummy_btree,
)
|> enemy.apply_gravity
Expand All @@ -54,6 +55,7 @@ pub fn enemy_tests() {
path: [],
last_path_updated: 0,
spotted_player: False,
last_bullet_fired: 0,
btree: dummy_btree,
)
|> enemy.apply_gravity
Expand All @@ -72,6 +74,7 @@ pub fn enemy_tests() {
path: [],
last_path_updated: 0,
spotted_player: False,
last_bullet_fired: 0,
btree: dummy_btree,
)
|> enemy.apply_damage(20)
Expand All @@ -89,6 +92,7 @@ pub fn enemy_tests() {
path: [],
last_path_updated: 0,
spotted_player: False,
last_bullet_fired: 0,
btree: dummy_btree,
)
|> enemy.is_enemy_dead,
Expand All @@ -104,6 +108,7 @@ pub fn enemy_tests() {
path: [],
last_path_updated: 0,
spotted_player: False,
last_bullet_fired: 0,
btree: dummy_btree,
)
|> enemy.is_enemy_dead,
Expand All @@ -121,6 +126,7 @@ pub fn enemy_tests() {
path: [],
last_path_updated: 0,
spotted_player: False,
last_bullet_fired: 0,
btree: dummy_btree,
),
Vector(10.0, 0.0, 0.0),
Expand All @@ -137,6 +143,7 @@ pub fn enemy_tests() {
path: [],
last_path_updated: 0,
spotted_player: False,
last_bullet_fired: 0,
btree: dummy_btree,
),
Vector(0.0, 0.0, 0.0),
Expand Down Expand Up @@ -442,5 +449,26 @@ pub fn enemy_tests() {
Enemy(..enemy, position: Vector(250.0, 250.0, 8.0)),
)
}),
it("is_facing_player_behavior", fn() {
let enemy = enemy.new_enemy(Vector(0.0, 0.0, 0.0))

let behavior_tree.BehaviorResult(success, out_enemy, _) =
enemy.is_facing_player_behavior(behavior_tree.BehaviorInput(
enemy,
enemy.Inputs([], dungeon, player.new_player(Vector(30.0, 0.0, 0.0))),
))
expect.to_be_true(success)
expect.to_equal(out_enemy, enemy)

let enemy = enemy.new_enemy(Vector(0.0, 0.0, 0.0))

let behavior_tree.BehaviorResult(success, out_enemy, _) =
enemy.is_facing_player_behavior(behavior_tree.BehaviorInput(
enemy,
enemy.Inputs([], dungeon, player.new_player(Vector(30.0, 30.0, 0.0))),
))
expect.to_be_false(success)
expect.to_equal(out_enemy, enemy)
}),
])
}
37 changes: 24 additions & 13 deletions test/vector_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -32,55 +32,55 @@ fn approximate_vector_equal(v1: Vector, v2: Vector) {

pub fn vector_tests() {
describe("vector", [
it("add_test", fn() {
it("add", fn() {
expect.to_equal(
vector.add(Vector(1.0, 2.0, 3.0), Vector(4.0, 5.0, 6.0)),
Vector(5.0, 7.0, 9.0),
)
}),
it("subtract_test", fn() {
it("subtract", fn() {
expect.to_equal(
vector.subtract(Vector(1.0, 2.0, 3.0), Vector(4.0, 5.0, 6.0)),
Vector(-3.0, -3.0, -3.0),
)
}),
it("dot_test", fn() {
it("dot", fn() {
expect.to_equal(
vector.dot(Vector(1.0, 2.0, 3.0), Vector(4.0, 5.0, 6.0)),
32.0,
)
}),
it("cross_test", fn() {
it("cross", fn() {
expect.to_equal(
vector.cross(Vector(1.0, 2.0, 3.0), Vector(2.0, -1.0, 0.0)),
Vector(3.0, 6.0, -5.0),
)
}),
it("multiply_test", fn() {
it("multiply", fn() {
expect.to_equal(
vector.multiply(Vector(1.0, 2.0, 3.0), 2.0),
Vector(2.0, 4.0, 6.0),
)
}),
it("divide_test", fn() {
it("divide", fn() {
expect.to_equal(
vector.divide(Vector(2.0, 4.0, 6.0), 2.0),
Vector(1.0, 2.0, 3.0),
)
}),
it("vector_mag_squared_test", fn() {
it("vector_mag_squared", fn() {
expect.to_equal(vector.magnitude_squared(Vector(1.0, 2.0, 3.0)), 14.0)
}),
it("vector_mag_test", fn() {
it("vector_mag", fn() {
expect.to_equal(vector.magnitude(Vector(3.0, 4.0, 0.0)), 5.0)
}),
it("normalize_test", fn() {
it("normalize", fn() {
expect.to_equal(
vector.normalize(Vector(3.0, 4.0, 0.0)),
Vector(0.6, 0.8, 0.0),
)
}),
it("limit_test", fn() {
it("limit", fn() {
expect.to_equal(
vector.limit(Vector(3.0, 4.0, 0.0), 6.0),
Vector(3.0, 4.0, 0.0),
Expand All @@ -90,13 +90,13 @@ pub fn vector_tests() {
Vector(0.6, 0.8, 0.0),
)
}),
it("distance_test", fn() {
it("distance", fn() {
expect.to_equal(
vector.distance(Vector(1.0, 2.0, 3.0), Vector(4.0, 2.0, 7.0)),
5.0,
)
}),
it("heading2d_test", fn() {
it("heading2d", fn() {
expect.to_equal(
vector.heading2d(Vector(0.0, 1.0, 0.0)),
elementary.pi() /. 2.0,
Expand All @@ -108,7 +108,7 @@ pub fn vector_tests() {
)
expect.to_equal(vector.heading2d(Vector(-1.0, 0.0, 0.0)), elementary.pi())
}),
it("rotate2d_test", fn() {
it("rotate2d", fn() {
approximate_vector_equal(
vector.rotate2d(Vector(0.0, 1.0, 0.0), elementary.pi()),
Vector(0.0, -1.0, 0.0),
Expand All @@ -118,5 +118,16 @@ pub fn vector_tests() {
Vector(0.0, 1.0, 0.0),
)
}),
it("from_angle2d", fn() {
approximate_vector_equal(vector.from_angle2d(0.0), Vector(1.0, 0.0, 0.0))
approximate_vector_equal(
vector.from_angle2d(elementary.pi()),
Vector(-1.0, 0.0, 0.0),
)
approximate_vector_equal(
vector.from_angle2d(elementary.pi() /. 2.0),
Vector(0.0, 1.0, 0.0),
)
}),
])
}

0 comments on commit 7d55440

Please sign in to comment.