* animate slime faster

* show loot text above head much closer to head
This commit is contained in:
2026-01-11 04:32:42 +01:00
parent a43a17ed10
commit 464f499b36
7 changed files with 160 additions and 80 deletions

View File

@@ -1,27 +1,23 @@
[gd_scene format=3 uid="uid://floating_text"]
[gd_scene format=3 uid="uid://dhhejfqlmhv52"]
[ext_resource type="Script" path="res://scripts/floating_text.gd" id="1"]
[ext_resource type="Script" uid="uid://dx5oym20rr2ei" path="res://scripts/floating_text.gd" id="1"]
[ext_resource type="FontFile" uid="uid://cbmcfue0ek0tk" path="res://assets/fonts/dmg_numbers.png" id="2_dmg_font"]
[sub_resource type="Theme" id="Theme_floating_text"]
default_font = ExtResource("2_dmg_font")
default_font_size = 12
[node name="FloatingText" type="Node2D"]
[node name="FloatingText" type="Node2D" unique_id=1350559946]
script = ExtResource("1")
[node name="ItemSprite" type="Sprite2D" parent="."]
[node name="ItemSprite" type="Sprite2D" parent="." unique_id=1657362510]
visible = false
offset = Vector2(0, -20)
scale = Vector2(1, 1)
offset = Vector2(0, -8)
[node name="Label" type="Label" parent="."]
[node name="Label" type="Label" parent="." unique_id=1387220833]
offset_right = 64.0
offset_bottom = 24.0
theme = SubResource("Theme_floating_text")
text = "+1 coin"
horizontal_alignment = 1
vertical_alignment = 1
outline_size = 2
outline_color = Color(0, 0, 0, 1)

View File

