GDScript to C#
This commit is contained in:
parent
aa25860d6d
commit
10e5f5bd63
38 changed files with 1170 additions and 709 deletions
7
SpaceCapture.csproj
Normal file
7
SpaceCapture.csproj
Normal file
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Godot.NET.Sdk/4.3.0">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<RootNamespace>SpaceCapture</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
19
SpaceCapture.sln
Normal file
19
SpaceCapture.sln
Normal file
|
@ -0,0 +1,19 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceCapture", "SpaceCapture.csproj", "{BDE35DF8-5DC8-4B5F-85AE-56A80323ED08}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BDE35DF8-5DC8-4B5F-85AE-56A80323ED08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BDE35DF8-5DC8-4B5F-85AE-56A80323ED08}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BDE35DF8-5DC8-4B5F-85AE-56A80323ED08}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{BDE35DF8-5DC8-4B5F-85AE-56A80323ED08}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{BDE35DF8-5DC8-4B5F-85AE-56A80323ED08}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{BDE35DF8-5DC8-4B5F-85AE-56A80323ED08}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
84
main.tscn
84
main.tscn
|
@ -1,62 +1,62 @@
|
|||
[gd_scene load_steps=19 format=3 uid="uid://cdaf4bh0jaa45"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/game_logic.gd" id="1_uj8ti"]
|
||||
[ext_resource type="Script" path="res://scripts/player.gd" id="2_plgh6"]
|
||||
[ext_resource type="Script" path="res://scripts/PlayerLocal.cs" id="1_3h84q"]
|
||||
[ext_resource type="Script" path="res://scripts/Planet.cs" id="1_rrxte"]
|
||||
[ext_resource type="Script" path="res://scripts/Player.cs" id="1_tr3cj"]
|
||||
[ext_resource type="Script" path="res://scripts/PlayerAI.cs" id="1_u42tr"]
|
||||
[ext_resource type="Texture2D" uid="uid://bydmcc5d53ldx" path="res://icons/paw_print.svg" id="3_mxakn"]
|
||||
[ext_resource type="Script" path="res://scripts/GameLogic.cs" id="3_q0fqo"]
|
||||
[ext_resource type="Texture2D" uid="uid://1xh0qil16bkh" path="res://icons/character.svg" id="3_qxmev"]
|
||||
[ext_resource type="Script" path="res://scripts/player_local.gd" id="4_7rlmh"]
|
||||
[ext_resource type="Texture2D" uid="uid://dx1wjviioa8u5" path="res://icons/chip.svg" id="4_pgo63"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/planets_ui.gd" id="7_khbdl"]
|
||||
[ext_resource type="PackedScene" uid="uid://dtlatmtuggp6x" path="res://scenes/backgrounds/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://b0ec8jbenscpp" path="res://scenes/backgrounds/stars.tscn" id="9_lum1l"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/PlanetsUI.cs" id="11_u25me"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_mxp7y"]
|
||||
script = ExtResource("2_plgh6")
|
||||
color = Color(0.745, 0.745, 0.745, 1)
|
||||
icon = ExtResource("3_mxakn")
|
||||
script = ExtResource("1_tr3cj")
|
||||
Color = Color(0.745, 0.745, 0.745, 1)
|
||||
Icon = ExtResource("3_mxakn")
|
||||
|
||||
[sub_resource type="Resource" id="Resource_1dtpf"]
|
||||
script = ExtResource("4_7rlmh")
|
||||
color = Color(0.236993, 0.373968, 1, 1)
|
||||
icon = ExtResource("3_qxmev")
|
||||
script = ExtResource("1_3h84q")
|
||||
Color = Color(0.236993, 0.373968, 1, 1)
|
||||
Icon = ExtResource("3_qxmev")
|
||||
|
||||
[sub_resource type="Resource" id="Resource_7wtc7"]
|
||||
script = ExtResource("7_v73fw")
|
||||
color = Color(0.835294, 0.160784, 0, 1)
|
||||
icon = ExtResource("4_pgo63")
|
||||
script = ExtResource("1_u42tr")
|
||||
Color = Color(0.835294, 0.160784, 0, 1)
|
||||
Icon = ExtResource("4_pgo63")
|
||||
|
||||
[sub_resource type="Resource" id="Resource_50eg2"]
|
||||
script = ExtResource("8_ve3b1")
|
||||
location = Vector2(200, 200)
|
||||
type = 0
|
||||
[sub_resource type="Resource" id="Resource_1nuc3"]
|
||||
script = ExtResource("1_rrxte")
|
||||
Location = Vector2(200, 200)
|
||||
Type = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_2525o"]
|
||||
script = ExtResource("8_ve3b1")
|
||||
location = Vector2(952, 448)
|
||||
type = 0
|
||||
[sub_resource type="Resource" id="Resource_qh3gq"]
|
||||
script = ExtResource("1_rrxte")
|
||||
Location = Vector2(952, 448)
|
||||
Type = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_irtww"]
|
||||
script = ExtResource("8_ve3b1")
|
||||
location = Vector2(450, 234)
|
||||
type = 0
|
||||
[sub_resource type="Resource" id="Resource_u1yeo"]
|
||||
script = ExtResource("1_rrxte")
|
||||
Location = Vector2(450, 234)
|
||||
Type = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_wdycy"]
|
||||
script = ExtResource("8_ve3b1")
|
||||
location = Vector2(702, 414)
|
||||
type = 0
|
||||
[sub_resource type="Resource" id="Resource_c2fhs"]
|
||||
script = ExtResource("1_rrxte")
|
||||
Location = Vector2(702, 414)
|
||||
Type = 0
|
||||
|
||||
[node name="Main" type="Node"]
|
||||
|
||||
[node name="GameLogic" type="Node" parent="." node_paths=PackedStringArray("planets_container", "trails_container", "fleets_container", "planets_ui")]
|
||||
script = ExtResource("1_uj8ti")
|
||||
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")])
|
||||
[node name="GameLogic" type="Node" parent="." node_paths=PackedStringArray("PlanetsContainer", "TrailsContainer", "FleetsContainer", "PlanetsUI")]
|
||||
script = ExtResource("3_q0fqo")
|
||||
PlanetsContainer = NodePath("../Planets")
|
||||
TrailsContainer = NodePath("../Trails")
|
||||
FleetsContainer = NodePath("../Fleets")
|
||||
PlanetsUI = NodePath("../PlanetsUI")
|
||||
Players = Array[ExtResource("1_tr3cj")]([SubResource("Resource_mxp7y"), SubResource("Resource_1dtpf"), SubResource("Resource_7wtc7")])
|
||||
Planets = Array[Object]([SubResource("Resource_1nuc3"), SubResource("Resource_qh3gq"), SubResource("Resource_u1yeo"), SubResource("Resource_c2fhs")])
|
||||
|
||||
[node name="Stars" parent="." instance=ExtResource("9_lum1l")]
|
||||
|
||||
|
@ -68,7 +68,7 @@ planets = Array[ExtResource("8_ve3b1")]([SubResource("Resource_50eg2"), SubResou
|
|||
|
||||
[node name="Fleets" type="Node2D" parent="."]
|
||||
|
||||
[node name="PlanetsUI" type="Control" parent="." node_paths=PackedStringArray("trails_container")]
|
||||
[node name="PlanetsUI" type="Control" parent="." node_paths=PackedStringArray("TrailsContainer")]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
@ -76,5 +76,5 @@ anchor_bottom = 1.0
|
|||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("7_khbdl")
|
||||
trails_container = NodePath("../Trails")
|
||||
script = ExtResource("11_u25me")
|
||||
TrailsContainer = NodePath("../Trails")
|
||||
|
|
|
@ -12,9 +12,13 @@ config_version=5
|
|||
|
||||
config/name="Space Capture"
|
||||
run/main_scene="res://main.tscn"
|
||||
config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
config/features=PackedStringArray("4.3", "C#", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="SpaceCapture"
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
|
|
79
scenes/control_planet/ControlPlanet.cs
Normal file
79
scenes/control_planet/ControlPlanet.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public partial class ControlPlanet : Node2D
|
||||
{
|
||||
Player _player;
|
||||
int _population;
|
||||
|
||||
/// <summary>
|
||||
/// The player currenly controlling this planet.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public Player Player
|
||||
{
|
||||
get => _player;
|
||||
set
|
||||
{
|
||||
_player = value;
|
||||
Color color = _player?.Color ?? Colors.Magenta;
|
||||
GetNode<ColorRect>("%Selection").Color = color;
|
||||
GetNode<Label>("%PopulationLabel").Modulate = color;
|
||||
GetNode<TextureRect>("%PopulationIcon").Modulate = color;
|
||||
GetNode<TextureRect>("%PopulationIcon").Texture = Player?.Icon;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of units currently stationed at the planet.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public int Population
|
||||
{
|
||||
get => _population;
|
||||
set
|
||||
{
|
||||
_population = value;
|
||||
GetNode<Label>("%PopulationLabel").Text = $"{value}";
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
IsSelected = false;
|
||||
}
|
||||
|
||||
[ExportCategory("UI")]
|
||||
object _placeholder1;
|
||||
|
||||
[Signal]
|
||||
public delegate void SelectedEventHandler(ControlPlanet planet);
|
||||
|
||||
[Signal]
|
||||
public delegate void PointerEnteredEventHandler(ControlPlanet planet);
|
||||
|
||||
[Signal]
|
||||
public delegate void PointerExitedEventHandler(ControlPlanet planet);
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => GetNode<ColorRect>("%Selection").Visible;
|
||||
set => GetNode<ColorRect>("%Selection").Visible = value;
|
||||
}
|
||||
|
||||
void OnInputListenerInputEvent(Node _viewport, InputEvent inputEvent, int _shape_idx)
|
||||
{
|
||||
// TODO: handle touch appropriately (InputEventScreenTouch).
|
||||
if (inputEvent is InputEventMouseButton e && e.ButtonIndex is MouseButton.Left && e.IsPressed())
|
||||
EmitSignal(SignalName.Selected, this);
|
||||
}
|
||||
|
||||
void OnInputListenerMouseEntered()
|
||||
=> EmitSignal(SignalName.PointerEntered, this);
|
||||
|
||||
void OnInputListenerMouseExited()
|
||||
=> EmitSignal(SignalName.PointerExited, this);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
extends Node2D
|
||||
class_name ControlPlanet
|
||||
|
||||
## The player currenly controlling this planet.
|
||||
var player: Player :
|
||||
set(value):
|
||||
player = value
|
||||
var color := Color.MAGENTA if player == null else player.color
|
||||
%Selection.color = color
|
||||
%PopulationLabel.modulate = color
|
||||
%PopulationIcon.modulate = color
|
||||
%PopulationIcon.texture = null if player == null else player.icon
|
||||
|
||||
## Number of units currently stationed at the planet.
|
||||
@export var population: int :
|
||||
set(value):
|
||||
population = value
|
||||
%PopulationLabel.text = "%s" % value
|
||||
|
||||
func _ready() -> void:
|
||||
is_selected = false
|
||||
|
||||
@export_category("UI")
|
||||
|
||||
signal selected(planet: Planet)
|
||||
signal pointer_entered(planet: Planet)
|
||||
signal pointer_exited(planet: Planet)
|
||||
|
||||
var is_selected: bool :
|
||||
get:
|
||||
return %Selection.visible
|
||||
set(value):
|
||||
%Selection.visible = value
|
||||
|
||||
func _on_input_listener_input_event(_viewport: Node, event: InputEvent, _shape_idx: int) -> void:
|
||||
# TODO: handle touch appropriately (InputEventScreenTouch).
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
selected.emit(self)
|
||||
|
||||
func _on_input_listener_mouse_entered() -> void:
|
||||
pointer_entered.emit(self)
|
||||
|
||||
func _on_input_listener_mouse_exited() -> void:
|
||||
pointer_exited.emit(self)
|
|
@ -1,7 +1,7 @@
|
|||
[gd_scene load_steps=7 format=3 uid="uid://dq00mi6jwsa1f"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://dnerbdusnaofr" path="res://scenes/procedural_planet/procedural_planet.tscn" id="1_8mf3x"]
|
||||
[ext_resource type="Script" path="res://scenes/control_planet/control_planet.gd" id="1_e208c"]
|
||||
[ext_resource type="Script" path="res://scenes/control_planet/ControlPlanet.cs" id="1_gbjyd"]
|
||||
[ext_resource type="Shader" path="res://scenes/control_planet/planet_selection.gdshader" id="2_bl05u"]
|
||||
[ext_resource type="Texture2D" uid="uid://1xh0qil16bkh" path="res://icons/character.svg" id="2_kglxp"]
|
||||
|
||||
|
@ -13,7 +13,7 @@ shader = ExtResource("2_bl05u")
|
|||
shader_parameter/size = 100.0
|
||||
|
||||
[node name="Planet" type="Node2D"]
|
||||
script = ExtResource("1_e208c")
|
||||
script = ExtResource("1_gbjyd")
|
||||
|
||||
[node name="InputListener" type="Area2D" parent="."]
|
||||
monitoring = false
|
||||
|
@ -72,7 +72,7 @@ mouse_filter = 2
|
|||
texture = ExtResource("2_kglxp")
|
||||
stretch_mode = 3
|
||||
|
||||
[connection signal="input_event" from="InputListener" to="." method="_on_input_listener_input_event"]
|
||||
[connection signal="mouse_entered" from="InputListener" to="." method="_on_input_listener_mouse_entered"]
|
||||
[connection signal="mouse_exited" from="InputListener" to="." method="_on_input_listener_mouse_exited"]
|
||||
[connection signal="input_event" from="InputListener" to="." method="OnInputListenerInputEvent"]
|
||||
[connection signal="mouse_entered" from="InputListener" to="." method="OnInputListenerMouseEntered"]
|
||||
[connection signal="mouse_exited" from="InputListener" to="." method="OnInputListenerMouseExited"]
|
||||
[connection signal="gui_input" from="Selection" to="." method="_on_selection_gui_input"]
|
||||
|
|
119
scenes/procedural_planet/ProceduralPlanet.cs
Normal file
119
scenes/procedural_planet/ProceduralPlanet.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public partial class ProceduralPlanet : Node2D
|
||||
{
|
||||
[ExportGroup("Planet")]
|
||||
|
||||
/// <summary>
|
||||
/// Size of the planet in pixels.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export(PropertyHint.Range, "30,500,.001,or_greater,or_less")]
|
||||
public float Size
|
||||
{
|
||||
get => Scale.X;
|
||||
set
|
||||
{
|
||||
Scale = value * Vector2.One;
|
||||
((ShaderMaterial)Material).SetShaderParameter("size", value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation speed of the planet in rad/sec.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export(PropertyHint.Range, "0,1,.001,or_greater,or_less")]
|
||||
public float RotationSpeed
|
||||
{
|
||||
get => (float)((ShaderMaterial)Material).GetShaderParameter("rotationSpeed").AsDouble();
|
||||
set => ((ShaderMaterial)Material).SetShaderParameter("rotationSpeed", value);
|
||||
}
|
||||
|
||||
[ExportGroup("Weather")]
|
||||
|
||||
/// <summary>
|
||||
/// Size of clouds between 0 (no clouds) and 1 (covered in clouds completely).
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export(PropertyHint.Range, "0,1,.001,or_greater,or_less")]
|
||||
public float CloudsSize
|
||||
{
|
||||
get => (float)((ShaderMaterial)Material).GetShaderParameter("cloudsSize").AsDouble();
|
||||
set => ((ShaderMaterial)Material).SetShaderParameter("cloudsSize", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Density of clouds between 0 (very thin) and 1 (very thick).
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export(PropertyHint.Range, "0,1,.001,or_greater,or_less")]
|
||||
public float CloudDensity
|
||||
{
|
||||
get => (float)((ShaderMaterial)Material).GetShaderParameter("cloudsDensity").AsDouble();
|
||||
set => ((ShaderMaterial)Material).SetShaderParameter("cloudsDensity", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How often clouds change shape.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export(PropertyHint.Range, "0,1,.001,or_greater")]
|
||||
public float CloudTurbulence
|
||||
{
|
||||
get => (float)((ShaderMaterial)Material).GetShaderParameter("cloudsTurbulence").AsDouble();
|
||||
set => ((ShaderMaterial)Material).SetShaderParameter("cloudsTurbulence", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wind speed in rad/sec.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export(PropertyHint.Range, "0,1,.001,or_greater")]
|
||||
public float WindSpeed
|
||||
{
|
||||
get => (float)((ShaderMaterial)Material).GetShaderParameter("windSpeed").AsDouble();
|
||||
set => ((ShaderMaterial)Material).SetShaderParameter("windSpeed", value);
|
||||
}
|
||||
|
||||
[ExportGroup("Atmosphere")]
|
||||
|
||||
/// <summary>
|
||||
/// Size of the atmosphere halo around the planet.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export(PropertyHint.Range, "0,1,.001")]
|
||||
public float AtmosphereSize
|
||||
{
|
||||
get => (float)((ShaderMaterial)Material).GetShaderParameter("atmosphereSize").AsDouble();
|
||||
set => ((ShaderMaterial)Material).SetShaderParameter("atmosphereSize", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Size of the atmosphere halo around the planet.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export(PropertyHint.Range, "0,1,.001")]
|
||||
public Color AtmosphereColor
|
||||
{
|
||||
get => ((ShaderMaterial)Material).GetShaderParameter("atmosphereColor").AsColor();
|
||||
set => ((ShaderMaterial)Material).SetShaderParameter("atmosphereColor", value);
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Size = 100f;
|
||||
RotationSpeed = .05f;
|
||||
|
||||
CloudsSize = .05f;
|
||||
CloudDensity = .22f;
|
||||
CloudTurbulence = .01f;
|
||||
WindSpeed = .22f;
|
||||
|
||||
AtmosphereSize = .3f;
|
||||
AtmosphereColor = new(0f, .3f, 1f, .3f);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
extends Node2D
|
||||
|
||||
@export_group("Planet")
|
||||
|
||||
## Size of the planet in pixels.
|
||||
@export_range(30, 500, .001, "or_greater", "or_less") var size: float = 100 :
|
||||
set(value):
|
||||
scale = value * Vector2.ONE
|
||||
material.set_shader_parameter('size', value)
|
||||
|
||||
# Rotation speed of the planet in rad/sec.
|
||||
@export_range(0, 1, .001, "or_greater", "or_less") var rotation_speed: float = 0.05 :
|
||||
set(value):
|
||||
material.set_shader_parameter('rotationSpeed', value)
|
||||
|
||||
@export_group("Weather")
|
||||
|
||||
# Size of clouds between 0 (no clouds) and 1 (covered in clouds completely).
|
||||
@export_range(0, 1, .001) var clouds_size: float = 0.05 :
|
||||
set(value):
|
||||
material.set_shader_parameter('cloudsSize', value)
|
||||
|
||||
# Density of clouds between 0 (very thin) and 1 (very thick).
|
||||
@export_range(0, 1, .001) var cloud_density: float = 0.22 :
|
||||
set(value):
|
||||
material.set_shader_parameter('cloudsDensity', value)
|
||||
|
||||
# How often clouds change shape.
|
||||
@export_range(0, 1, .001, "or_greater") var cloud_turbulence: float = 0.01 :
|
||||
set(value):
|
||||
material.set_shader_parameter('cloudsTurbulence', value)
|
||||
|
||||
# Wind speed in rad/sec.
|
||||
@export_range(0, 1, .001, "or_greater") var wind_speed: float = 0.22 :
|
||||
set(value):
|
||||
material.set_shader_parameter('windSpeed', value)
|
||||
|
||||
@export_group("Atmosphere")
|
||||
|
||||
## Size of the atmosphere halo around the planet.
|
||||
@export_range(0, 1, .001) var atmosphere_size: float = .3 :
|
||||
set(value):
|
||||
material.set_shader_parameter('atmosphereSize', value)
|
||||
|
||||
## Color of the atmosphere halo around the planet.
|
||||
@export var atmosphere_color: Color = Color(0., .3, 1., .3) :
|
||||
set(value):
|
||||
material.set_shader_parameter('atmosphereColor', value)
|
|
@ -1,6 +1,6 @@
|
|||
[gd_scene load_steps=8 format=3 uid="uid://dnerbdusnaofr"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/procedural_planet/procedural_planet.gd" id="1_2swpa"]
|
||||
[ext_resource type="Script" path="res://scenes/procedural_planet/ProceduralPlanet.cs" id="3_dyl4c"]
|
||||
[ext_resource type="Shader" path="res://scenes/procedural_planet/procedural_planet.gdshader" id="1_tl7a6"]
|
||||
[ext_resource type="Texture2D" uid="uid://dr8pb2ip0k08j" path="res://scenes/procedural_planet/albedo.png" id="3_pc1s8"]
|
||||
|
||||
|
@ -35,4 +35,4 @@ size = Vector2(2, 2)
|
|||
material = SubResource("ShaderMaterial_m4mhh")
|
||||
scale = Vector2(100, 100)
|
||||
texture = SubResource("PlaceholderTexture2D_ipvyw")
|
||||
script = ExtResource("1_2swpa")
|
||||
script = ExtResource("3_dyl4c")
|
||||
|
|
126
scenes/ships_fleet/ShipsFleet.cs
Normal file
126
scenes/ships_fleet/ShipsFleet.cs
Normal file
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public partial class ShipsFleet : Node2D
|
||||
{
|
||||
const int RankSize = 7;
|
||||
const float RanksDistance = 12f;
|
||||
const float ShoulderDistance = 8f;
|
||||
const float DisperseAtDistance = 100f;
|
||||
|
||||
int _count;
|
||||
GameState _game;
|
||||
|
||||
[Export]
|
||||
public int DepartedAt { get; set; } // Time.GetTicksMsec()
|
||||
|
||||
[Export]
|
||||
public int ArrivesAt { get; set; } // Time.GetTicksMsec()
|
||||
|
||||
[Export]
|
||||
public Vector2 From { get; set; }
|
||||
|
||||
[Export]
|
||||
public Vector2 To { get; set; }
|
||||
|
||||
[Export]
|
||||
public Player Player { get; set; }
|
||||
|
||||
[Export]
|
||||
public int Count
|
||||
{
|
||||
get => _count;
|
||||
set
|
||||
{
|
||||
_count = value;
|
||||
UpdateShipsCount();
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public Trail Trail { get; set; }
|
||||
|
||||
public void RegisterGame(GameState game)
|
||||
{
|
||||
_game = game;
|
||||
_game.GameTicked += UpdatePosition;
|
||||
UpdatePosition(game.CurrentTick);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
if (_game is not null)
|
||||
{
|
||||
_game.GameTicked -= UpdatePosition;
|
||||
_game = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Graphics
|
||||
|
||||
List<Ship> _ships = [];
|
||||
|
||||
[Export]
|
||||
public PackedScene ShipTemplate { get; set; }
|
||||
|
||||
void UpdateShipsCount()
|
||||
{
|
||||
int diff = Count - _ships.Count;
|
||||
|
||||
for (int i = 0; i < diff; i++)
|
||||
{
|
||||
Ship ship = ShipTemplate.Instantiate<Ship>();
|
||||
ship.Color = Player.Color;
|
||||
AddChild(ship);
|
||||
ship.Position = From;
|
||||
ship.Rotation = Random.Shared.NextSingle() * (2f * MathF.PI);
|
||||
ship.Velocity = (Random.Shared.NextSingle() * 60f + 60f) * Vector2.FromAngle(ship.Rotation);
|
||||
_ships.Add(ship);
|
||||
}
|
||||
|
||||
for (int i = 0; i < -diff; i++)
|
||||
{
|
||||
RemoveChild(_ships[^1]);
|
||||
_ships.RemoveAt(_ships.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool _arrived = false;
|
||||
|
||||
void UpdatePosition(int tick)
|
||||
{
|
||||
if (!_arrived)
|
||||
{
|
||||
int clampedTick = Math.Clamp(tick, DepartedAt, ArrivesAt);
|
||||
float progress = (clampedTick - DepartedAt) / (float)(ArrivesAt - DepartedAt);
|
||||
Vector2 fleetPosition = From.Lerp(To, Math.Clamp(progress, 0f, 1f));
|
||||
float dispersiveness = Math.Clamp(MathF.Sqrt(Math.Min(fleetPosition.DistanceSquaredTo(From), fleetPosition.DistanceSquaredTo(To))) / DisperseAtDistance, 0f, 1f);
|
||||
|
||||
float angle = From.AngleToPoint(To);
|
||||
int ranksCount = Mathf.CeilToInt((float)_ships.Count / RankSize);
|
||||
for (int i = 0; i < _ships.Count; i++)
|
||||
{
|
||||
int rank = Mathf.FloorToInt((float)i / RankSize);
|
||||
int positionInRank = i % RankSize;
|
||||
var shipsInRank = rank != ranksCount - 1 ? RankSize : (_ships.Count - (ranksCount - 1) * RankSize);
|
||||
Vector2 assignedPosition = new(rank * -RanksDistance - Math.Abs(positionInRank - shipsInRank / 2f) * 5f, (float)(positionInRank - (shipsInRank - 1) * .5f) * ShoulderDistance);
|
||||
_ships[i].TargetPosition = fleetPosition + Vector2.Zero.Lerp(assignedPosition.Rotated(angle) + Vector2.FromAngle(MathF.Sin(i + tick / 10f)) * 2f, dispersiveness);
|
||||
}
|
||||
|
||||
if (tick >= ArrivesAt)
|
||||
{
|
||||
_arrived = true;
|
||||
Trail.ShowTrail = false;
|
||||
|
||||
// TODO: fade out ships.
|
||||
|
||||
GetTree().CreateTimer(5.0).Timeout += QueueFree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
58
scenes/ships_fleet/ship/Ship.cs
Normal file
58
scenes/ships_fleet/ship/Ship.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public partial class Ship : Node2D
|
||||
{
|
||||
const float Acceleration = 100f;
|
||||
const float RotationSpeed = 3f;
|
||||
const float MaxSpeed = 100f;
|
||||
|
||||
Color _color;
|
||||
|
||||
[Export]
|
||||
public Color Color
|
||||
{
|
||||
get => _color;
|
||||
set
|
||||
{
|
||||
_color = value;
|
||||
UpdateColor(_color);
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public Vector2 TargetPosition { get; set; }
|
||||
|
||||
[Export]
|
||||
public Vector2 Velocity { get; set; }
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
Vector2 targetVelocity = (TargetPosition - GlobalPosition).Normalized() * MaxSpeed;
|
||||
Vector2 adjustedTargetVelocity = targetVelocity - Velocity;
|
||||
Vector2 acceleration = adjustedTargetVelocity == Vector2.Zero ? Vector2.Zero : adjustedTargetVelocity.Normalized() * Acceleration;
|
||||
|
||||
float decelerateAtDistance = Velocity.LengthSquared() / (2f * Acceleration);
|
||||
float distanceSquared = GlobalPosition.DistanceSquaredTo(TargetPosition);
|
||||
bool slow_down = distanceSquared < decelerateAtDistance*decelerateAtDistance;
|
||||
if (slow_down)
|
||||
acceleration = Velocity.Normalized() * -Acceleration;
|
||||
|
||||
Velocity += acceleration * (float)delta;
|
||||
|
||||
float speedSquared = Velocity.LengthSquared();
|
||||
if (speedSquared > MaxSpeed*MaxSpeed)
|
||||
Velocity = Velocity / MathF.Sqrt(speedSquared) * MaxSpeed;
|
||||
|
||||
GlobalPosition += Velocity * (float)delta;
|
||||
|
||||
Rotation = Mathf.RotateToward(Rotation, GlobalPosition.AngleToPoint(TargetPosition), Math.Min(1f, (float)delta) * RotationSpeed);
|
||||
}
|
||||
|
||||
void UpdateColor(Color shipColor)
|
||||
{
|
||||
((ShaderMaterial)GetNode<Line2D>("%LineTrail").Material).SetShaderParameter("color", shipColor);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
class_name Ship
|
||||
extends Node2D
|
||||
|
||||
const ACCELERATION = 100.
|
||||
const ROTATION_SPEED = 3.
|
||||
const MAX_SPEED = 100.
|
||||
|
||||
@export var color: Color :
|
||||
set(value):
|
||||
color = value
|
||||
_update_color(color)
|
||||
|
||||
@export var target_position: Vector2
|
||||
|
||||
var velocity: Vector2
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var target_velocity := (target_position - global_position).normalized() * MAX_SPEED
|
||||
var adjusted_target_velocity := target_velocity - velocity
|
||||
var acceleration := Vector2.ZERO if adjusted_target_velocity == Vector2.ZERO else adjusted_target_velocity.normalized() * ACCELERATION
|
||||
|
||||
var decelerate_at_distance := velocity.length_squared() / (2. * ACCELERATION)
|
||||
var distance_squared := global_position.distance_squared_to(target_position)
|
||||
var slow_down := distance_squared < decelerate_at_distance*decelerate_at_distance
|
||||
if slow_down:
|
||||
acceleration = velocity.normalized() * -ACCELERATION
|
||||
|
||||
velocity += acceleration * delta
|
||||
|
||||
var speed_squared := velocity.length_squared()
|
||||
if speed_squared > MAX_SPEED*MAX_SPEED:
|
||||
velocity = velocity / sqrt(speed_squared) * MAX_SPEED
|
||||
|
||||
global_position += velocity * delta
|
||||
|
||||
rotation = rotate_toward(rotation, global_position.angle_to_point(target_position), min(1., delta) * ROTATION_SPEED)
|
||||
|
||||
func _update_color(ship_color: Color) -> void:
|
||||
%LineTrail.material.set_shader_parameter('color', ship_color)
|
|
@ -1,6 +1,6 @@
|
|||
[gd_scene load_steps=7 format=3 uid="uid://ctybb4niolni3"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ships_fleet/ship/ship.gd" id="1_h6q7s"]
|
||||
[ext_resource type="Script" path="res://scenes/ships_fleet/ship/Ship.cs" id="1_qapu0"]
|
||||
[ext_resource type="Shader" path="res://scenes/ships_fleet/ship/trail.gdshader" id="2_vgm83"]
|
||||
[ext_resource type="Texture2D" uid="uid://dp0ugxgq8stt1" path="res://scenes/ships_fleet/ship/ship_trail.png" id="3_hk6vp"]
|
||||
[ext_resource type="Script" path="res://scenes/line_trail/line_trail_2d.gd" id="4_wo8wd"]
|
||||
|
@ -12,7 +12,7 @@ shader = ExtResource("2_vgm83")
|
|||
shader_parameter/color = Color(1, 0, 1, 1)
|
||||
|
||||
[node name="Ship" type="Node2D"]
|
||||
script = ExtResource("1_h6q7s")
|
||||
script = ExtResource("1_qapu0")
|
||||
|
||||
[node name="LineTrail" type="Line2D" parent="." node_paths=PackedStringArray("tracking")]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
extends Node2D
|
||||
class_name ShipsFleet
|
||||
|
||||
const RANK_SIZE = 7
|
||||
const RANKS_DISTANCE = 12.
|
||||
const SHOULDER_DISTANCE = 8.
|
||||
const DISPERSE_AT_DISTANCE = 100.
|
||||
|
||||
@export var departed_at: int # Time.get_ticks_msec()
|
||||
|
||||
@export var arrives_at: int # 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_ships_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 _ships: Array[Ship] = []
|
||||
|
||||
@export var ship_template: PackedScene
|
||||
|
||||
func _update_ships_count() -> void:
|
||||
var diff = count - _ships.size()
|
||||
|
||||
for i in range(diff):
|
||||
var ship: Ship = ship_template.instantiate()
|
||||
ship.color = player.color
|
||||
add_child(ship)
|
||||
ship.position = from
|
||||
ship.rotation = randf_range(0, 2 * PI)
|
||||
ship.velocity = randf_range(60., 120.) * Vector2.from_angle(ship.rotation)
|
||||
_ships.push_back(ship)
|
||||
|
||||
for i in range(-diff):
|
||||
var ship: Ship = _ships.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)
|
||||
var fleet_position: Vector2 = lerp(from, to, clampf(progress, 0., 1.))
|
||||
var dispersiveness := clampf(sqrt(minf(fleet_position.distance_squared_to(from), fleet_position.distance_squared_to(to))) / DISPERSE_AT_DISTANCE, 0., 1.)
|
||||
|
||||
var angle = from.angle_to_point(to)
|
||||
var ranks_count := ceili(float(_ships.size()) / RANK_SIZE)
|
||||
for i in range(_ships.size()):
|
||||
var rank := floori(float(i) / RANK_SIZE)
|
||||
var position_in_rank := i % RANK_SIZE
|
||||
var ships_in_rank := RANK_SIZE if rank != ranks_count - 1 else (_ships.size() - (ranks_count - 1) * RANK_SIZE)
|
||||
var assigned_position := Vector2(rank * -RANKS_DISTANCE - abs(position_in_rank - ships_in_rank / 2.) * 5., float(position_in_rank - (ships_in_rank - 1) * .5) * SHOULDER_DISTANCE)
|
||||
_ships[i].target_position = fleet_position + lerp(Vector2.ZERO, assigned_position.rotated(angle) + Vector2.from_angle(sin(i + tick / 10.)) * 2., dispersiveness)
|
||||
|
||||
if tick >= arrives_at:
|
||||
_arrived = true
|
||||
trail.show_trail = false
|
||||
|
||||
# TODO: fade out ships.
|
||||
|
||||
await get_tree().create_timer(5.).timeout
|
||||
queue_free()
|
||||
|
||||
#endregion
|
|
@ -1,8 +1,8 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://cpffyaoh8x5bp"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ships_fleet/ships_fleet.gd" id="1_qfy0m"]
|
||||
[ext_resource type="Script" path="res://scenes/ships_fleet/ShipsFleet.cs" id="1_isl3a"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctybb4niolni3" path="res://scenes/ships_fleet/ship/ship.tscn" id="2_6eafk"]
|
||||
|
||||
[node name="ShipsFleet" type="Node2D"]
|
||||
script = ExtResource("1_qfy0m")
|
||||
ship_template = ExtResource("2_6eafk")
|
||||
script = ExtResource("1_isl3a")
|
||||
ShipTemplate = ExtResource("2_6eafk")
|
||||
|
|
18
scenes/templates/SceneTemplates.cs
Normal file
18
scenes/templates/SceneTemplates.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class SceneTemplates : Resource
|
||||
{
|
||||
public static SceneTemplates Scenes { get; } = ResourceLoader.Load<SceneTemplates>("res://scenes/templates/scene_templates.tres");
|
||||
|
||||
[Export]
|
||||
public PackedScene ControlPlanet { get; set; }
|
||||
|
||||
[Export]
|
||||
public PackedScene Trail { get; set; }
|
||||
|
||||
[Export]
|
||||
public PackedScene ShipsFleet { get; set; }
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
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
|
|
@ -1,12 +1,12 @@
|
|||
[gd_resource type="Resource" script_class="SceneTemplates" load_steps=5 format=3 uid="uid://cp0dudi0f5r62"]
|
||||
[gd_resource type="Resource" 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"]
|
||||
[ext_resource type="PackedScene" uid="uid://dq00mi6jwsa1f" path="res://scenes/control_planet/control_planet.tscn" id="1_a0tpk"]
|
||||
[ext_resource type="Script" path="res://scenes/templates/SceneTemplates.cs" id="1_ybx2m"]
|
||||
[ext_resource type="PackedScene" uid="uid://cpffyaoh8x5bp" path="res://scenes/ships_fleet/ships_fleet.tscn" id="2_ocr7v"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckfk1xgxfk1c3" path="res://scenes/trail/trail.tscn" id="3_gygkc"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_yvn3m")
|
||||
control_planet = ExtResource("1_3rbtf")
|
||||
trail = ExtResource("4_hiuw1")
|
||||
ships_fleet = ExtResource("3_x17ul")
|
||||
script = ExtResource("1_ybx2m")
|
||||
ControlPlanet = ExtResource("1_a0tpk")
|
||||
Trail = ExtResource("3_gygkc")
|
||||
ShipsFleet = ExtResource("2_ocr7v")
|
||||
|
|
100
scenes/trail/Trail.cs
Normal file
100
scenes/trail/Trail.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public partial class Trail : Sprite2D
|
||||
{
|
||||
static float s_invTextureWidth = float.NaN;
|
||||
|
||||
Color _color;
|
||||
bool _showTrail = true;
|
||||
Vector2 _startPosition;
|
||||
Vector2 _endPosition;
|
||||
float _opacity = 0f;
|
||||
|
||||
[Export]
|
||||
public Color Color
|
||||
{
|
||||
get => _color;
|
||||
set
|
||||
{
|
||||
_color = value;
|
||||
UpdateColor(_color);
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public bool ShowTrail
|
||||
{
|
||||
get => _showTrail;
|
||||
set
|
||||
{
|
||||
_showTrail = value;
|
||||
Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public bool AutoFree { get; set; }
|
||||
|
||||
[Export]
|
||||
public Vector2 StartPosition
|
||||
{
|
||||
get => _startPosition;
|
||||
set
|
||||
{
|
||||
_startPosition = value;
|
||||
UpdateTransform(value, _endPosition);
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public Vector2 EndPosition
|
||||
{
|
||||
get => _endPosition;
|
||||
set
|
||||
{
|
||||
_endPosition = value;
|
||||
UpdateTransform(_startPosition, value);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTransform(Vector2 start, Vector2 end)
|
||||
{
|
||||
GlobalPosition = (start + end) * .5f;
|
||||
LookAt(end);
|
||||
|
||||
if (float.IsNaN(s_invTextureWidth))
|
||||
s_invTextureWidth = 1f / Texture.GetWidth();
|
||||
|
||||
Scale = new Vector2((end - start).Length() * s_invTextureWidth, 1f);
|
||||
}
|
||||
|
||||
void UpdateColor(Color trailColor)
|
||||
{
|
||||
Color c = new(trailColor.R, trailColor.G, trailColor.B, trailColor.A * _opacity);
|
||||
((ShaderMaterial)Material).SetShaderParameter("color", c);
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
float prevOpacity = _opacity;
|
||||
_opacity = Utils.Damp(_opacity, ShowTrail ? 1f : 0f, 1e-4f, delta);
|
||||
if (_opacity != prevOpacity)
|
||||
{
|
||||
if (_opacity < .01f)
|
||||
{
|
||||
_opacity = 0f;
|
||||
Visible = false;
|
||||
if (AutoFree)
|
||||
QueueFree();
|
||||
}
|
||||
else if (_opacity > .99f)
|
||||
{
|
||||
_opacity = 1f;
|
||||
}
|
||||
|
||||
UpdateColor(Color);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
extends Sprite2D
|
||||
class_name Trail
|
||||
|
||||
static var _inv_texture_width := NAN
|
||||
|
||||
@export var color: Color :
|
||||
set(value):
|
||||
color = value
|
||||
_update_color(color)
|
||||
|
||||
@export var show_trail: bool = true :
|
||||
set(value):
|
||||
show_trail = value
|
||||
visible = true
|
||||
|
||||
@export var auto_free: bool
|
||||
|
||||
var start_position: Vector2 :
|
||||
set(value):
|
||||
start_position = value
|
||||
_update_transform(value, end_position)
|
||||
|
||||
var end_position: Vector2 :
|
||||
set(value):
|
||||
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)
|
||||
|
||||
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(trail_color: Color) -> void:
|
||||
var c := Color(trail_color.r, trail_color.g, trail_color.b, trail_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_trail 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)
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[ext_resource type="Shader" path="res://scenes/trail/trail.gdshader" id="1_fjdrv"]
|
||||
[ext_resource type="Texture2D" uid="uid://qb1fvyepkup2" path="res://scenes/trail/trail.png" id="2_nw1t2"]
|
||||
[ext_resource type="Script" path="res://scenes/trail/trail.gd" id="3_cnvkj"]
|
||||
[ext_resource type="Script" path="res://scenes/trail/Trail.cs" id="3_hnvmo"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_f7lnm"]
|
||||
resource_local_to_scene = true
|
||||
|
@ -14,4 +14,4 @@ texture_repeat = 2
|
|||
material = SubResource("ShaderMaterial_f7lnm")
|
||||
scale = Vector2(10, 1)
|
||||
texture = ExtResource("2_nw1t2")
|
||||
script = ExtResource("3_cnvkj")
|
||||
script = ExtResource("3_hnvmo")
|
||||
|
|
151
scripts/GameLogic.cs
Normal file
151
scripts/GameLogic.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public partial class GameLogic : Node
|
||||
{
|
||||
const float LogicTicksPerSecond = 20f;
|
||||
const float LogicSecondsPerTick = 1f / LogicTicksPerSecond;
|
||||
|
||||
GameState _game;
|
||||
List<ControlPlanet> _planetControls = [];
|
||||
|
||||
/// <summary>
|
||||
/// Container for planets.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public Node2D PlanetsContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Container for trails.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public Node2D TrailsContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Container for fleets.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public Node2D FleetsContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// UI to interact with planets.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public PlanetsUI PlanetsUI { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Game players.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public Array<Player> Players { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Game planets.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public Array<Planet> Planets { get; set; }
|
||||
|
||||
double _extraTime;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// New game.
|
||||
_game = new();
|
||||
|
||||
// Add players.
|
||||
for (int i = 0; i < Players.Count; i++)
|
||||
_game.AddPlayer();
|
||||
|
||||
// Add planets.
|
||||
for (int i = 0; i < Planets.Count; i++)
|
||||
{
|
||||
// Data.
|
||||
GameState.PlanetData data = new();
|
||||
data.Position = (Vector2I)Planets[i].Location;
|
||||
data.GrowEveryTicks = 10;
|
||||
data.PlayerId = i < Players.Count - 1 ? i + 1 : 0;
|
||||
data.Population = 0;
|
||||
_game.AddPlanet(data);
|
||||
|
||||
// UI control.
|
||||
ControlPlanet control = SceneTemplates.Scenes.ControlPlanet.Instantiate<ControlPlanet>();
|
||||
control.Position = Planets[i].Location;
|
||||
control.Player = Players[data.PlayerId];
|
||||
control.Population = data.Population;
|
||||
_planetControls.Add(control);
|
||||
PlanetsContainer.AddChild(control);
|
||||
PlanetsUI.RegisterPlanet(control);
|
||||
}
|
||||
|
||||
// Register UI signals.
|
||||
_game.PlanetPlayerChanged += SetPlanetPlayer;
|
||||
_game.PlanetPopulationChanged += SetPlanetPopulation;
|
||||
_game.FleetDispatched += ShowDispatchedFleet;
|
||||
PlanetsUI.FleetDispatched += DispatchFleet;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
_extraTime += delta;
|
||||
|
||||
while (_extraTime > LogicSecondsPerTick)
|
||||
{
|
||||
_extraTime -= LogicSecondsPerTick;
|
||||
_game.Tick();
|
||||
}
|
||||
}
|
||||
|
||||
#region Graphics
|
||||
|
||||
void SetPlanetPlayer(int planetId, int playerId)
|
||||
=> _planetControls[planetId].Player = Players[playerId];
|
||||
|
||||
void SetPlanetPopulation(int planetId, int population)
|
||||
=> _planetControls[planetId].Population = population;
|
||||
|
||||
void ShowDispatchedFleet(int fromPlanetId, int toPlanetId, int count, int playerId, int departedAtTick, int arrivesAtTick)
|
||||
{
|
||||
Trail trail = SceneTemplates.Scenes.Trail.Instantiate<Trail>();
|
||||
trail.StartPosition = Planets[fromPlanetId].Location;
|
||||
trail.EndPosition = Planets[toPlanetId].Location;
|
||||
trail.Color = Players[playerId].Color;
|
||||
trail.AutoFree = true;
|
||||
TrailsContainer.AddChild(trail);
|
||||
|
||||
ShipsFleet fleet = SceneTemplates.Scenes.ShipsFleet.Instantiate<ShipsFleet>();
|
||||
fleet.DepartedAt = departedAtTick;
|
||||
fleet.ArrivesAt = arrivesAtTick;
|
||||
fleet.From = Planets[fromPlanetId].Location;
|
||||
fleet.To = Planets[toPlanetId].Location;
|
||||
fleet.Player = Players[playerId];
|
||||
fleet.Count = count;
|
||||
fleet.Trail = trail;
|
||||
FleetsContainer.AddChild(fleet);
|
||||
|
||||
fleet.RegisterGame(_game);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
void DispatchFleet(ControlPlanet from, ControlPlanet to)
|
||||
{
|
||||
int playerId = Players.IndexOf(from.Player);
|
||||
int fromPlanetId = _planetControls.IndexOf(from);
|
||||
int toPlanetId = _planetControls.IndexOf(to);
|
||||
int maxCount = Mathf.CeilToInt(from.Population / 2f);
|
||||
_game.DispatchFleet(playerId, fromPlanetId, toPlanetId, maxCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
220
scripts/GameState.cs
Normal file
220
scripts/GameState.cs
Normal file
|
@ -0,0 +1,220 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public partial class GameState : Resource
|
||||
{
|
||||
public class PlayerData
|
||||
{
|
||||
public List<Command> Commands { get; set; } = [];
|
||||
}
|
||||
|
||||
public class PlanetData
|
||||
{
|
||||
public Vector2I Position { get; set; }
|
||||
public int GrowEveryTicks { get; set; }
|
||||
public int PlayerId { get; set; }
|
||||
public int Population { get; set; }
|
||||
}
|
||||
|
||||
public class FleetData
|
||||
{
|
||||
public int Count { get; set; }
|
||||
public int PlayerId { get; set; }
|
||||
public int ToPlanetId { get; set; }
|
||||
public int ArrivesAtTick { get; set; }
|
||||
}
|
||||
|
||||
public enum CommandType
|
||||
{
|
||||
DispatchFleet,
|
||||
}
|
||||
|
||||
public abstract class Command(CommandType type)
|
||||
{
|
||||
public CommandType Type => type;
|
||||
public int WaitTick { get; set; }
|
||||
}
|
||||
|
||||
public class DispatchFleetCommand() : Command(CommandType.DispatchFleet)
|
||||
{
|
||||
public int FromPlanetId { get; set; }
|
||||
public int ToPlanetId { get; set; }
|
||||
public int MaxCount { get; set; }
|
||||
}
|
||||
|
||||
const int NeutralPlayerId = 0;
|
||||
const int FleetTakeoffPlusLandingTime = 20; // ticks
|
||||
const int FleetSpeed = 2; // pixels/tick
|
||||
|
||||
[Signal]
|
||||
public delegate void GameTickedEventHandler(int tick);
|
||||
|
||||
[Signal]
|
||||
public delegate void PlanetPopulationChangedEventHandler(int planetId, int population);
|
||||
|
||||
[Signal]
|
||||
public delegate void PlanetPlayerChangedEventHandler(int planetId, int playerId);
|
||||
|
||||
[Signal]
|
||||
public delegate void FleetDispatchedEventHandler(int fromPlanetId, int toPlanetId, int count, int playerId, int departedAtTick, int arrivesAtTick);
|
||||
|
||||
List<PlayerData> _players = [];
|
||||
List<PlanetData> _planets = [];
|
||||
List<FleetData> _fleets = [];
|
||||
|
||||
public int CurrentTick { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Add a player to the game.
|
||||
/// <para>
|
||||
/// The first player is expected to be the game master / neutral player,
|
||||
/// who does not play and for whom the population never increases.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public int AddPlayer(PlayerData data = null)
|
||||
{
|
||||
_players.Add(data ?? new());
|
||||
return _players.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a planet to the game.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public int AddPlanet(PlanetData data = null)
|
||||
{
|
||||
_planets.Add(data ?? new());
|
||||
return _planets.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advance game status by one tick.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void Tick()
|
||||
{
|
||||
CurrentTick++;
|
||||
|
||||
// Handle planets growth.
|
||||
for (int planetId = 0; planetId < _planets.Count; planetId++)
|
||||
{
|
||||
PlanetData planet = _planets[planetId];
|
||||
if (planet.PlayerId is not NeutralPlayerId && CurrentTick % planet.GrowEveryTicks is 0)
|
||||
{
|
||||
planet.Population++;
|
||||
EmitSignal(SignalName.PlanetPopulationChanged, planetId, planet.Population);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle player commands.
|
||||
for (int player_id = 0; player_id < _players.Count; player_id++)
|
||||
{
|
||||
PlayerData player = _players[player_id];
|
||||
if (player.Commands.Count > 0 && CurrentTick >= player.Commands[0].WaitTick)
|
||||
{
|
||||
Command command = player.Commands[^1];
|
||||
player.Commands.RemoveAt(player.Commands.Count - 1);
|
||||
|
||||
switch (command.Type)
|
||||
{
|
||||
case CommandType.DispatchFleet:
|
||||
HandleDispatchFleet(player_id, (DispatchFleetCommand)command);
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Assert(false, $"Unknown command: {command}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle fleets arrival.
|
||||
while (_fleets.Count > 0 && CurrentTick >= _fleets[0].ArrivesAtTick)
|
||||
{
|
||||
FleetData fleet = _fleets[^1];
|
||||
_fleets.RemoveAt(_fleets.Count - 1);
|
||||
|
||||
_handle_fleet_arrival(fleet);
|
||||
}
|
||||
|
||||
EmitSignal(SignalName.GameTicked, CurrentTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch a fleet.
|
||||
/// </summary>
|
||||
/// <param name="playerId"></param>
|
||||
/// <param name="fromPlanetId"></param>
|
||||
/// <param name="toPlanetId"></param>
|
||||
/// <param name="maxCount"></param>
|
||||
public void DispatchFleet(int playerId, int fromPlanetId, int toPlanetId, int maxCount)
|
||||
=> IssueCommand(playerId, new DispatchFleetCommand() {
|
||||
FromPlanetId = fromPlanetId,
|
||||
ToPlanetId = toPlanetId,
|
||||
MaxCount = maxCount,
|
||||
});
|
||||
|
||||
void IssueCommand(int player_id, Command command)
|
||||
{
|
||||
List<Command> commands = _players[player_id].Commands;
|
||||
int index = commands.Select(x => x.WaitTick).ToList().BinarySearch(command.WaitTick);
|
||||
if (index < 0)
|
||||
index = ~index;
|
||||
commands.Insert(index, command);
|
||||
}
|
||||
|
||||
void HandleDispatchFleet(int playerId, DispatchFleetCommand dispatch)
|
||||
{
|
||||
PlanetData from = _planets[dispatch.FromPlanetId];
|
||||
if (from.PlayerId == playerId)
|
||||
{
|
||||
PlanetData to = _planets[dispatch.ToPlanetId];
|
||||
int count = Math.Min(dispatch.MaxCount, from.Population);
|
||||
from.Population -= count;
|
||||
EmitSignal(SignalName.PlanetPopulationChanged, dispatch.FromPlanetId, from.Population);
|
||||
|
||||
FleetData fleet = new() {
|
||||
Count = count,
|
||||
PlayerId = playerId,
|
||||
ToPlanetId = dispatch.ToPlanetId,
|
||||
ArrivesAtTick = CurrentTick + FleetTakeoffPlusLandingTime + Mathf.CeilToInt(from.Position.DistanceTo(to.Position) / FleetSpeed),
|
||||
};
|
||||
|
||||
int index = _fleets.Select(x => x.ArrivesAtTick).ToList().BinarySearch(fleet.ArrivesAtTick);
|
||||
if (index < 0)
|
||||
index = ~index;
|
||||
_fleets.Insert(index, fleet);
|
||||
|
||||
EmitSignal(SignalName.FleetDispatched, dispatch.FromPlanetId, fleet.ToPlanetId, fleet.Count, fleet.PlayerId, CurrentTick, fleet.ArrivesAtTick);
|
||||
}
|
||||
}
|
||||
|
||||
void _handle_fleet_arrival(FleetData fleet)
|
||||
{
|
||||
PlanetData to = _planets[fleet.ToPlanetId];
|
||||
if (fleet.PlayerId == to.PlayerId)
|
||||
{
|
||||
to.Population += fleet.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
to.Population -= fleet.Count;
|
||||
if (to.Population <= 0)
|
||||
{
|
||||
to.PlayerId = fleet.PlayerId;
|
||||
to.Population = Mathf.Abs(to.Population);
|
||||
EmitSignal(SignalName.PlanetPlayerChanged, fleet.ToPlanetId, to.PlayerId);
|
||||
}
|
||||
|
||||
EmitSignal(SignalName.PlanetPopulationChanged, fleet.ToPlanetId, to.Population);
|
||||
}
|
||||
}
|
||||
}
|
18
scripts/Planet.cs
Normal file
18
scripts/Planet.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class Planet : Resource
|
||||
{
|
||||
public enum PlanetType
|
||||
{
|
||||
Terrestrial,
|
||||
}
|
||||
|
||||
[Export]
|
||||
public Vector2 Location { get; set; }
|
||||
|
||||
[Export]
|
||||
public PlanetType Type { get; set; }
|
||||
}
|
17
scripts/Player.cs
Normal file
17
scripts/Player.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class Player : Resource
|
||||
{
|
||||
[Export]
|
||||
public Color Color { get; set; } = Colors.Magenta;
|
||||
|
||||
[Export]
|
||||
public Texture2D Icon { get; set; }
|
||||
|
||||
public virtual void ProcessGameTick(GameLogic game)
|
||||
{
|
||||
}
|
||||
}
|
11
scripts/PlayerAI.cs
Normal file
11
scripts/PlayerAI.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class PlayerAI : Player
|
||||
{
|
||||
public override void ProcessGameTick(GameLogic game)
|
||||
{
|
||||
}
|
||||
}
|
11
scripts/PlayerLocal.cs
Normal file
11
scripts/PlayerLocal.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class PlayerLocal : Player
|
||||
{
|
||||
public override void ProcessGameTick(GameLogic game)
|
||||
{
|
||||
}
|
||||
}
|
31
scripts/Utils.cs
Normal file
31
scripts/Utils.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public static class Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Time-independent damping with exponential-decay.
|
||||
/// </summary>
|
||||
/// <param name="current">Current value.</param>
|
||||
/// <param name="target">Target value.</param>
|
||||
/// <param name="smoothing">The proportion of <paramref name="current"/> remaining after one second, the rest is <paramref name="target"/>.</param>
|
||||
/// <param name="deltaTime">Seconds passed since last invocation.</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static T Damp<T>(T current, T target, float smoothing, double deltaTime)
|
||||
where T : IAdditionOperators<T, T, T>, ISubtractionOperators<T, T, T>, IMultiplyOperators<T, float, T>
|
||||
=> Lerp(current, target, (float)(1d - Math.Pow(smoothing, deltaTime)));
|
||||
|
||||
/// <inheritdoc cref="Damp{T}(T, T, float, double)" />
|
||||
public static Godot.Vector2 Damp(Godot.Vector2 current, Godot.Vector2 target, float smoothing, double deltaTime)
|
||||
=> current.Lerp(target, (float)(1d - Math.Pow(smoothing, deltaTime)));
|
||||
|
||||
/// <inheritdoc cref="Mathf.Lerp(double, double, double)"/>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static T Lerp<T>(T from, T to, float weight)
|
||||
where T : IAdditionOperators<T, T, T>, ISubtractionOperators<T, T, T>, IMultiplyOperators<T, float, T>
|
||||
=> from + (to - from) * weight;
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
class_name GameLogic
|
||||
extends Node
|
||||
|
||||
const LOGIC_TICKS_PER_SECOND := 20.
|
||||
const LOGIC_SECONDS_PER_TICK := 1. / LOGIC_TICKS_PER_SECOND
|
||||
|
||||
## Container for planets.
|
||||
@export var planets_container: Node2D
|
||||
## Container for trails.
|
||||
@export var trails_container: Node2D
|
||||
## Container for fleets.
|
||||
@export var fleets_container: Node2D
|
||||
|
||||
## UI to interact with planets.
|
||||
@export var planets_ui: PlanetsUI
|
||||
|
||||
## Game players.
|
||||
@export var players: Array[Player]
|
||||
|
||||
## Game planets.
|
||||
@export var planets: Array[Planet]
|
||||
|
||||
var _game: GameState
|
||||
var _planet_controls: Array[ControlPlanet]
|
||||
|
||||
var _extra_time: float
|
||||
|
||||
func _ready() -> void:
|
||||
# New game.
|
||||
_game = GameState.new()
|
||||
|
||||
# Add players.
|
||||
for i in range(players.size()):
|
||||
_game.add_player()
|
||||
|
||||
# Add planets.
|
||||
for i in range(planets.size()):
|
||||
# Data.
|
||||
var data := GameState.PlanetData.new()
|
||||
data.position = Vector2i(planets[i].location)
|
||||
data.grow_every_ticks = 10
|
||||
data.player_id = (i + 1) if (i < players.size() - 1) else 0
|
||||
data.population = 0
|
||||
_game.add_planet(data)
|
||||
|
||||
# UI control.
|
||||
var control: ControlPlanet = SceneTemplates.scenes.control_planet.instantiate()
|
||||
control.position = planets[i].location
|
||||
control.player = players[data.player_id]
|
||||
control.population = data.population
|
||||
_planet_controls.push_back(control)
|
||||
planets_container.add_child(control)
|
||||
planets_ui.register_planet(control)
|
||||
|
||||
# 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:
|
||||
_extra_time += delta
|
||||
|
||||
while _extra_time > LOGIC_SECONDS_PER_TICK:
|
||||
_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
|
|
@ -1,132 +0,0 @@
|
|||
class_name GameState
|
||||
extends Resource
|
||||
|
||||
class PlayerData:
|
||||
var commands: Array[Command] = []
|
||||
|
||||
class PlanetData:
|
||||
var position: Vector2i
|
||||
var grow_every_ticks: int
|
||||
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,
|
||||
}
|
||||
|
||||
class Command:
|
||||
var type: CommandType
|
||||
var wait_tick: int
|
||||
|
||||
class DispatchFleedCommand extends Command:
|
||||
var from_planet_id: int
|
||||
var to_planet_id: int
|
||||
var max_count: int
|
||||
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] = []
|
||||
var _fleets: Array[FleetData] = []
|
||||
|
||||
var current_tick: int
|
||||
|
||||
## Add a player to the game.
|
||||
## The first player is expected to be the game master / neutral player,
|
||||
## who does not play and for whom the population never increases.
|
||||
func add_player(data := PlayerData.new()) -> int:
|
||||
_players.push_back(data)
|
||||
return _players.size() - 1
|
||||
|
||||
## Add a planet to the game.
|
||||
func add_planet(data := PlanetData.new()) -> int:
|
||||
_planets.push_back(data)
|
||||
return _planets.size() - 1
|
||||
|
||||
## Advance game status by one tick.
|
||||
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 != 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:
|
||||
var command: Command = player.commands.pop_back()
|
||||
match command.type:
|
||||
CommandType.DISPATCH_FLEET:
|
||||
_handle_dispatch_fleet(player_id, command)
|
||||
_:
|
||||
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.
|
||||
func dispatch_fleet(player_id: int, from_planet_id: int, to_planet_id: int, max_count: int) -> void:
|
||||
var dispatch := DispatchFleedCommand.new()
|
||||
dispatch.from_planet_id = from_planet_id
|
||||
dispatch.to_planet_id = to_planet_id
|
||||
dispatch.max_count = max_count
|
||||
_issue_command(player_id, dispatch)
|
||||
|
||||
func _issue_command(player_id: int, command: Command) -> void:
|
||||
var commands := _players[player_id].commands
|
||||
var index := commands.map(func (x): return x.wait_tick).bsearch(command.wait_tick, false)
|
||||
commands.insert(index, command)
|
||||
|
||||
func _handle_dispatch_fleet(player_id: int, dispatch: DispatchFleedCommand) -> void:
|
||||
var from = _planets[dispatch.from_planet_id]
|
||||
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 -= fleet.count
|
||||
if to.population <= 0:
|
||||
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)
|
|
@ -1,10 +0,0 @@
|
|||
class_name Planet
|
||||
extends Resource
|
||||
|
||||
enum PlanetType {
|
||||
TERRESTRIAL,
|
||||
}
|
||||
|
||||
@export var location: Vector2
|
||||
|
||||
@export var type: PlanetType
|
|
@ -1,9 +0,0 @@
|
|||
class_name Player
|
||||
extends Resource
|
||||
|
||||
@export var color: Color = Color.MAGENTA
|
||||
|
||||
@export var icon: Texture2D
|
||||
|
||||
func process_game_tick(_game: GameLogic) -> void:
|
||||
pass
|
|
@ -1,5 +0,0 @@
|
|||
class_name AIPlayer
|
||||
extends Player
|
||||
|
||||
func process_game_tick(_game: GameLogic) -> void:
|
||||
pass
|
|
@ -1,5 +0,0 @@
|
|||
class_name LocalPlayer
|
||||
extends Player
|
||||
|
||||
func process_game_tick(_game: GameLogic) -> void:
|
||||
pass
|
115
scripts/ui/PlanetsUI.cs
Normal file
115
scripts/ui/PlanetsUI.cs
Normal file
|
@ -0,0 +1,115 @@
|
|||
using Godot;
|
||||
|
||||
namespace SpaceCapture;
|
||||
|
||||
public partial class PlanetsUI : Control
|
||||
{
|
||||
Trail _trail;
|
||||
|
||||
/// <summary>
|
||||
/// Container for trails.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Export]
|
||||
public Node2D TrailsContainer { get; set; }
|
||||
|
||||
[Signal]
|
||||
public delegate void FleetDispatchedEventHandler(ControlPlanet from, ControlPlanet to);
|
||||
|
||||
public void RegisterPlanet(ControlPlanet control)
|
||||
{
|
||||
control.Selected += OnPlanetSelected;
|
||||
control.PointerEntered += OnPlanetPointerEntered;
|
||||
control.PointerExited += OnPlanetPointerExited;
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_trail = SceneTemplates.Scenes.Trail.Instantiate<Trail>();
|
||||
_trail.Visible = false;
|
||||
TrailsContainer.AddChild(_trail);
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
UpdateTrail(delta, false);
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent inputEvent)
|
||||
{
|
||||
if (inputEvent is InputEventMouse e)
|
||||
HandleMouseInput(e);
|
||||
}
|
||||
|
||||
#region Mouse input
|
||||
|
||||
Vector2 _lastCursorPosition;
|
||||
|
||||
void HandleMouseInput(InputEventMouse inputEvent)
|
||||
{
|
||||
if (inputEvent is InputEventMouseMotion e)
|
||||
{
|
||||
// The trail follows the cursor.
|
||||
_lastCursorPosition = e.GlobalPosition;
|
||||
}
|
||||
|
||||
if (inputEvent is InputEventMouseButton e2 && e2.ButtonIndex is MouseButton.Left && e2.IsReleased())
|
||||
{
|
||||
// The trail disappears when no longer dragging.
|
||||
_draggingFromPlanet = false;
|
||||
|
||||
// Detect mouse released on a planet and spawn a fleet.
|
||||
if (_selectedPlanet?.Player is PlayerLocal && _pointedPlanet is not null)
|
||||
EmitSignal(SignalName.FleetDispatched, _selectedPlanet, _pointedPlanet);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Planets
|
||||
|
||||
ControlPlanet _selectedPlanet;
|
||||
ControlPlanet _pointedPlanet;
|
||||
bool _draggingFromPlanet;
|
||||
|
||||
void OnPlanetSelected(ControlPlanet planet)
|
||||
{
|
||||
if (_selectedPlanet is not null)
|
||||
_selectedPlanet.IsSelected = false;
|
||||
|
||||
_selectedPlanet = planet;
|
||||
_selectedPlanet.IsSelected = true;
|
||||
|
||||
_draggingFromPlanet = true;
|
||||
UpdateTrail(0.0, true);
|
||||
}
|
||||
|
||||
void OnPlanetPointerEntered(ControlPlanet planet)
|
||||
{
|
||||
_pointedPlanet = planet;
|
||||
}
|
||||
|
||||
void OnPlanetPointerExited(ControlPlanet planet)
|
||||
{
|
||||
if (_pointedPlanet == planet)
|
||||
_pointedPlanet = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Trail
|
||||
|
||||
void UpdateTrail(double delta, bool snapPosition)
|
||||
{
|
||||
_trail.ShowTrail = _draggingFromPlanet && _selectedPlanet is not null && _selectedPlanet.Player is PlayerLocal;
|
||||
if (_trail.ShowTrail)
|
||||
{
|
||||
_trail.Color = _selectedPlanet.Player.Color;
|
||||
var targetPosition = _pointedPlanet?.GlobalPosition ?? _lastCursorPosition;
|
||||
_trail.StartPosition = _selectedPlanet.GlobalPosition;
|
||||
_trail.EndPosition = snapPosition ? targetPosition : Utils.Damp(_trail.EndPosition, targetPosition, 1e-20f, delta);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
class_name PlanetsUI
|
||||
extends Control
|
||||
|
||||
## Container for trails.
|
||||
@export var trails_container: Node2D
|
||||
|
||||
var _trail: Trail
|
||||
|
||||
signal fleet_dispatched(from: ControlPlanet, to: ControlPlanet)
|
||||
|
||||
func register_planet(control: ControlPlanet) -> void:
|
||||
control.selected.connect(_on_planet_selected)
|
||||
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)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouse:
|
||||
_handle_mouse_input(event)
|
||||
|
||||
#region Mouse input
|
||||
|
||||
var _last_cursor_position: Vector2
|
||||
|
||||
func _handle_mouse_input(event: InputEventMouse) -> void:
|
||||
if event is InputEventMouseMotion:
|
||||
# The trail follows the cursor.
|
||||
_last_cursor_position = event.global_position
|
||||
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_released():
|
||||
# The trail disappears when no longer dragging.
|
||||
_dragging_from_planet = false
|
||||
|
||||
# Detect mouse released on a planet and spawn a fleet.
|
||||
if _selected_planet != null and _selected_planet.player is LocalPlayer and _pointed_planet != null:
|
||||
fleet_dispatched.emit(_selected_planet, _pointed_planet)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Planets
|
||||
|
||||
var _selected_planet: ControlPlanet
|
||||
var _pointed_planet: ControlPlanet
|
||||
var _dragging_from_planet: bool
|
||||
|
||||
func _on_planet_selected(planet: ControlPlanet) -> void:
|
||||
if _selected_planet != null:
|
||||
_selected_planet.is_selected = false
|
||||
|
||||
_selected_planet = planet
|
||||
_selected_planet.is_selected = true
|
||||
|
||||
_dragging_from_planet = true
|
||||
_update_trail(0., true)
|
||||
|
||||
func _on_planet_pointer_entered(planet: ControlPlanet) -> void:
|
||||
_pointed_planet = planet
|
||||
|
||||
func _on_planet_pointer_exited(planet: ControlPlanet) -> void:
|
||||
if _pointed_planet == planet:
|
||||
_pointed_planet = null
|
||||
|
||||
#endregion
|
||||
|
||||
#region Trail
|
||||
|
||||
func _update_trail(delta: float, snap_position: bool) -> void:
|
||||
_trail.show_trail = _dragging_from_planet and _selected_planet != null and _selected_planet.player is LocalPlayer
|
||||
if _trail.show_trail:
|
||||
_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
|
|
@ -1,10 +0,0 @@
|
|||
class_name Utils
|
||||
|
||||
## Time-independent damping with exponential-decay.
|
||||
##
|
||||
## [smoothing] is the proportion of [current] remaining after one second, the
|
||||
## rest is [target]. [delta_time] represents the number of seconds passed since
|
||||
## last invocation.
|
||||
static func damp(current: Variant, target: Variant, smoothing: float, delta_time: float) -> Variant :
|
||||
# https://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
|
||||
return lerp(current, target, 1. - pow(smoothing, delta_time))
|
Loading…
Reference in a new issue