diff --git a/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.ase b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.ase new file mode 100644 index 0000000..95adade Binary files /dev/null and b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.ase differ diff --git a/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.png b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.png new file mode 100644 index 0000000..48dfcdb Binary files /dev/null and b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.png differ diff --git a/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.png.import b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.png.import new file mode 100644 index 0000000..de3d1c9 --- /dev/null +++ b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hunting.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://30uwkdbnuu3h" +path="res://.godot/imported/hammerheadRibozyme-hunting.png-9b9929a81db6474a29fbff93ee4f1cef.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://molecular/assets/predator/hammerheadRibozyme-hunting.png" +dest_files=["res://.godot/imported/hammerheadRibozyme-hunting.png-9b9929a81db6474a29fbff93ee4f1cef.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.ase b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.ase new file mode 100644 index 0000000..192c797 Binary files /dev/null and b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.ase differ diff --git a/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.png b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.png new file mode 100644 index 0000000..7bd82fd Binary files /dev/null and b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.png differ diff --git a/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.png.import b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.png.import new file mode 100644 index 0000000..6a3eb81 --- /dev/null +++ b/evolve-die-repeat/molecular/assets/predator/hammerheadRibozyme-hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jyuf4lgjo64" +path="res://.godot/imported/hammerheadRibozyme-hurt.png-6a7eeaef121ff058edf73e3d18dfd77e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://molecular/assets/predator/hammerheadRibozyme-hurt.png" +dest_files=["res://.godot/imported/hammerheadRibozyme-hurt.png-6a7eeaef121ff058edf73e3d18dfd77e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/evolve-die-repeat/molecular/molecular_player.gd b/evolve-die-repeat/molecular/molecular_player.gd index 28a3131..c5dac79 100644 --- a/evolve-die-repeat/molecular/molecular_player.gd +++ b/evolve-die-repeat/molecular/molecular_player.gd @@ -1,16 +1,18 @@ extends CharacterBody2D -@export var attack_duration = 0.4 -@export var attack_cooldown_duration = 0.6 +@export var attack_duration: float = 0.4 +@export var attack_cooldown_duration: float = 0.6 @onready var attack_area: Area2D = $AttackArea @onready var attack_timer: Timer = $AttackTimer @onready var attack_cooldown_timer: Timer = $AttackCooldownTimer @onready var sprite: AnimatedSprite2D = $AnimatedSprite2D -@export var speed = 200 -var damage = 10 -var can_attack = true +# TODO: these stats are shared across most entities; extract to resource? +@export var speed: int = 200 +var damage: int = 10 +var can_attack: bool = true +var desired_rotation: float = 0 func _ready() -> void: var screen_size = get_viewport_rect().size @@ -20,6 +22,7 @@ func _ready() -> void: attack_timer.wait_time = attack_duration attack_cooldown_timer.wait_time = attack_cooldown_duration + func _process(delta): velocity = Vector2.ZERO if Input.is_action_pressed("move_right"): @@ -33,10 +36,15 @@ func _process(delta): if Input.is_action_pressed("try_attack"): try_attack() + if not velocity.is_zero_approx(): - self.rotation = atan2(velocity.y, velocity.x) + self.desired_rotation = atan2(velocity.y, velocity.x) + + # smoothly rotate + if self.rotation != self.desired_rotation: + self.rotation = lerp_angle(self.rotation, self.desired_rotation, clampf(4 * delta, 0, 1)) + move_and_collide(speed * velocity * delta) - #position += speed * velocity * delta position = GameManager.get_boundaried_position(position) func try_attack() -> void: @@ -60,7 +68,7 @@ func _on_cooldown_timeout() -> void: func _on_attack_hit(body: Node2D) -> void: var hit_hittable = false - if body.is_in_group("prey") or body.is_in_group("predators"): + if body.is_in_group("prey") or body.is_in_group("predator"): if body.has_method("handle_damage"): body.handle_damage(damage, self) hit_hittable = true @@ -70,3 +78,7 @@ func _on_attack_hit(body: Node2D) -> void: await get_tree().create_timer(0.2).timeout sprite.play("default") # TODO: resource handling logic + +func handle_damage(dmg: int, src: Node) -> void: + # TODO: damage logic + pass diff --git a/evolve-die-repeat/molecular/predator/hammerhead_predator.gd b/evolve-die-repeat/molecular/predator/hammerhead_predator.gd index 29822de..45742ae 100644 --- a/evolve-die-repeat/molecular/predator/hammerhead_predator.gd +++ b/evolve-die-repeat/molecular/predator/hammerhead_predator.gd @@ -1,14 +1,18 @@ extends AbstractPredator2D -# TODO: attacking logic + behaviour -# TODO: movement is buged (seems to not move/teleport somewhat +# FIXME: (general) tracking across wrapping boundary # TODO: mirroring (thought, extracct that to general function/resource? -@onready var sprite = $AnimatedSprite2D -@onready var fsm = $StateMachine - -@export var speed = 0.8 +var can_attack: bool = true var desired_rotation: float = self.rotation +@onready var sprite = $AnimatedSprite2D +@onready var fsm: StateMachine = $StateMachine +@onready var attack_cooldown_timer: Timer = $AttackCooldownTimer + +@export var damage: int = 15 +@export var attack_range = 20 +@export var sight_range = 200 +@export var speed = 0.8 func _ready() -> void: health = maxHealth @@ -24,7 +28,7 @@ func _process(delta: float) -> void: func _physics_process(delta: float) -> void: pass -# FIXME: (also goes for prey) this is framerate dependent +# FIXME: (also goes for prey) this is framerate dependent UNLESS called from a _physics function. func move(motion: Vector3, mod: float = 1.0) -> void: move_and_collide(Vector2(motion.x, motion.y).normalized() * self.speed * mod) # Moves along the given vector self.desired_rotation = atan2(motion.y, motion.x) @@ -32,7 +36,45 @@ func move(motion: Vector3, mod: float = 1.0) -> void: # Apply boundary to new position position = GameManager.get_boundaried_position(position) +func try_attack(target: Node) -> void: + if not can_attack: + return + attack(target) + +func attack(target: Node) -> void: + can_attack = false + var hit_hittable = false + if target.is_in_group("prey") or target.is_in_group("player"): + if target.has_method("handle_damage"): + target.handle_damage(damage, self) + hit_hittable = true + elif target.is_in_group("resources"): + # TODO: resource handling logic + pass + if hit_hittable: + attack_cooldown_timer.start() + + +func handle_damage(dmg: int, src: Node) -> void: + health = max(0, health-dmg) + if health == 0: + die() + if health < maxHealth/2: + become_injured() + fsm.transition_to_next_state(fsm.States.FLEEING, {"threat": src}) + elif health < maxHealth: + become_injured() + fsm.transition_to_next_state(fsm.States.HUNTING, {"target": src}) + +func die() -> void: + super.die() + +func become_injured() -> void: + sprite.play("Hurt") func _on_sight_body_entered(body: Node2D) -> void: - if body.is_in_group("prey") or (health < maxHealth and body.is_in_group("player")): + if fsm.map(fsm.state) != fsm.States.HUNTING and body.is_in_group("prey") or (health < maxHealth and body.is_in_group("player")): fsm.transition_to_next_state(fsm.States.HUNTING, {"target": body}) + +func _on_attack_cooldown_timer_timeout() -> void: + can_attack = true diff --git a/evolve-die-repeat/molecular/predator/hammerhead_predator.tscn b/evolve-die-repeat/molecular/predator/hammerhead_predator.tscn index fc2b904..cea4fa0 100644 --- a/evolve-die-repeat/molecular/predator/hammerhead_predator.tscn +++ b/evolve-die-repeat/molecular/predator/hammerhead_predator.tscn @@ -2,9 +2,12 @@ [ext_resource type="Script" uid="uid://d07cjelbqbiug" path="res://molecular/predator/hammerhead_predator.gd" id="1_xp037"] [ext_resource type="Texture2D" uid="uid://ch5rddsumyyhm" path="res://molecular/assets/predator/predator-healthy.png" id="2_34kwa"] +[ext_resource type="Texture2D" uid="uid://30uwkdbnuu3h" path="res://molecular/assets/predator/hammerheadRibozyme-hunting.png" id="3_0ts4d"] [ext_resource type="Script" uid="uid://cygrmt03sx0k1" path="res://molecular/predator/state_machine.gd" id="3_xp037"] [ext_resource type="Script" uid="uid://xbiqj7ubmj7d" path="res://molecular/prey/state_idle.gd" id="4_8a23j"] +[ext_resource type="Texture2D" uid="uid://jyuf4lgjo64" path="res://molecular/assets/predator/hammerheadRibozyme-hurt.png" id="4_shhro"] [ext_resource type="Script" uid="uid://ubcu8fdfxxj1" path="res://molecular/prey/state_random_movement.gd" id="5_6rsu5"] +[ext_resource type="Script" uid="uid://bc7apl71t0q04" path="res://molecular/predator/state_hunting.gd" id="8_7qt2q"] [sub_resource type="AtlasTexture" id="AtlasTexture_8a23j"] atlas = ExtResource("2_34kwa") @@ -18,6 +21,30 @@ region = Rect2(64, 0, 64, 64) atlas = ExtResource("2_34kwa") region = Rect2(128, 0, 64, 64) +[sub_resource type="AtlasTexture" id="AtlasTexture_nu6jw"] +atlas = ExtResource("3_0ts4d") +region = Rect2(0, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8inuv"] +atlas = ExtResource("3_0ts4d") +region = Rect2(64, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_orf3n"] +atlas = ExtResource("3_0ts4d") +region = Rect2(128, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vkkje"] +atlas = ExtResource("4_shhro") +region = Rect2(0, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jfmyn"] +atlas = ExtResource("4_shhro") +region = Rect2(64, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_f26lq"] +atlas = ExtResource("4_shhro") +region = Rect2(128, 0, 64, 64) + [sub_resource type="SpriteFrames" id="SpriteFrames_shhro"] animations = [{ "frames": [{ @@ -33,6 +60,34 @@ animations = [{ "loop": true, "name": &"Healthy", "speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_nu6jw") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8inuv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_orf3n") +}], +"loop": true, +"name": &"Hunting", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_vkkje") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_jfmyn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_f26lq") +}], +"loop": true, +"name": &"Hurt", +"speed": 5.0 }] [node name="HammerheadPredator" type="CharacterBody2D" unique_id=678504815 groups=["predator"]] @@ -45,18 +100,20 @@ maxHealth = 50 metadata/_custom_type_script = "uid://dgfimmq53whll" [node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=410999609] +rotation = 4.712389 sprite_frames = SubResource("SpriteFrames_shhro") -animation = &"Healthy" +animation = &"Hunting" [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="." unique_id=1596156928] light_mask = 4 visibility_layer = 4 position = Vector2(0.11167908, 1.1167793) +rotation = -1.5707964 polygon = PackedVector2Array(-22.184862, -27.994831, 23.481365, -27.21198, 13.82622, 25.891317, -6.005971, 25.891317) [node name="StateMachine" type="Node" parent="." unique_id=1857729810 node_paths=PackedStringArray("initial_state")] script = ExtResource("3_xp037") -initial_state = NodePath("Idle") +initial_state = NodePath("RandomMovement") metadata/_custom_type_script = "uid://ck7k8ht54snsy" [node name="Idle" type="Node" parent="StateMachine" unique_id=265876039] @@ -73,6 +130,10 @@ metadata/_custom_type_script = "uid://co2xp7gauamql" [node name="Timer" type="Timer" parent="StateMachine/RandomMovement" unique_id=447822526] one_shot = true +[node name="Hunting" type="Node" parent="StateMachine" unique_id=1569866955] +script = ExtResource("8_7qt2q") +metadata/_custom_type_script = "uid://co2xp7gauamql" + [node name="Sight" type="Area2D" parent="." unique_id=1608385873] collision_layer = 0 collision_mask = 7 @@ -80,6 +141,15 @@ collision_mask = 7 [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Sight" unique_id=1707240701] light_mask = 4 visibility_layer = 4 +position = Vector2(-1.0900421, 1.6350927) +rotation = -1.5707964 polygon = PackedVector2Array(-27.769547, -29.426758, 31.88504, -29.184647, 12.700996, 28.7294, 56.058624, 148.93633, 22.979004, 163.77974, -19.854843, 161.65926, -53.782654, 143.84715, -8.333115, 30.157196) +[node name="AttackCooldownTimer" type="Timer" parent="." unique_id=435253442] +wait_time = 0.5 +one_shot = true + +[connection signal="timeout" from="StateMachine/Idle/Timer" to="StateMachine/Idle" method="_on_timer_timeout"] +[connection signal="timeout" from="StateMachine/RandomMovement/Timer" to="StateMachine/RandomMovement" method="_on_timer_timeout"] [connection signal="body_entered" from="Sight" to="." method="_on_sight_body_entered"] +[connection signal="timeout" from="AttackCooldownTimer" to="." method="_on_attack_cooldown_timer_timeout"] diff --git a/evolve-die-repeat/molecular/predator/state_hunting.gd b/evolve-die-repeat/molecular/predator/state_hunting.gd new file mode 100644 index 0000000..05c79cb --- /dev/null +++ b/evolve-die-repeat/molecular/predator/state_hunting.gd @@ -0,0 +1,26 @@ +extends State + +var target: Node2D + +func enter(previous_state_path: String, data := {}) -> void: + if data.has("target"): + target = data["target"] + owner.sprite.play("Hunting") + else: + # default behaviour; do nothing + finished.emit(owner.fsm.States.IDLE, {}) + +func physics_update(_delta: float) -> void: + if target == owner or target == null or owner.position.distance_to(target.position) > owner.sight_range: + finished.emit(owner.fsm.States.IDLE, {}) + return + + # alternatively, we could use a collision shape and inbuilt signals, but im not sure if that works better (mixing signals and custom fsm stuff i mean + if owner.position.distance_to(target.position) > owner.attack_range: + owner.move(move_towards(target.position)) + else: + owner.attack(target) + +func move_towards(pos: Vector2) -> Vector3: + var diff = target.position - owner.position + return Vector3(diff.x, diff.y ,0) diff --git a/evolve-die-repeat/molecular/predator/state_hunting.gd.uid b/evolve-die-repeat/molecular/predator/state_hunting.gd.uid new file mode 100644 index 0000000..fe62562 --- /dev/null +++ b/evolve-die-repeat/molecular/predator/state_hunting.gd.uid @@ -0,0 +1 @@ +uid://bc7apl71t0q04 diff --git a/evolve-die-repeat/molecular/predator/state_machine.gd b/evolve-die-repeat/molecular/predator/state_machine.gd index a57990a..665b3e9 100644 --- a/evolve-die-repeat/molecular/predator/state_machine.gd +++ b/evolve-die-repeat/molecular/predator/state_machine.gd @@ -16,3 +16,13 @@ func transition_to_next_state(target: int, data: Dictionary = {}) -> void: States.FLEEING: _transition_to_next_state("Fleeing", data) States.HUNTING: _transition_to_next_state("Hunting", data) _: push_error("Trying to transition to unknown state {target}") + +func map(state: Node) -> States: + match state.name: + "Idle": return States.IDLE + "RandomMovement": return States.RANDOMMOVEMENT + "Feeding": return States.FEEDING + "Fleeing": return States.FLEEING + "Hunting": return States.HUNTING + _: push_error("Unknown state {state.name}") + return map(self.initial_state) diff --git a/evolve-die-repeat/molecular/prey/state_fleeing.gd b/evolve-die-repeat/molecular/prey/state_fleeing.gd index 0623491..659b39f 100644 --- a/evolve-die-repeat/molecular/prey/state_fleeing.gd +++ b/evolve-die-repeat/molecular/prey/state_fleeing.gd @@ -8,7 +8,7 @@ func enter(previous_state_path: String, data := {}) -> void: threat = data["threat"] else: # default behaviour; do nothing - threat = owner + finished.emit(owner.fsm.States.IDLE, {}) func physics_update(_delta: float) -> void: if owner.position.distance_to(threat.position) > threshold or threat == owner: diff --git a/evolve-die-repeat/molecular/prey/state_idle.gd b/evolve-die-repeat/molecular/prey/state_idle.gd index 0e9cc81..50bb7cc 100644 --- a/evolve-die-repeat/molecular/prey/state_idle.gd +++ b/evolve-die-repeat/molecular/prey/state_idle.gd @@ -2,25 +2,18 @@ extends State @onready var timer = $Timer var dir = Vector3(1, 1, 0) -var threshold = 1 -var max = 30 func enter(previous_state_path: String, data := {}) -> void: timer.start((float)(randi() % 5)/5) func physics_update(_delta: float) -> void: - if threshold == max: - owner.move(_delta * Vector3(randfn(0, 1), randfn(0, 1), 0), 4) - threshold = 1 - else: - threshold += 1 + owner.move(_delta * Vector3(randfn(0, 1), randfn(0, 1), 0), 1) func _on_timer_timeout() -> void: if (randi() % 4 != 0): finished.emit(owner.fsm.States.RANDOMMOVEMENT, {}) else: - finished.emit(owner.fsm.States.RANDOMMOVEMENT, {}) -# finished.emit(owner.fsm.States.IDLE, {}) + finished.emit(owner.fsm.States.IDLE, {}) func exit() -> void: timer.stop() diff --git a/evolve-die-repeat/molecular/prey/state_random_movement.gd b/evolve-die-repeat/molecular/prey/state_random_movement.gd index 3a48457..2cee56d 100644 --- a/evolve-die-repeat/molecular/prey/state_random_movement.gd +++ b/evolve-die-repeat/molecular/prey/state_random_movement.gd @@ -4,7 +4,7 @@ extends State var dir: Vector3 = Vector3(0,0,0); func enter(previous_state_path: String, data := {}) -> void: - timer.start((float)(randi() % 10)/20) + timer.start((float)(randi() % 10)/5) dir = calc_dir(randi() % 360) func physics_update(_delta: float) -> void: @@ -14,7 +14,8 @@ func calc_dir(angle: float) -> Vector3: return Vector3(cos(angle), sin(angle), 0) func _on_timer_timeout() -> void: - finished.emit(owner.fsm.States.IDLE, {}) + finished.emit(owner.fsm.States.RANDOMMOVEMENT, {}) + #finished.emit(owner.fsm.States.IDLE, {}) func exit() -> void: timer.stop() diff --git a/evolve-die-repeat/shared/state_machine.gd b/evolve-die-repeat/shared/state_machine.gd index 4af2e2b..0a1ec17 100644 --- a/evolve-die-repeat/shared/state_machine.gd +++ b/evolve-die-repeat/shared/state_machine.gd @@ -40,3 +40,9 @@ func _transition_to_next_state(target_path: String, data: Dictionary = {}) -> vo func transition_to_next_state(target: int, data: Dictionary = {}) -> void: push_error("Child FSM failed to implement transition function.") + +# maps child node name to States enum +# requires the recursive stap as push_error does not count as a valid return value. +func map(state: Node) -> int: + push_error("Child FSM failed to implement map function.") + return -1