@@ -13,14 +13,44 @@ var detection_range: float = 70.0 # Range to detect players (much smaller)
# Jump mechanics
var is_jumping: bool = false
var jump_anim_frames = [2, 3, 4, 5, 7, 2] # Jump animation sequence
var jump_anim_index: int = 0
# Animation frames
const FRAME_IDLE = 0
const FRAMES_MOVE = [0, 1, 2] # Slow move
const FRAMES_DAMAGE = [8, 9]
const FRAMES_DEATH = [8, 9, 10, 11, 12, 13, 14]
# Animation system (similar to player)
const ANIMATIONS = {
"IDLE": {
"frames": [0],
"frameDurations": [500],
"loop": true,
"nextAnimation": null
},
"MOVE": {
"frames": [0, 1, 2],
"frameDurations": [200, 200, 200],
"loop": true,
"nextAnimation": null
},
"JUMP": {
"frames": [2, 3, 4, 5, 7, 2],
"frameDurations": [100, 100, 100, 100, 100, 100],
"loop": false,
"nextAnimation": "MOVE"
},
"DAMAGE": {
"frames": [8, 9],
"frameDurations": [150, 150],
"loop": false,
"nextAnimation": "IDLE"
},
"DIE": {
"frames": [8, 9, 10, 11, 12, 13, 14],
"frameDurations": [70, 70, 70, 70, 70, 70, 200],
"loop": false,
"nextAnimation": null
}
}
var current_animation = "IDLE"
var current_frame = 0
var time_since_last_frame = 0.0
func _ready():
super._ready()
@@ -71,7 +101,7 @@ func _ai_behavior(delta):
func _idle_behavior(_delta):
velocity = Vector2.ZERO
anim_frame = FRAME_IDLE
_set_animation("IDLE")
# Check if player is nearby
if target_player:
@@ -94,6 +124,8 @@ func _idle_behavior(_delta):
state_timer = move_duration
func _moving_behavior(_delta):
_set_animation("MOVE")
# Move slowly towards player
if target_player and is_instance_valid(target_player):
var direction = (target_player.global_position - global_position).normalized()
@@ -117,9 +149,8 @@ func _moving_behavior(_delta):
func _start_jump():
state = SlimeState.JUMPING
is_jumping = true
jump_anim_index = 0
state_timer = 0.6 # Jump duration
anim_time = 0.0
_set_animation("JUMP")
# Jump towards player if nearby
if target_player and is_instance_valid(target_player):
@@ -142,57 +173,46 @@ func _jumping_behavior(_delta):
func _damaged_behavior(_delta):
velocity = Vector2.ZERO
_set_animation("DAMAGE")
# Stay in damaged state briefly
if state_timer <= 0:
state = SlimeState.IDLE
state_timer = idle_duration
func _set_animation(anim_name: String):
if current_animation != anim_name:
current_animation = anim_name
current_frame = 0
time_since_last_frame = 0.0
func _update_animation(delta):
if state == SlimeState.DYING or state == SlimeState.DAMAGED:
return # Animation handled elsewhere
# Update animation frame timing
time_since_last_frame += delta
if time_since_last_frame >= ANIMATIONS[current_animation]["frameDurations"][current_frame] / 1000.0:
current_frame += 1
if current_frame >= len(ANIMATIONS[current_animation]["frames"]):
current_frame -= 1 # Prevent out of bounds
if ANIMATIONS[current_animation]["loop"]:
current_frame = 0
elif ANIMATIONS[current_animation]["nextAnimation"] != null:
current_frame = 0
current_animation = ANIMATIONS[current_animation]["nextAnimation"]
time_since_last_frame = 0.0
if state == SlimeState.IDLE:
anim_frame = FRAME_IDLE
elif state == SlimeState.JUMPING:
# Animate jump sequence
anim_time += delta
if anim_time >= 0.1: # Fast jump animation
anim_time = 0.0
jump_anim_index += 1
if jump_anim_index < jump_anim_frames.size():
anim_frame = jump_anim_frames[jump_anim_index]
elif state == SlimeState.MOVING:
# Animate slow move (frames 0, 1, 2)
anim_time += delta
if anim_time >= anim_speed:
anim_time = 0.0
var move_index = FRAMES_MOVE.find(anim_frame)
if move_index == -1:
move_index = 0
else:
move_index = (move_index + 1) % FRAMES_MOVE.size()
anim_frame = FRAMES_MOVE[move_index]
# Calculate frame index
var frame_index = ANIMATIONS[current_animation]["frames"][current_frame]
# Set sprite frame (slime looks same in all directions)
if sprite:
sprite.frame = anim_frame
sprite.frame = frame_index
anim_frame = frame_index # Keep anim_frame updated for compatibility
func _on_take_damage():
# Play damage animation
state = SlimeState.DAMAGED
state_timer = 0.3
anim_time = 0.0
# Animate damage frames
_play_damage_anim()
func _play_damage_anim():
for frame in FRAMES_DAMAGE:
anim_frame = frame
if sprite:
sprite.frame = frame
await get_tree().create_timer(0.1).timeout
_set_animation("DAMAGE")
func _die():
if is_dead:
@@ -204,6 +224,7 @@ func _die():
# Set state before calling parent _die()
state = SlimeState.DYING
velocity = Vector2.ZERO
_set_animation("DIE")
# Call parent _die() which handles death sync and _play_death_animation()
super._die()
@@ -220,12 +241,13 @@ func _update_client_visuals():
sprite.frame = anim_frame
func _play_death_animation():
# Play death animation sequence
for frame in FRAMES_DEATH:
anim_frame = frame
if sprite:
sprite.frame = frame
await get_tree().create_timer(0.15).timeout
_set_animation("DIE")
# Wait for death animation to complete
var total_duration = 0.0
for duration in ANIMATIONS["DIE"]["frameDurations"]:
total_duration += duration / 1000.0
await get_tree().create_timer(total_duration).timeout
# Fade out
if sprite:

View File

