WIP: merge recipies
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
[gd_scene format=3 uid="uid://bavtajgxrkrc1"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/Boulder.gd" id="1_boulder"]
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_1"]
|
||||
radius = 0.45
|
||||
height = 0.9
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
albedo_color = Color(0.32, 0.28, 0.22, 1)
|
||||
roughness = 1.0
|
||||
metallic = 0.08
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||
radius = 0.45
|
||||
|
||||
[node name="Boulder" type="CharacterBody3D"]
|
||||
script = ExtResource("1_boulder")
|
||||
|
||||
[node name="BoulderMesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.45, 0)
|
||||
mesh = SubResource("SphereMesh_1")
|
||||
material_override = SubResource("StandardMaterial3D_1")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.45, 0)
|
||||
shape = SubResource("SphereShape3D_1")
|
||||
@@ -0,0 +1,25 @@
|
||||
[gd_scene format=3 uid="uid://d4leath8bvq2r"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/Leather.gd" id="1_leather"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_1"]
|
||||
size = Vector3(0.5, 0.06, 0.35)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
albedo_color = Color(0.62, 0.38, 0.20, 1)
|
||||
roughness = 0.85
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_1"]
|
||||
size = Vector3(0.5, 0.06, 0.35)
|
||||
|
||||
[node name="Leather" type="CharacterBody3D"]
|
||||
script = ExtResource("1_leather")
|
||||
|
||||
[node name="LeatherMesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
|
||||
mesh = SubResource("BoxMesh_1")
|
||||
material_override = SubResource("StandardMaterial3D_1")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
|
||||
shape = SubResource("BoxShape3D_1")
|
||||
@@ -0,0 +1,27 @@
|
||||
[gd_scene format=3 uid="uid://cbvs3rvwslsd2"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/LeatherBoots.gd" id="1_boots"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_1"]
|
||||
size = Vector3(0.4, 0.2, 0.55)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
albedo_color = Color(0.45, 0.25, 0.10, 1)
|
||||
roughness = 0.8
|
||||
metallic = 0.1
|
||||
|
||||
[node name="LeatherBoots" type="Node3D"]
|
||||
script = ExtResource("1_boots")
|
||||
|
||||
[node name="BootsMesh" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("BoxMesh_1")
|
||||
material_override = SubResource("StandardMaterial3D_1")
|
||||
|
||||
[node name="Tooltip" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
|
||||
billboard = 1
|
||||
double_sided = true
|
||||
text = "[E] Leather Boots
|
||||
+10 Move Speed"
|
||||
font_size = 32
|
||||
outline_size = 6
|
||||
@@ -0,0 +1,28 @@
|
||||
[gd_scene format=3 uid="uid://c3sticlaxmn1p"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/Stick.gd" id="1_stick"]
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_1"]
|
||||
top_radius = 0.06
|
||||
bottom_radius = 0.06
|
||||
height = 0.65
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
albedo_color = Color(0.55, 0.38, 0.18, 1)
|
||||
roughness = 0.95
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||
radius = 0.07
|
||||
height = 0.65
|
||||
|
||||
[node name="Stick" type="CharacterBody3D"]
|
||||
script = ExtResource("1_stick")
|
||||
|
||||
[node name="StickMesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.32, 0)
|
||||
mesh = SubResource("CylinderMesh_1")
|
||||
material_override = SubResource("StandardMaterial3D_1")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.32, 0)
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
@@ -0,0 +1,113 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
signal destroyed
|
||||
|
||||
enum State { IDLE, FLYING }
|
||||
|
||||
const AIR_FRICTION := 0.88
|
||||
const MIN_SPEED := 0.5
|
||||
const WALL_BOUNCE := 0.4
|
||||
const WALL_SELF_DMG := 0.4
|
||||
|
||||
var kickable_type: String = "boulder"
|
||||
var state: State = State.IDLE
|
||||
var fly_vel: Vector3 = Vector3.ZERO
|
||||
var health: float = 150.0
|
||||
var dead: bool = false
|
||||
var damage_modifier: float = 1.8
|
||||
|
||||
@onready var mesh_node: MeshInstance3D = $BoulderMesh
|
||||
var boulder_mat: StandardMaterial3D
|
||||
|
||||
const COLOR_IDLE := Color(0.32, 0.28, 0.22)
|
||||
const COLOR_IMPACT := Color(1.0, 1.0, 1.0)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("kickable")
|
||||
boulder_mat = mesh_node.material_override.duplicate() as StandardMaterial3D
|
||||
mesh_node.material_override = boulder_mat
|
||||
|
||||
func apply_collision_damage(dmg: float) -> void:
|
||||
_take_damage(dmg)
|
||||
|
||||
func receive_kick(direction: Vector3, force: float) -> void:
|
||||
fly_vel = direction * (force * 0.6)
|
||||
fly_vel.y = 0.0
|
||||
state = State.FLYING
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if state == State.IDLE:
|
||||
return
|
||||
_fly(delta)
|
||||
|
||||
func _fly(delta: float) -> void:
|
||||
var speed_now := Vector2(fly_vel.x, fly_vel.z).length()
|
||||
velocity = fly_vel
|
||||
velocity.y = 0.0
|
||||
move_and_slide()
|
||||
|
||||
var handled := false
|
||||
for i in get_slide_collision_count():
|
||||
var col := get_slide_collision(i)
|
||||
var col3d := col.get_collider() as Node3D
|
||||
if col3d == null:
|
||||
continue
|
||||
if col3d.has_meta("is_wall"):
|
||||
var normal := col.get_normal()
|
||||
normal.y = 0.0
|
||||
if normal.length() > 0.01:
|
||||
fly_vel = fly_vel.bounce(normal.normalized()) * WALL_BOUNCE
|
||||
else:
|
||||
fly_vel = Vector3.ZERO
|
||||
_take_damage(speed_now * WALL_SELF_DMG)
|
||||
handled = true
|
||||
break
|
||||
elif col3d.is_in_group("enemies") or col3d.is_in_group("kickable"):
|
||||
if col3d == self:
|
||||
continue
|
||||
KickSystem.resolve(self, col3d, fly_vel)
|
||||
if not dead and is_instance_valid(col3d):
|
||||
var kick_dir := col3d.global_position - global_position
|
||||
kick_dir.y = 0.0
|
||||
if kick_dir.length() > 0.01:
|
||||
col3d.call("receive_kick", kick_dir.normalized(), speed_now * 0.5)
|
||||
fly_vel *= 0.55
|
||||
handled = true
|
||||
break
|
||||
|
||||
if not handled:
|
||||
fly_vel = velocity
|
||||
fly_vel.y = 0.0
|
||||
|
||||
fly_vel *= pow(AIR_FRICTION, delta * 60.0)
|
||||
|
||||
if Vector2(fly_vel.x, fly_vel.z).length() < MIN_SPEED:
|
||||
fly_vel = Vector3.ZERO
|
||||
velocity = Vector3.ZERO
|
||||
state = State.IDLE
|
||||
|
||||
mesh_node.rotation.y += delta * speed_now * 0.15
|
||||
|
||||
func _take_damage(dmg: float) -> void:
|
||||
if dead:
|
||||
return
|
||||
health -= dmg
|
||||
_flash()
|
||||
if health <= 0.0:
|
||||
_die()
|
||||
|
||||
func _die() -> void:
|
||||
dead = true
|
||||
state = State.IDLE
|
||||
set_physics_process(false)
|
||||
emit_signal("destroyed")
|
||||
var tw := create_tween()
|
||||
tw.tween_property(self, "scale", Vector3(2.0, 0.1, 2.0), 0.15)
|
||||
tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.12)
|
||||
tw.tween_callback(queue_free)
|
||||
|
||||
func _flash() -> void:
|
||||
var tw := create_tween()
|
||||
tw.tween_property(boulder_mat, "albedo_color", COLOR_IMPACT, 0.04)
|
||||
var target_color := COLOR_IDLE.lerp(Color.RED, clampf(1.0 - health / 150.0, 0.0, 0.6))
|
||||
tw.tween_property(boulder_mat, "albedo_color", target_color, 0.18)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c1fcxciue3squ
|
||||
+16
-2
@@ -1,12 +1,17 @@
|
||||
class_name Enemy
|
||||
extends CharacterBody3D
|
||||
|
||||
const PICKUP_SCENE := preload("res://scenes/Pickup.tscn")
|
||||
const LEATHER_SCENE := preload("res://scenes/Leather.tscn")
|
||||
|
||||
signal died(points: int)
|
||||
signal merged(upgrade: bool)
|
||||
|
||||
enum State { CHASING, FLYING, STUNNED, DEAD, MERGING }
|
||||
|
||||
static var first_leather_spawned: bool = false
|
||||
|
||||
var kickable_type: String = ""
|
||||
var move_speed: float = 3.0
|
||||
var health: int = 30
|
||||
var damage_to_player: int = 8
|
||||
@@ -46,6 +51,7 @@ func _ready() -> void:
|
||||
|
||||
func setup(type: String, wave: int) -> void:
|
||||
enemy_type = type
|
||||
kickable_type = type
|
||||
wave_num = wave
|
||||
match type:
|
||||
"slime":
|
||||
@@ -107,7 +113,7 @@ const AVOID_STRENGTH := 2.2
|
||||
|
||||
func _avoid_rocks(desired: Vector3) -> Vector3:
|
||||
var push := Vector3.ZERO
|
||||
for rock in get_tree().get_nodes_in_group("rocks"):
|
||||
for rock in get_tree().get_nodes_in_group("kickable"):
|
||||
if not is_instance_valid(rock):
|
||||
continue
|
||||
var away := global_position - (rock as Node3D).global_position
|
||||
@@ -147,7 +153,7 @@ func _fly(delta: float) -> void:
|
||||
chain_dir.y = 0.0
|
||||
if chain_dir.length() > 0.01:
|
||||
col3d.call("receive_kick", chain_dir.normalized(), speed_now * chain_factor)
|
||||
elif col3d.is_in_group("rocks"):
|
||||
elif col3d.is_in_group("kickable"):
|
||||
KickSystem.resolve(self, col3d, fly_vel)
|
||||
var rock_dir := col3d.global_position - global_position
|
||||
rock_dir.y = 0.0
|
||||
@@ -251,6 +257,14 @@ func _wall_impact_effect() -> void:
|
||||
tw.tween_property(mat, "albedo_color", COLOR_STUN, 0.12)
|
||||
|
||||
func _try_drop_pickup() -> void:
|
||||
if enemy_level == 1:
|
||||
var drop_leather := not first_leather_spawned or randf() < 0.20
|
||||
if drop_leather:
|
||||
first_leather_spawned = true
|
||||
var leather := LEATHER_SCENE.instantiate() as Node3D
|
||||
get_parent().add_child(leather)
|
||||
leather.global_position = global_position
|
||||
|
||||
var roll := randf()
|
||||
var p_type := ""
|
||||
var p_heal := 0
|
||||
|
||||
+23
-2
@@ -24,12 +24,21 @@ static func resolve(owner: Node3D, other: Node3D, owner_vel: Vector3) -> bool:
|
||||
var speed_b := Vector2(other_vel.x, other_vel.z).length()
|
||||
var collision_speed := speed_a + speed_b
|
||||
|
||||
# ── 1. Merge ──────────────────────────────────────────────────────────────
|
||||
# ── 1. Recipe merge (MergeRecipes) ────────────────────────────────────────
|
||||
var kt_a: String = owner.get("kickable_type") if owner.get("kickable_type") != null else ""
|
||||
var kt_b: String = other.get("kickable_type") if other.get("kickable_type") != null else ""
|
||||
if kt_a != "" and kt_b != "":
|
||||
var recipe: Dictionary = MergeRecipes.find(kt_a, kt_b, collision_speed)
|
||||
if not recipe.is_empty():
|
||||
_execute_recipe(owner, other, recipe)
|
||||
return true
|
||||
|
||||
# ── 2. Merge (enemy-to-enemy) ─────────────────────────────────────────────
|
||||
if owner.has_method("can_merge_with") and owner.call("can_merge_with", other, collision_speed):
|
||||
owner.call("do_merge_with", other)
|
||||
return true
|
||||
|
||||
# ── 2. Damage ─────────────────────────────────────────────────────────────
|
||||
# ── 3. Damage ─────────────────────────────────────────────────────────────
|
||||
var mod_a: float = owner.get("damage_modifier") if owner.get("damage_modifier") != null else 0.0
|
||||
var mod_b: float = other.get("damage_modifier") if other.get("damage_modifier") != null else 0.0
|
||||
|
||||
@@ -39,3 +48,15 @@ static func resolve(owner: Node3D, other: Node3D, owner_vel: Vector3) -> bool:
|
||||
owner.call("apply_collision_damage", collision_speed * mod_b)
|
||||
|
||||
return false
|
||||
|
||||
static func _execute_recipe(a: Node3D, b: Node3D, recipe: Dictionary) -> void:
|
||||
var pos := (a.global_position + b.global_position) * 0.5
|
||||
var parent := a.get_parent()
|
||||
a.queue_free()
|
||||
b.queue_free()
|
||||
var scene: PackedScene = load(recipe["result_scene"])
|
||||
if scene == null or parent == null:
|
||||
return
|
||||
var result := scene.instantiate() as Node3D
|
||||
parent.add_child(result)
|
||||
result.global_position = pos
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
enum State { IDLE, FLYING }
|
||||
|
||||
const AIR_FRICTION := 0.90
|
||||
const MIN_SPEED := 0.3
|
||||
const WALL_BOUNCE := 0.7
|
||||
|
||||
var kickable_type: String = "leather"
|
||||
var state: State = State.IDLE
|
||||
var fly_vel: Vector3 = Vector3.ZERO
|
||||
var damage_modifier: float = 0.0
|
||||
|
||||
@onready var mesh_node: MeshInstance3D = $LeatherMesh
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("kickable")
|
||||
|
||||
func apply_collision_damage(_dmg: float) -> void:
|
||||
pass
|
||||
|
||||
func receive_kick(direction: Vector3, force: float) -> void:
|
||||
fly_vel = direction * force
|
||||
fly_vel.y = 0.0
|
||||
state = State.FLYING
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if state == State.IDLE:
|
||||
return
|
||||
_fly(delta)
|
||||
|
||||
func _fly(delta: float) -> void:
|
||||
var speed_now := Vector2(fly_vel.x, fly_vel.z).length()
|
||||
velocity = fly_vel
|
||||
velocity.y = 0.0
|
||||
move_and_slide()
|
||||
|
||||
var handled := false
|
||||
for i in get_slide_collision_count():
|
||||
var col := get_slide_collision(i)
|
||||
var col3d := col.get_collider() as Node3D
|
||||
if col3d == null:
|
||||
continue
|
||||
if col3d.has_meta("is_wall"):
|
||||
var normal := col.get_normal()
|
||||
normal.y = 0.0
|
||||
if normal.length() > 0.01:
|
||||
fly_vel = fly_vel.bounce(normal.normalized()) * WALL_BOUNCE
|
||||
else:
|
||||
fly_vel = Vector3.ZERO
|
||||
handled = true
|
||||
break
|
||||
elif col3d.is_in_group("enemies") or col3d.is_in_group("kickable"):
|
||||
if col3d == self:
|
||||
continue
|
||||
KickSystem.resolve(self, col3d, fly_vel)
|
||||
if is_instance_valid(col3d):
|
||||
var kick_dir := col3d.global_position - global_position
|
||||
kick_dir.y = 0.0
|
||||
if kick_dir.length() > 0.01:
|
||||
col3d.call("receive_kick", kick_dir.normalized(), speed_now * 0.4)
|
||||
fly_vel *= 0.35
|
||||
handled = true
|
||||
break
|
||||
|
||||
if not handled:
|
||||
fly_vel = velocity
|
||||
fly_vel.y = 0.0
|
||||
|
||||
fly_vel *= pow(AIR_FRICTION, delta * 60.0)
|
||||
|
||||
if Vector2(fly_vel.x, fly_vel.z).length() < MIN_SPEED:
|
||||
fly_vel = Vector3.ZERO
|
||||
velocity = Vector3.ZERO
|
||||
state = State.IDLE
|
||||
|
||||
mesh_node.rotation.y += delta * speed_now * 0.2
|
||||
@@ -0,0 +1 @@
|
||||
uid://dndcs6xc7m08x
|
||||
@@ -0,0 +1,25 @@
|
||||
extends Node3D
|
||||
|
||||
var tier: int = 1
|
||||
|
||||
@onready var tooltip: Label3D = $Tooltip
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("interactable")
|
||||
tooltip.visible = false
|
||||
var tw := create_tween().set_loops()
|
||||
tw.tween_property(self, "position:y", 0.3, 0.7)
|
||||
tw.tween_property(self, "position:y", 0.1, 0.7)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
rotation.y += delta * 1.2
|
||||
var players := get_tree().get_nodes_in_group("player")
|
||||
if players.is_empty():
|
||||
tooltip.visible = false
|
||||
return
|
||||
var p := players[0] as Node3D
|
||||
tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5
|
||||
|
||||
func interact(player: Node) -> void:
|
||||
player.call("apply_upgrade_boots", 10.0, tier)
|
||||
queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0j8nw7eynmk8
|
||||
+12
-3
@@ -3,6 +3,7 @@ extends Node3D
|
||||
const PLAYER_SCENE := preload("res://scenes/Player.tscn")
|
||||
const ENEMY_SCENE := preload("res://scenes/Enemy.tscn")
|
||||
const ROCK_SCENE := preload("res://scenes/Rock.tscn")
|
||||
const STICK_SCENE := preload("res://scenes/Stick.tscn")
|
||||
const LEVEL_SCENE := preload("res://scenes/Level.tscn")
|
||||
|
||||
const CAM_DIST := 8.0
|
||||
@@ -43,6 +44,7 @@ func _ready() -> void:
|
||||
_create_ui()
|
||||
_spawn_player()
|
||||
_spawn_rocks()
|
||||
_spawn_sticks()
|
||||
_start_game()
|
||||
add_to_group("main")
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
@@ -104,14 +106,20 @@ func _spawn_rocks() -> void:
|
||||
for i in range(limit):
|
||||
_spawn_single_rock()
|
||||
|
||||
func _spawn_sticks() -> void:
|
||||
for i in range(2):
|
||||
var stick := STICK_SCENE.instantiate()
|
||||
stick.position = _safe_item_position()
|
||||
add_child(stick)
|
||||
|
||||
func _spawn_single_rock() -> void:
|
||||
var rock := ROCK_SCENE.instantiate()
|
||||
rock.position = _safe_rock_position()
|
||||
rock.position = _safe_item_position()
|
||||
add_child(rock)
|
||||
rock.connect("destroyed", _on_rock_destroyed)
|
||||
rocks_on_field += 1
|
||||
|
||||
func _safe_rock_position() -> Vector3:
|
||||
func _safe_item_position() -> Vector3:
|
||||
var player_pos := player.global_position if is_instance_valid(player) else Vector3.ZERO
|
||||
for _attempt in range(30):
|
||||
var angle := randf() * TAU
|
||||
@@ -120,7 +128,7 @@ func _safe_rock_position() -> Vector3:
|
||||
if player_pos.distance_to(pos) < 4.5:
|
||||
continue
|
||||
var clear := true
|
||||
for r in get_tree().get_nodes_in_group("rocks"):
|
||||
for r in get_tree().get_nodes_in_group("kickable"):
|
||||
if (r as Node3D).global_position.distance_to(pos) < 1.5:
|
||||
clear = false
|
||||
break
|
||||
@@ -162,6 +170,7 @@ func _start_game() -> void:
|
||||
score = 0
|
||||
kills = 0
|
||||
kills_for_next = 10
|
||||
Enemy.first_leather_spawned = false
|
||||
_update_labels()
|
||||
spawn_timer.wait_time = 1.4
|
||||
spawn_timer.connect("timeout", _on_spawn_timer)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
class_name MergeRecipes
|
||||
|
||||
# Add new crafting recipes here.
|
||||
# speed_threshold = minimum collision_speed to trigger the merge.
|
||||
static var _list: Array[Dictionary] = [
|
||||
{
|
||||
"ingredients": ["leather", "stick"],
|
||||
"result_scene": "res://scenes/LeatherBoots.tscn",
|
||||
"speed_threshold": 0.5,
|
||||
},
|
||||
{
|
||||
"ingredients": ["rock", "rock"],
|
||||
"result_scene": "res://scenes/Boulder.tscn",
|
||||
"speed_threshold": 5.0,
|
||||
},
|
||||
]
|
||||
|
||||
static func find(type_a: String, type_b: String, speed: float) -> Dictionary:
|
||||
for r in _list:
|
||||
var a: String = r["ingredients"][0]
|
||||
var b: String = r["ingredients"][1]
|
||||
var match_ab := (a == type_a and b == type_b) or (a == type_b and b == type_a)
|
||||
if match_ab and speed >= float(r.get("speed_threshold", 3.0)):
|
||||
return r
|
||||
return {}
|
||||
@@ -0,0 +1 @@
|
||||
uid://uy8s3uoktnly
|
||||
+26
-1
@@ -67,6 +67,11 @@ func _make_kick_arc_mesh() -> ArrayMesh:
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||
return mesh
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
if event.keycode == KEY_E:
|
||||
_try_interact()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not is_alive:
|
||||
return
|
||||
@@ -130,7 +135,7 @@ func _do_kick() -> void:
|
||||
forward.y = 0.0
|
||||
forward = forward.normalized() if forward.length() > 0.01 else Vector3(0.0, 0.0, -1.0)
|
||||
var half_cos: float = cos(deg_to_rad(kick_angle * 0.5))
|
||||
var enemies := get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("rocks")
|
||||
var enemies := get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("kickable")
|
||||
var kicked_any := false
|
||||
for e in enemies:
|
||||
if not is_instance_valid(e):
|
||||
@@ -149,6 +154,20 @@ func _do_kick() -> void:
|
||||
if kicked_any:
|
||||
_squish_effect()
|
||||
|
||||
func _try_interact() -> void:
|
||||
var best: Node3D = null
|
||||
var best_dist := 2.5
|
||||
for node in get_tree().get_nodes_in_group("interactable"):
|
||||
var n := node as Node3D
|
||||
if n == null:
|
||||
continue
|
||||
var d := global_position.distance_to(n.global_position)
|
||||
if d < best_dist:
|
||||
best_dist = d
|
||||
best = n
|
||||
if best != null:
|
||||
best.call("interact", self)
|
||||
|
||||
func set_aim_direction(yaw_rad: float) -> void:
|
||||
_aim_yaw = yaw_rad
|
||||
_is_aiming = true
|
||||
@@ -197,3 +216,9 @@ func apply_upgrade(id: String) -> void:
|
||||
max_health += 30
|
||||
health = min(health + 30, max_health)
|
||||
emit_signal("health_changed", health, max_health)
|
||||
|
||||
func apply_upgrade_boots(speed_bonus: float, _tier: int) -> void:
|
||||
move_speed += speed_bonus
|
||||
var tw := create_tween()
|
||||
tw.tween_property(player_mat, "albedo_color", Color(1.0, 0.85, 0.2), 0.1)
|
||||
tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.4)
|
||||
|
||||
+3
-2
@@ -9,6 +9,7 @@ const MIN_SPEED := 0.5
|
||||
const WALL_BOUNCE := 0.5
|
||||
const WALL_SELF_DMG := 0.6
|
||||
|
||||
var kickable_type: String = "rock"
|
||||
var state: State = State.IDLE
|
||||
var fly_vel: Vector3 = Vector3.ZERO
|
||||
var health: float = 60.0
|
||||
@@ -22,7 +23,7 @@ const COLOR_IDLE := Color(0.45, 0.38, 0.30)
|
||||
const COLOR_IMPACT := Color(1.0, 1.0, 1.0)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("rocks")
|
||||
add_to_group("kickable")
|
||||
rock_mat = mesh_node.material_override.duplicate() as StandardMaterial3D
|
||||
mesh_node.material_override = rock_mat
|
||||
|
||||
@@ -64,7 +65,7 @@ func _fly(delta: float) -> void:
|
||||
_take_damage(speed_now * WALL_SELF_DMG)
|
||||
handled = true
|
||||
break
|
||||
elif col3d.is_in_group("enemies") or col3d.is_in_group("rocks"):
|
||||
elif col3d.is_in_group("enemies") or col3d.is_in_group("kickable"):
|
||||
if col3d == self:
|
||||
continue
|
||||
KickSystem.resolve(self, col3d, fly_vel)
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
signal destroyed
|
||||
|
||||
enum State { IDLE, FLYING }
|
||||
|
||||
const AIR_FRICTION := 0.84
|
||||
const MIN_SPEED := 0.5
|
||||
const WALL_BOUNCE := 0.5
|
||||
const WALL_SELF_DMG := 0.5
|
||||
|
||||
var kickable_type: String = "stick"
|
||||
var state: State = State.IDLE
|
||||
var fly_vel: Vector3 = Vector3.ZERO
|
||||
var health: float = 40.0
|
||||
var dead: bool = false
|
||||
var damage_modifier: float = 0.6
|
||||
|
||||
@onready var mesh_node: MeshInstance3D = $StickMesh
|
||||
var stick_mat: StandardMaterial3D
|
||||
|
||||
const COLOR_IDLE := Color(0.55, 0.38, 0.18)
|
||||
const COLOR_IMPACT := Color(1.0, 1.0, 1.0)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("kickable")
|
||||
stick_mat = mesh_node.material_override.duplicate() as StandardMaterial3D
|
||||
mesh_node.material_override = stick_mat
|
||||
|
||||
func apply_collision_damage(dmg: float) -> void:
|
||||
_take_damage(dmg)
|
||||
|
||||
func receive_kick(direction: Vector3, force: float) -> void:
|
||||
fly_vel = direction * force
|
||||
fly_vel.y = 0.0
|
||||
state = State.FLYING
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if state == State.IDLE:
|
||||
return
|
||||
_fly(delta)
|
||||
|
||||
func _fly(delta: float) -> void:
|
||||
var speed_now := Vector2(fly_vel.x, fly_vel.z).length()
|
||||
velocity = fly_vel
|
||||
velocity.y = 0.0
|
||||
move_and_slide()
|
||||
|
||||
var handled := false
|
||||
for i in get_slide_collision_count():
|
||||
var col := get_slide_collision(i)
|
||||
var col3d := col.get_collider() as Node3D
|
||||
if col3d == null:
|
||||
continue
|
||||
if col3d.has_meta("is_wall"):
|
||||
var normal := col.get_normal()
|
||||
normal.y = 0.0
|
||||
if normal.length() > 0.01:
|
||||
fly_vel = fly_vel.bounce(normal.normalized()) * WALL_BOUNCE
|
||||
else:
|
||||
fly_vel = Vector3.ZERO
|
||||
_take_damage(speed_now * WALL_SELF_DMG)
|
||||
handled = true
|
||||
break
|
||||
elif col3d.is_in_group("enemies") or col3d.is_in_group("kickable"):
|
||||
if col3d == self:
|
||||
continue
|
||||
KickSystem.resolve(self, col3d, fly_vel)
|
||||
if not dead and is_instance_valid(col3d):
|
||||
var kick_dir := col3d.global_position - global_position
|
||||
kick_dir.y = 0.0
|
||||
if kick_dir.length() > 0.01:
|
||||
col3d.call("receive_kick", kick_dir.normalized(), speed_now * 0.65)
|
||||
fly_vel *= 0.45
|
||||
handled = true
|
||||
break
|
||||
|
||||
if not handled:
|
||||
fly_vel = velocity
|
||||
fly_vel.y = 0.0
|
||||
|
||||
fly_vel *= pow(AIR_FRICTION, delta * 60.0)
|
||||
|
||||
if Vector2(fly_vel.x, fly_vel.z).length() < MIN_SPEED:
|
||||
fly_vel = Vector3.ZERO
|
||||
velocity = Vector3.ZERO
|
||||
state = State.IDLE
|
||||
|
||||
mesh_node.rotation.z += delta * speed_now * 0.3
|
||||
|
||||
func _take_damage(dmg: float) -> void:
|
||||
if dead:
|
||||
return
|
||||
health -= dmg
|
||||
_flash()
|
||||
if health <= 0.0:
|
||||
_die()
|
||||
|
||||
func _die() -> void:
|
||||
dead = true
|
||||
state = State.IDLE
|
||||
set_physics_process(false)
|
||||
emit_signal("destroyed")
|
||||
var tw := create_tween()
|
||||
tw.tween_property(self, "scale", Vector3(1.6, 0.1, 1.6), 0.12)
|
||||
tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.1)
|
||||
tw.tween_callback(queue_free)
|
||||
|
||||
func _flash() -> void:
|
||||
var tw := create_tween()
|
||||
tw.tween_property(stick_mat, "albedo_color", COLOR_IMPACT, 0.04)
|
||||
var target_color := COLOR_IDLE.lerp(Color.RED, clampf(1.0 - health / 40.0, 0.0, 0.6))
|
||||
tw.tween_property(stick_mat, "albedo_color", target_color, 0.18)
|
||||
@@ -0,0 +1 @@
|
||||
uid://ceo530cbtsr6e
|
||||
Reference in New Issue
Block a user