diff --git a/main.tscn b/main.tscn index 733336b..d56db54 100644 --- a/main.tscn +++ b/main.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=21 format=3 uid="uid://cdaf4bh0jaa45"] +[gd_scene load_steps=18 format=3 uid="uid://cdaf4bh0jaa45"] [ext_resource type="Script" path="res://scripts/game_logic.gd" id="1_uj8ti"] -[ext_resource type="Resource" uid="uid://cp0dudi0f5r62" path="res://scenes/templates/templates.tres" id="2_1lypt"] [ext_resource type="Script" path="res://scripts/player.gd" id="2_plgh6"] [ext_resource type="Texture2D" uid="uid://bydmcc5d53ldx" path="res://icons/paw_print.svg" id="3_mxakn"] [ext_resource type="Texture2D" uid="uid://1xh0qil16bkh" path="res://icons/character.svg" id="3_qxmev"] @@ -11,8 +10,6 @@ [ext_resource type="PackedScene" uid="uid://dtlatmtuggp6x" path="res://scenes/background_nebula/background_nebula.tscn" id="7_sv4lv"] [ext_resource type="Script" path="res://scripts/player_ai.gd" id="7_v73fw"] [ext_resource type="Script" path="res://scripts/planet.gd" id="8_ve3b1"] -[ext_resource type="PackedScene" uid="uid://cpffyaoh8x5bp" path="res://scenes/ships_fleet/ships_fleet.tscn" id="9_be18b"] -[ext_resource type="PackedScene" uid="uid://ckfk1xgxfk1c3" path="res://scenes/trail/trail.tscn" id="10_b5ql7"] [sub_resource type="Resource" id="Resource_mxp7y"] script = ExtResource("2_plgh6") @@ -51,10 +48,11 @@ type = 0 [node name="Main" type="Node"] -[node name="GameLogic" type="Node" parent="." node_paths=PackedStringArray("planets_container", "planets_ui")] +[node name="GameLogic" type="Node" parent="." node_paths=PackedStringArray("planets_container", "trails_container", "fleets_container", "planets_ui")] script = ExtResource("1_uj8ti") -templates = ExtResource("2_1lypt") planets_container = NodePath("../Planets") +trails_container = NodePath("../Trails") +fleets_container = NodePath("../Fleets") planets_ui = NodePath("../PlanetsUI") players = Array[ExtResource("2_plgh6")]([SubResource("Resource_mxp7y"), SubResource("Resource_1dtpf"), SubResource("Resource_7wtc7")]) planets = Array[ExtResource("8_ve3b1")]([SubResource("Resource_50eg2"), SubResource("Resource_2525o"), SubResource("Resource_irtww"), SubResource("Resource_wdycy")]) @@ -63,11 +61,11 @@ planets = Array[ExtResource("8_ve3b1")]([SubResource("Resource_50eg2"), SubResou [node name="Planets" type="Node2D" parent="."] -[node name="ShipsFleet" parent="." instance=ExtResource("9_be18b")] -position = Vector2(246, 394) -count = 29 +[node name="Trails" type="Node2D" parent="."] -[node name="PlanetsUI" type="Control" parent="." node_paths=PackedStringArray("trail")] +[node name="Fleets" type="Node2D" parent="."] + +[node name="PlanetsUI" type="Control" parent="." node_paths=PackedStringArray("trails_container")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -76,7 +74,4 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 script = ExtResource("7_khbdl") -trail = NodePath("Trail") - -[node name="Trail" parent="PlanetsUI" instance=ExtResource("10_b5ql7")] -visible = false +trails_container = NodePath("../Trails") diff --git a/scenes/procedural_planet/procedural_planet.tscn b/scenes/procedural_planet/procedural_planet.tscn index 2ab3eb1..4c47671 100644 --- a/scenes/procedural_planet/procedural_planet.tscn +++ b/scenes/procedural_planet/procedural_planet.tscn @@ -15,6 +15,7 @@ seamless = true noise = SubResource("FastNoiseLite_xihql") [sub_resource type="ShaderMaterial" id="ShaderMaterial_m4mhh"] +resource_local_to_scene = true shader = ExtResource("1_tl7a6") shader_parameter/size = 100.0 shader_parameter/rotationSpeed = 0.05 diff --git a/scenes/ships_fleet/ships_fleet.gd b/scenes/ships_fleet/ships_fleet.gd index 60b9c32..5425887 100644 --- a/scenes/ships_fleet/ships_fleet.gd +++ b/scenes/ships_fleet/ships_fleet.gd @@ -1,37 +1,70 @@ extends Node2D -class_name ShipFleet +class_name ShipsFleet -@export var departed_at: int +@export var departed_at: float # Time.get_ticks_msec() -@export var arrives_at: int +@export var arrives_at: float # Time.get_ticks_msec() @export var from: Vector2 @export var to: Vector2 +@export var player: Player + @export var count: int : set(value): count = value - _update_instances_count() + _update__instances_count() + +@export var trail: Trail + +var _game: GameState + +func register_game(game: GameState) -> void: + _game = game + _game.game_ticked.connect(_update_position) + _update_position(game.current_tick) + +func _exit_tree() -> void: + if _game != null: + _game.game_ticked.disconnect(_update_position) + _game = null #region Graphics -var instances: Array[Node2D] = [] +var _instances: Array[Node2D] = [] @export var ship_template: PackedScene -func _update_instances_count() -> void : - var diff = count - instances.size() +func _update__instances_count() -> void: + var diff = count - _instances.size() for i in range(diff): - var j = float(1 + instances.size()) + var j = float(1 + _instances.size()) var ship := ship_template.instantiate() add_child(ship) ship.position = Vector2.from_angle(j) * j * 2. - instances.push_back(ship) + _instances.push_back(ship) for i in range(-diff): - var ship: Node2D = instances.pop_back() + var ship: Node2D = _instances.pop_back() remove_child(ship) +var _arrived := false + +func _update_position(tick: int) -> void: + if not _arrived: + var clamped_tick := clampi(tick, departed_at, arrives_at) + var progress := float(clamped_tick - departed_at) / float(arrives_at - departed_at) + position = lerp(from, to, smoothstep(0., 1., progress)) + if tick >= arrives_at: + _arrived = true + trail.show = false + + for ship in _instances: + ship.emitting = false + + await get_tree().create_timer(5.).timeout + queue_free() + #endregion diff --git a/scenes/templates/scene_templates.gd b/scenes/templates/scene_templates.gd new file mode 100644 index 0000000..9089808 --- /dev/null +++ b/scenes/templates/scene_templates.gd @@ -0,0 +1,10 @@ +class_name SceneTemplates +extends Resource + +static var scenes = preload("res://scenes/templates/scene_templates.tres") + +@export var control_planet: PackedScene + +@export var trail: PackedScene + +@export var ships_fleet: PackedScene diff --git a/scenes/templates/scene_templates.tres b/scenes/templates/scene_templates.tres new file mode 100644 index 0000000..1ec1b07 --- /dev/null +++ b/scenes/templates/scene_templates.tres @@ -0,0 +1,12 @@ +[gd_resource type="Resource" script_class="TemplateScenes" load_steps=5 format=3 uid="uid://cp0dudi0f5r62"] + +[ext_resource type="PackedScene" uid="uid://dq00mi6jwsa1f" path="res://scenes/control_planet/control_planet.tscn" id="1_3rbtf"] +[ext_resource type="Script" path="res://scenes/templates/scene_templates.gd" id="1_yvn3m"] +[ext_resource type="PackedScene" uid="uid://cpffyaoh8x5bp" path="res://scenes/ships_fleet/ships_fleet.tscn" id="3_x17ul"] +[ext_resource type="PackedScene" uid="uid://ckfk1xgxfk1c3" path="res://scenes/trail/trail.tscn" id="4_hiuw1"] + +[resource] +script = ExtResource("1_yvn3m") +control_planet = ExtResource("1_3rbtf") +trail = ExtResource("4_hiuw1") +ships_fleet = ExtResource("3_x17ul") diff --git a/scenes/templates/template_scenes.gd b/scenes/templates/template_scenes.gd deleted file mode 100644 index 4a64ddb..0000000 --- a/scenes/templates/template_scenes.gd +++ /dev/null @@ -1,4 +0,0 @@ -class_name TemplateScenes -extends Resource - -@export var control_planet: PackedScene diff --git a/scenes/templates/templates.tres b/scenes/templates/templates.tres deleted file mode 100644 index 11026ef..0000000 --- a/scenes/templates/templates.tres +++ /dev/null @@ -1,8 +0,0 @@ -[gd_resource type="Resource" script_class="TemplateScenes" load_steps=3 format=3 uid="uid://cp0dudi0f5r62"] - -[ext_resource type="PackedScene" uid="uid://dq00mi6jwsa1f" path="res://scenes/control_planet/control_planet.tscn" id="1_3rbtf"] -[ext_resource type="Script" path="res://scenes/templates/template_scenes.gd" id="1_yvn3m"] - -[resource] -script = ExtResource("1_yvn3m") -control_planet = ExtResource("1_3rbtf") diff --git a/scenes/trail/trail.gd b/scenes/trail/trail.gd index d5b5539..088fa80 100644 --- a/scenes/trail/trail.gd +++ b/scenes/trail/trail.gd @@ -1,11 +1,19 @@ extends Sprite2D class_name Trail -@onready var inv_texture_width = 1. / texture.get_width() +static var _inv_texture_width := NAN @export var color: Color : set(value): - material.set_shader_parameter('color', value) + color = value + _update_color(color) + +@export var show: bool = true : + set(value): + show = value + visible = true + +@export var auto_free: bool var start_position: Vector2 : set(value): @@ -17,7 +25,31 @@ var end_position: Vector2 : end_position = value _update_transform(start_position, value) +var _opacity := 0. + func _update_transform(start: Vector2, end: Vector2) -> void: global_position = (start + end) * .5 - look_at(end) - scale = Vector2((end - start).length() * inv_texture_width, 1.) + look_at(end) + + if is_nan(_inv_texture_width): + _inv_texture_width = 1. / texture.get_width() + + scale = Vector2((end - start).length() * _inv_texture_width, 1.) + +func _update_color(color: Color) -> void: + var c := Color(color.r, color.g, color.b, color.a * _opacity) + material.set_shader_parameter('color', c) + +func _process(delta: float) -> void: + var prev_opacity := _opacity + _opacity = Utils.damp(_opacity, 1. if show else 0., 1e-4, delta) + if _opacity != prev_opacity: + if _opacity < .01: + _opacity = 0. + visible = false + if auto_free: + queue_free() + elif _opacity > .99: + _opacity = 1. + + _update_color(color) diff --git a/scenes/trail/trail.tscn b/scenes/trail/trail.tscn index 02750df..f756c4b 100644 --- a/scenes/trail/trail.tscn +++ b/scenes/trail/trail.tscn @@ -5,6 +5,7 @@ [ext_resource type="Script" path="res://scenes/trail/trail.gd" id="3_cnvkj"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_f7lnm"] +resource_local_to_scene = true shader = ExtResource("1_fjdrv") shader_parameter/color = Color(1, 0, 1, 1) diff --git a/scripts/game_logic.gd b/scripts/game_logic.gd index 9cf36a7..447f1ed 100644 --- a/scripts/game_logic.gd +++ b/scripts/game_logic.gd @@ -4,13 +4,14 @@ extends Node const LOGIC_TICKS_PER_SECOND := 20. const LOGIC_SECONDS_PER_TICK := 1. / LOGIC_TICKS_PER_SECOND -## Templates for spawnable stuff. -@export var templates: TemplateScenes - ## Container for planets. @export var planets_container: Node2D +## Container for trails. +@export var trails_container: Node2D +## Container for fleets. +@export var fleets_container: Node2D -## Interface to interact with planets. +## UI to interact with planets. @export var planets_ui: PlanetsUI ## Game players. @@ -43,7 +44,7 @@ func _ready() -> void: _game.add_planet(data) # UI control. - var control: ControlPlanet = templates.control_planet.instantiate() + var control: ControlPlanet = SceneTemplates.scenes.control_planet.instantiate() control.position = planets[i].location control.player = players[data.player_id] control.population = data.population @@ -54,6 +55,7 @@ func _ready() -> void: # Register UI signals. _game.planet_player_changed.connect(_set_planet_player) _game.planet_population_changed.connect(_set_planet_population) + _game.fleet_dispatched.connect(_show_dispatched_fleet) planets_ui.fleet_dispatched.connect(_dispatch_fleet) func _process(delta: float) -> void: @@ -63,15 +65,43 @@ func _process(delta: float) -> void: _extra_time -= LOGIC_SECONDS_PER_TICK _game.tick() +#region Graphics + func _set_planet_player(planet_id: int, player_id: int) -> void: _planet_controls[planet_id].player = players[player_id] func _set_planet_population(planet_id: int, population: int) -> void: _planet_controls[planet_id].population = population +func _show_dispatched_fleet(from_planet_id: int, to_planet_id: int, count: int, player_id: int, departed_at_tick: int, arrives_at_tick: int) -> void: + var trail: Trail = SceneTemplates.scenes.trail.instantiate() + trail.start_position = planets[from_planet_id].location + trail.end_position = planets[to_planet_id].location + trail.color = players[player_id].color + trail.auto_free = true + trails_container.add_child(trail) + + var fleet: ShipsFleet = SceneTemplates.scenes.ships_fleet.instantiate() + fleet.departed_at = departed_at_tick + fleet.arrives_at = arrives_at_tick + fleet.from = planets[from_planet_id].location + fleet.to = planets[to_planet_id].location + fleet.player = players[player_id] + fleet.count = count + fleet.trail = trail + fleets_container.add_child(fleet) + + fleet.register_game(_game) + +#endregion + +#region Commands + func _dispatch_fleet(from: ControlPlanet, to: ControlPlanet) -> void: var player_id := players.find(from.player) var from_planet_id := _planet_controls.find(from) var to_planet_id := _planet_controls.find(to) var max_count: int = ceil(from.population / 2.) _game.dispatch_fleet(player_id, from_planet_id, to_planet_id, max_count) + +#endregion diff --git a/scripts/game_state.gd b/scripts/game_state.gd index 441e60d..226b580 100644 --- a/scripts/game_state.gd +++ b/scripts/game_state.gd @@ -7,11 +7,14 @@ class PlayerData: class PlanetData: var position: Vector2i var grow_every_ticks: int - var player_id: int ## 0 = none + var player_id: int var population: int class FleetData: var count: int + var player_id: int + var to_planet_id: int + var arrives_at_tick: int enum CommandType { DISPATCH_FLEET, @@ -28,9 +31,14 @@ class DispatchFleedCommand extends Command: func _init(): type = CommandType.DISPATCH_FLEET +const NEUTRAL_PLAYER_ID := 0 +const FLEET_TAKEOFF_PLUS_LANDING_TIME := 20 # ticks +const FLEET_SPEED := 2 # pixels/tick + signal game_ticked(tick: int) signal planet_population_changed(planet_id: int, population: int) signal planet_player_changed(planet_id: int, player_id: int) +signal fleet_dispatched(from_planet_id: int, to_planet_id: int, count: int, player_id: int, departed_at_tick: int, arrives_at_tick: int) var _players: Array[PlayerData] = [] var _planets: Array[PlanetData] = [] @@ -54,12 +62,14 @@ func add_planet(data := PlanetData.new()) -> int: func tick() -> void: current_tick += 1 + # Handle planets growth. for planet_id in range(_planets.size()): var planet := _planets[planet_id] - if planet.player_id != 0 and current_tick % planet.grow_every_ticks == 0: + if planet.player_id != NEUTRAL_PLAYER_ID and current_tick % planet.grow_every_ticks == 0: planet.population += 1 planet_population_changed.emit(planet_id, planet.population) + # Handle player commands. for player_id in range(_players.size()): var player := _players[player_id] if player.commands.size() > 0 && current_tick >= player.commands[0].wait_tick: @@ -70,6 +80,11 @@ func tick() -> void: _: assert(false, "Unknown command: %s" % [command]) + # Handle fleets arrival. + while _fleets.size() > 0 && current_tick >= _fleets[0].arrives_at_tick: + var fleet: FleetData = _fleets.pop_back() + _handle_fleet_arrival(fleet) + game_ticked.emit(current_tick) ## Dispatch a fleet. @@ -87,16 +102,31 @@ func _issue_command(player_id: int, command: Command) -> void: func _handle_dispatch_fleet(player_id: int, dispatch: DispatchFleedCommand) -> void: var from = _planets[dispatch.from_planet_id] - var to = _planets[dispatch.to_planet_id] - var count = min(dispatch.max_count, from.population) - from.population -= count - if from.player_id == to.player_id: - to.population += count + if from.player_id == player_id: + var to = _planets[dispatch.to_planet_id] + var count = min(dispatch.max_count, from.population) + from.population -= count + planet_population_changed.emit(dispatch.from_planet_id, from.population) + + var fleet := FleetData.new() + fleet.count = count + fleet.player_id = player_id + fleet.to_planet_id = dispatch.to_planet_id + fleet.arrives_at_tick = current_tick + FLEET_TAKEOFF_PLUS_LANDING_TIME + ceili(from.position.distance_to(to.position) / FLEET_SPEED) + + var index := _fleets.map(func (x): return x.arrives_at_tick).bsearch(fleet.arrives_at_tick, false) + _fleets.insert(index, fleet) + + fleet_dispatched.emit(dispatch.from_planet_id, fleet.to_planet_id, fleet.count, fleet.player_id, current_tick, fleet.arrives_at_tick) + +func _handle_fleet_arrival(fleet: FleetData) -> void: + var to = _planets[fleet.to_planet_id] + if fleet.player_id == to.player_id: + to.population += fleet.count else: - to.population -= count + to.population -= fleet.count if to.population <= 0: - to.player_id = from.player_id - to.population = abs(from.population) - planet_player_changed.emit(dispatch.to_planet_id, to.player_id) - planet_population_changed.emit(dispatch.from_planet_id, from.population) - planet_population_changed.emit(dispatch.to_planet_id, to.population) + to.player_id = fleet.player_id + to.population = abs(to.population) + planet_player_changed.emit(fleet.to_planet_id, to.player_id) + planet_population_changed.emit(fleet.to_planet_id, to.population) diff --git a/scripts/ui/planets_ui.gd b/scripts/ui/planets_ui.gd index 2e3a300..df231df 100644 --- a/scripts/ui/planets_ui.gd +++ b/scripts/ui/planets_ui.gd @@ -1,7 +1,10 @@ class_name PlanetsUI extends Control -@export var trail: Trail +## Container for trails. +@export var trails_container: Node2D + +var _trail: Trail signal fleet_dispatched(from: ControlPlanet, to: ControlPlanet) @@ -10,6 +13,11 @@ func register_planet(control: ControlPlanet) -> void: control.pointer_entered.connect(_on_planet_pointer_entered) control.pointer_exited.connect(_on_planet_pointer_exited) +func _ready() -> void: + _trail = SceneTemplates.scenes.trail.instantiate() + _trail.visible = false + trails_container.add_child(_trail) + func _process(delta: float) -> void: _update_trail(delta, false) @@ -63,18 +71,12 @@ func _on_planet_pointer_exited(planet: ControlPlanet) -> void: #region Trail -var _trail_opacity := 0. - func _update_trail(delta: float, snap_position: bool) -> void: - var show_trail := _dragging_from_planet and _selected_planet != null and _selected_planet.player is LocalPlayer - if show_trail or trail.visible: - _trail_opacity = Utils.damp(_trail_opacity, 1. if show_trail else 0., 1e-4, delta) - trail.color = Color(_selected_planet.player.color, _trail_opacity) - trail.visible = _trail_opacity > .01 - - if show_trail: - var target_position = _last_cursor_position if _pointed_planet == null else _pointed_planet.global_position - trail.start_position = _selected_planet.global_position - trail.end_position = target_position if snap_position else Utils.damp(trail.end_position, target_position, 1e-20, delta) + _trail.show = _dragging_from_planet and _selected_planet != null and _selected_planet.player is LocalPlayer + if _trail.show: + _trail.color = _selected_planet.player.color + var target_position = _last_cursor_position if _pointed_planet == null else _pointed_planet.global_position + _trail.start_position = _selected_planet.global_position + _trail.end_position = target_position if snap_position else Utils.damp(_trail.end_position, target_position, 1e-20, delta) #endregion