diff --git a/components/NavigationComponent.cs b/components/NavigationComponent.cs index ae6166a..f7024bb 100644 --- a/components/NavigationComponent.cs +++ b/components/NavigationComponent.cs @@ -11,7 +11,7 @@ using GoDotLog; /// /// -public class NavigationComponent : Node +public class NavigationComponent : Spatial { public class NavigationPoint { @@ -83,6 +83,9 @@ public class NavigationComponent : Node private Vector3 _currentGoalPositionWorld = Vector3.Zero; private Quat _currentGoalOrientationWorld = Quat.Identity; + private Area _pathCollisionQueryVolume; + private CollisionShape _pathCollisionQueryShape; + private List _pathWorldNavigationPoints; private HexCell[] _path; @@ -90,6 +93,12 @@ public class NavigationComponent : Node { base._Ready(); _pathWorldNavigationPoints = new List(); + + _pathCollisionQueryVolume = (Area)FindNode("PathCollisionQueryVolume"); + Debug.Assert(_pathCollisionQueryVolume != null); + _pathCollisionQueryShape = (CollisionShape)_pathCollisionQueryVolume.FindNode("CollisionShape"); + Debug.Assert(_pathCollisionQueryShape != null); + } public override void _Process(float delta) @@ -158,6 +167,8 @@ public class NavigationComponent : Node { throw new NotImplementedException(); } + + CheckPathCollision(fromTransformWorld.origin, navigationPoint.WorldPosition); } @@ -178,15 +189,31 @@ public class NavigationComponent : Node } - bool SweptSphereHasCollision(Vector3 fromPosition, Vector3 toPosition, float radius) + bool CheckPathCollision(Vector3 fromPositionWorld, Vector3 toPositionWorld) { - if ((fromPosition - toPosition).LengthSquared() < 0.001) + Vector3 fromPositionLocal = GlobalTransform.XformInv(fromPositionWorld); + Vector3 toPositionLocal = GlobalTransform.XformInv(toPositionWorld); + float distance = (toPositionLocal - fromPositionLocal).Length(); + Vector3 direction = (toPositionLocal - fromPositionLocal) / distance; + Vector3 side = Vector3.Up.Cross(direction); + Basis orientation = new Basis(side, Vector3.Up, direction); + + _pathCollisionQueryVolume.Transform = + new Transform(orientation, 0.5f * toPositionLocal + 0.5f * fromPositionLocal).Scaled(new Vector3(0.5f, 0.5f, distance)); + + var collisionBodies = _pathCollisionQueryVolume.GetOverlappingBodies(); + + if (collisionBodies.Count > 0) + { + GD.Print("There is a body: " + collisionBodies[0]); + return true; + } + + if ((fromPositionWorld - toPositionWorld).LengthSquared() < 0.001) { return false; } - Vector3 direction = (toPosition - fromPosition).Normalized(); - // TODO: Complete Implementation Debug.Assert(false); return true; @@ -206,7 +233,7 @@ public class NavigationComponent : Node Vector3 startPoint = navigationPoints[startIndex].WorldPosition; Vector3 endPoint = navigationPoints[endIndex].WorldPosition; - if (SweptSphereHasCollision(startPoint, endPoint, 0.25f)) + if (CheckPathCollision(startPoint, endPoint)) { smoothedPath.Add(navigationPoints[endIndex-1]); smoothedPath.Add(navigationPoints[endIndex]); diff --git a/entities/Player.cs b/entities/Player.cs index 05c20b9..9d2c0cb 100644 --- a/entities/Player.cs +++ b/entities/Player.cs @@ -10,6 +10,8 @@ using GodotComponentTest.utils; public class Player : Entity, IInteractionInterface { // public members + [Export] public NodePath TileWorldNode; + public Vector3 TargetPosition = Vector3.Zero; public int goldCount = 0; @@ -47,7 +49,7 @@ public class Player : Entity, IInteractionInterface _groundMotion = new GroundMotionComponent(); _worldInfo = (WorldInfoComponent)FindNode("WorldInfo", false); _navigationComponent = (NavigationComponent)FindNode("Navigation", false); - _navigationComponent.TileWorld = _worldInfo.TileWorld; + _navigationComponent.TileWorld = GetNode(TileWorldNode); TaskQueueComponent = new TaskQueueComponent(); _itemAttractorArea = (Area)FindNode("ItemAttractorArea", false); diff --git a/scenes/Game.tscn b/scenes/Game.tscn index 5491aa2..0fdf809 100644 --- a/scenes/Game.tscn +++ b/scenes/Game.tscn @@ -1,12 +1,12 @@ -[gd_scene load_steps=15 format=2] +[gd_scene load_steps=12 format=2] -[ext_resource path="res://scenes/StreamContainer.cs" type="Script" id=1] +[ext_resource path="res://scenes/StreamContainer.tscn" type="PackedScene" id=1] [ext_resource path="res://entities/Player.tscn" type="PackedScene" id=2] +[ext_resource path="res://scenes/Camera.tscn" type="PackedScene" id=3] [ext_resource path="res://utils/TileHighlight.tscn" type="PackedScene" id=5] [ext_resource path="res://entities/Chest.tscn" type="PackedScene" id=7] [ext_resource path="res://scenes/TileWorld.tscn" type="PackedScene" id=8] [ext_resource path="res://scenes/Game.cs" type="Script" id=9] -[ext_resource path="res://scenes/DebugCamera.gd" type="Script" id=10] [ext_resource path="res://ui/WorldGeneratorUI.gd" type="Script" id=12] [ext_resource path="res://entities/Axe.tscn" type="PackedScene" id=14] [ext_resource path="res://systems/InteractionSystem.cs" type="Script" id=15] @@ -27,16 +27,6 @@ tracks/0/keys = { "values": [ Vector2( 1, 1 ), Vector2( 2, 2 ), Vector2( 1, 1 ) ] } -[sub_resource type="CubeMesh" id=1] -size = Vector3( 1, 1, 1 ) - -[sub_resource type="SpatialMaterial" id=2] -params_blend_mode = 3 -albedo_color = Color( 1, 1, 1, 0.156863 ) - -[sub_resource type="BoxShape" id=9] -extents = Vector3( 20, 1, 20 ) - [node name="Game" type="Spatial"] script = ExtResource( 9 ) @@ -48,6 +38,7 @@ visible = false transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0 ) [node name="TileWorld" parent="." instance=ExtResource( 8 )] +GenerationMapType = 0 Size = 128 [node name="GameUI" type="HBoxContainer" parent="."] @@ -282,37 +273,15 @@ rect_min_size = Vector2( 100, 100 ) stretch_mode = 1 flip_v = true -[node name="StreamContainer" type="Spatial" parent="."] -transform = Transform( 1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, 0, 0, -4.76837e-07 ) -script = ExtResource( 1 ) -Dimensions = Vector2( 35, 30 ) -World = NodePath("../TileWorld") +[node name="StreamContainer" parent="." instance=ExtResource( 1 )] -[node name="ActiveTiles" type="Spatial" parent="StreamContainer"] - -[node name="Bounds" type="MeshInstance" parent="StreamContainer"] -transform = Transform( 4, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0 ) -visible = false -mesh = SubResource( 1 ) -skeleton = NodePath("../..") -material/0 = SubResource( 2 ) - -[node name="Area" type="Area" parent="StreamContainer"] - -[node name="CollisionShape" type="CollisionShape" parent="StreamContainer/Area"] -transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0 ) -shape = SubResource( 9 ) - -[node name="Camera" type="Camera" parent="."] -transform = Transform( 1, 0, 0, 0, 0.60042, 0.799685, 0, -0.799685, 0.60042, -4.76837e-07, 6.37557, 4.57224 ) -current = true -fov = 60.0 -script = ExtResource( 10 ) +[node name="Camera" parent="." instance=ExtResource( 3 )] [node name="InteractionSystem" type="Node" parent="."] script = ExtResource( 15 ) [node name="Player" parent="." instance=ExtResource( 2 )] +TileWorldNode = NodePath("../TileWorld") [node name="Entities" type="Spatial" parent="."] diff --git a/scenes/StreamContainer.cs b/scenes/StreamContainer.cs index 20e8f94..e64975b 100644 --- a/scenes/StreamContainer.cs +++ b/scenes/StreamContainer.cs @@ -226,13 +226,11 @@ public class StreamContainer : Spatial public void OnTileClicked(HexTile3D tile) { -// GD.Print("Clicked on Tile at " + tile.OffsetCoords); EmitSignal("TileClicked", tile); } public void OnTileHovered(HexTile3D tile) { -// GD.Print("Hovered on Tile at " + tile.OffsetCoords); EmitSignal("TileHovered", tile); } diff --git a/scenes/StreamContainer.gd b/scenes/StreamContainer.gd deleted file mode 100644 index c895ed2..0000000 --- a/scenes/StreamContainer.gd +++ /dev/null @@ -1,91 +0,0 @@ -extends Spatial - -onready var hexgrid = preload("res://addons/gdhexgrid/HexGrid.gd").new() -onready var hextile3d = preload("res://scenes/HexTile3D.tscn") -onready var active_tiles = $ActiveTiles - - -export var world_rect: Rect2 = Rect2() setget set_rect -var offset_coord_rect: Rect2 = Rect2() - -var bottom_left_cell: HexCell = HexCell.new() -var top_right_cell: HexCell = HexCell.new() - -var tiles = {} -var tiles_by_offset_coord = {} - -onready var bounds = $Bounds - - -func _ready(): - set_rect(world_rect) - - -func is_hex_coord_in_rect (coord: Vector2): - var rect_end = offset_coord_rect.end - return coord.x >= offset_coord_rect.position.x and coord.x < offset_coord_rect.end.x and coord.y >= offset_coord_rect.position.y and coord.y < offset_coord_rect.end.y - - -func instantiate_hextile3d(): - return hextile3d.instance() - - -func add_hextile_to_tree(hextile3d): - active_tiles.add_child(hextile3d) - - -func create_hextile3d_at (coord: Vector2) -> HexTile3D: - if not coord in tiles_by_offset_coord.keys(): - var new_hextile3d = instantiate_hextile3d() - add_hextile_to_tree(new_hextile3d) - new_hextile3d.game_tile.offset_coords = coord - new_hextile3d.transform.origin.y = -9999 - tiles_by_offset_coord[coord] = new_hextile3d - - return tiles_by_offset_coord[coord] - - -func cleanup_tiles(): - var num_deleted = 0 - var children = active_tiles.get_children() - for child in children: - var tile_offset_coords = child.game_tile.offset_coords - if not is_hex_coord_in_rect(tile_offset_coords): - tiles_by_offset_coord.erase(tile_offset_coords) - child.queue_free() - active_tiles.remove_child(child) - num_deleted = num_deleted + 1 - -# print ("deleted ", num_deleted, " tiles") - - -func set_rect(rect: Rect2): - world_rect = rect - - if bounds == null: - return - - var world_rect_center = rect.get_center() - - bounds.transform = Transform( - Vector3 (world_rect.size.x / 2, 0, 0), - Vector3(0, 1, 0), Vector3(0, 0, world_rect.size.y / 2), - Vector3(world_rect_center.x, 0, world_rect_center.y) - ) - - bottom_left_cell = hexgrid.get_hex_at(Vector2(rect.position.x, rect.end.y)) - top_right_cell = hexgrid.get_hex_at(Vector2(rect.end.x, rect.position.y)) - - offset_coord_rect = Rect2( - bottom_left_cell.offset_coords.x, bottom_left_cell.offset_coords.y, - top_right_cell.offset_coords.x - bottom_left_cell.offset_coords.x, top_right_cell.offset_coords.y - bottom_left_cell.offset_coords.y - ) - -# print ("----") -# print ("world_rect: ", world_rect) -# print ("cells: ", bottom_left_cell.offset_coords, " to ", top_right_cell.offset_coords) -# print ("offset_coord_rect: ", offset_coord_rect.position, " size: ", offset_coord_rect.size) - - cleanup_tiles() - - return world_rect diff --git a/scenes/TileWorld.cs b/scenes/TileWorld.cs index d0044ba..f9460e5 100644 --- a/scenes/TileWorld.cs +++ b/scenes/TileWorld.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Diagnostics; using Godot.Collections; +using Namespace; using Array = Godot.Collections.Array; using Vector2 = Godot.Vector2; using Vector3 = Godot.Vector3; @@ -28,7 +29,18 @@ public class TileWorld : Spatial delegate void WorldGenerated(); // public members + public enum MapType + { + Noise, + Debug, + Flat, + } + + [ExportFlagsEnum(typeof(MapType))] public MapType GenerationMapType = MapType.Debug; + [Export] public int Size = 64; + [Export] public bool DebugMap = false; + public float HeightScale = 2.0f; public Image Heightmap; public Image Colormap; @@ -100,10 +112,15 @@ public class TileWorld : Spatial OnMapGenerationStart(); - GenerateNoiseMap(); -// GenerateDebugMap(); - - OnHeightMapChanged(); + switch (GenerationMapType) + { + case MapType.Debug: GenerateDebugMap(); + break; + case MapType.Flat: GenerateFlatMap(); + break; + case MapType.Noise: GenerateNoiseMap(); + break; + } } private void GenerateDebugMap() @@ -128,7 +145,35 @@ public class TileWorld : Spatial } } - Colormap.SetPixel(Size - 1, Size - 1, new Color(1, 1, 1, 1)); + // Colormap.SetPixel(Size - 1, Size - 1, new Color(1, 1, 1, 1)); + Colormap.Fill(Colors.ForestGreen); + Colormap.Unlock(); + + OnMapGenerationComplete(); + } + + + private void GenerateFlatMap() + { + Colormap = new Image(); + Colormap.Create(Size, Size, false, Image.Format.Rgba8); + + Heightmap = new Image(); + Heightmap.Create(Size, Size, false, Image.Format.Rf); + + Heightmap.Lock(); + Colormap.Lock(); + + foreach (int coord_x in Enumerable.Range(0, Size)) + { + foreach (int coord_y in Enumerable.Range(0, Size)) + { + Heightmap.SetPixel(coord_x, coord_y, + new Color(0, 0, 0, 1)); + } + } + + Colormap.Fill(Colors.ForestGreen); Colormap.Unlock(); OnMapGenerationComplete(); @@ -163,7 +208,7 @@ public class TileWorld : Spatial { child.QueueFree(); } - + foreach (Node child in Entities.GetChildren()) { child.QueueFree(); @@ -329,6 +374,17 @@ public class TileWorld : Spatial } + public void SetTileColorAtOffset(Vector2 offsetCoord, Color color) + { + Vector2 textureCoord = OffsetToTextureCoord(offsetCoord); + + Colormap.Lock(); + Colormap.SetPixel((int) textureCoord.x, (int) textureCoord.y, color); + Colormap.Unlock(); + + EmitSignal("WorldGenerated"); + } + public Vector2 WorldToOffsetCoords(Vector3 worldCoord) { return _hexGrid.GetHexAt(new Vector2(worldCoord.x, worldCoord.z)).OffsetCoords; @@ -337,13 +393,9 @@ public class TileWorld : Spatial public Vector3 GetTileWorldCenterFromOffset(Vector2 offsetCoord) { - Vector2 tileCenter = _hexGrid.GetHexCenterFromOffset(offsetCoord); + Vector2 tileCenter = _hexGrid.GetHexCenterFromOffset(offsetCoord - Vector2.One * Mathf.Round(Size / 2f)); - // TODO: coordinates do not match for bigger maps - return new Vector3( - tileCenter.x - Mathf.Round(Size * 0.75f * 0.5f), - GetHeightAtOffset(offsetCoord), - tileCenter.y + ((Mathf.Sqrt(3) / 2) * Mathf.Round(Size * 0.5f))); + return new Vector3(tileCenter.x, GetHeightAtOffset(offsetCoord), tileCenter.y); } diff --git a/scenes/tests/EditorUI.cs b/scenes/tests/EditorUI.cs new file mode 100644 index 0000000..cee3067 --- /dev/null +++ b/scenes/tests/EditorUI.cs @@ -0,0 +1,87 @@ +using Godot; +using System; + +public class EditorUI : Control +{ + // exported members + [Export] public NodePath World; + [Export] public NodePath StreamContainer; + + // public members + public Vector2 currentTileOffset = Vector2.Zero; + + // private members + private Button _resetButton; + private Button _grassButton; + private Button _sandButton; + private Button _waterButton; + + private TileWorld _tileWorld; + private StreamContainer _streamContainer; + + private enum TileType + { + None, + Grass, + Sand, + Water + } + private TileType _currentTileType = TileType.None; + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + _tileWorld = (TileWorld) GetNode(World); + _streamContainer = (StreamContainer)GetNode(StreamContainer); + + // signals + _resetButton = (Button) FindNode("ResetButton"); + _resetButton.Connect("pressed", this, nameof(OnResetButton)); + + _grassButton = (Button) FindNode("GrassButton"); + _grassButton.Connect("pressed", this, nameof(OnGrassButton)); + + _sandButton = (Button) FindNode("SandButton"); + _sandButton.Connect("pressed", this, nameof(OnSandButton)); + + _waterButton = (Button) FindNode("WaterButton"); + _waterButton.Connect("pressed", this, nameof(OnWaterButton)); + } + + + public void OnResetButton() + { + GD.Print("Resetting Map"); + _tileWorld.Seed = _tileWorld.Seed + 1; + _tileWorld.Generate(12); + } + + public void OnGrassButton() + { + _currentTileType = TileType.Grass; + } + + public void OnSandButton() + { + _currentTileType = TileType.Sand; + + } + + public void OnWaterButton() + { + _currentTileType = TileType.Water; + } + + public void OnTileClicked(Vector2 offsetCoord) + { + switch (_currentTileType) + { + case TileType.Grass:_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Green); + break; + case TileType.Water:_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Blue); + break; + case TileType.Sand:_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Yellow); + break; + } + } +} diff --git a/scenes/tests/NavigationTests.cs b/scenes/tests/NavigationTests.cs new file mode 100644 index 0000000..0990155 --- /dev/null +++ b/scenes/tests/NavigationTests.cs @@ -0,0 +1,115 @@ +using Godot; +using System; + +public class NavigationTests : Spatial +{ + // Declare member variables here. Examples: + // private int a = 2; + // private string b = "text"; + + private StaticBody _groundLayer; + private HexGrid _hexGrid; + private HexCell _currentTile; + private HexCell _lastTile; + + private Spatial _mouseHighlight; + private ShaderMaterial _tileMaterial; + + private EditorUI _editorUi; + private TileWorld _tileWorld; + private StreamContainer _streamContainer; + private Player _player; + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + _hexGrid = new HexGrid(); + _currentTile = new HexCell(); + _lastTile = new HexCell(); + + _tileMaterial = GD.Load("materials/HexTileTextureLookup.tres"); + + _groundLayer = GetNode("GroundLayer"); + + _mouseHighlight = GetNode("MouseHighlight"); + + _editorUi = GetNode("EditorUI"); + _tileWorld = GetNode("TileWorld"); + _tileWorld.Connect("WorldGenerated", this, nameof(OnWorldGenerated)); + _streamContainer = GetNode("StreamContainer"); + + _streamContainer.SetCenterTile(_currentTile); + + _player = GetNode("Player"); + + // input handling + _groundLayer.Connect("input_event", this, nameof(OnGroundLayerInputEvent)); + _streamContainer.Connect("TileClicked", this, nameof(OnTileClicked)); + _streamContainer.Connect("TileHovered", this, nameof(OnTileHovered)); + } + + public void OnWorldGenerated() + { + _streamContainer.OnWorldGenerated(); + + // Properly place the Player + Vector2 centerTileCoord = (Vector2.One * _tileWorld.Size / 2).Round(); + Vector3 worldCenterTileCoords = _tileWorld.GetTileWorldCenterFromOffset(centerTileCoord); + worldCenterTileCoords.y = _tileWorld.GetHeightAtOffset(centerTileCoord); + Transform playerTransform = Transform.Identity; + playerTransform.origin = worldCenterTileCoords; + _player.Transform = playerTransform; + + ImageTexture newWorldTexture = new ImageTexture(); + newWorldTexture.CreateFromImage(_tileWorld.Colormap, + (uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat)); + _tileMaterial.SetShaderParam("MapAlbedoTexture", newWorldTexture); + _tileMaterial.SetShaderParam("TextureSize", (int)_tileWorld.Colormap.GetSize().x); + } + + + public void UpdateCurrentTile(HexCell tile) + { + if (_currentTile.AxialCoords == tile.AxialCoords) + { + return; + } + + _lastTile = _currentTile; + _currentTile = tile; + + GD.Print("Current tile: " + _currentTile.OffsetCoords); + + if (_lastTile.OffsetCoords != _currentTile.OffsetCoords && _editorUi != null) + { + _editorUi.currentTileOffset = _currentTile.OffsetCoords; + } + + Vector2 planeCoords = _hexGrid.GetHexCenterFromOffset(_currentTile.OffsetCoords); + Transform tileTransform = Transform.Identity; + tileTransform.origin.x = planeCoords.x; + tileTransform.origin.y = _tileWorld.GetHeightAtOffset(_currentTile.OffsetCoords) + 0.1f; + tileTransform.origin.z = planeCoords.y; + + _mouseHighlight.Transform = tileTransform; + } + + public void OnGroundLayerInputEvent(Node camera, InputEvent inputEvent, Vector3 position, Vector3 normal, + int shapeIndex) + { + UpdateCurrentTile(_hexGrid.GetHexAt(new Vector2(position.x, position.z))); + } + + public void OnTileClicked(HexTile3D tile) + { + if (_editorUi != null) + { + _editorUi.OnTileClicked(tile.OffsetCoords); + } + } + + public void OnTileHovered(HexTile3D tile) + { + UpdateCurrentTile(tile.Cell); + } +} diff --git a/scenes/tests/NavigationTests.tscn b/scenes/tests/NavigationTests.tscn new file mode 100644 index 0000000..50046a5 --- /dev/null +++ b/scenes/tests/NavigationTests.tscn @@ -0,0 +1,101 @@ +[gd_scene load_steps=12 format=2] + +[ext_resource path="res://entities/Player.tscn" type="PackedScene" id=1] +[ext_resource path="res://scenes/TileWorld.tscn" type="PackedScene" id=2] +[ext_resource path="res://utils/TileHighlight.tscn" type="PackedScene" id=3] +[ext_resource path="res://scenes/tests/NavigationTests.cs" type="Script" id=4] +[ext_resource path="res://scenes/StreamContainer.tscn" type="PackedScene" id=5] +[ext_resource path="res://scenes/Camera.tscn" type="PackedScene" id=6] +[ext_resource path="res://scenes/tests/EditorUI.cs" type="Script" id=7] + +[sub_resource type="ButtonGroup" id=4] +resource_local_to_scene = false +resource_name = "TileTypeButtonGroup" + +[sub_resource type="BoxShape" id=1] +extents = Vector3( 50, 1, 50 ) + +[sub_resource type="SpatialMaterial" id=3] +albedo_color = Color( 0.180392, 0.384314, 0.0235294, 1 ) + +[sub_resource type="CubeMesh" id=2] +material = SubResource( 3 ) + +[node name="NavigationTests" type="Spatial"] +script = ExtResource( 4 ) + +[node name="EditorUI" type="Control" parent="."] +margin_right = 40.0 +margin_bottom = 40.0 +script = ExtResource( 7 ) +World = NodePath("../TileWorld") +StreamContainer = NodePath("../StreamContainer") + +[node name="HBoxContainer" type="HBoxContainer" parent="EditorUI"] +margin_left = 5.0 +margin_top = 5.0 +margin_right = 40.0 +margin_bottom = 40.0 + +[node name="ResetButton" type="Button" parent="EditorUI/HBoxContainer"] +margin_right = 48.0 +margin_bottom = 35.0 +text = "Reset" + +[node name="ModeLabel" type="Label" parent="EditorUI/HBoxContainer"] +margin_left = 52.0 +margin_top = 10.0 +margin_right = 88.0 +margin_bottom = 24.0 +text = "Mode" + +[node name="GrassButton" type="Button" parent="EditorUI/HBoxContainer"] +margin_left = 92.0 +margin_right = 140.0 +margin_bottom = 35.0 +toggle_mode = true +group = SubResource( 4 ) +text = "Grass" + +[node name="WaterButton" type="Button" parent="EditorUI/HBoxContainer"] +margin_left = 144.0 +margin_right = 194.0 +margin_bottom = 35.0 +toggle_mode = true +group = SubResource( 4 ) +text = "Water" + +[node name="SandButton" type="Button" parent="EditorUI/HBoxContainer"] +margin_left = 198.0 +margin_right = 240.0 +margin_bottom = 35.0 +toggle_mode = true +group = SubResource( 4 ) +text = "Sand" + +[node name="Player" parent="." instance=ExtResource( 1 )] +TileWorldNode = NodePath("../TileWorld") + +[node name="GroundLayer" type="StaticBody" parent="."] + +[node name="CollisionShape" type="CollisionShape" parent="GroundLayer"] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0 ) +shape = SubResource( 1 ) + +[node name="MeshInstance" type="MeshInstance" parent="GroundLayer"] +transform = Transform( 50, 0, 0, 0, 1, 0, 0, 0, 50, 0, -1.05, 0 ) +mesh = SubResource( 2 ) + +[node name="TileWorld" parent="." instance=ExtResource( 2 )] +GenerationMapType = 2 +Size = 20 +DebugMap = true + +[node name="MouseHighlight" parent="." instance=ExtResource( 3 )] + +[node name="StreamContainer" parent="." instance=ExtResource( 5 )] + +[node name="Camera" parent="." instance=ExtResource( 6 )] +transform = Transform( 1, 0, 0, 0, 0.60042, 0.799685, 0, -0.799685, 0.60042, -4.76837e-07, 9.56665, 7.86873 ) + +[editable path="Player"] diff --git a/utils/ExportFlagsEnumAttribute.cs b/utils/ExportFlagsEnumAttribute.cs new file mode 100644 index 0000000..e941efe --- /dev/null +++ b/utils/ExportFlagsEnumAttribute.cs @@ -0,0 +1,47 @@ +// Source: https://gist.github.com/kleonc/a2bab51686ac6f4d7cb28aec88efa5d9 + +using System; +using System.Collections.Generic; +using System.Linq; +using Godot; + +namespace Namespace +{ + using UnderlyingType = UInt64; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class ExportFlagsEnumAttribute : ExportAttribute + { + public ExportFlagsEnumAttribute(Type enumType) + : base(PropertyHint.Flags, GetFlagsEnumHintString(enumType)) + { } + + private static string GetFlagsEnumHintString(Type enumType) + { + Dictionary> flagNamesByFlag = new Dictionary>(); + UnderlyingType flag = (UnderlyingType)1; + foreach (string name in Enum.GetNames(enumType)) + { + UnderlyingType value = (UnderlyingType)Convert.ChangeType(Enum.Parse(enumType, name), typeof(UnderlyingType)); + while (value > flag) + { + if (!flagNamesByFlag.ContainsKey(flag)) + { + flagNamesByFlag.Add(flag, new List()); + } + flag <<= 1; + } + if (value == flag) + { + if (!flagNamesByFlag.TryGetValue(flag, out List names)) + { + names = new List(); + flagNamesByFlag.Add(flag, names); + } + names.Add(name); + } + } + return string.Join(", ", flagNamesByFlag.Values.Select(flagNames => string.Join(" / ", flagNames))); + } + } +} \ No newline at end of file