├── .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