diff --git a/Resource/Grid.gd b/Resource/Grid.gd new file mode 100644 index 0000000..2a27621 --- /dev/null +++ b/Resource/Grid.gd @@ -0,0 +1,67 @@ +extends Resource + +class_name Grid + +# The grid size. +@export var size := Vector2(100, 100) +# The of a cell in pixels +@export var cellSize := Vector2(16, 16) + +@export var cameraPosition := Vector2.ONE +func setCameraPosition(pos: Vector2): + cameraPosition = pos +@export var cameraZoom := Vector2(2, 2) +func setCameraZoom(zoom: Vector2): + cameraZoom = zoom +@export var screenCenter := Vector2.ZERO +func setScreenCenter(pos: Vector2): + screenCenter = pos + +# Half of ``cell_size``. +# We will use this to calculate the center of a grid cell in pixels, on the screen. +# That's how we can place units in the center of a cell. +var _halfCellSize: Vector2 = cellSize / 2 + +# Returns the position of a cell's center in pixels. +# We'll place units and have them move through cells using this function. +func calculateMapPosition(gridPosition: Vector2) -> Vector2: + return cameraPosition + (gridPosition * cellSize + _halfCellSize) / cameraZoom + +# Returns the coordinates of the cell on the grid given a position on the map. +# This is the complementary of `calculate_map_position()` above. +# When designing a level, you'll place units visually in the editor. We'll use this function to find +# the grid coordinates they're placed on, and call `calculate_map_position()` to snap them to the +# cell's center. +func calculateGridCoordinates(mapPosition: Vector2) -> Vector2: + return (mapPosition / cellSize).floor() + + +# Returns true if the `cell_coordinates` are within the grid. +# This method and the following one allow us to ensure the cursor or units can never go past the +# map's limit. +func isWithinBounds(cellCoordinates: Vector2) -> bool: + var out := cellCoordinates.x >= 0 and cellCoordinates.x < size.x + return out and cellCoordinates.y >= 0 and cellCoordinates.y < size.y + + +# Makes the `grid_position` fit within the grid's bounds. +# This is a clamp function designed specifically for our grid coordinates. +# The Vector2 class comes with its `Vector2.clamp()` method, but it doesn't work the same way: it +# limits the vector's length instead of clamping each of the vector's components individually. +# That's why we need to code a new method. +func clamp(gridPosition: Vector2) -> Vector2: + var out := gridPosition + out.x = clamp(out.x, 0, size.x - 1.0) + out.y = clamp(out.y, 0, size.y - 1.0) + return out + + +# Given Vector2 coordinates, calculates and returns the corresponding integer index. You can use +# this function to convert 2D coordinates to a 1D array's indices. +# +# There are two cases where you need to convert coordinates like so: +# 1. We'll need it for the AStar algorithm, which requires a unique index for each point on the +# graph it uses to find a path. +# 2. You can use it for performance. More on that below. +func asIndex(cell: Vector2) -> int: + return int(cell.x + size.x * cell.y) diff --git a/Resource/Grid.gd.uid b/Resource/Grid.gd.uid new file mode 100644 index 0000000..3d0ed28 --- /dev/null +++ b/Resource/Grid.gd.uid @@ -0,0 +1 @@ +uid://blwwie08bb4s diff --git a/Resource/Grid.tres b/Resource/Grid.tres new file mode 100644 index 0000000..943266f --- /dev/null +++ b/Resource/Grid.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="Grid" load_steps=2 format=3 uid="uid://bpf7mj7w5kftq"] + +[ext_resource type="Script" uid="uid://blwwie08bb4s" path="res://Resource/Grid.gd" id="1_ubiq0"] + +[resource] +script = ExtResource("1_ubiq0") +size = Vector2(60, 60) +cellSize = Vector2(16, 16) +metadata/_custom_type_script = "uid://blwwie08bb4s" diff --git a/Scenes/Camera/camera_controller.gd b/Scenes/Camera/camera_controller.gd index ea45e16..efecbd6 100644 --- a/Scenes/Camera/camera_controller.gd +++ b/Scenes/Camera/camera_controller.gd @@ -1,5 +1,10 @@ extends Node2D +# To change the zoom +@export var grid: Resource + +@export var CameraSpeedMult = 2 + # Called when the node enters the scene tree for the first time. func _ready() -> void: pass # Replace with function body. @@ -8,15 +13,24 @@ func _ready() -> void: # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: if Input.is_action_pressed("MoveUp"): - global_position += Vector2.UP * 2 + _moveCamera(Vector2.UP) if Input.is_action_pressed("MoveDown"): - global_position += Vector2.DOWN * 2 + _moveCamera(Vector2.DOWN) if Input.is_action_pressed("MoveLeft"): - global_position += Vector2.LEFT * 2 + _moveCamera(Vector2.LEFT) if Input.is_action_pressed("MoveRight"): - global_position += Vector2.RIGHT * 2 + _moveCamera(Vector2.RIGHT) if Input.is_action_just_released("ZoomIn"): - $SmartCamera2D.zoom += Vector2.ONE + _zoomCamera(Vector2.ONE) if Input.is_action_just_released("ZoomOut"): - $SmartCamera2D.zoom -= Vector2.ONE \ No newline at end of file + _zoomCamera(-Vector2.ONE) + +func _moveCamera(direction: Vector2): + position += direction * CameraSpeedMult + grid.setCameraPosition($SmartCamera2D.position) + grid.setScreenCenter($SmartCamera2D.get_screen_center_position()) + +func _zoomCamera(direction: Vector2): + $SmartCamera2D.zoom += direction + grid.setCameraZoom($SmartCamera2D.zoom) diff --git a/Scenes/Camera/camera_controller.tscn b/Scenes/Camera/camera_controller.tscn index 3a09f4d..d2d6b3f 100644 --- a/Scenes/Camera/camera_controller.tscn +++ b/Scenes/Camera/camera_controller.tscn @@ -9,6 +9,11 @@ script = ExtResource("1_ig7ij") metadata/_edit_group_ = true [node name="SmartCamera2D" type="Camera2D" parent="."] +limit_left = 0 +limit_top = 0 +limit_right = 800 +limit_bottom = 480 +limit_smoothed = true position_smoothing_enabled = true script = ExtResource("2_du7i2") target = NodePath("..") diff --git a/Scenes/Main/cursor.gd b/Scenes/Main/cursor.gd new file mode 100644 index 0000000..32685a3 --- /dev/null +++ b/Scenes/Main/cursor.gd @@ -0,0 +1,75 @@ +## Player-controlled cursor. Allows them to navigate the game grid, select units, and move them. +## Supports both keyboard and mouse (or touch) input. +@tool +class_name Cursor +extends Node2D + +## Emitted when clicking on the currently hovered cell or when pressing "ui_accept". +signal accept_pressed(cell) +## Emitted when the cursor moved to a new cell. +signal moved(new_cell) + +## Grid resource, giving the node access to the grid size, and more. +@export var grid: Resource +## Time before the cursor can move again in seconds. +@export var ui_cooldown := 0.1 + +## Coordinates of the current cell the cursor is hovering. +var cell := Vector2.ZERO: + set(value): + print("Setting cell to: ", value) + # We first clamp the cell coordinates and ensure that we aren't + # trying to move outside the grid boundaries + var new_cell: Vector2 = grid.clamp(value) + print("New cell: ", new_cell) + if new_cell.is_equal_approx(cell): + return + + cell = new_cell + print("Cell is ", cell) + # If we move to a new cell, we update the cursor's position, emit + # a signal, and start the cooldown timer that will limit the rate + # at which the cursor moves when we keep the direction key held + # down + position = grid.calculateMapPosition(cell) + print("Position is ", position) + emit_signal("moved", cell) + _timer.start() + +@onready var _timer: Timer = $Timer + + +func _ready() -> void: + _timer.wait_time = ui_cooldown + position = grid.calculateMapPosition(cell) + + +func _unhandled_input(event: InputEvent) -> void: + # Navigating cells with the mouse. + if event is InputEventMouseMotion: + cell = grid.calculateGridCoordinates(event.position) + # Trying to select something in a cell. + elif event.is_action_pressed("Select") or event.is_action_pressed("ui_accept"): + emit_signal("accept_pressed", cell) + get_viewport().set_input_as_handled() + + var should_move := event.is_pressed() + if event.is_echo(): + should_move = should_move and _timer.is_stopped() + + if not should_move: + return + + # Moves the cursor by one grid cell. + if event.is_action("ui_right"): + cell += Vector2.RIGHT + elif event.is_action("ui_up"): + cell += Vector2.UP + elif event.is_action("ui_left"): + cell += Vector2.LEFT + elif event.is_action("ui_down"): + cell += Vector2.DOWN + + +func _draw() -> void: + draw_rect(Rect2(-grid.cellSize / 2, grid.cellSize), Color.ALICE_BLUE, false, 2.0) diff --git a/Scenes/Main/cursor.gd.uid b/Scenes/Main/cursor.gd.uid new file mode 100644 index 0000000..71f2b20 --- /dev/null +++ b/Scenes/Main/cursor.gd.uid @@ -0,0 +1 @@ +uid://cidjtc27oj1gn diff --git a/Scenes/Main/game_board.gd b/Scenes/Main/game_board.gd new file mode 100644 index 0000000..dff2698 --- /dev/null +++ b/Scenes/Main/game_board.gd @@ -0,0 +1,142 @@ +## Represents and manages the game board. Stores references to entities that are in each cell and +## tells whether cells are occupied or not. +## Units can only move around the grid one at a time. +class_name GameBoard +extends Node2D + +const DIRECTIONS = [Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN] + +## Resource of type Grid. +@export var grid: Resource + +## Mapping of coordinates of a cell to a reference to the unit it contains. +var _units := {} +var _active_unit: Unit +var _walkable_cells := [] + +#@onready var _unit_overlay: UnitOverlay = $UnitOverlay +#@onready var _unit_path: UnitPath = $UnitPath + + +func _ready() -> void: + _reinitialize() + + +func _unhandled_input(event: InputEvent) -> void: + if _active_unit and event.is_action_pressed("ui_cancel"): + _deselect_active_unit() + _clear_active_unit() + + +func _get_configuration_warning() -> String: + var warning := "" + if not grid: + warning = "You need a Grid resource for this node to work." + return warning + + +## Returns `true` if the cell is occupied by a unit. +func is_occupied(cell: Vector2) -> bool: + return _units.has(cell) + + +## Returns an array of cells a given unit can walk using the flood fill algorithm. +func get_walkable_cells(unit: Unit) -> Array: + return _flood_fill(unit.cell, unit.move_range) + + +## Clears, and refills the `_units` dictionary with game objects that are on the board. +func _reinitialize() -> void: + _units.clear() + + for child in get_children(): + var unit := child as Unit + if not unit: + continue + _units[unit.cell] = unit + + +## Returns an array with all the coordinates of walkable cells based on the `max_distance`. +func _flood_fill(cell: Vector2, max_distance: int) -> Array: + var array := [] + var stack := [cell] + while not stack.size() == 0: + var current = stack.pop_back() + if not grid.is_within_bounds(current): + continue + if current in array: + continue + + var difference: Vector2 = (current - cell).abs() + var distance := int(difference.x + difference.y) + if distance > max_distance: + continue + + array.append(current) + for direction in DIRECTIONS: + var coordinates: Vector2 = current + direction + if is_occupied(coordinates): + continue + if coordinates in array: + continue + # Minor optimization: If this neighbor is already queued + # to be checked, we don't need to queue it again + if coordinates in stack: + continue + + stack.append(coordinates) + return array + + +## Updates the _units dictionary with the target position for the unit and asks the _active_unit to walk to it. +func _move_active_unit(new_cell: Vector2) -> void: + if is_occupied(new_cell) or not new_cell in _walkable_cells: + return + # warning-ignore:return_value_discarded + _units.erase(_active_unit.cell) + _units[new_cell] = _active_unit + _deselect_active_unit() +# _active_unit.walk_along(_unit_path.current_path) + await _active_unit.walk_finished + _clear_active_unit() + + +## Selects the unit in the `cell` if there's one there. +## Sets it as the `_active_unit` and draws its walkable cells and interactive move path. +func _select_unit(cell: Vector2) -> void: + if not _units.has(cell): + return + + _active_unit = _units[cell] + _active_unit.is_selected = true + _walkable_cells = get_walkable_cells(_active_unit) +# _unit_overlay.draw(_walkable_cells) +# _unit_path.initialize(_walkable_cells) + + +## Deselects the active unit, clearing the cells overlay and interactive path drawing. +func _deselect_active_unit() -> void: + _active_unit.is_selected = false +# _unit_overlay.clear() +# _unit_path.stop() + + +## Clears the reference to the _active_unit and the corresponding walkable cells. +func _clear_active_unit() -> void: + _active_unit = null + _walkable_cells.clear() + + +## Selects or moves a unit based on where the cursor is. +func _on_Cursor_accept_pressed(cell: Vector2) -> void: + if not _active_unit: + _select_unit(cell) + elif _active_unit.is_selected: + _move_active_unit(cell) + + +## Updates the interactive path's drawing if there's an active and selected unit. +func _on_Cursor_moved(new_cell: Vector2) -> void: +# if _active_unit and _active_unit.is_selected: +# _unit_path.draw(_active_unit.cell, new_cell) + pass diff --git a/Scenes/Main/game_board.gd.uid b/Scenes/Main/game_board.gd.uid new file mode 100644 index 0000000..1ab09c7 --- /dev/null +++ b/Scenes/Main/game_board.gd.uid @@ -0,0 +1 @@ +uid://14cwbxcvt5dx diff --git a/Scenes/Main/main.gd b/Scenes/Main/main.gd index 9c3e456..d4ac94f 100644 --- a/Scenes/Main/main.gd +++ b/Scenes/Main/main.gd @@ -15,12 +15,14 @@ var _MovingMarker: Node2D = _MovingMarkerScene.instantiate() # Called when the node enters the scene tree for the first time. func _ready() -> void: - add_child(_MovingMarker) - _MovingMarker.hide() - - _createUnit(Vector2i(10,10)) - for unit in _Units: - add_child(unit) +# add_child(_MovingMarker) +# _MovingMarker.hide() +# +# _createUnit(Vector2i(10,10)) +# for unit in _Units: +# add_child(unit) + + $CameraController.position = get_viewport().get_camera_2d().get_screen_center_position() pass diff --git a/Scenes/Main/main.tscn b/Scenes/Main/main.tscn index bf73e5b..b866930 100644 --- a/Scenes/Main/main.tscn +++ b/Scenes/Main/main.tscn @@ -1,8 +1,13 @@ -[gd_scene load_steps=4 format=3 uid="uid://d05j5yuhlsxp0"] +[gd_scene load_steps=9 format=3 uid="uid://d05j5yuhlsxp0"] [ext_resource type="PackedScene" uid="uid://cywuuce71rmgb" path="res://Scenes/Map/map.tscn" id="1_1r6ip"] [ext_resource type="Script" uid="uid://btdvxp8ckmeb3" path="res://Scenes/Main/main.gd" id="1_qw60k"] [ext_resource type="PackedScene" uid="uid://bfvijh611aggp" path="res://Scenes/Camera/camera_controller.tscn" id="3_qw60k"] +[ext_resource type="Script" uid="uid://14cwbxcvt5dx" path="res://Scenes/Main/game_board.gd" id="4_5yls4"] +[ext_resource type="Resource" uid="uid://bpf7mj7w5kftq" path="res://Resource/Grid.tres" id="5_p6jpk"] +[ext_resource type="PackedScene" uid="uid://b1d6lktijxy3s" path="res://Scenes/Unit/move/unit.tscn" id="6_2a143"] +[ext_resource type="Script" uid="uid://cidjtc27oj1gn" path="res://Scenes/Main/cursor.gd" id="7_y3v7k"] +[ext_resource type="Texture2D" uid="uid://bckknh8k5fh1s" path="res://Graphics/TileMaps/kenney_rpgUrbanKit/Tiles/tile_0448.png" id="8_hryqi"] [node name="Main" type="Node"] script = ExtResource("1_qw60k") @@ -10,3 +15,24 @@ script = ExtResource("1_qw60k") [node name="Map" parent="." instance=ExtResource("1_1r6ip")] [node name="CameraController" parent="." instance=ExtResource("3_qw60k")] +grid = ExtResource("5_p6jpk") + +[node name="GameBoard" type="Node2D" parent="."] +script = ExtResource("4_5yls4") +grid = ExtResource("5_p6jpk") + +[node name="Unit" parent="GameBoard" instance=ExtResource("6_2a143")] +grid = ExtResource("5_p6jpk") + +[node name="Cursor" type="Node2D" parent="GameBoard"] +script = ExtResource("7_y3v7k") +grid = ExtResource("5_p6jpk") + +[node name="Sprite2D" type="Sprite2D" parent="GameBoard/Cursor"] +position = Vector2(10, -10) +texture = ExtResource("8_hryqi") + +[node name="Timer" type="Timer" parent="GameBoard/Cursor"] +wait_time = 0.1 + +[editable path="CameraController"] diff --git a/Scenes/Unit/move/unit.tscn b/Scenes/Unit/move/unit.tscn new file mode 100644 index 0000000..378ecfd --- /dev/null +++ b/Scenes/Unit/move/unit.tscn @@ -0,0 +1,87 @@ +[gd_scene load_steps=11 format=3 uid="uid://b1d6lktijxy3s"] + +[ext_resource type="Script" uid="uid://c8ocnhejcdc77" path="res://unit.gd" id="1_astap"] +[ext_resource type="Texture2D" uid="uid://cgvyfsuri6vmx" path="res://Graphics/TileMaps/kenney_rpgUrbanKit/Tilemap/tilemap.png" id="2_fhoaw"] +[ext_resource type="Texture2D" uid="uid://dlaevn54qcvej" path="res://Graphics/TileMaps/kenney_rpgUrbanKit/Tiles/tile_0267.png" id="3_fhoaw"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_4o1a4"] +atlas = ExtResource("2_fhoaw") +region = Rect2(408, 51, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_hn0wa"] +atlas = ExtResource("2_fhoaw") +region = Rect2(408, 68, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jt11o"] +atlas = ExtResource("2_fhoaw") +region = Rect2(408, 85, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_e33ge"] +atlas = ExtResource("2_fhoaw") +region = Rect2(408, 68, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_px3ay"] +atlas = ExtResource("2_fhoaw") +region = Rect2(408, 85, 16, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6ceyn"] +atlas = ExtResource("2_fhoaw") +region = Rect2(34, 255, 16, 16) + +[sub_resource type="SpriteFrames" id="SpriteFrames_lgeeq"] +animations = [{ +"frames": [], +"loop": true, +"name": &"default", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_4o1a4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_hn0wa") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_jt11o") +}], +"loop": true, +"name": &"down", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_e33ge") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_px3ay") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_6ceyn") +}, { +"duration": 1.0, +"texture": null +}], +"loop": true, +"name": &"selected", +"speed": 5.0 +}] + +[node name="Unit" type="Path2D"] +script = ExtResource("1_astap") + +[node name="PathFollow2D" type="PathFollow2D" parent="."] +rotates = false + +[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="PathFollow2D"] +visible = false +sprite_frames = SubResource("SpriteFrames_lgeeq") +animation = &"down" + +[node name="Sprite2D" type="Sprite2D" parent="PathFollow2D"] +texture = ExtResource("3_fhoaw") diff --git a/Scenes/Unit/unit.gd b/Scenes/Unit/unit.gd index 7f756ff..50d8455 100644 --- a/Scenes/Unit/unit.gd +++ b/Scenes/Unit/unit.gd @@ -2,36 +2,60 @@ extends Node2D class_name Unit +signal walk_finished + +@export var grid: Resource + var _spawnPosition: Vector2 -### Marker -# For now the marker will be spawned and deleted by the unit. -# Later it will be handled by the main scene. -# Load marker scene. -# var marker_scene: PackedScene = preload("res://Scenes/Unit/marker.tscn") -# var marker -## WE NOW USE A SIMPLE SPRITE2D FOR THE MARKER - - @onready var _readyToSelectMarker: Sprite2D = $ReadyToSelectMarker @onready var _selectedMarker: Sprite2D = $SelectedMarker -#@onready var _movingMarker: Sprite2D = $MovingMarker var _readyToSelect: bool = false -var _selected: bool = false -#var _moving: bool = false + +var isSelected := false: + set(value): + isSelected = value + if isSelected: _selectUnit() + else: _deselectUnit() var TargetPosition: Vector2 +var _isMoving := false: + set(value): + _isMoving = value + set_process(_isMoving) +## Unit Data +### Distance in cells +@export var moveRange := 6 +### Speed along the path +@export var moveSpeed := 600.0 + +# Coordinates of the current cell the cursor moved to +var cell := Vector2.ZERO: + set(value): + # When changing the cell's value, we don't want to allow coordinates outside the grid. + cell = grid.clamp(value) +@onready var _path: Path2D = $Path2D +@onready var _pathFollow: PathFollow2D = $Path2D/PathFollow2D # Called when the node enters the scene tree for the first time. func _ready() -> void: -# marker = marker_scene.instantiate() -# marker.hide() + # Spawning global_position = _spawnPosition - _readyToSelectMarker.hide() _selectedMarker.hide() -# _movingMarker.hide() + + # Pathing and Grid + set_process(false) + _pathFollow.rotates = false + + cell = grid.calculateGridCoordinates(position) + position = grid.calculateMapPosition(cell) + + # We create the curve resource here because creating it in the editor prevents + # us from moving the unit. + if not Engine.is_editor_hint(): + _path.curve = Curve2D.new() # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: @@ -42,31 +66,13 @@ func _input(event: InputEvent): print_debug("Action is Select") # We combine it with the fact that it is already marked (@see _markUnit) - if _readyToSelect: _selectUnit() - else: _deselectUnit() - - -# if event.is_action_pressed("SetMarker"): -# print("Action is SetMarker") -# if _selected: -# print("Setting marker to ", event.position) -# #marker.global_position = event.position -## marker.show() -## _movingMarker.position = get_global_mouse_position() -## _movingMarker.show() - -func _getUnitPosition(): - return $AnimatedSprite2D.global_position - -func _setSelected(selected: bool): - _selected = selected + if _readyToSelect: isSelected = true + else: isSelected = false func _selectUnit(): - _setSelected(true) _selectedMarker.show() func _deselectUnit(): - _setSelected(false) _selectedMarker.hide() func _gets_selected(viewport: Node, event: InputEvent, shape_index: int): @@ -82,4 +88,4 @@ func _unMarkUnit(): _readyToSelectMarker.hide() func moveToTarget(): - \ No newline at end of file + pass diff --git a/Scenes/Unit/unit.tscn b/Scenes/Unit/unit.tscn index a8b5c28..9c5ba51 100644 --- a/Scenes/Unit/unit.tscn +++ b/Scenes/Unit/unit.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=92 format=3 uid="uid://dy7rltpxyqyw7"] +[gd_scene load_steps=93 format=3 uid="uid://dy7rltpxyqyw7"] [ext_resource type="Script" uid="uid://dpu6c0bpm0dvl" path="res://Scenes/Unit/unit.gd" id="1_15sed"] [ext_resource type="Texture2D" uid="uid://cgvyfsuri6vmx" path="res://Graphics/TileMaps/kenney_rpgUrbanKit/Tilemap/tilemap.png" id="1_hgpyh"] +[ext_resource type="Resource" uid="uid://bpf7mj7w5kftq" path="res://Resource/Grid.tres" id="2_jbdwb"] [ext_resource type="Texture2D" uid="uid://bprproedmlhtr" path="res://Graphics/TileMaps/kenney_rpgUrbanKit/Tiles/tile_0168.png" id="3_ladk0"] [ext_resource type="Texture2D" uid="uid://b7ra2w7rdeqij" path="res://Graphics/TileMaps/kenney_rpgUrbanKit/Tiles/tile_0169.png" id="4_iuf4a"] [ext_resource type="Texture2D" uid="uid://cxtkb8rqq0j6r" path="res://Graphics/TileMaps/kenney_rpgUrbanKit/Tiles/tile_0407.png" id="5_ulevp"] @@ -628,6 +629,7 @@ size = Vector2(16, 16) [node name="Unit" type="Node2D"] texture_filter = 1 script = ExtResource("1_15sed") +grid = ExtResource("2_jbdwb") [node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] sprite_frames = SubResource("SpriteFrames_7f253") @@ -653,5 +655,9 @@ texture = ExtResource("5_ulevp") [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] shape = SubResource("RectangleShape2D_15sed") +[node name="Path2D" type="Path2D" parent="."] + +[node name="PathFollow2D" type="PathFollow2D" parent="Path2D"] + [connection signal="mouse_entered" from="Area2D" to="." method="_markUnit"] [connection signal="mouse_exited" from="Area2D" to="." method="_unMarkUnit"] diff --git a/project.godot b/project.godot index 68443bb..8b33805 100644 --- a/project.godot +++ b/project.godot @@ -83,5 +83,6 @@ ZoomOut={ [rendering] +textures/canvas_textures/default_texture_filter=0 renderer/rendering_method="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility" diff --git a/unit.gd b/unit.gd new file mode 100644 index 0000000..49ff227 --- /dev/null +++ b/unit.gd @@ -0,0 +1,93 @@ +## Represents a unit on the game board. +## The board manages its position inside the game grid. +## The unit itself holds stats and a visual representation that moves smoothly in the game world. +@tool +class_name Unit_Move +extends Path2D + +## Emitted when the unit reached the end of a path along which it was walking. +signal walk_finished + +## Shared resource of type Grid, used to calculate map coordinates. +@export var grid: Resource +## Distance to which the unit can walk in cells. +@export var move_range := 6 +## The unit's move speed when it's moving along a path. +@export var move_speed := 600.0 +## Texture representing the unit. +@export var skin: Texture: + set(value): + skin = value + if not _sprite: + # This will resume execution after this node's _ready() + await ready + _sprite.texture = value +## Offset to apply to the `skin` sprite in pixels. +@export var skin_offset := Vector2.ZERO: + set(value): + skin_offset = value + if not _sprite: + await ready + _sprite.position = value + +## Coordinates of the current cell the cursor moved to. +var cell := Vector2.ZERO: + 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: + set(value): + is_selected = value + if is_selected: + _sprite.play("selected") + else: + _sprite.play("idle") + +var _is_walking := false: + set(value): + _is_walking = value + set_process(_is_walking) + +@onready var _sprite: AnimatedSprite2D = $PathFollow2D/AnimatedSprite2D +#@onready var _anim_player: AnimationPlayer = $AnimationPlayer +@onready var _path_follow: PathFollow2D = $PathFollow2D + + +func _ready() -> void: + set_process(false) + _path_follow.rotates = false + + cell = grid.calculateGridCoordinates(position) + position = grid.calculateMapPosition(cell) + + # We create the curve resource here because creating it in the editor prevents us from + # moving the unit. + if not Engine.is_editor_hint(): + curve = Curve2D.new() + + +func _process(delta: float) -> void: + _path_follow.progress += move_speed * delta + + if _path_follow.progress_ratio >= 1.0: + _is_walking = false + # Setting this value to 0.0 causes a Zero Length Interval error + _path_follow.progress = 0.00001 + position = grid.calculateMapPosition(cell) + curve.clear_points() + 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 + + curve.add_point(Vector2.ZERO) + for point in path: + curve.add_point(grid.calculateMapPosition(point) - position) + cell = path[-1] + _is_walking = true diff --git a/unit.gd.uid b/unit.gd.uid new file mode 100644 index 0000000..6dc2a0d --- /dev/null +++ b/unit.gd.uid @@ -0,0 +1 @@ +uid://c8ocnhejcdc77