Compare commits

..

No commits in common. "a634da6ecffbf972d369c9e19ff76afb55fcdc1b" and "2bc92d958097fca323de94d89558577b001e8303" have entirely different histories.

20 changed files with 171 additions and 390 deletions

54
gdd.md
View File

@ -8,10 +8,26 @@
* Education
* Supply chains
## Grid
## Interaction Modes
Interaction modes can be changed via keyboard shortcut.
### Selection Mode
This is the default interaction mode.
It can be accessed at any time by pressing the `esc` key.
### Build Mode
Build mode can be entered by pressing the `b` key.
## Critical Path
### Grid
Add a 10x10 grid.
Tiles are highlighted a different colour when the player hovers over them with the cursor.
Tiles can be interacted with, which turns them another colour.
### Diffusion
@ -46,19 +62,7 @@ private float TransferHeat(float t0, float alpha, float nx, float ny, float px,
}
```
## Build Mode
Build mode can be entered by pressing the `b` key.
Build the cursor will start flashing when entering build mode.
Once in build mode, a tile type must be selected to build anything.
Tiles cab be selected with the buttons along the bottom of the screen or by keyboard shortcuts.
The keyboard shortcut to select a tile type is indicated on the button.
To deselect a tile type, press `esc`.
Press `esc` again to exit build mode.
## Tile Types
### Tile Types
Tiles start as wild tiles.
The player can use a UI element or a keyboard shortcut to enable build mode.
@ -67,14 +71,14 @@ In build mode, the player can click on a wile tile to turn it into a developed t
* Wild tiles are green
* Developed tiles are grey
## Heat
### Heat
Developed tiles produce heat while wild tiles absorb it.
Every tile has a floating-point heat value which diffuses to its neighbours.
Add a heat map view which can be toggled or cycled to.
It is possible to cycle the view using a UI element.
## Tile Health
### Tile Health
Add another value to wild tiles: health.
A wild tile can accept a certain amount of heat each tick from surrounding tiles.
@ -92,13 +96,13 @@ Its effectiveness at absorbing heat scales with its remaining health.
Diffusion still occurs to move heat away from depleted wild tiles.
## Rewilding
### Rewilding
Add a rewilding mode option to the UI.
In rewilding mode, the player can click on a developed tile to turn in into a wild tile.
The wild tile starts with 0 health.
## Wild Recovery
### Wild Recovery
Depleted wile tiles can recover health each tick.
The recovery rate is based on the difference between the damage threshold and the actual amount they received.
@ -107,7 +111,7 @@ The recovery rate is increased if the tile has wild neighbours.
neighbour multiplier = lerp(1, max, number of neighbours / 4)
health restored = max((damage threshold - heat received), 0) * neighbour multiplier.
## Developed tile types
### Developed tile types
Split developed tiles into 3 types:
@ -120,19 +124,19 @@ These types generate different amounts of heat:
industrial > commercial > residential
## Journeys
### Journeys
Developed tiles have a chance to generate journeys between them.
A journey instances a vehicle which traverses the map at a constant speed from its origin to its destination tile.
A vehicle adds a constant amount of heat to every tile it traverses.
## Moving In
### Moving In
Residential tiles have two new integer resources, current residents and maximum residents.
Every tick there is a chance for residents to move in to a residential tile if the current number is less than the maximum number of residents.
The initial number of maximm residents per residential tile is 4.
## Jobs
### Jobs
Industrial and commercial tiles have two new integer resources, available jobs and occupied jobs.
The chance for residents to move in depends on there being being fewer occupied than available jobs.
@ -141,12 +145,12 @@ Their chance of generating a journey is proportional to
occupied jobs / available jobs .
## Commutes
### Commutes
A resident moves in to a residential tile and gets a job at an industrial or commercial 'job' tile.
The residential tile regularly generates journeys to the job tile, and the job tile regularly generates journeys in the other direction.
## Comfortable Living Temperatures
### Comfortable Living Temperatures
If a residential tile is too hot, people will not want to live there.
Residential tiles have a critical temperature at which the chance of someone moving into a tile becomes negative, becoming a chance that someone moves out.
@ -157,7 +161,7 @@ When someone moves out, one of the jobs associated with that residential tile is
# Not critical
## Wealth
### Wealth
Journeys generate a new integer tile resource, wealth.
When a journey is completed, the destination tile accrues 1 wealth.

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=10 format=2]
[gd_scene load_steps=11 format=2]
[ext_resource path="res://nodes/grid.tscn" type="PackedScene" id=1]
[ext_resource path="res://nodes/grid_cursor.tscn" type="PackedScene" id=2]
@ -7,7 +7,8 @@
[ext_resource path="res://nodes/interaction_modes/interaction_mode.tscn" type="PackedScene" id=5]
[ext_resource path="res://nodes/ui/build_mode_ui.tscn" type="PackedScene" id=6]
[ext_resource path="res://nodes/interaction_modes/build_mode.tscn" type="PackedScene" id=7]
[ext_resource path="res://nodes/ui/mode_selection_ui.tscn" type="PackedScene" id=9]
[ext_resource path="res://nodes/interaction_modes/selection_mode.tscn" type="PackedScene" id=8]
[ext_resource path="res://nodes/ui/selection_mode_ui.tscn" type="PackedScene" id=9]
[sub_resource type="Curve" id=1]
_data = [ Vector2( 0, 0 ), 0.0, 5.0, 0, 0, Vector2( 0.5, 1 ), 0.0, 0.0, 0, 0, Vector2( 1, 0 ), -4.0, 0.0, 0, 0 ]
@ -24,12 +25,14 @@ DiffusionCoefficient = 0.1
Grid = NodePath("../Grid")
_pulseShape = SubResource( 1 )
[node name="Interaction Modes" parent="." instance=ExtResource( 5 )]
_buildModePath = NodePath("Build Mode")
[node name="Interaction Mode" parent="." instance=ExtResource( 5 )]
_buildModePath = NodePath("../Build Mode")
_selectionModePath = NodePath("../Selection Mode")
[node name="Build Mode" parent="Interaction Modes" instance=ExtResource( 7 )]
_gridPath = NodePath("../../Grid")
_cursorPath = NodePath("../../Cursor")
[node name="Build Mode" parent="." instance=ExtResource( 7 )]
_key = 66
[node name="Selection Mode" parent="." instance=ExtResource( 8 )]
[node name="UI" type="Control" parent="."]
anchor_right = 1.0
@ -39,19 +42,20 @@ mouse_filter = 2
[node name="Debug" parent="UI" instance=ExtResource( 3 )]
[node name="Build Mode" parent="UI" instance=ExtResource( 6 )]
_buildModePath = NodePath("../../Interaction Modes/Build Mode")
visible = false
_buildModePath = NodePath("../../Build Mode")
[node name="Mode Selection" parent="UI" instance=ExtResource( 9 )]
[node name="Selection Mode" parent="UI" instance=ExtResource( 9 )]
_selectionModePath = NodePath("../../Selection Mode")
[connection signal="OnPauseChanged" from="Clock" to="UI/Debug" method="_on_Clock_OnPauseChanged"]
[connection signal="OnTick" from="Clock" to="Grid" method="_on_Clock_OnTick"]
[connection signal="OnTick" from="Clock" to="UI/Debug" method="_on_Clock_OnTick"]
[connection signal="OnInteractionModeChanged" from="Interaction Modes" to="Cursor" method="_on_Interaction_Mode_OnInteractionModeChanged"]
[connection signal="OnInteractionModeChanged" from="Interaction Modes" to="UI/Debug" method="_on_Interaction_Mode_OnInteractionModeChanged"]
[connection signal="OnModeEntered" from="Interaction Modes/Build Mode" to="Interaction Modes" method="_on_Build_Mode_OnModeEntered"]
[connection signal="OnModeEntered" from="Interaction Modes/Build Mode" to="UI/Mode Selection" method="EnableBuildMode"]
[connection signal="OnModeExited" from="Interaction Modes/Build Mode" to="Interaction Modes" method="_on_Build_Mode_OnModeExited"]
[connection signal="SelectedTileTypeChanged" from="Interaction Modes/Build Mode" to="UI/Build Mode" method="UpdateButtonToggleState"]
[connection signal="OnExit" from="UI/Build Mode" to="Interaction Modes" method="Reset"]
[connection signal="OnExit" from="UI/Build Mode" to="UI/Mode Selection" method="Enable"]
[connection signal="BuildModeEnabled" from="UI/Mode Selection" to="Interaction Modes/Build Mode" method="Enable"]
[connection signal="OnInteractionModeChanged" from="Interaction Mode" to="Cursor" method="_on_Interaction_Mode_OnInteractionModeChanged"]
[connection signal="OnInteractionModeChanged" from="Interaction Mode" to="UI/Debug" method="_on_Interaction_Mode_OnInteractionModeChanged"]
[connection signal="OnModeEntered" from="Build Mode" to="Interaction Mode" method="_on_Build_Mode_OnModeEntered"]
[connection signal="OnModeExited" from="Build Mode" to="Interaction Mode" method="_on_Build_Mode_OnModeExited"]
[connection signal="OnModeEntered" from="Selection Mode" to="Interaction Mode" method="_on_Selection_Mode_OnModeEntered"]
[connection signal="OnModeExited" from="Selection Mode" to="Interaction Mode" method="_on_Selection_Mode_OnModeExited"]
[connection signal="OnExit" from="UI/Build Mode" to="Interaction Mode" method="Reset"]
[connection signal="EnableBuildMode" from="UI/Selection Mode" to="Build Mode" method="Enable"]

View File

@ -1,9 +1,6 @@
[gd_scene load_steps=3 format=2]
[gd_scene load_steps=2 format=2]
[ext_resource path="res://scripts/interaction_modes/BuildMode.cs" type="Script" id=1]
[ext_resource path="res://resources/tile_types/tile_type_collections/buildable_tiles.tres" type="Resource" id=2]
[node name="Build Mode" type="Node"]
script = ExtResource( 1 )
_key = 66
_buildableTilesResource = ExtResource( 2 )

View File

@ -1,3 +1,7 @@
[gd_scene format=2]
[gd_scene load_steps=2 format=2]
[ext_resource path="res://scripts/interaction_modes/SelectionMode.cs" type="Script" id=1]
[node name="Selection Mode" type="Node"]
script = ExtResource( 1 )
_key = 16777217

View File

@ -3,16 +3,24 @@
[ext_resource path="res://scripts/ui/BuildModeUI.cs" type="Script" id=1]
[node name="Build Mode" type="Control"]
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
margin_top = -600.0
margin_bottom = -600.0
script = ExtResource( 1 )
[node name="Exit" type="Button" parent="."]
anchor_top = 1.0
anchor_bottom = 1.0
margin_top = -40.0
margin_right = 40.0
grow_vertical = 0
margin_left = 2.0
margin_top = 562.0
margin_right = 38.0
margin_bottom = 598.0
text = "Exit"
[connection signal="pressed" from="Exit" to="." method="Exit"]
[node name="Developed" type="Button" parent="."]
margin_left = 54.0
margin_top = 562.0
margin_right = 90.0
margin_bottom = 598.0
text = "Developed"
[connection signal="pressed" from="Exit" to="." method="_on_Exit_pressed"]

View File

@ -1,8 +1,8 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://scripts/ui/ModeSelectionUI.cs" type="Script" id=1]
[ext_resource path="res://scripts/ui/SelectionModeUI.cs" type="Script" id=1]
[node name="Mode Selection UI" type="Control"]
[node name="Selection Mode UI" type="Control"]
anchor_top = 1.0
anchor_bottom = 1.0
margin_top = -600.0
@ -16,4 +16,4 @@ margin_right = 38.0
margin_bottom = 598.0
text = "Build Mode"
[connection signal="pressed" from="Build Mode" to="." method="EnableBuildMode"]
[connection signal="pressed" from="Build Mode" to="." method="_on_Build_Mode_pressed"]

View File

@ -1,10 +0,0 @@
[gd_resource type="Resource" load_steps=2 format=2]
[ext_resource path="res://scripts/TileType.cs" type="Script" id=1]
[resource]
script = ExtResource( 1 )
Name = "Developed"
BuildLabel = "[D]eveloped"
Key = 68
Color = Color( 0.372549, 0.372549, 0.372549, 1 )

View File

@ -1,8 +0,0 @@
[gd_resource type="Resource" load_steps=3 format=2]
[ext_resource path="res://scripts/TileTypeCollection.cs" type="Script" id=1]
[ext_resource path="res://resources/tile_types/developed.tres" type="Resource" id=2]
[resource]
script = ExtResource( 1 )
_tileTypeResources = [ ExtResource( 2 ) ]

View File

@ -21,9 +21,6 @@ public class GridCursor : Sprite
private const string T = "t";
public int X { get; private set; }
public int Y { get; private set; }
public override void _Ready()
{
_grid = GetNode<WorldGrid>(Grid);
@ -38,14 +35,29 @@ public class GridCursor : Sprite
{
var pos = mouseMoveEvent.Position;
_grid.GetGridPos(pos, out var x, out var y);
X = x;
Y = y;
this.Visible = _grid.IsInBounds(X, Y);
var position = new Vector2(X + .5f, Y + .5f) * _grid.CellSize;
this.Visible = _grid.IsInBounds(x, y);
var position = new Vector2(x + .5f, y + .5f) * _grid.CellSize;
this.Position = position;
return;
}
if (@event is InputEventMouseButton mouseButtonEvent)
{
var button = (ButtonList)mouseButtonEvent.ButtonIndex;
if (button != ButtonList.Left)
return;
if (!mouseButtonEvent.Pressed)
{
_material.SetShaderParam(T, 0f);
return;
}
var pos = mouseButtonEvent.Position;
_grid.GetGridPos(pos, out var x, out var y);
_grid.SetTileValue(x, y, 1.0f);
_material.SetShaderParam(T, 1f);
}
}
public override void _Process(float delta)

View File

@ -5,5 +5,4 @@ public struct Tile
public bool isHighlighted;
public float value;
public ShaderMaterial material;
public TileType type;
}

View File

@ -1,21 +0,0 @@
using Godot;
public class TileType : Resource
{
[Export]
public string Name { get; private set; }
[Export]
public string BuildLabel { get; private set; }
[Export]
public KeyList Key { get; private set; }
[Export]
public Color Color { get; private set; }
public override string ToString()
{
return Name;
}
}

View File

@ -1,46 +0,0 @@
using Godot;
using System;
using System.Collections;
using System.Collections.Generic;
public class TileTypeCollection : Resource, IReadOnlyList<TileType>
{
[Export]
private List<Resource> _tileTypeResources = new List<Resource>();
private List<TileType> TileTypes
{
get
{
if (_tileTypes != null)
return _tileTypes;
_tileTypes = new List<TileType>();
foreach (var resource in _tileTypeResources)
{
if (!(resource is TileType))
throw new InvalidCastException($"{resource} must be a {typeof(TileType)}");
_tileTypes.Add((TileType)resource);
}
return _tileTypes;
}
}
private List<TileType> _tileTypes = null;
public TileType this[int index] => TileTypes[index];
public int Count => TileTypes.Count;
public IEnumerator<TileType> GetEnumerator()
{
return TileTypes.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return TileTypes.GetEnumerator();
}
}

View File

@ -52,14 +52,12 @@ public class WorldGrid : Node2D
_tiles[x, y].isHighlighted ^= true;
}
public void SetTileType(int x, int y, TileType tileType)
public void SetTileValue(int x, int y, float value)
{
if (!IsInBounds(x, y))
return;
GD.Print($"set ({x}, {y}) to {tileType}");
_tiles[x, y].type = tileType;
_tiles[x, y].material.SetShaderParam("lowColor", tileType.Color);
_tiles[x, y].value = value;
}
public void GetGridPos(Vector2 position, out int x, out int y)

View File

@ -3,130 +3,7 @@ using System;
public class BuildMode : Mode
{
[Signal]
delegate void SelectedTileTypeChanged();
private const int NO_TILE_TYPE_SELECTED = -1;
[Export]
private NodePath _gridPath;
private WorldGrid _grid;
[Export]
private NodePath _cursorPath;
private GridCursor _cursor;
[Export]
private Resource _buildableTilesResource;
public TileTypeCollection BuildableTiles { get; private set; }
public TileType SelectedTileType
{
get => _selectedTileType;
set
{
if (_selectedTileType == value)
return;
if (value != null)
{
GD.Print($"{GetType()}: selected {value} tile type");
}
else if (_selectedTileType != null)
{
GD.Print($"{GetType()}: cleared tile selection");
}
_selectedTileType = value;
EmitSignal(nameof(SelectedTileTypeChanged), _selectedTileType);
}
}
private TileType _selectedTileType = null;
public override void _Ready()
{
base._Ready();
BuildableTiles = (TileTypeCollection)_buildableTilesResource;
foreach (var tt in BuildableTiles)
{
GD.Print(tt.Name);
}
_grid = GetNode<WorldGrid>(_gridPath);
_cursor = GetNode<GridCursor>(_cursorPath);
}
private void HandleKeyInput(InputEventKey keyEvent)
{
if (!(keyEvent.Pressed))
return;
var key = (KeyList)keyEvent.Scancode;
if (key == KeyList.Escape)
{
if (SelectedTileType != null)
{
SelectedTileType = null;
return;
}
Disable();
}
foreach (var tt in BuildableTiles)
{
if (key != tt.Key)
continue;
SelectedTileType = tt;
}
}
private void HandleMouseInput(InputEventMouse mouseEvent)
{
if (!(mouseEvent is InputEventMouseButton buttonEvent))
return;
if (!buttonEvent.Pressed)
return;
var button = (ButtonList)buttonEvent.ButtonIndex;
if (button != ButtonList.Left)
return;
if (SelectedTileType == null)
return;
_grid.SetTileType(_cursor.X, _cursor.Y, SelectedTileType);
}
public override void _Input(InputEvent @event)
{
base._Input(@event);
if (!Active)
return;
if (@event is InputEventKey keyEvent)
{
HandleKeyInput(keyEvent);
return;
}
if (@event is InputEventMouse mouseEvent)
{
HandleMouseInput(mouseEvent);
return;
}
}
protected override void OnEnabled()
{
}
protected override void OnDisabled()
{
SelectedTileType = null;
}
public int SelectedTileType { get; set; } = NO_TILE_TYPE_SELECTED;
}

View File

@ -6,52 +6,70 @@ public class InteractionModes : Node
[Export]
private NodePath _buildModePath;
private BuildMode _buildMode;
[Export]
private NodePath _selectionModePath;
private SelectionMode _selectionMode;
[Signal]
delegate void OnInteractionModeChanged(Mode oldMode, Mode newMode);
private Mode Mode
{
get => _current;
set
{
if (_current == value)
return;
if (value != null)
{
GD.Print($"set interaction mode: {value}");
}
else if (_current != null)
{
GD.Print($"clear interaction mode");
}
var old = _current;
_current = value;
EmitSignal(nameof(OnInteractionModeChanged), old, _current);
}
}
private Mode _current = null;
public override void _Ready()
{
base._Ready();
_selectionMode = GetNode<SelectionMode>(_selectionModePath);
_buildMode = GetNode<BuildMode>(_buildModePath);
}
public override void _Process(float delta)
{
base._Process(delta);
if (_current == null)
{
Reset();
}
}
public void _on_Build_Mode_OnModeEntered()
{
Mode = _buildMode;
_selectionMode.Disable();
ChangeMode(_selectionMode, _buildMode);
}
public void _on_Build_Mode_OnModeExited()
{
Mode = null;
}
public void _on_Selection_Mode_OnModeEntered()
{
_buildMode.Disable();
ChangeMode(_buildMode, _selectionMode);
}
public void _on_Selection_Mode_OnModeExited()
{
}
private void ChangeMode(Mode oldMode, Mode newMode)
{
if (oldMode == newMode)
{
throw new InvalidOperationException();
}
if (_current != null && oldMode != _current)
{
throw new InvalidOperationException();
}
_current = newMode;
EmitSignal(nameof(OnInteractionModeChanged), oldMode, newMode);
}
public void Reset()
{
_buildMode.Disable();
_selectionMode.Enable();
}
}

View File

@ -50,16 +50,11 @@ public abstract class Mode : Node
public void Enable()
{
Active = true;
OnEnabled();
}
protected abstract void OnEnabled();
public void Disable()
{
Active = false;
OnDisabled();
}
protected abstract void OnDisabled();
public override string ToString()
{

View File

@ -2,12 +2,5 @@ using Godot;
using System;
public class SelectionMode : Mode
{
protected override void OnDisabled()
{
}
protected override void OnEnabled()
{
}
}

View File

@ -1,6 +1,5 @@
using Godot;
using System;
using System.Collections.Generic;
public class BuildModeUI : Control
{
@ -11,63 +10,19 @@ public class BuildModeUI : Control
private NodePath _buildModePath;
private BuildMode _buildMode;
private readonly Dictionary<TileType, Button> _typeButtons
= new Dictionary<TileType, Button>();
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
_buildMode = GetNode<BuildMode>(_buildModePath);
var buttonHeight = 40;
var tileType = _buildMode.BuildableTiles[0];
int x = 50;
SpawnButton(tileType, buttonHeight, ref x);
Visible = false;
}
private void UpdateButtonToggleState(TileType tileType)
public void _on_Exit_pressed()
{
foreach (var kvp in _typeButtons)
{
var button = _typeButtons[kvp.Key];
button.SetPressedNoSignal(kvp.Key == tileType);
}
Exit();
}
private void SpawnButton(TileType tileType, int buttonHeight, ref int x)
{
const int MARGIN = 5;
var button = new Button();
this.AddChild(button);
button.Text = tileType.BuildLabel;
var parameters = new Godot.Collections.Array(button, tileType);
button.Connect("pressed", this, nameof(SelectTileType), parameters);
button.SetAnchorsPreset(LayoutPreset.BottomLeft);
button.MarginBottom = 0;
var size = button.RectSize;
button.RectSize = new Vector2(size.x, buttonHeight);
button.ToggleMode = true;
button.SetPosition(new Vector2(x, -buttonHeight));
x += Mathf.RoundToInt(size.x) + MARGIN;
_typeButtons[tileType] = button;
}
private void SelectTileType(Button button, TileType tileType)
{
_buildMode.SelectedTileType = button.Pressed
? tileType
: null;
}
public void Exit()
private void Exit()
{
EmitSignal(nameof(OnExit));
}

View File

@ -1,24 +0,0 @@
using Godot;
using System;
public class ModeSelectionUI : Control
{
[Signal]
delegate void BuildModeEnabled();
public override void _Process(float delta)
{
base._Process(delta);
}
public void EnableBuildMode()
{
EmitSignal(nameof(BuildModeEnabled));
Visible = false;
}
public void Enable()
{
Visible = true;
}
}

View File

@ -0,0 +1,26 @@
using Godot;
using System;
public class SelectionModeUI : Control
{
[Signal]
delegate void EnableBuildMode();
[Export]
private NodePath _selectionModePath;
public SelectionMode _selectionMode;
public void _on_Build_Mode_pressed() => EmitSignal(nameof(EnableBuildMode));
public override void _Ready()
{
_selectionMode = GetNode<SelectionMode>(_selectionModePath);
}
public override void _Process(float delta)
{
base._Process(delta);
Visible = _selectionMode.Active;
}
}