* animate slime faster
* show loot text above head much closer to head
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user