From 7d55440dec5352ff74f5f0198d234320c9ae41e4 Mon Sep 17 00:00:00 2001 From: Ace Date: Sat, 29 Jun 2024 15:33:22 -0700 Subject: [PATCH] enemies actually fire bullets --- src/enemy.gleam | 62 +++++++++++++++++++++++++++++++++++++++++- src/vector.gleam | 5 ++++ test/enemy_test.gleam | 28 +++++++++++++++++++ test/vector_test.gleam | 37 ++++++++++++++++--------- 4 files changed, 118 insertions(+), 14 deletions(-) diff --git a/src/enemy.gleam b/src/enemy.gleam index 8031f3b..87b8824 100644 --- a/src/enemy.gleam +++ b/src/enemy.gleam @@ -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 @@ -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. @@ -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, ) @@ -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 @@ -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, diff --git a/src/vector.gleam b/src/vector.gleam index 20be7fc..ac43661 100644 --- a/src/vector.gleam +++ b/src/vector.gleam @@ -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) +} diff --git a/test/enemy_test.gleam b/test/enemy_test.gleam index 84c5c04..9a22fd8 100644 --- a/test/enemy_test.gleam +++ b/test/enemy_test.gleam @@ -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 @@ -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 @@ -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) @@ -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, @@ -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, @@ -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), @@ -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), @@ -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) + }), ]) } diff --git a/test/vector_test.gleam b/test/vector_test.gleam index a70f25d..ecd171e 100644 --- a/test/vector_test.gleam +++ b/test/vector_test.gleam @@ -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), @@ -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, @@ -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), @@ -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), + ) + }), ]) }