@@ -22,6 +22,7 @@ func setup(text_value: String, text_color: Color, show_time: float = 0.5, fade_t
label.text = text
label.modulate = color
label.modulate.a = 1.0 # Start fully visible
label.z_index = 10 # Render above other objects
# Setup item sprite if texture provided
if item_sprite and item_texture:
@@ -32,9 +33,10 @@ func setup(text_value: String, text_color: Color, show_time: float = 0.5, fade_t
item_sprite.frame = sprite_frame
item_sprite.modulate = Color.WHITE
item_sprite.modulate.a = 1.0
item_sprite.z_index = 10 # Render above other objects
# Position sprite above label (if label exists) or centered
if label:
item_sprite.position = Vector2(0, -24) # Above the text (sprite is ~16px tall)
item_sprite.position = Vector2(0, -12) # Just above the text (sprite is ~16px tall)
else:
item_sprite.position = Vector2(0, 0)
@@ -96,4 +98,3 @@ func _animate_coin():
await get_tree().create_timer(coin_frame_time).timeout
if item_sprite and is_instance_valid(item_sprite) and item_sprite.visible:
item_sprite.frame = target_frame

View File

@@ -2634,9 +2634,10 @@ func _place_key_in_room(room: Dictionary):
valid_positions.append(Vector2(world_x, world_y))
if valid_positions.size() > 0:
# Pick a random position
# Use deterministic seed for key placement (ensures same position on host and clients)
var rng = RandomNumberGenerator.new()
rng.randomize()
var key_seed = dungeon_seed + (room.x * 1000) + (room.y * 100) + 5000 # Offset to avoid collisions with other objects
rng.seed = key_seed
var key_pos = valid_positions[rng.randi() % valid_positions.size()]
# Spawn key loot

View File

@@ -530,21 +530,21 @@ func _open_chest(by_player: Node = null):
by_player.heal(heal_amount)
# Show pickup notification with apple graphic
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_item_pickup_notification(by_player, "+" + str(int(heal_amount)) + " HP", selected_loot.color, items_texture, 20, 14, (8 * 20) + 11)
_show_item_pickup_notification(by_player, "+" + str(int(heal_amount)) + " HP", selected_loot.color, items_texture, 20, 14, (8 * 20) + 10)
"banana":
var heal_amount = 20.0
if by_player.has_method("heal"):
by_player.heal(heal_amount)
# Show pickup notification with banana graphic
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_item_pickup_notification(by_player, "+" + str(int(heal_amount)) + " HP", selected_loot.color, items_texture, 20, 14, (8 * 20) + 12)
_show_item_pickup_notification(by_player, "+" + str(int(heal_amount)) + " HP", selected_loot.color, items_texture, 20, 14, (8 * 20) + 11)
"cherry":
var heal_amount = 20.0
if by_player.has_method("heal"):
by_player.heal(heal_amount)
# Show pickup notification with cherry graphic
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_item_pickup_notification(by_player, "+" + str(int(heal_amount)) + " HP", selected_loot.color, items_texture, 20, 14, (8 * 20) + 13)
_show_item_pickup_notification(by_player, "+" + str(int(heal_amount)) + " HP", selected_loot.color, items_texture, 20, 14, (8 * 20) + 12)
"key":
if by_player.has_method("add_key"):
by_player.add_key(1)
@@ -562,7 +562,8 @@ func _open_chest(by_player: Node = null):
# Sync chest opening visual to all clients (item already given on server)
if multiplayer.has_multiplayer_peer():
_sync_chest_open.rpc(selected_loot.type if by_player else "coin")
var player_peer_id = by_player.get_multiplayer_authority() if by_player else 0
_sync_chest_open.rpc(selected_loot.type if by_player else "coin", player_peer_id)
@rpc("any_peer", "reliable")
func _request_chest_open(player_peer_id: int):
@@ -593,7 +594,7 @@ func _request_chest_open(player_peer_id: int):
_open_chest(player)
@rpc("any_peer", "reliable")
func _sync_chest_open(_loot_type_str: String = "coin"):
func _sync_chest_open(loot_type_str: String = "coin", player_peer_id: int = 0):
# Sync chest opening to all clients (only visual - item already given on server)
if not is_chest_opened and sprite and chest_opened_frame >= 0:
is_chest_opened = true
@@ -602,6 +603,37 @@ func _sync_chest_open(_loot_type_str: String = "coin"):
# Play chest open sound on clients
if has_node("SfxChestOpen"):
$SfxChestOpen.play()
# Show pickup notification on client side
if player_peer_id > 0:
var players = get_tree().get_nodes_in_group("player")
var player = null
for p in players:
if p.get_multiplayer_authority() == player_peer_id:
player = p
break
if player and is_instance_valid(player):
# Show notification based on loot type (same as server)
match loot_type_str:
"coin":
var coin_texture = load("res://assets/gfx/pickups/gold_coin.png")
_show_item_pickup_notification(player, "+1 COIN", Color(1.0, 0.84, 0.0), coin_texture, 6, 1, 0)
"apple":
var heal_amount = 20.0
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_item_pickup_notification(player, "+" + str(int(heal_amount)) + " HP", Color.GREEN, items_texture, 20, 14, (8 * 20) + 10)
"banana":
var heal_amount = 20.0
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_item_pickup_notification(player, "+" + str(int(heal_amount)) + " HP", Color.YELLOW, items_texture, 20, 14, (8 * 20) + 11)
"cherry":
var heal_amount = 20.0
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_item_pickup_notification(player, "+" + str(int(heal_amount)) + " HP", Color.RED, items_texture, 20, 14, (8 * 20) + 12)
"key":
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_item_pickup_notification(player, "+1 KEY", Color.YELLOW, items_texture, 20, 14, (13 * 20) + 10)
func _show_item_pickup_notification(player: Node, text: String, text_color: Color, item_texture: Texture2D = null, sprite_hframes: int = 1, sprite_vframes: int = 1, sprite_frame: int = 0):
# Show item graphic and text above player's head for 0.5s, then fade out over 0.5s
@@ -611,7 +643,8 @@ func _show_item_pickup_notification(player: Node, text: String, text_color: Colo
var parent = player.get_parent()
if parent:
parent.add_child(floating_text)
floating_text.global_position = player.global_position + Vector2(0, -20)
# Position at player.position.y - 20 (just above head)
floating_text.global_position = Vector2(player.global_position.x, player.global_position.y - 20)
floating_text.setup(text, text_color, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame) # Show for 0.5s, fade over 0.5s
func play_destroy_sound():

View File

@@ -381,7 +381,7 @@ func _process_pickup_on_server(player: Node):
player.heal(heal_amount)
# Show floating text with item graphic and heal amount
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 11)
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 10)
self.visible = false
@@ -399,7 +399,7 @@ func _process_pickup_on_server(player: Node):
player.heal(heal_amount)
# Show floating text with item graphic and heal amount
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 12)
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 11)
self.visible = false
@@ -417,7 +417,7 @@ func _process_pickup_on_server(player: Node):
player.heal(heal_amount)
# Show floating text with item graphic and heal amount
var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 13)
_show_floating_text(player, "+" + str(int(actual_heal)) + " HP", Color.GREEN, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 12)
self.visible = false
@@ -545,5 +545,5 @@ func _show_floating_text(player: Node, text: String, color: Color, show_time: fl
var parent = player.get_parent()
if parent:
parent.add_child(floating_text)
floating_text.global_position = player.global_position + Vector2(0, -20)
floating_text.global_position = Vector2(player.global_position.x, player.global_position.y)
floating_text.setup(text, color, show_time, fade_time, item_texture, sprite_hframes, sprite_vframes, sprite_frame)

View File

@@ -2148,12 +2148,39 @@ func _die():
is_dead = true # Ensure flag is set
velocity = Vector2.ZERO
is_knocked_back = false
is_lifting = false
is_pushing = false
held_object = null
# Don't force release - let them carry the corpse
# We'll handle release during respawn
# CRITICAL: Release any held object/player BEFORE dying to restore their collision layers
if held_object:
var released_obj = held_object
held_object = null
is_lifting = false
is_pushing = false
grab_offset = Vector2.ZERO
push_axis = Vector2.ZERO
# Re-enable collision for released object/player
if _is_box(released_obj):
released_obj.set_collision_layer_value(2, true)
released_obj.set_collision_mask_value(1, true)
released_obj.set_collision_mask_value(2, true)
if "is_being_held" in released_obj:
released_obj.is_being_held = false
if "held_by_player" in released_obj:
released_obj.held_by_player = null
elif _is_player(released_obj):
released_obj.set_collision_layer_value(1, true)
released_obj.set_collision_mask_value(1, true)
if released_obj.has_method("set_being_held"):
released_obj.set_being_held(false)
# Sync release to network
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
_sync_release.rpc(released_obj.get_path())
print(name, " released ", released_obj.name, " on death")
else:
is_lifting = false
is_pushing = false
print(name, " died!")