├── .gitattributes
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── BuildingSystem
├── LICENSE.md
├── Scripts
│ ├── BuildableInstance.cs
│ ├── BuildingSystem.cs
│ ├── BuildingSystemGrid.cs
│ ├── EventBus.cs
│ ├── GridCell.cs
│ ├── MouseObject.cs
│ ├── MouseTile.cs
│ ├── Resources
│ │ ├── BuildableResource.cs
│ │ └── BuildableResourceLibrary.cs
│ ├── SaveSystem
│ │ ├── BSSaveClasses.cs
│ │ ├── BSSaveSystem.cs
│ │ ├── SaveExtensions.cs
│ │ └── SaveSystem.cs
│ ├── UI
│ │ ├── Blur.cs
│ │ ├── InfoInterface.cs
│ │ ├── LoadInterface.cs
│ │ ├── MainMenu.cs
│ │ ├── ObjectMenu.cs
│ │ ├── SaveInterface.cs
│ │ └── Slot.cs
│ └── Utils
│ │ ├── AnimationUtils.cs
│ │ ├── BSContants.cs
│ │ ├── BSEnums.cs
│ │ ├── BSUtils.cs
│ │ ├── CameraController.cs
│ │ ├── CryptoUtils.cs
│ │ └── import_objects.gd
├── assets
│ ├── campfire_pit.tscn
│ ├── floor.tscn
│ ├── kenny_sifi.glb
│ ├── kenny_sifi_colormap.png
│ ├── kenny_survival.glb
│ ├── kenny_survival_colormap.png
│ ├── resources
│ │ ├── complete_object_library.tres
│ │ ├── sifi
│ │ │ ├── bed_double.tres
│ │ │ ├── chair_armrest.tres
│ │ │ ├── computer.tres
│ │ │ ├── floor.tres
│ │ │ ├── floor_big.tres
│ │ │ ├── floor_detail.tres
│ │ │ ├── floor_panel.tres
│ │ │ ├── floor_wide.tres
│ │ │ ├── rail.tres
│ │ │ ├── rail_narrow.tres
│ │ │ ├── structure_panel.tres
│ │ │ ├── structure_panel_big.tres
│ │ │ ├── table.tres
│ │ │ ├── table_display_planet.tres
│ │ │ ├── table_display_small.tres
│ │ │ ├── wall.tres
│ │ │ ├── wall_banner.tres
│ │ │ ├── wall_door.tres
│ │ │ ├── wall_door_wide.tres
│ │ │ ├── wall_narrow.tres
│ │ │ ├── wall_pillar.tres
│ │ │ ├── wall_pillar_banner.tres
│ │ │ ├── wall_window_banner.tres
│ │ │ ├── wall_window_closed.tres
│ │ │ └── wall_window_open.tres
│ │ ├── sifi_objects_library.tres
│ │ ├── survival
│ │ │ ├── campfire_pit.tres
│ │ │ ├── chest.tres
│ │ │ ├── fence_doorway.tres
│ │ │ ├── fence_fortified.tres
│ │ │ ├── floor.tres
│ │ │ └── floor_old.tres
│ │ └── survival_objects_library.tres
│ ├── scenes
│ │ ├── sifi
│ │ │ ├── bed_double.tscn
│ │ │ ├── chair_armrest.tscn
│ │ │ ├── computer.tscn
│ │ │ ├── floor.tscn
│ │ │ ├── floor_big.tscn
│ │ │ ├── floor_detail.tscn
│ │ │ ├── floor_panel.tscn
│ │ │ ├── floor_wide.tscn
│ │ │ ├── rail.tscn
│ │ │ ├── rail_narrow.tscn
│ │ │ ├── structure_panel.tscn
│ │ │ ├── structure_panel_big.tscn
│ │ │ ├── table.tscn
│ │ │ ├── table_display_planet.tscn
│ │ │ ├── table_display_small.tscn
│ │ │ ├── wall.tscn
│ │ │ ├── wall_banner.tscn
│ │ │ ├── wall_door.tscn
│ │ │ ├── wall_door_wide.tscn
│ │ │ ├── wall_narrow.tscn
│ │ │ ├── wall_pillar.tscn
│ │ │ ├── wall_pillar_banner.tscn
│ │ │ ├── wall_window_banner.tscn
│ │ │ ├── wall_window_closed.tscn
│ │ │ └── wall_window_open.tscn
│ │ └── survival
│ │ │ ├── campfire_pit.tscn
│ │ │ ├── chest.tscn
│ │ │ ├── fence_doorway.tscn
│ │ │ ├── fence_fortified.tscn
│ │ │ ├── floor.tscn
│ │ │ └── floor_old.tscn
│ ├── shaders
│ │ ├── grid_lines.gdshader
│ │ └── menu_blur.gdshader
│ └── textures
│ │ ├── grass.png
│ │ ├── grid_cell_transparent.png
│ │ ├── sifi
│ │ ├── bed-double.png
│ │ ├── chair-armrest.png
│ │ ├── computer.png
│ │ ├── floor-detail.png
│ │ ├── floor-panel.png
│ │ ├── floor.png
│ │ ├── rail-narrow.png
│ │ ├── rail.png
│ │ ├── structure-panel.png
│ │ ├── table-display-planet.png
│ │ ├── table-display-small.png
│ │ ├── table.png
│ │ ├── wall-banner.png
│ │ ├── wall-door.png
│ │ ├── wall-pillar.png
│ │ ├── wall-window.png
│ │ └── wall.png
│ │ └── survival
│ │ ├── campfire-pit.png
│ │ ├── chest.png
│ │ ├── fence-doorway.png
│ │ ├── fence-fortified.png
│ │ ├── floor-old.png
│ │ └── floor.png
├── demo_scene.tscn
├── icon.png
├── scenes
│ ├── building_system.tscn
│ ├── building_system_grid.tscn
│ ├── event_bus.tscn
│ └── mouse_tile.tscn
├── screenshots
│ ├── .gdignore
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ └── 5.png
└── ui
│ ├── object_menu.tscn
│ └── slot.tscn
├── GodotInGameBuildingSystem.csproj
├── GodotInGameBuildingSystem.sln
├── LICENSE
├── Readme.md
├── docfx.json
├── docs
└── GodotInGameBuildingSystem.xml
├── icon.svg
└── project.godot
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Normalize EOL for all files that Git considers text files.
2 | * text=auto eol=lf
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot 4+ specific ignores
2 | .godot/
3 | # Godot-specific ignores
4 | **/*.import
5 | export_presets.cfg
6 | .export/
7 | # Mono-specific ignores
8 | .mono/
9 | # Ignore Godot user-specific files
10 | *.tscn.user
11 | *.tres.user
12 | *.godot/user.scn
13 | *.user
14 | *.lock
15 | # Ignore system files
16 | .DS_Store/
17 | Thumbs.db
18 | # Ignore build results
19 | bin/
20 | obj/
21 | # Addons (ignore unless you're developing the addon)
22 | addons/
23 | # If using VSCode, you might want to ignore the .vscode directory
24 | # .vscode/
25 |
26 | # assets
27 | _site
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "configurations": [
4 | {
5 | "name": "Launch",
6 | "type": "coreclr",
7 | "request": "launch",
8 | "preLaunchTask": "build",
9 | "program": "${env:GODOT4}",
10 | "cwd": "${workspaceFolder}",
11 | "console": "internalConsole",
12 | "stopAtEntry": false,
13 | "args": [
14 | "--path",
15 | "${workspaceRoot}"
16 | ]
17 | },
18 | {
19 | "name": "Launch (Select Scene)",
20 | "type": "coreclr",
21 | "request": "launch",
22 | "preLaunchTask": "build",
23 | "program": "${env:GODOT4}",
24 | "cwd": "${workspaceFolder}",
25 | "console": "internalConsole",
26 | "stopAtEntry": false,
27 | "args": [
28 | "--path",
29 | "${workspaceRoot}",
30 | "${command:godot.csharp.getLaunchScene}"
31 | ]
32 | },
33 | {
34 | "name": "Launch Editor",
35 | "type": "coreclr",
36 | "request": "launch",
37 | "preLaunchTask": "build",
38 | "program": "${env:GODOT4}",
39 | "cwd": "${workspaceFolder}",
40 | "console": "internalConsole",
41 | "stopAtEntry": false,
42 | "args": [
43 | "--path",
44 | "${workspaceRoot}",
45 | "--editor"
46 | ]
47 | },
48 | {
49 | "name": "Attach to Process",
50 | "type": "coreclr",
51 | "request": "attach"
52 | }
53 | ]
54 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "shell",
8 | "args": [
9 | "build",
10 | "/property:GenerateFullPaths=true",
11 | "/consoleloggerparameters:NoSummary"
12 | ],
13 | "group": {
14 | "kind": "build",
15 | "isDefault": true
16 | },
17 | "presentation": {
18 | "reveal": "silent"
19 | },
20 | "problemMatcher": "$msCompile"
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/BuildingSystem/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Marko Dmitrovic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/BuildableInstance.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Godot.GodotInGameBuildingSystem;
4 |
5 | /// Represents an instance of a buildable object in the grid building system.
6 | public partial class BuildableInstance : Node3D
7 | {
8 | /// Gets the buildable resource associated with this instance.
9 | public BuildableResource BuildableResource { get; private set; }
10 |
11 | /// Gets the object instance of the buildable resource.
12 | public Node3D ObjectInstance { get; private set; }
13 |
14 | private StaticBody3D _body;
15 | private CollisionShape3D _collider;
16 | private MeshInstance3D _demolitionVisual;
17 | // We have a reference to cells this object is in.
18 | // This is an optimization, similar to Godot parent object, but here we have multiple parents.
19 | private List _cells;
20 |
21 | /// Creates a new instance of the class with the specified buildable resource and layer mask.
22 | /// This will instantiate the 3D object of the buildable resource and create a collider for it.
23 | /// The buildable resource.
24 | /// The layer mask.
25 | /// The created .
26 | public static BuildableInstance Create(BuildableResource resource, uint layerMask)
27 | {
28 | var instance = new BuildableInstance();
29 | instance.Initialize(resource, layerMask);
30 | return instance;
31 | }
32 |
33 | private void Initialize(BuildableResource resource, uint layerMask)
34 | {
35 | ObjectInstance = resource.Object3DModel.Instantiate();
36 | AddChild(ObjectInstance);
37 | BuildableResource = resource;
38 | _cells = new();
39 | CreateCollider(layerMask);
40 | CreateDemolishVisual();
41 | }
42 |
43 | /// Adds a grid cell to the buildable instance.
44 | /// The grid cell to add.
45 | public void AddCell(GridCell cell)
46 | {
47 | _cells.Add(cell);
48 | }
49 |
50 | /// Clears the buildable instance and removes it from the grid cells.
51 | public void ClearObject()
52 | {
53 | foreach (var cell in _cells)
54 | {
55 | if (BuildableResource.SnapBehaviour == SnapBehaviour.Ground)
56 | {
57 | cell.ClearGroundObject();
58 | }
59 | else
60 | {
61 | cell.ClearWallObject(this);
62 | }
63 | }
64 | _cells = new();
65 | ObjectInstance.QueueFree();
66 | QueueFree();
67 | }
68 |
69 | /// Sets the demolition view of the buildable instance.
70 | /// A value indicating whether the demolition view is enabled.
71 | public void SetDemolitionView(bool enabled)
72 | {
73 | if (enabled)
74 | {
75 | _demolitionVisual.Visible = true;
76 | ObjectInstance.Visible = false;
77 | }
78 | else
79 | {
80 | _demolitionVisual.Visible = false;
81 | ObjectInstance.Visible = true;
82 | }
83 | }
84 |
85 | private void CreateCollider(uint layerMask)
86 | {
87 | var shape = new BoxShape3D
88 | {
89 | Size = GetSize()
90 | };
91 |
92 | _collider = new CollisionShape3D
93 | {
94 | Shape = shape
95 | };
96 | _body = new StaticBody3D
97 | {
98 | CollisionLayer = layerMask
99 | };
100 | _body.AddChild(_collider);
101 |
102 | AddChild(_body);
103 | // Optionally set floor colider offset based on the thickness of the floor
104 | Vector3 offset = BuildableResource.SnapBehaviour == SnapBehaviour.Wall ? new Vector3(0, (float)BuildableResource.Size.Y / 2, 0) : Vector3.Zero;
105 | _collider.Position = offset;
106 | }
107 |
108 | private void CreateDemolishVisual()
109 | {
110 | BoxMesh cubeMesh = new()
111 | {
112 | Size = GetSize()
113 | };
114 |
115 | StandardMaterial3D material = new()
116 | {
117 | AlbedoColor = new Color(Colors.DarkRed, 0.8f),
118 | Transparency = BaseMaterial3D.TransparencyEnum.Alpha,
119 | };
120 |
121 | _demolitionVisual = new()
122 | {
123 | Mesh = cubeMesh,
124 | MaterialOverride = material
125 | };
126 |
127 | AddChild(_demolitionVisual);
128 | _demolitionVisual.Visible = false;
129 | // Same as collider
130 | Vector3 offset = BuildableResource.SnapBehaviour == SnapBehaviour.Wall ? new Vector3(0, (float)BuildableResource.Size.Y / 2, 0) : Vector3.Zero;
131 | _demolitionVisual.Position = offset;
132 | }
133 |
134 | private Vector3 GetSize()
135 | {
136 | var size = new Vector3(1, 1, 1);
137 | switch (BuildableResource.SnapBehaviour)
138 | {
139 | case SnapBehaviour.Ground:
140 | size = new Vector3(BuildableResource.Size.X, 0.2f, BuildableResource.Size.Z);
141 | break;
142 | case SnapBehaviour.Wall:
143 | size = new Vector3(BuildableResource.Size.X, BuildableResource.Size.Y, 0.2f);
144 | break;
145 | case SnapBehaviour.Free:
146 | {
147 | var meshInstance = BSUtils.FindMeshInstance(ObjectInstance);
148 | if (meshInstance != null)
149 | {
150 | Aabb aabb = meshInstance.Mesh.GetAabb();
151 | Vector3 freeSize = aabb.Size;
152 | GD.Print("Mesh size: " + freeSize);
153 | size = new Vector3(freeSize.X, freeSize.Y, freeSize.Z);
154 | }
155 | }
156 | break;
157 | default:
158 | break;
159 | }
160 | return size;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/BuildingSystemGrid.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Godot.GodotInGameBuildingSystem;
4 | ///
5 | /// Represents a grid-based building system in a 3D space.
6 | ///
7 | public partial class BuildingSystemGrid : Node3D
8 | {
9 | ///
10 | /// Gets or sets the array of grid cells in the building system.
11 | ///
12 | public GridCell[] GridCells { get; private set; }
13 | private int _xSize = 200;
14 | private int _zSize = 200;
15 | private float _cellSize = 1f;
16 | private float _cellHeight = 3f;
17 | private CollisionShape3D _collisionShape3D;
18 | private StaticBody3D _ground;
19 | private uint _floorLayerMask;
20 | private uint _wallLayerMask;
21 | private MeshInstance3D _gridVisual;
22 |
23 | ///
24 | public override void _Ready()
25 | {
26 | _ground = GetNode("GroundStaticBody3D");
27 | _collisionShape3D = GetNode("GroundStaticBody3D/Ground");
28 | _gridVisual = GetNode("GridVisual");
29 | }
30 |
31 | /// Initializes the building system grid with the specified parameters.
32 | /// The size of the grid along the X-axis.
33 | /// The size of the grid along the Z-axis.
34 | /// The size of each grid cell.
35 | /// The height of each grid cell.
36 | /// The layer mask for the ground objects.
37 | /// The layer mask for the floor objects.
38 | /// The layer mask for the wall objects.
39 | public void Initialize(
40 | int xSize,
41 | int zSize,
42 | float cellSize,
43 | float cellHeight,
44 | uint groundLayerMask,
45 | uint floorLayerMask,
46 | uint wallLayerMask
47 | )
48 | {
49 | _xSize = xSize;
50 | _zSize = zSize;
51 | _cellSize = cellSize;
52 | _cellHeight = cellHeight;
53 | GridCells = new GridCell[_xSize * _zSize];
54 | for (int i = 0; i < GridCells.Length; i++)
55 | {
56 | GridCells[i] = new GridCell();
57 | }
58 |
59 | _floorLayerMask = floorLayerMask;
60 | _wallLayerMask = wallLayerMask;
61 | _ground.CollisionLayer = groundLayerMask;
62 |
63 | }
64 |
65 | /// Sets the active state of the building system grid.
66 | /// The active state of the building system grid.
67 |
68 | public void SetActive(bool isActive)
69 | {
70 | _collisionShape3D.Disabled = !isActive;
71 | _gridVisual.Visible = isActive;
72 | }
73 |
74 | /// Gets the global position of a cell in the grid.
75 | /// The X-coordinate of the cell.
76 | /// The Z-coordinate of the cell.
77 | /// The global position of the cell.
78 | public Vector3 GetGlobalPosition(int x, int z)
79 | {
80 | return new Vector3(x, 0, z) * _cellSize + GlobalPosition - new Vector3(_xSize / 2, 0, _zSize / 2);
81 | }
82 |
83 | /// Gets the global position of a cell in the grid.
84 | /// The X-coordinate of the cell.
85 | /// The Z-coordinate of the cell.
86 | /// The global position of the cell.
87 | public Vector3 GetCellGlobalPosition(int x, int z)
88 | {
89 | return new Vector3(x, 0, z) * _cellSize + GlobalPosition - new Vector3(_xSize / 2, 0, _zSize / 2) + new Vector3(_cellSize / 2, 0, _cellSize / 2); ;
90 | }
91 |
92 | /// Gets the grid position of a global position.
93 | /// The global position.
94 | /// The grid position.
95 | public Vector2I GetGridPosition(Vector3 globalPosition)
96 | {
97 | int x = Mathf.FloorToInt((globalPosition.X - GlobalPosition.X) / _cellSize) + _xSize / 2;
98 | int z = Mathf.FloorToInt((globalPosition.Z - GlobalPosition.Z) / _cellSize) + _zSize / 2;
99 | return new Vector2I(x, z);
100 | }
101 |
102 | /// Gets the snapped position of the mouse on the grid for a buildable object.
103 | /// The position of the mouse.
104 | /// The buildable object to snap to the grid.
105 | /// The snapped position of the mouse on the grid.
106 | public Vector3 GetMouseSnappedPosition(Vector3 mousePosition, MouseObject mouseObject)
107 | {
108 | // As we are using layer mask to detect only ground/grid layer
109 | // there is no need for extra check whether the mouse is over the grid
110 |
111 | float x = Mathf.Round((mousePosition - GlobalPosition).X);
112 | float z = Mathf.Round((mousePosition - GlobalPosition).Z);
113 |
114 | float xOffset = 0;
115 | float zOffset = 0;
116 |
117 | if (mouseObject.HasXOffset || mouseObject.HasZOffset)
118 | {
119 | // We need to check object size and type
120 | // If size is not an even number, we need to add offset to snap at the mid point
121 | // instead of grid line/borders
122 | if (Math.Abs(mouseObject.GetYRotationInDegrees()) != 90f)
123 | {
124 | xOffset = mouseObject.HasXOffset ? _cellSize / 2 : 0;
125 | zOffset = mouseObject.HasZOffset ? _cellSize / 2 : 0;
126 | }
127 | else
128 | {
129 | xOffset = mouseObject.HasZOffset ? _cellSize / 2 : 0;
130 | zOffset = mouseObject.HasXOffset ? _cellSize / 2 : 0;
131 | }
132 | // Also keep snap point inside current cell
133 | if (xOffset != 0) xOffset *= x < (mousePosition - GlobalPosition).X ? 1 : -1;
134 | if (zOffset != 0) zOffset *= z < (mousePosition - GlobalPosition).Z ? 1 : -1;
135 | }
136 |
137 | return new Vector3(x + xOffset, 0, z + zOffset) + GlobalPosition;
138 |
139 | }
140 |
141 | /// Gets the grid cell at the specified coordinates.
142 | /// The X-coordinate of the cell.
143 | /// The Y-coordinate of the cell.
144 | /// The grid cell at the specified coordinates.
145 | public GridCell GetGridCell(int x, int y)
146 | {
147 | if (IsValidGridIndex(x, y))
148 | {
149 | int index = GetGridIndexWithoutValidation(x, y);
150 | return GridCells[index];
151 | }
152 | return null;
153 | }
154 |
155 | /// Checks if the grid index is valid.
156 | /// The X-coordinate of the index.
157 | /// The Y-coordinate of the index.
158 | /// True if the grid index is valid, false otherwise.
159 | public bool IsValidGridIndex(int x, int y) => x <= _xSize && y <= _zSize && x > 0 && y > 0;
160 |
161 | /// Tries to place an object on the grid.
162 | /// The object to be placed.
163 | /// The rotation of the object.
164 | /// The position to place the object.
165 | public void TryToPlaceObject(BuildableResource selectedObject, float yRotation, Vector3 position)
166 | {
167 | // As this is already placed and positioned properlly
168 | // for optimization, there will be no additional calculations and snaping to grid again
169 |
170 | // Get object size based on rotation
171 | int xLength = Math.Abs(yRotation) != 90f ? selectedObject.Size.X : selectedObject.Size.Z;
172 | int zLength = Math.Abs(yRotation) != 90f ? selectedObject.Size.Z : selectedObject.Size.X;
173 |
174 | // This is for the walls
175 | if (xLength == 0) xLength = 1;
176 | if (zLength == 0) zLength = 1;
177 |
178 | // Split by 2 so we can get start position
179 | var gridPosition = GetGridPosition(position);
180 | int xStart = gridPosition.X - xLength / 2;
181 | int zStart = gridPosition.Y - zLength / 2;
182 |
183 | // TODO - Handle edge case where wall is placed on max edge(no pun intended)
184 |
185 | // Without this check it can happen that by dragging or fast clicking
186 | // object gets placed on the existing one or overlap
187 | for (int i = xStart; i < xStart + xLength; i++)
188 | {
189 | for (int j = zStart; j < zStart + zLength; j++)
190 | {
191 | var gridCell = GetGridCell(i, j);
192 | if (selectedObject.SnapBehaviour == SnapBehaviour.Ground && gridCell?.HasGroundObject() == true) return;
193 | else if (selectedObject.SnapBehaviour == SnapBehaviour.Wall)
194 | {
195 | if (Math.Abs(yRotation) != 90f)
196 | {
197 | if (gridCell.HasWallObject(Side.MinusZ)) return;
198 | }
199 | else
200 | {
201 | if (gridCell.HasWallObject(Side.MinusX)) return;
202 | }
203 | }
204 | }
205 | }
206 |
207 | var buildableInstance = BuildableInstance.Create(selectedObject, selectedObject.SnapBehaviour == SnapBehaviour.Ground ? _floorLayerMask : _wallLayerMask);
208 | AddChild(buildableInstance);
209 | buildableInstance.GlobalPosition = position;
210 | buildableInstance.RotateY(Mathf.DegToRad(yRotation));
211 |
212 | // Add simple small animation when placing objects
213 | AnimationUtils.AnimatePlacement(buildableInstance.ObjectInstance, this);
214 |
215 | for (int i = xStart; i < xStart + xLength; i++)
216 | {
217 | for (int j = zStart; j < zStart + zLength; j++)
218 | {
219 | var gridCell = GetGridCell(i, j);
220 | if (selectedObject.SnapBehaviour == SnapBehaviour.Ground) gridCell.SetGroundObject(buildableInstance);
221 | else if (selectedObject.SnapBehaviour == SnapBehaviour.Wall)
222 | {
223 | if (Math.Abs(yRotation) != 90f)
224 | {
225 | gridCell.SetWallObject(buildableInstance, Side.MinusZ);
226 | }
227 | else
228 | {
229 | gridCell.SetWallObject(buildableInstance, Side.MinusX);
230 | }
231 | }
232 | buildableInstance.AddCell(gridCell);
233 | // CreateDebugMesh(GetGlobalPosition(i, j) + new Vector3(cellSize / 2, 0, cellSize / 2));
234 | }
235 | }
236 | }
237 |
238 | /// Resets the building system grid by clearing all objects from the grid cells.
239 | public void ResetGrid()
240 | {
241 | foreach (var cell in GridCells)
242 | {
243 | cell.GroundObject?.ClearObject();
244 | foreach (var wall in cell.WallObjects)
245 | {
246 | wall?.ClearObject();
247 | }
248 | }
249 | }
250 |
251 | // Leaving this for debugging purposes
252 | private void CreateDebugMesh(Vector3 position)
253 | {
254 | var meshInstance = new MeshInstance3D()
255 | {
256 | Name = $"DebugMesh{position}"
257 | };
258 |
259 | var capsuleMesh = new CapsuleMesh
260 | {
261 | Radius = 0.1f,
262 | Height = 0.5f
263 | };
264 |
265 | var material = new StandardMaterial3D
266 | {
267 | AlbedoColor = new Color(1.0f, 0.0f, 0.0f) // Set the color to red
268 | };
269 |
270 | capsuleMesh.Material = material;
271 | meshInstance.Mesh = capsuleMesh;
272 |
273 | AddChild(meshInstance);
274 | meshInstance.GlobalPosition = position;
275 | }
276 |
277 | private int GetGridIndexWithoutValidation(Vector2I gridPosition) => (gridPosition.X * _zSize) + gridPosition.Y;
278 |
279 | private int GetGridIndexWithoutValidation(int row, int column) => (row * _zSize) + column;
280 | }
281 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/EventBus.cs:
--------------------------------------------------------------------------------
1 | namespace Godot.GodotInGameBuildingSystem;
2 |
3 | ///
4 | /// Represents main event bus that handles various signals related to the building system and game menus.
5 | ///
6 | public partial class EventBus : Node
7 | {
8 | // Building menu signals
9 |
10 | /// Signal emitted when a slot is clicked in the building menu.
11 | [Signal]
12 | public delegate void SlotClickedEventHandler(int index, int button);
13 |
14 | /// Signal emitted when the level is changed.
15 | [Signal]
16 | public delegate void LevelChangedEventHandler(int level);
17 |
18 | /// Signal emitted when the build mode is changed.
19 | [Signal]
20 | public delegate void BuildModeChangedEventHandler(bool enabled);
21 |
22 | /// Signal emitted when the demolition mode is changed.
23 | [Signal]
24 | public delegate void DemolitionModeChangedEventHandler(bool enabled);
25 |
26 | // Main menu signals
27 |
28 | /// Signal emitted when a new game is started.
29 | [Signal]
30 | public delegate void NewGameEventHandler();
31 |
32 | /// Signal emitted when the game is saved.
33 | [Signal]
34 | public delegate void SaveGameEventHandler(bool overwrite);
35 |
36 | /// Signal emitted when a game is loaded.
37 | [Signal]
38 | public delegate void LoadGameEventHandler(string filename);
39 |
40 | /// Signal emitted when the game is exited.
41 | [Signal]
42 | public delegate void ExitGameEventHandler();
43 |
44 | /// Signal emitted when the game menu is toggled.
45 | [Signal]
46 | public delegate void ToggleMenuEventHandler();
47 |
48 | /// Signal emitted when the load menu is opened.
49 | [Signal]
50 | public delegate void OpenLoadMenuEventHandler();
51 |
52 | /// Signal emitted when the save menu is opened.
53 | [Signal]
54 | public delegate void OpenSaveMenuEventHandler();
55 |
56 | // Mouse node signals
57 |
58 | /// Signal emitted when the mouse enters a tile body.
59 | [Signal]
60 | public delegate void MouseTileBodyEnteredEventHandler();
61 |
62 | /// Signal emitted when the mouse exits a tile body.
63 | [Signal]
64 | public delegate void MouseTileBodyExitedEventHandler();
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/GridCell.cs:
--------------------------------------------------------------------------------
1 | namespace Godot.GodotInGameBuildingSystem;
2 |
3 | /// Represents a cell in a grid-based building system.
4 | public partial class GridCell
5 | {
6 | /// Gets the ground object placed on this grid cell.
7 | public BuildableInstance GroundObject { get; private set; }
8 |
9 | /// Gets the wall objects placed on this grid cell.
10 | public BuildableInstance[] WallObjects { get; private set; }
11 |
12 | /// Initializes a new instance of the class.
13 | public GridCell()
14 | {
15 | GroundObject = null;
16 | WallObjects = new BuildableInstance[2];
17 | }
18 |
19 | /// Sets the ground object on this grid cell.
20 | /// The buildable instance representing the ground object.
21 | public void SetGroundObject(BuildableInstance buildableInstance)
22 | {
23 | if (buildableInstance.BuildableResource.SnapBehaviour == SnapBehaviour.Ground)
24 | {
25 | GroundObject = buildableInstance;
26 | }
27 | }
28 |
29 | /// Clears the ground object from this grid cell.
30 | public void ClearGroundObject()
31 | {
32 | GroundObject = null;
33 | }
34 |
35 | /// Sets the wall object on this grid cell.
36 | /// The buildable instance representing the wall object.
37 | /// The of the grid cell where the wall object is placed.
38 | public void SetWallObject(BuildableInstance buildableInstance, Side side)
39 | {
40 | if (buildableInstance.BuildableResource.SnapBehaviour == SnapBehaviour.Wall)
41 | {
42 | WallObjects[(int)side] = buildableInstance;
43 | }
44 | }
45 |
46 | /// Clears the specified wall object from this grid cell.
47 | /// The wall object to clear.
48 | public void ClearWallObject(BuildableInstance wall)
49 | {
50 | for (int i = 0; i < WallObjects.Length; i++)
51 | {
52 | if (WallObjects[i] == wall)
53 | {
54 | WallObjects[i] = null;
55 | }
56 | }
57 | }
58 |
59 | /// Clears the wall object from the specified side of this grid cell.
60 | /// The of the grid cell where the wall object is placed.
61 | public void ClearWallObject(Side side)
62 | {
63 | WallObjects[(int)side] = null;
64 | }
65 |
66 | /// Determines whether this grid cell has a ground object.
67 | /// true if this grid cell has a ground object; otherwise, false.
68 | public bool HasGroundObject() => GroundObject != null;
69 |
70 | /// Determines whether this grid cell has a wall object on the specified side.
71 | /// The of the grid cell to check.
72 | /// true if this grid cell has a wall object on the specified side; otherwise, false.
73 | public bool HasWallObject(Side side) => WallObjects[(int)side] != null;
74 |
75 | /// Gets the wall object placed on the specified side of this grid cell.
76 | /// The of the grid cell to get the wall object from.
77 | /// The wall object placed on the specified side of this grid cell.
78 | public BuildableInstance GetWallObject(Side side)
79 | {
80 | return WallObjects[(int)side];
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/MouseObject.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Godot.GodotInGameBuildingSystem;
4 |
5 | /// Represents a 3D mouse object in a grid building system.
6 | /// This class is used to represent a 3D object that is controlled by the mouse in a grid-based building system.
7 | /// It contains methods to update the visual representation of the mouse object, rotate the object, check for collisions, and manage the visual collision tile grid.
8 | ///
9 | public partial class MouseObject : Node3D
10 | {
11 | #region Public Variables
12 | /// Gets whether the buildable resource has an X offset.
13 | /// The X offset is used as optimization to center the buildable resource on the grid.
14 | public bool HasXOffset { get; private set; }
15 |
16 | /// Gets whether the buildable resource has a Z offset.
17 | /// The Z offset is used as optimization to center the buildable resource on the grid.
18 | public bool HasZOffset { get; private set; }
19 |
20 | #endregion Public Variables
21 |
22 | #region Private Variables
23 |
24 | private Node3D _mouseNodeChild;
25 | private MeshInstance3D _mouseMesh;
26 | private List _mouseTiles;
27 | private bool _tileGridVisible = false;
28 |
29 | #endregion Private Variables
30 |
31 | #region OnReady Variables
32 |
33 | private Node3D _objectContainer;
34 | private Node3D _gridContainer;
35 | private PackedScene _mouseTile;
36 | private EventBus _eventBus;
37 | private uint _layerMask = 1u << 2;
38 |
39 | #endregion OnReady Variables
40 |
41 | #region Built-In Methods
42 |
43 | ///
44 | public override void _Ready()
45 | {
46 | _objectContainer = GetNode("ObjectContainer");
47 | _gridContainer = GetNode("GridContainer");
48 | _mouseTile = GD.Load("res://BuildingSystem/scenes/mouse_tile.tscn");
49 | _eventBus = BSUtils.GetEventBus(this);
50 | _mouseTiles = new();
51 |
52 | _eventBus.MouseTileBodyEntered += OnMouseTileBodyEntered;
53 | _eventBus.MouseTileBodyExited += OnMouseTileBodyExited;
54 | }
55 |
56 | #endregion Built-In Methods
57 |
58 | #region Public Methods
59 |
60 | /// Sets the layer mask for the mouse tiles.
61 | /// The layer mask to set.
62 | public void SetLayerMask(uint layerMask)
63 | {
64 | _layerMask = layerMask;
65 | if (_mouseTiles.Count > 0)
66 | {
67 | foreach (var tile in _mouseTiles)
68 | {
69 | tile.SetLayerMask(layerMask);
70 | }
71 | }
72 | }
73 | /// Updates the visual representation of the mouse object with the specified buildable object.
74 | /// The buildable object to update the visual representation with.
75 | public void UpdateVisual(BuildableResource buildableObject)
76 | {
77 | ClearMouseObject();
78 | var objectInstance = buildableObject.Object3DModel.Instantiate();
79 |
80 | _objectContainer.AddChild(objectInstance);
81 | _mouseNodeChild = objectInstance;
82 |
83 | HasXOffset = buildableObject.Size.X % 2 != 0;
84 | HasZOffset = buildableObject.Size.Z % 2 != 0;
85 |
86 | CreateTilesGrid(new Vector2I(buildableObject.Size.X, buildableObject.Size.Z));
87 | }
88 |
89 | /// Clears the mouse object and the grid.
90 | public void ClearMouseObject()
91 | {
92 | _mouseNodeChild?.QueueFree();
93 | _mouseNodeChild = null;
94 | ClearGrid();
95 | }
96 |
97 | /// Clears the grid and removes all mouse tiles.
98 | public void ClearGrid()
99 | {
100 | foreach (var child in _mouseTiles)
101 | {
102 | child.QueueFree();
103 | }
104 | _mouseTiles = new();
105 | ResetRotation(_gridContainer);
106 | }
107 |
108 | /// Rotates the mouse object and the grid by the specified rotation in degrees.
109 | /// The rotation in degrees.
110 | public void Rotate(int rotation)
111 | {
112 | //Reset rotation
113 | ResetRotation(_mouseNodeChild);
114 | ResetRotation(_gridContainer);
115 | // Finally rotate the object
116 | _mouseNodeChild.RotateY(Mathf.DegToRad(rotation));
117 | _gridContainer.RotateY(Mathf.DegToRad(rotation));
118 | }
119 |
120 | /// Checks if the mouse object exists.
121 | public bool HasMouseObject() => _mouseNodeChild != null;
122 |
123 | /// Gets the Y rotation of the mouse object in radians.
124 | public float GetYRotationInRadians() => _mouseNodeChild.Rotation.Y;
125 |
126 | /// Gets the Y rotation of the mouse object in degrees.
127 | public float GetYRotationInDegrees() => _mouseNodeChild.RotationDegrees.Y;
128 |
129 | /// Checks if the mouse object is colliding.
130 | public bool IsColliding() => _mouseNodeChild != null && !_mouseNodeChild.Visible;
131 |
132 | /// Checks if any of the mouse object's children are colliding.
133 | public bool AreChildrenColliding()
134 | {
135 | foreach (var child in _mouseTiles)
136 | {
137 | if (child.IsColliding()) return true;
138 | }
139 | return false;
140 | }
141 |
142 | /// Sets the visibility of the tile grid.
143 | /// Whether the tile grid should be visible or not.
144 | public void SetTileGridVisible(bool visible)
145 | {
146 | foreach (var child in _mouseTiles)
147 | {
148 | child.SetVisibility(visible);
149 | }
150 | _tileGridVisible = visible;
151 | }
152 |
153 | #endregion Public Methods
154 |
155 | #region Private Methods
156 |
157 | private static void ResetRotation(Node3D node)
158 | {
159 | Transform3D transform = node.Transform;
160 | transform.Basis = Basis.Identity;
161 | node.Transform = transform;
162 | }
163 |
164 | private void CreateTilesGrid(Vector2I size, int cellSize = 1)
165 | {
166 | int width = size.X * cellSize;
167 | int height = size.Y * cellSize;
168 |
169 | for (int i = 0; i < size.X; i++)
170 | {
171 | for (int j = 0; j < size.Y; j++)
172 | {
173 | var mouseTileInstance = _mouseTile.Instantiate();
174 | _gridContainer.AddChild(mouseTileInstance);
175 | mouseTileInstance.SetLayerMask(_layerMask);
176 | if (cellSize != BSConstants.DEFAULT_CELL_SIZE)
177 | {
178 | mouseTileInstance.SetSize(cellSize);
179 | }
180 | mouseTileInstance.Position = new Vector3(i - width / 2f + cellSize / 2f, 0.15f, j - height / 2f + cellSize / 2f);
181 | _mouseTiles.Add(mouseTileInstance);
182 | }
183 | }
184 | }
185 |
186 | #endregion Private Methods
187 |
188 | #region Event Handlers
189 |
190 | private void OnMouseTileBodyEntered()
191 | {
192 | _mouseNodeChild.Visible = false;
193 | if (!_tileGridVisible) SetTileGridVisible(true);
194 | }
195 |
196 | private void OnMouseTileBodyExited()
197 | {
198 | if (!AreChildrenColliding())
199 | {
200 | if (_mouseNodeChild != null) _mouseNodeChild.Visible = true;
201 | SetTileGridVisible(false);
202 | }
203 | }
204 |
205 | #endregion Event Handlers
206 | }
207 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/MouseTile.cs:
--------------------------------------------------------------------------------
1 | namespace Godot.GodotInGameBuildingSystem;
2 |
3 | /// Represents a tile that is a part of mouse object visual.
4 | ///
5 | /// Mouse tile has two states - normal and colliding. When the tile is colliding with another body, it changes its color to red.
6 | /// This is the place to update the visual representation of the tile when it is colliding with another body.
7 | ///
8 | public partial class MouseTile : Node3D
9 | {
10 | private MeshInstance3D _meshInstance;
11 | private Area3D _area;
12 | private CollisionShape3D _collisionShape3D;
13 | private EventBus _eventBus;
14 |
15 | private int _colideCount = 0;
16 | private StandardMaterial3D _overrideMaterial;
17 | private uint _layerMask = 1u << 2;
18 |
19 | ///
20 | public override void _Ready()
21 | {
22 | _meshInstance = GetNode("MeshInstance3D");
23 | _area = GetNode("Area3D");
24 | _collisionShape3D = GetNode("Area3D/CollisionShape3D");
25 | _eventBus = BSUtils.GetEventBus(this);
26 | _area.BodyEntered += (Node3D body) => AreaBodyEntered(body, _area);
27 | _area.BodyExited += (Node3D body) => AreaBodyExited(body, _area);
28 | // Create a basic material to override the tile color when colliding
29 | _overrideMaterial = new StandardMaterial3D
30 | {
31 | AlbedoColor = new Color(Colors.DarkRed, 0.8f), // Initial color white
32 | Transparency = BaseMaterial3D.TransparencyEnum.Alpha,
33 | RenderPriority = 2
34 | };
35 | }
36 |
37 | /// Sets the collision layer mask for the tile.
38 | /// The layer mask to set.
39 | public void SetLayerMask(uint layerMask) => _area.CollisionMask = layerMask;
40 |
41 | /// Sets the size of the tile.
42 | /// The size of the tile.
43 | public void SetSize(int size)
44 | {
45 | var planeMesh = _meshInstance.Mesh as PlaneMesh;
46 | planeMesh.Size = new Vector2(size, size);
47 | var shape = _collisionShape3D.Shape as BoxShape3D;
48 | shape.Size = new Vector3(size * 0.8f, 0.3f, size * 0.8f);
49 | }
50 |
51 | /// Sets the visibility of the tile.
52 | /// Whether the tile should be visible or not.
53 | public void SetVisibility(bool visible)
54 | {
55 | _meshInstance.Visible = visible;
56 | }
57 |
58 | /// Checks if the tile is currently colliding with any other bodies.
59 | /// True if the tile is colliding, false otherwise.
60 | public bool IsColliding() => _colideCount != 0;
61 |
62 | private void AreaBodyEntered(Node3D body, Area3D area)
63 | {
64 | _colideCount++;
65 | _meshInstance.MaterialOverride ??= _overrideMaterial;
66 | _eventBus.EmitSignal("MouseTileBodyEntered");
67 | }
68 |
69 | private void AreaBodyExited(Node3D body, Area3D area)
70 | {
71 | _colideCount--;
72 | if (_colideCount == 0)
73 | {
74 | _meshInstance.MaterialOverride = null;
75 | _eventBus.EmitSignal("MouseTileBodyExited");
76 | }
77 | }
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/Resources/BuildableResource.cs:
--------------------------------------------------------------------------------
1 | namespace Godot.GodotInGameBuildingSystem;
2 |
3 | /// Represents a buildable resource in the grid building system.
4 | ///
5 | /// Every object that can be built in the grid building system needs to be a buildable resource.
6 | /// Creating a buildable resource allows you to define the visual representation of the object, its size, snap behavior, and other properties.
7 | ///
8 | [GlobalClass]
9 | public partial class BuildableResource : Resource
10 | {
11 | /// Gets or sets the name of the buildable resource.
12 | [Export]
13 | public string Name { get; set; }
14 |
15 | /// Gets or sets the description of the buildable resource.
16 | [Export(PropertyHint.MultilineText)]
17 | public string Description { get; set; }
18 |
19 | /// Gets or sets the texture atlas for the buildable resource to be used in UI.
20 | ///
21 | /// Use texture atlas or texture image.
22 | /// Atlas has a priority over texture image.
23 | ///
24 | [Export]
25 | public AtlasTexture TextureAtlas { get; set; }
26 |
27 | /// Gets or sets the texture image for the buildable resource to be used in UI.
28 | ///
29 | /// Use texture atlas or texture image.
30 | /// Atlas has a priority over texture image.
31 | ///
32 | [Export]
33 | public Texture2D TextureImage { get; set; }
34 |
35 | /// Gets or sets the 3D model for the buildable resource.
36 | [Export]
37 | public PackedScene Object3DModel { get; set; }
38 |
39 | /// Gets or sets the snap behavior of the buildable resource.
40 | [Export]
41 | public SnapBehaviour SnapBehaviour { get; set; }
42 |
43 | /// Gets or sets the size of the buildable resource.
44 | ///
45 | /// Size is only used for walls and floors.
46 | /// For walls, set X and Y and leave Z at 0.
47 | /// For floors, set X and Z and leave Y at 0.
48 | ///
49 | [Export]
50 | public Vector3I Size { get; set; }
51 |
52 | /// Initializes a new instance of the class.
53 | /// Empty constructor required for Godot serialization.
54 | public BuildableResource() { }
55 | }
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/Resources/BuildableResourceLibrary.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | namespace Godot.GodotInGameBuildingSystem;
3 |
4 | /// Represents a library of
5 | ///
6 | /// This class is used to store an array of buildable objects that can be built in the grid building system.
7 | /// It provides an easy way of creating and managing buildable objects using multiple libraries(for testing or menus etc.).
8 | ///
9 | [GlobalClass]
10 | public partial class BuildableResourceLibrary : Resource
11 | {
12 | /// Gets or sets the array of buildable objects.
13 | [Export]
14 | public BuildableResource[] BuildableObjects { get; set; }
15 |
16 | /// Initializes a new instance of the class with an empty array of buildable objects.
17 | /// Empty constructor required for Godot serialization.
18 | public BuildableResourceLibrary() : this(new BuildableResource[99]) { }
19 |
20 | /// Initializes a new instance of the class with the specified array of buildable objects.
21 | /// The array of buildable objects.
22 | public BuildableResourceLibrary(BuildableResource[] buildableObjects)
23 | {
24 | BuildableObjects = buildableObjects;
25 | }
26 |
27 | /// Gets the buildable resource with the specified name.
28 | /// The name of the buildable resource.
29 | /// The buildable resource with the specified name, or null if not found.
30 | public BuildableResource GetByName(string name)
31 | {
32 | return BuildableObjects.FirstOrDefault(obj => obj.Name == name);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/SaveSystem/BSSaveClasses.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Godot.GodotInGameBuildingSystem;
4 |
5 | /// Represents a save object.
6 | public class SaveObject
7 | {
8 | /// Gets or sets the name of the object.
9 | public string Name { get; set; }
10 |
11 | /// Gets or sets the resource path of the object.
12 | public string ResourcePath { get; set; }
13 |
14 | /// Gets or sets the Y rotation in radians of the object.
15 | public float YRotationRadiants { get; set; }
16 | }
17 |
18 | /// Represents a save object placed on a grid.
19 | public class SaveGridObject : SaveObject
20 | {
21 | /// Gets or sets the position of the object on the grid.
22 | public GridPosition Position { get; set; }
23 | }
24 |
25 | /// Represents a save object placed freely in the scene.
26 | public class SaveFreeObject : SaveObject
27 | {
28 | /// Gets or sets the position of the object in the scene.
29 | public FreePosition Position { get; set; }
30 | }
31 |
32 | /// Represents a position on a grid.
33 | public class GridPosition
34 | {
35 | /// Gets or sets the X coordinate of the position.
36 | public float X { get; set; }
37 |
38 | /// Gets or sets the Z coordinate of the position.
39 | public float Z { get; set; }
40 | }
41 |
42 | /// Represents a position in the scene.
43 | public class FreePosition
44 | {
45 | /// Gets or sets the X coordinate of the position.
46 | public float X { get; set; }
47 |
48 | /// Gets or sets the Y coordinate of the position.
49 | public float Y { get; set; }
50 |
51 | /// Gets or sets the Z coordinate of the position.
52 | public float Z { get; set; }
53 | }
54 |
55 | /// Represents a save grid.
56 | public class SaveGrid
57 | {
58 | /// Gets or sets the index of the grid.
59 | public int Index { get; set; }
60 |
61 | /// Gets or sets the list of objects placed on the grid.
62 | public List Objects { get; set; }
63 | }
64 |
65 | /// Represents a save file.
66 | public class SaveFile
67 | {
68 | /// Gets or sets the list of grids in the save file.
69 | public List Grids { get; set; }
70 |
71 | /// Gets or sets the list of free objects in the save file.
72 | public List FreeObjects { get; set; }
73 | }
74 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/SaveSystem/BSSaveSystem.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Godot.GodotInGameBuildingSystem;
4 |
5 | /// Represents a save system for the grid building system.
6 | public class BSSaveSystem
7 | {
8 | private string _currentSaveFile = string.Empty;
9 |
10 | /// Initializes a new instance of the class.
11 | public BSSaveSystem() { }
12 |
13 | /// Saves the current state of the grid building system.
14 | /// Whether to overwrite the existing save file.
15 | /// The list of free objects to save.
16 | /// The list of grids to save.
17 | public void Save(bool overwrite, List freeObjectList, List grids)
18 | {
19 | List saveGrids = new();
20 | List freeObjects = new();
21 |
22 | foreach (var freeObject in freeObjectList)
23 | {
24 | freeObjects.Add(freeObject.ToSaveFreeObject());
25 | }
26 |
27 | for (int i = 0; i < grids.Count; i++)
28 | {
29 | var grid = grids[i].ToSaveGrid();
30 | if (grid.Objects.Count > 0)
31 | {
32 | grid.Index = i;
33 | saveGrids.Add(grid);
34 | }
35 | }
36 |
37 | SaveFile saveFile = new()
38 | {
39 | Grids = saveGrids,
40 | FreeObjects = freeObjects
41 | };
42 |
43 | _currentSaveFile = overwrite ? SaveSystem.Save(saveFile, _currentSaveFile) : SaveSystem.Save(saveFile);
44 | }
45 |
46 | /// Loads a saved state of the grid building system.
47 | /// The name of the save file to load.
48 | /// The buildable object library.
49 | /// The list of free objects to load.
50 | /// The container node for free objects.
51 | /// The layer mask for free objects.
52 | /// The list of grids to load.
53 | public void Load(
54 | string filename,
55 | BuildableResourceLibrary buildableObjectLibrary,
56 | List freeObjectList,
57 | Node3D freeObjectContainer,
58 | uint freeLayerMask,
59 | List grids
60 | )
61 | {
62 | _currentSaveFile = filename;
63 | var saveFile = SaveSystem.Load(filename);
64 |
65 | if (saveFile != null)
66 | {
67 | for (int i = 0; i < saveFile.Grids.Count; i++)
68 | {
69 | if (saveFile.Grids[i].Index == i)
70 | {
71 | foreach (var gridObject in saveFile.Grids[i].Objects)
72 | {
73 | var buildableObject = buildableObjectLibrary.GetByName(gridObject.Name);
74 | grids[i].TryToPlaceObject(buildableObject, gridObject.YRotationRadiants, gridObject.Position.ToVector3(i));
75 | }
76 | }
77 | }
78 | }
79 |
80 | foreach (var freeSaveObject in saveFile.FreeObjects)
81 | {
82 | var buildableObject = buildableObjectLibrary.GetByName(freeSaveObject.Name);
83 | var buildableInstance = BuildableInstance.Create(buildableObject, freeLayerMask);
84 | freeObjectContainer.AddChild(buildableInstance);
85 | buildableInstance.GlobalPosition = freeSaveObject.Position.ToVector3();
86 | buildableInstance.RotateY(Mathf.DegToRad(freeSaveObject.YRotationRadiants));
87 |
88 | freeObjectList.Add(buildableInstance);
89 | }
90 | }
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/SaveSystem/SaveExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Godot.GodotInGameBuildingSystem;
4 |
5 | /// Provides extension methods for saving and converting positions and objects in the grid building system.
6 | public static class SaveExtensions
7 | {
8 | /// Converts a to a with the specified Y coordinate.
9 | /// The grid position to convert.
10 | /// The Y coordinate of the resulting vector.
11 | /// A representing the converted position.
12 | public static Vector3 ToVector3(this GridPosition position, float y)
13 | {
14 | return new Vector3(position.X, y, position.Z);
15 | }
16 |
17 | /// Converts a to a .
18 | /// The vector position to convert.
19 | /// A representing the converted position.
20 | public static GridPosition ToGridPosition(this Vector3 position)
21 | {
22 | return new GridPosition()
23 | {
24 | X = position.X,
25 | Z = position.Z
26 | };
27 | }
28 |
29 | /// Converts a to a .
30 | /// The free position to convert.
31 | /// A representing the converted position.
32 | public static Vector3 ToVector3(this FreePosition position)
33 | {
34 | return new Vector3(position.X, position.Y, position.Z);
35 | }
36 |
37 | /// Converts a to a .
38 | /// The vector position to convert.
39 | /// A representing the converted position.
40 | public static FreePosition ToFreePosition(this Vector3 position)
41 | {
42 | return new FreePosition()
43 | {
44 | X = position.X,
45 | Y = position.Y,
46 | Z = position.Z
47 | };
48 | }
49 |
50 | /// Converts a to a .
51 | /// The buildable object to convert.
52 | /// A representing the converted object.
53 | public static SaveGridObject ToSaveGridObject(this BuildableInstance buildableObject)
54 | {
55 | return new SaveGridObject
56 | {
57 | Name = buildableObject.BuildableResource.Name,
58 | ResourcePath = buildableObject.BuildableResource.ResourcePath,
59 | Position = buildableObject.GlobalPosition.ToGridPosition(),
60 | YRotationRadiants = buildableObject.RotationDegrees.Y
61 | };
62 | }
63 |
64 | /// Converts a to a .
65 | /// The buildable object to convert.
66 | /// A representing the converted object.
67 | public static SaveFreeObject ToSaveFreeObject(this BuildableInstance buildableObject)
68 | {
69 | return new SaveFreeObject
70 | {
71 | Name = buildableObject.BuildableResource.Name,
72 | ResourcePath = buildableObject.BuildableResource.ResourcePath,
73 | Position = buildableObject.ObjectInstance.Position.ToFreePosition(),
74 | YRotationRadiants = buildableObject.ObjectInstance.Rotation.Y
75 | };
76 | }
77 |
78 | /// Converts a to a .
79 | /// The building system grid to convert.
80 | /// A representing the converted grid.
81 | public static SaveGrid ToSaveGrid(this BuildingSystemGrid grid)
82 | {
83 | List saveObjects = new();
84 | foreach (var gridCell in grid.GridCells)
85 | {
86 | if (gridCell.GroundObject != null) saveObjects.Add(gridCell.GroundObject.ToSaveGridObject());
87 | foreach (var wall in gridCell.WallObjects)
88 | {
89 | if (wall != null) saveObjects.Add(wall.ToSaveGridObject());
90 | }
91 | }
92 | SaveGrid saveGrid = new()
93 | {
94 | Objects = saveObjects
95 | };
96 | return saveGrid;
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/SaveSystem/SaveSystem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text.Json;
6 |
7 | namespace Godot.GodotInGameBuildingSystem;
8 |
9 | /// Provides functionality to save and load game data.
10 | /// This is a generic class that can be used to save and load any type of data.
11 | public static class SaveSystem
12 | {
13 | /// Determines whether encryption is enabled for saving data.
14 | /// Encryption is enabled by default to prevent tempering with save data outside the game.
15 | private static readonly bool useEncryption = false;
16 |
17 | /// The file extension used for saving data.
18 | ///
19 | /// The default extension is "json".
20 | /// Extension can be whatever or nothing, for example ".sav"
21 | /// When encryption is disabled, put "json" for readability as json is used to serialize object to a file.
22 | ///
23 | private const string saveExtension = "json";
24 |
25 | /// The folder where save files are stored.
26 | ///
27 | /// The default folder is "user://".
28 | /// For reference https://docs.godotengine.org/en/stable/tutorials/io/data_paths.html#accessing-persistent-user-data-user
29 | ///
30 | private static readonly string saveFolder = "user://";
31 |
32 | /// The base file name used for saving data.
33 | private static readonly string baseFileName = "savegame";
34 |
35 | /// Generates a unique save file name based on the current date and time.
36 | /// The generated save file name.
37 | private static string GenerateSaveFileName() => $"{saveFolder}{baseFileName}_{DateTime.Now.ToFileTime()}.{saveExtension}";
38 |
39 | /// Saves the specified data to a file.
40 | ///
41 | /// It uses JsonSerializer to serialize the data to a JSON string. Be sure to provide valid data types.
42 | ///
43 | /// The type of data to save.
44 | /// The data to save.
45 | /// The name of the save file. If not provided, a unique name will be generated.
46 | /// The name of the saved file.
47 | public static string Save(T savefile, string saveFileName = null)
48 | {
49 | saveFileName = saveFileName == null ? GenerateSaveFileName() : saveFolder + saveFileName;
50 | using var saveGame = FileAccess.Open(saveFileName, FileAccess.ModeFlags.Write);
51 | if (FileAccess.GetOpenError() != Error.Ok)
52 | {
53 | GD.PrintErr(FileAccess.GetOpenError());
54 | return saveFileName;
55 | }
56 | try
57 | {
58 | var jsonString = JsonSerializer.Serialize(savefile);
59 | if (useEncryption) jsonString = CryptoUtils.EncryptString(jsonString);
60 | saveGame.StoreString(jsonString);
61 | }
62 | catch (Exception e)
63 | {
64 | GD.PrintErr(e);
65 | }
66 | return saveFileName;
67 | }
68 |
69 | /// Loads data from the specified save file.
70 | ///
71 | /// It uses JsonSerializer to deserialize the data.
72 | ///
73 | /// The type of data to load.
74 | /// The name of the save file to load.
75 | /// The loaded data.
76 | public static T Load(string saveFileName)
77 | {
78 | using var saveGame = FileAccess.Open(saveFolder + saveFileName, FileAccess.ModeFlags.Read);
79 | if (FileAccess.GetOpenError() != Error.Ok)
80 | {
81 | GD.PrintErr(FileAccess.GetOpenError());
82 | return default;
83 | }
84 | try
85 | {
86 | var jsonString = saveGame.GetAsText();
87 | if (useEncryption) jsonString = CryptoUtils.DecryptString(jsonString);
88 | var saveFile = JsonSerializer.Deserialize(jsonString);
89 | return saveFile;
90 | }
91 | catch (Exception e)
92 | {
93 | if (e is FormatException) GD.PrintErr("Save file is not encrypted, disable encryption to load.");
94 | else GD.PrintErr(e);
95 | }
96 | return default;
97 | }
98 |
99 | /// Loads the most recent save file.
100 | /// The type of data to load.
101 | /// The loaded data from the most recent save file.
102 | public static T LoadMostRecentFile()
103 | {
104 | var saveFiles = GetSaveFilesInfo();
105 | if (saveFiles.Count > 0)
106 | {
107 | return Load(saveFiles.FirstOrDefault().Name);
108 | }
109 | return default;
110 | }
111 |
112 | /// Retrieves information about all the save files in the save folder.
113 | /// A list of objects representing the save files.
114 | public static List GetSaveFilesInfo()
115 | {
116 | var directoryPath = ProjectSettings.GlobalizePath("user://");
117 | if (!Directory.Exists(directoryPath))
118 | {
119 | GD.Print($"Directory does not exist: {directoryPath}");
120 | return new List();
121 | }
122 |
123 | DirectoryInfo dirInfo = new(directoryPath);
124 | FileInfo[] files = dirInfo.GetFiles();
125 |
126 | return files.OrderByDescending(f => f.LastWriteTime).ToList();
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/UI/Blur.cs:
--------------------------------------------------------------------------------
1 | namespace Godot.GodotInGameBuildingSystem;
2 |
3 | /// Represents a custom control that applies a blur effect to its content.
4 | public partial class Blur : ColorRect
5 | {
6 | /// Called when the node is ready.
7 | public override void _Ready()
8 | {
9 | Size = GetViewportRect().Size;
10 | ShaderMaterial material = (ShaderMaterial)Material;
11 | material.SetShaderParameter("viewport_size", GetViewportRect().Size);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BuildingSystem/Scripts/UI/InfoInterface.cs:
--------------------------------------------------------------------------------
1 | namespace Godot.GodotInGameBuildingSystem;
2 |
3 | /// Represents the user interface for displaying information about the game.
4 | public partial class InfoInterface : Control
5 | {
6 | [Export]
7 | public EventBus EventBus { get; set; }
8 |
9 | private Label _levelLabel;
10 | private Label _buildModeLabel;
11 | private Label _demolitionModeLabel;
12 |
13 | /// Called when the node is ready to be used.
14 | public override void _Ready()
15 | {
16 | _levelLabel = GetNode