Started working on navigation / editing sandbox.

WorldChunkRefactoring
Martin Felis 2023-07-09 00:16:37 +02:00
parent aaf9c85be7
commit 1397c0a7eb
10 changed files with 457 additions and 150 deletions

View File

@ -11,7 +11,7 @@ using GoDotLog;
/// <summary>
/// </summary>
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<NavigationPoint> _pathWorldNavigationPoints;
private HexCell[] _path;
@ -90,6 +93,12 @@ public class NavigationComponent : Node
{
base._Ready();
_pathWorldNavigationPoints = new List<NavigationPoint>();
_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]);

View File

@ -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<TileWorld>(TileWorldNode);
TaskQueueComponent = new TaskQueueComponent();
_itemAttractorArea = (Area)FindNode("ItemAttractorArea", false);

View File

@ -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="."]

View File

@ -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);
}

View File

@ -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

View File

@ -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();
@ -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);
}

87
scenes/tests/EditorUI.cs Normal file
View File

@ -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;
}
}
}

View File

@ -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<ShaderMaterial>("materials/HexTileTextureLookup.tres");
_groundLayer = GetNode<StaticBody>("GroundLayer");
_mouseHighlight = GetNode<Spatial>("MouseHighlight");
_editorUi = GetNode<EditorUI>("EditorUI");
_tileWorld = GetNode<TileWorld>("TileWorld");
_tileWorld.Connect("WorldGenerated", this, nameof(OnWorldGenerated));
_streamContainer = GetNode<StreamContainer>("StreamContainer");
_streamContainer.SetCenterTile(_currentTile);
_player = GetNode<Player>("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);
}
}

View File

@ -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"]

View File

@ -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<UnderlyingType, List<string>> flagNamesByFlag = new Dictionary<UnderlyingType, List<string>>();
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<string>());
}
flag <<= 1;
}
if (value == flag)
{
if (!flagNamesByFlag.TryGetValue(flag, out List<string> names))
{
names = new List<string>();
flagNamesByFlag.Add(flag, names);
}
names.Add(name);
}
}
return string.Join(", ", flagNamesByFlag.Values.Select(flagNames => string.Join(" / ", flagNames)));
}
}
}