Add unit movement slide animation
This commit is contained in:
@@ -7,6 +7,7 @@ signal changed
|
||||
signal log_added(message: String)
|
||||
signal dialogue_requested(lines: Array)
|
||||
signal combat_feedback_requested(unit_id: String, text: String, kind: String)
|
||||
signal unit_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i)
|
||||
|
||||
const TEAM_PLAYER := "player"
|
||||
const TEAM_ENEMY := "enemy"
|
||||
@@ -539,8 +540,10 @@ func try_move_selected(cell: Vector2i) -> bool:
|
||||
if not get_unit_at(cell).is_empty():
|
||||
return false
|
||||
|
||||
var from_cell: Vector2i = unit["pos"]
|
||||
unit["pos"] = cell
|
||||
unit["moved"] = true
|
||||
unit_motion_requested.emit(str(unit.get("id", "")), from_cell, cell)
|
||||
_emit_log("%s moved to %s." % [unit["name"], _format_cell(cell)])
|
||||
_run_events("unit_reaches_tile", str(unit.get("team", "")), turn_number, unit)
|
||||
_check_battle_status()
|
||||
@@ -1837,8 +1840,10 @@ func _enemy_take_action(enemy: Dictionary) -> void:
|
||||
best_cell = cell
|
||||
|
||||
if best_cell != enemy["pos"]:
|
||||
var from_cell: Vector2i = enemy["pos"]
|
||||
enemy["pos"] = best_cell
|
||||
enemy["moved"] = true
|
||||
unit_motion_requested.emit(str(enemy.get("id", "")), from_cell, best_cell)
|
||||
_emit_log("%s advances to %s." % [enemy["name"], _format_cell(best_cell)])
|
||||
_run_events("unit_reaches_tile", str(enemy.get("team", "")), turn_number, enemy)
|
||||
if battle_status != STATUS_ACTIVE:
|
||||
|
||||
@@ -33,6 +33,7 @@ const SFX_VOLUME_DB := -4.0
|
||||
const UI_SFX_VOLUME_DB := -8.0
|
||||
const FLOATING_TEXT_LIFETIME := 1.0
|
||||
const FLOATING_TEXT_RISE := 42.0
|
||||
const UNIT_MOVE_ANIMATION_DURATION := 0.18
|
||||
|
||||
var state: BattleState = BattleStateScript.new()
|
||||
var campaign_state: CampaignState = CampaignStateScript.new()
|
||||
@@ -110,6 +111,7 @@ var bgm_player: AudioStreamPlayer
|
||||
var current_bgm_key := ""
|
||||
var last_announced_battle_status := ""
|
||||
var floating_texts: Array[Dictionary] = []
|
||||
var unit_motion_by_unit: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -119,6 +121,7 @@ func _ready() -> void:
|
||||
state.log_added.connect(_on_log_added)
|
||||
state.dialogue_requested.connect(_on_dialogue_requested)
|
||||
state.combat_feedback_requested.connect(_on_combat_feedback_requested)
|
||||
state.unit_motion_requested.connect(_on_unit_motion_requested)
|
||||
campaign_state.load_campaign(CAMPAIGN_PATH)
|
||||
campaign_state.load_or_start(campaign_state.get_start_scenario_id())
|
||||
if campaign_state.has_pending_post_battle_choice():
|
||||
@@ -738,16 +741,30 @@ func _handle_key(event: InputEventKey) -> void:
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if floating_texts.is_empty():
|
||||
return
|
||||
var active_texts: Array[Dictionary] = []
|
||||
for popup in floating_texts:
|
||||
var next_popup := popup.duplicate(true)
|
||||
next_popup["age"] = float(next_popup.get("age", 0.0)) + delta
|
||||
if float(next_popup["age"]) < float(next_popup.get("duration", FLOATING_TEXT_LIFETIME)):
|
||||
active_texts.append(next_popup)
|
||||
floating_texts = active_texts
|
||||
queue_redraw()
|
||||
var needs_redraw := false
|
||||
if not floating_texts.is_empty():
|
||||
var active_texts: Array[Dictionary] = []
|
||||
for popup in floating_texts:
|
||||
var next_popup := popup.duplicate(true)
|
||||
next_popup["age"] = float(next_popup.get("age", 0.0)) + delta
|
||||
if float(next_popup["age"]) < float(next_popup.get("duration", FLOATING_TEXT_LIFETIME)):
|
||||
active_texts.append(next_popup)
|
||||
floating_texts = active_texts
|
||||
needs_redraw = true
|
||||
|
||||
if not unit_motion_by_unit.is_empty():
|
||||
var active_motion := {}
|
||||
for unit_id in unit_motion_by_unit.keys():
|
||||
var motion: Dictionary = unit_motion_by_unit[unit_id]
|
||||
var next_motion := motion.duplicate(true)
|
||||
next_motion["age"] = float(next_motion.get("age", 0.0)) + delta
|
||||
if float(next_motion["age"]) < float(next_motion.get("duration", UNIT_MOVE_ANIMATION_DURATION)):
|
||||
active_motion[unit_id] = next_motion
|
||||
unit_motion_by_unit = active_motion
|
||||
needs_redraw = true
|
||||
|
||||
if needs_redraw:
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _can_select(unit: Dictionary) -> bool:
|
||||
@@ -815,7 +832,7 @@ func _draw_units() -> void:
|
||||
if not unit.get("alive", false) or not unit.get("deployed", true):
|
||||
continue
|
||||
|
||||
var rect := _rect_for_cell(unit["pos"])
|
||||
var rect := _visual_rect_for_unit(unit)
|
||||
var center := rect.position + rect.size * 0.5
|
||||
var team_color := PLAYER_COLOR if unit.get("team", "") == BattleState.TEAM_PLAYER else ENEMY_COLOR
|
||||
var body_color := team_color.darkened(0.35) if unit.get("acted", false) else team_color
|
||||
@@ -836,6 +853,26 @@ func _draw_units() -> void:
|
||||
draw_arc(center, 29, 0.0, TAU, 48, Color(1.0, 0.92, 0.25), 3.0)
|
||||
|
||||
|
||||
func _visual_rect_for_unit(unit: Dictionary) -> Rect2:
|
||||
var cell: Vector2i = unit.get("pos", Vector2i(-1, -1))
|
||||
var rect := _rect_for_cell(cell)
|
||||
var unit_id := str(unit.get("id", ""))
|
||||
if unit_id.is_empty() or not unit_motion_by_unit.has(unit_id):
|
||||
return rect
|
||||
|
||||
var motion: Dictionary = unit_motion_by_unit[unit_id]
|
||||
var from_cell: Vector2i = motion.get("from", cell)
|
||||
var to_cell: Vector2i = motion.get("to", cell)
|
||||
var duration := max(0.01, float(motion.get("duration", UNIT_MOVE_ANIMATION_DURATION)))
|
||||
var age := float(motion.get("age", 0.0))
|
||||
var progress := clampf(age / duration, 0.0, 1.0)
|
||||
var smooth_progress := progress * progress * (3.0 - 2.0 * progress)
|
||||
var from_rect := _rect_for_cell(from_cell)
|
||||
var to_rect := _rect_for_cell(to_cell)
|
||||
rect.position = from_rect.position.lerp(to_rect.position, smooth_progress)
|
||||
return rect
|
||||
|
||||
|
||||
func _draw_floating_texts() -> void:
|
||||
if floating_texts.is_empty():
|
||||
return
|
||||
@@ -1262,6 +1299,18 @@ func _on_combat_feedback_requested(unit_id: String, text: String, kind: String)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _on_unit_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i) -> void:
|
||||
if unit_id.is_empty() or from_cell == to_cell:
|
||||
return
|
||||
unit_motion_by_unit[unit_id] = {
|
||||
"from": from_cell,
|
||||
"to": to_cell,
|
||||
"age": 0.0,
|
||||
"duration": UNIT_MOVE_ANIMATION_DURATION
|
||||
}
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _floating_text_origin(cell: Vector2i) -> Vector2:
|
||||
var rect := _rect_for_cell(cell)
|
||||
return rect.position + Vector2(TILE_SIZE * 0.5, 18.0)
|
||||
@@ -1447,6 +1496,7 @@ func _on_restart_pressed() -> void:
|
||||
battle_result_summary.clear()
|
||||
post_battle_dialogue_started = false
|
||||
floating_texts.clear()
|
||||
unit_motion_by_unit.clear()
|
||||
shop_sell_mode = false
|
||||
selected_skill_id = ""
|
||||
selected_item_id = ""
|
||||
@@ -1491,6 +1541,7 @@ func _load_scenario(scenario_id: String) -> void:
|
||||
battle_result_summary.clear()
|
||||
post_battle_dialogue_started = false
|
||||
floating_texts.clear()
|
||||
unit_motion_by_unit.clear()
|
||||
selected_skill_id = ""
|
||||
selected_item_id = ""
|
||||
_hide_tactic_menu()
|
||||
@@ -2370,6 +2421,7 @@ func _show_campaign_complete() -> void:
|
||||
battle_result_summary.clear()
|
||||
post_battle_dialogue_started = true
|
||||
floating_texts.clear()
|
||||
unit_motion_by_unit.clear()
|
||||
move_cells.clear()
|
||||
attack_cells.clear()
|
||||
skill_cells.clear()
|
||||
|
||||
Reference in New Issue
Block a user