Created
January 30, 2023 13:50
-
-
Save thoraxe/df4643c78e4cb6ca8fc4e72406db974d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Represents a unit on the game board. | |
# The board manages the Unit's position inside the game grid. | |
# The unit itself is only a visual representation that moves smoothly in the game world. | |
# We use the tool mode so the `skin` and `skin_offset` below update in the editor. | |
@tool | |
class_name Unit | |
extends Path2D | |
# Preload the `Grid.tres` resource you created in the previous part. | |
@export var grid: Resource = preload("res://Grid.tres") | |
# Distance to which the unit can walk in cells. | |
# We'll use this to limit the cells the unit can move to. | |
@export var move_range := 6 | |
# Texture representing the unit. | |
# With the `tool` mode, assigning a new texture to this property in the inspector will update the | |
# unit's sprite instantly. | |
@export var skin: Texture: | |
get: | |
return skin | |
set(value): | |
skin = value | |
# Setter functions are called during the node's `_init()` callback, before they entered the | |
# tree. At that point in time, the `_sprite` variable is `null`. If so, we have to wait to | |
# update the sprite's properties. | |
if not _sprite: | |
# The yield keyword allows us to wait until the unit node's `_ready()` callback ended. | |
await(ready) | |
_sprite.texture = value | |
# Our unit's skin is just a sprite in this demo and depending on its size, we need to offset it so | |
# the sprite aligns with the shadow. | |
@export var skin_offset := Vector2.ZERO: | |
get: | |
return skin_offset | |
set(value): | |
skin_offset = value | |
if not _sprite: | |
await(ready) | |
_sprite.position = value | |
# The unit's move speed in pixels, when it's moving along a path. | |
@export var move_speed := 600.0 | |
# Coordinates of the grid's cell the unit is on. | |
var cell := Vector2.ZERO: | |
get: | |
return cell | |
set(value): | |
# When changing the `cell`'s value, we don't want to allow coordinates outside the grid, so we clamp | |
# them. | |
cell = grid.clamp(value) | |
# Toggles the "selected" animation on the unit. | |
var is_selected := false: | |
# The `is_selected` property toggles playback of the "selected" animation. | |
get: | |
return is_selected | |
set(value): | |
is_selected = value | |
if is_selected: | |
_anim_player.play("selected") | |
else: | |
_anim_player.play("idle") | |
# Through its setter function, the `_is_walking` property toggles processing for this unit. | |
# See `_set_is_walking()` at the bottom of this code snippet. | |
var _is_walking := false: | |
get: | |
return _is_walking | |
set(value): | |
_is_walking = value | |
set_process(_is_walking) | |
@onready var _sprite: Sprite2D = $PathFollow2D/Sprite | |
@onready var _anim_player: AnimationPlayer = $AnimationPlayer | |
@onready var _path_follow: PathFollow2D = $PathFollow2D | |
# Emitted when the unit reached the end of a path along which it was walking. | |
# We'll use this to notify the game board that a unit reached its destination and we can let the | |
# player select another unit. | |
signal walk_finished | |
func _ready() -> void: | |
# We'll use the `_process()` callback to move the unit along a path. Unless it has a path to | |
# walk, we don't want it to update every frame. See `walk_along()` below. | |
set_process(false) | |
# The following lines initialize the `cell` property and snap the unit to the cell's center on the map. | |
self.cell = grid.calculate_grid_coordinates(position) | |
position = grid.calculate_map_position(cell) | |
if not Engine.is_editor_hint(): | |
# We create the curve resource here because creating it in the editor prevents us from | |
# moving the unit. | |
curve = Curve2D.new() | |
var points := [ | |
Vector2(2, 2), | |
Vector2(2, 5), | |
Vector2(8, 5), | |
Vector2(8, 7), | |
] | |
walk_along(PackedVector2Array(points)) | |
# When active, moves the unit along its `curve` with the help of the PathFollow2D node. | |
func _process(delta: float) -> void: | |
# Every frame, the `PathFollow2D.offset` property moves the sprites along the `curve`. | |
# The great thing about this is it moves an exact number of pixels taking turns into account. | |
_path_follow.h_offset += move_speed * delta | |
# When we increase the `offset` above, the `unit_offset` also updates. It represents how far you | |
# are along the `curve` in percent, where a value of `1.0` means you reached the end. | |
# When that is the case, the unit is done moving. | |
if _path_follow.progress_ratio >= 1.0: | |
# Setting `_is_walking` to `false` also turns off processing. | |
self._is_walking = false | |
# Below, we reset the offset to `0.0`, which snaps the sprites back to the Unit node's | |
# position, we position the node to the center of the target grid cell, and we clear the | |
# curve. | |
# In the process loop, we only moved the sprite, and not the unit itself. The following | |
# lines move the unit in a way that's transparent to the player. | |
_path_follow.h_offset = 0.0 | |
position = grid.calculate_map_position(cell) | |
curve.clear_points() | |
# Finally, we emit a signal. We'll use this one with the game board. | |
emit_signal("walk_finished") | |
# Starts walking along the `path`. | |
# `path` is an array of grid coordinates that the function converts to map coordinates. | |
func walk_along(path: PackedVector2Array) -> void: | |
if path.is_empty(): | |
return | |
# This code converts the `path` to points on the `curve`. That property comes from the `Path2D` | |
# class the Unit extends. | |
curve.add_point(Vector2.ZERO) | |
for point in path: | |
curve.add_point(grid.calculate_map_position(point) - position) | |
# We instantly change the unit's cell to the target position. You could also do that when it | |
# reaches the end of the path, using `grid.calculate_grid_coordinates()`, instead. | |
# I did it here because we have the coordinates provided by the `path` argument. | |
# The cell itself represents the grid coordinates the unit will stand on. | |
cell = path[-1] | |
# The `_is_walking` property triggers the move animation and turns on `_process()`. See | |
# `_set_is_walking()` below. | |
self._is_walking = true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment