add cellular automata generator
This commit is contained in:
360
addons/ca_level_generator/CaLevelGeneratorDock.cs
Normal file
360
addons/ca_level_generator/CaLevelGeneratorDock.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
#if TOOLS
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.Tools.CaLevelGenerator;
|
||||
|
||||
[Tool]
|
||||
public partial class CaLevelGeneratorDock : Control
|
||||
{
|
||||
public CaLevelGeneratorPlugin Plugin { get; set; }
|
||||
|
||||
// Controls referenced in Task 8 wiring
|
||||
private OptionButton _layerDropdown;
|
||||
private OptionButton _terrainDropdown;
|
||||
private HBoxContainer _rawTileRow;
|
||||
private SpinBox _sourceIdSpin;
|
||||
private SpinBox _atlasXSpin;
|
||||
private SpinBox _atlasYSpin;
|
||||
private Button _caveBtn;
|
||||
private Button _platformBtn;
|
||||
private Button _terrainBtn;
|
||||
private SpinBox _widthSpin;
|
||||
private SpinBox _heightSpin;
|
||||
private SpinBox _offsetXSpin;
|
||||
private SpinBox _offsetYSpin;
|
||||
private HSlider _densitySlider;
|
||||
private Label _densityLabel;
|
||||
private HSlider _smoothPassSlider;
|
||||
private Label _smoothPassLabel;
|
||||
private CheckBox _borderCheck;
|
||||
private SpinBox _seedSpin;
|
||||
private Button _genBtn;
|
||||
private Button _smoothBtn;
|
||||
private Button _clearBtn;
|
||||
|
||||
private CaMode _mode = CaMode.Cave;
|
||||
|
||||
private System.Collections.Generic.List<TileMapLayer> _layers = new();
|
||||
private TileMapLayer _selectedLayer;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
CustomMinimumSize = new Vector2(200, 0);
|
||||
var scroll = new ScrollContainer();
|
||||
scroll.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
||||
scroll.SizeFlagsVertical = SizeFlags.ExpandFill;
|
||||
AddChild(scroll);
|
||||
|
||||
var vbox = new VBoxContainer();
|
||||
vbox.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||
scroll.AddChild(vbox);
|
||||
|
||||
// Target Layer
|
||||
vbox.AddChild(new Label { Text = "Target Layer" });
|
||||
_layerDropdown = new OptionButton();
|
||||
_layerDropdown.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||
vbox.AddChild(_layerDropdown);
|
||||
|
||||
// Terrain
|
||||
vbox.AddChild(new Label { Text = "Terrain" });
|
||||
_terrainDropdown = new OptionButton();
|
||||
_terrainDropdown.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||
vbox.AddChild(_terrainDropdown);
|
||||
|
||||
// Raw tile row (hidden when terrain is available)
|
||||
_rawTileRow = new HBoxContainer();
|
||||
_rawTileRow.Visible = false;
|
||||
_rawTileRow.AddChild(new Label { Text = "Src" });
|
||||
_sourceIdSpin = new SpinBox { MinValue = 0, MaxValue = 99, Value = 0 };
|
||||
_rawTileRow.AddChild(_sourceIdSpin);
|
||||
_rawTileRow.AddChild(new Label { Text = "X" });
|
||||
_atlasXSpin = new SpinBox { MinValue = 0, MaxValue = 99, Value = 0 };
|
||||
_rawTileRow.AddChild(_atlasXSpin);
|
||||
_rawTileRow.AddChild(new Label { Text = "Y" });
|
||||
_atlasYSpin = new SpinBox { MinValue = 0, MaxValue = 99, Value = 0 };
|
||||
_rawTileRow.AddChild(_atlasYSpin);
|
||||
vbox.AddChild(_rawTileRow);
|
||||
|
||||
// Mode toggle
|
||||
vbox.AddChild(new Label { Text = "Mode" });
|
||||
var modeRow = new HBoxContainer();
|
||||
modeRow.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||
_caveBtn = MakeModeButton("Cave", () => SetMode(CaMode.Cave));
|
||||
_platformBtn = MakeModeButton("Platform", () => SetMode(CaMode.Platform));
|
||||
_terrainBtn = MakeModeButton("Terrain", () => SetMode(CaMode.Terrain));
|
||||
_caveBtn.ButtonPressed = true;
|
||||
modeRow.AddChild(_caveBtn);
|
||||
modeRow.AddChild(_platformBtn);
|
||||
modeRow.AddChild(_terrainBtn);
|
||||
vbox.AddChild(modeRow);
|
||||
|
||||
vbox.AddChild(new HSeparator());
|
||||
|
||||
// Width / Height
|
||||
var dimRow = new HBoxContainer();
|
||||
dimRow.AddChild(MakeSpinColumn("Width", out _widthSpin, 5, 500, 40));
|
||||
dimRow.AddChild(MakeSpinColumn("Height", out _heightSpin, 5, 500, 22));
|
||||
vbox.AddChild(dimRow);
|
||||
|
||||
// Offset X / Y
|
||||
var offRow = new HBoxContainer();
|
||||
offRow.AddChild(MakeSpinColumn("Offset X", out _offsetXSpin, -9999, 9999, 0));
|
||||
offRow.AddChild(MakeSpinColumn("Offset Y", out _offsetYSpin, -9999, 9999, 0));
|
||||
vbox.AddChild(offRow);
|
||||
|
||||
// Fill Density
|
||||
var densHeader = new HBoxContainer();
|
||||
densHeader.AddChild(new Label { Text = "Fill Density", SizeFlagsHorizontal = SizeFlags.ExpandFill });
|
||||
_densityLabel = new Label { Text = "48%" };
|
||||
densHeader.AddChild(_densityLabel);
|
||||
vbox.AddChild(densHeader);
|
||||
_densitySlider = new HSlider { MinValue = 0, MaxValue = 100, Value = 48, Step = 1 };
|
||||
_densitySlider.ValueChanged += v => _densityLabel.Text = $"{(int)v}%";
|
||||
vbox.AddChild(_densitySlider);
|
||||
|
||||
// Smoothing Passes
|
||||
var smoothHeader = new HBoxContainer();
|
||||
smoothHeader.AddChild(new Label { Text = "Smoothing Passes", SizeFlagsHorizontal = SizeFlags.ExpandFill });
|
||||
_smoothPassLabel = new Label { Text = "4" };
|
||||
smoothHeader.AddChild(_smoothPassLabel);
|
||||
vbox.AddChild(smoothHeader);
|
||||
_smoothPassSlider = new HSlider { MinValue = 0, MaxValue = 10, Value = 4, Step = 1 };
|
||||
_smoothPassSlider.ValueChanged += v => _smoothPassLabel.Text = $"{(int)v}";
|
||||
vbox.AddChild(_smoothPassSlider);
|
||||
|
||||
// Border walls
|
||||
_borderCheck = new CheckBox { Text = "Solid border walls", ButtonPressed = true };
|
||||
vbox.AddChild(_borderCheck);
|
||||
|
||||
// Seed
|
||||
vbox.AddChild(new Label { Text = "Seed" });
|
||||
var seedRow = new HBoxContainer();
|
||||
_seedSpin = new SpinBox { MinValue = 0, MaxValue = int.MaxValue, Value = 12345,
|
||||
AllowGreater = true, SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
||||
var randBtn = new Button { Text = "🎲" };
|
||||
randBtn.Pressed += () => _seedSpin.Value = GD.Randi();
|
||||
seedRow.AddChild(_seedSpin);
|
||||
seedRow.AddChild(randBtn);
|
||||
vbox.AddChild(seedRow);
|
||||
|
||||
vbox.AddChild(new HSeparator());
|
||||
|
||||
// Action buttons
|
||||
_genBtn = new Button { Text = "▶ Generate", SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
||||
vbox.AddChild(_genBtn);
|
||||
|
||||
var actionRow = new HBoxContainer();
|
||||
actionRow.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||
_smoothBtn = new Button { Text = "✦ Smooth", SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
||||
_clearBtn = new Button { Text = "✕ Clear", SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
||||
actionRow.AddChild(_smoothBtn);
|
||||
actionRow.AddChild(_clearBtn);
|
||||
vbox.AddChild(actionRow);
|
||||
|
||||
_layerDropdown.ItemSelected += OnLayerSelected;
|
||||
_terrainDropdown.ItemSelected += _ => SaveSettings();
|
||||
_genBtn.Pressed += OnGenerate;
|
||||
_smoothBtn.Pressed += OnSmooth;
|
||||
_clearBtn.Pressed += OnClear;
|
||||
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
public void OnSceneChanged(Node sceneRoot)
|
||||
{
|
||||
ScanLayers(sceneRoot);
|
||||
RestoreLastLayer(sceneRoot?.SceneFilePath ?? "");
|
||||
}
|
||||
|
||||
private void SetMode(CaMode mode)
|
||||
{
|
||||
_mode = mode;
|
||||
_caveBtn.ButtonPressed = mode == CaMode.Cave;
|
||||
_platformBtn.ButtonPressed = mode == CaMode.Platform;
|
||||
_terrainBtn.ButtonPressed = mode == CaMode.Terrain;
|
||||
}
|
||||
|
||||
private static Button MakeModeButton(string text, System.Action onPressed)
|
||||
{
|
||||
var btn = new Button { Text = text, ToggleMode = true,
|
||||
SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
||||
btn.Pressed += onPressed;
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static Control MakeSpinColumn(string label, out SpinBox spin,
|
||||
double min, double max, double value)
|
||||
{
|
||||
var col = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
||||
col.AddChild(new Label { Text = label });
|
||||
spin = new SpinBox { MinValue = min, MaxValue = max, Value = value,
|
||||
SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
||||
col.AddChild(spin);
|
||||
return col;
|
||||
}
|
||||
|
||||
private void ScanLayers(Node root)
|
||||
{
|
||||
_layers.Clear();
|
||||
_layerDropdown.Clear();
|
||||
if (root == null) return;
|
||||
CollectLayers(root);
|
||||
foreach (var layer in _layers)
|
||||
_layerDropdown.AddItem(layer.Name);
|
||||
if (_layers.Count > 0) OnLayerSelected(0);
|
||||
}
|
||||
|
||||
private void CollectLayers(Node node)
|
||||
{
|
||||
if (node is TileMapLayer layer) _layers.Add(layer);
|
||||
foreach (Node child in node.GetChildren()) CollectLayers(child);
|
||||
}
|
||||
|
||||
private void OnLayerSelected(long index)
|
||||
{
|
||||
if (index < 0 || index >= _layers.Count) return;
|
||||
_selectedLayer = _layers[(int)index];
|
||||
PopulateTerrainDropdown(_selectedLayer);
|
||||
|
||||
var sceneRoot = EditorInterface.Singleton.GetEditedSceneRoot();
|
||||
if (sceneRoot != null)
|
||||
{
|
||||
var key = $"ca_generator/last_layer_{sceneRoot.SceneFilePath}";
|
||||
EditorInterface.Singleton.GetEditorSettings()
|
||||
.SetSetting(key, _selectedLayer.GetPath().ToString());
|
||||
}
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private void RestoreLastLayer(string sceneFilePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sceneFilePath) || _layers.Count == 0) return;
|
||||
var key = $"ca_generator/last_layer_{sceneFilePath}";
|
||||
var es = EditorInterface.Singleton.GetEditorSettings();
|
||||
if (!es.HasSetting(key)) return;
|
||||
var path = es.GetSetting(key).AsString();
|
||||
for (int i = 0; i < _layers.Count; i++)
|
||||
{
|
||||
if (_layers[i].GetPath().ToString() == path)
|
||||
{
|
||||
_layerDropdown.Select(i);
|
||||
OnLayerSelected(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateTerrainDropdown(TileMapLayer layer)
|
||||
{
|
||||
_terrainDropdown.Clear();
|
||||
var tileSet = layer.TileSet;
|
||||
bool hasTerrains = false;
|
||||
|
||||
if (tileSet != null)
|
||||
{
|
||||
for (int ts = 0; ts < tileSet.GetTerrainSetsCount(); ts++)
|
||||
{
|
||||
for (int t = 0; t < tileSet.GetTerrainsCount(ts); t++)
|
||||
{
|
||||
_terrainDropdown.AddItem(tileSet.GetTerrainName(ts, t));
|
||||
_terrainDropdown.SetItemMetadata(
|
||||
_terrainDropdown.ItemCount - 1,
|
||||
Variant.From((ts << 16) | t));
|
||||
hasTerrains = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_terrainDropdown.Visible = hasTerrains;
|
||||
_rawTileRow.Visible = !hasTerrains;
|
||||
}
|
||||
|
||||
private CaGeneratorSettings BuildSettings()
|
||||
{
|
||||
int terrainSet = -1, terrain = 0;
|
||||
if (_terrainDropdown.Visible && _terrainDropdown.ItemCount > 0)
|
||||
{
|
||||
int packed = _terrainDropdown.GetSelectedMetadata().AsInt32();
|
||||
terrainSet = packed >> 16;
|
||||
terrain = packed & 0xFFFF;
|
||||
}
|
||||
|
||||
return new CaGeneratorSettings
|
||||
{
|
||||
Mode = _mode,
|
||||
Width = (int)_widthSpin.Value,
|
||||
Height = (int)_heightSpin.Value,
|
||||
Offset = new Vector2I((int)_offsetXSpin.Value, (int)_offsetYSpin.Value),
|
||||
FillDensity = (float)_densitySlider.Value / 100f,
|
||||
SmoothingPasses = (int)_smoothPassSlider.Value,
|
||||
BorderWalls = _borderCheck.ButtonPressed,
|
||||
Seed = (int)_seedSpin.Value,
|
||||
TerrainSet = terrainSet,
|
||||
Terrain = terrain,
|
||||
SourceId = (int)_sourceIdSpin.Value,
|
||||
AtlasCoords = new Vector2I((int)_atlasXSpin.Value, (int)_atlasYSpin.Value),
|
||||
};
|
||||
}
|
||||
|
||||
private void OnGenerate()
|
||||
{
|
||||
if (_selectedLayer == null) return;
|
||||
var settings = BuildSettings();
|
||||
var grid = CaGenerator.Generate(settings);
|
||||
TilemapPainter.Paint(_selectedLayer, grid, settings, Plugin.UndoRedo);
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private void OnSmooth()
|
||||
{
|
||||
if (_selectedLayer == null) return;
|
||||
TilemapPainter.Smooth(_selectedLayer, BuildSettings(), Plugin.UndoRedo);
|
||||
}
|
||||
|
||||
private void OnClear()
|
||||
{
|
||||
if (_selectedLayer == null) return;
|
||||
TilemapPainter.Clear(_selectedLayer, BuildSettings(), Plugin.UndoRedo);
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
var es = EditorInterface.Singleton.GetEditorSettings();
|
||||
es.SetSetting("ca_generator/mode", (int)_mode);
|
||||
es.SetSetting("ca_generator/width", (int)_widthSpin.Value);
|
||||
es.SetSetting("ca_generator/height", (int)_heightSpin.Value);
|
||||
es.SetSetting("ca_generator/offset_x", (int)_offsetXSpin.Value);
|
||||
es.SetSetting("ca_generator/offset_y", (int)_offsetYSpin.Value);
|
||||
es.SetSetting("ca_generator/fill_density", _densitySlider.Value);
|
||||
es.SetSetting("ca_generator/smoothing_passes", (int)_smoothPassSlider.Value);
|
||||
es.SetSetting("ca_generator/border_walls", _borderCheck.ButtonPressed);
|
||||
es.SetSetting("ca_generator/seed", (int)_seedSpin.Value);
|
||||
es.SetSetting("ca_generator/source_id", (int)_sourceIdSpin.Value);
|
||||
es.SetSetting("ca_generator/atlas_x", (int)_atlasXSpin.Value);
|
||||
es.SetSetting("ca_generator/atlas_y", (int)_atlasYSpin.Value);
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
var es = EditorInterface.Singleton.GetEditorSettings();
|
||||
if (es == null) return;
|
||||
|
||||
SetMode((CaMode)(es.HasSetting("ca_generator/mode") ? es.GetSetting("ca_generator/mode").AsInt32() : 0));
|
||||
_widthSpin.Value = es.HasSetting("ca_generator/width") ? es.GetSetting("ca_generator/width").AsDouble() : 40.0;
|
||||
_heightSpin.Value = es.HasSetting("ca_generator/height") ? es.GetSetting("ca_generator/height").AsDouble() : 22.0;
|
||||
_offsetXSpin.Value = es.HasSetting("ca_generator/offset_x") ? es.GetSetting("ca_generator/offset_x").AsDouble() : 0.0;
|
||||
_offsetYSpin.Value = es.HasSetting("ca_generator/offset_y") ? es.GetSetting("ca_generator/offset_y").AsDouble() : 0.0;
|
||||
_densitySlider.Value = es.HasSetting("ca_generator/fill_density") ? es.GetSetting("ca_generator/fill_density").AsDouble() : 48.0;
|
||||
_smoothPassSlider.Value = es.HasSetting("ca_generator/smoothing_passes") ? es.GetSetting("ca_generator/smoothing_passes").AsDouble() : 4.0;
|
||||
_borderCheck.ButtonPressed = es.HasSetting("ca_generator/border_walls") ? es.GetSetting("ca_generator/border_walls").AsBool() : true;
|
||||
_seedSpin.Value = es.HasSetting("ca_generator/seed") ? es.GetSetting("ca_generator/seed").AsDouble() : 12345.0;
|
||||
|
||||
_sourceIdSpin.Value = es.HasSetting("ca_generator/source_id") ? es.GetSetting("ca_generator/source_id").AsDouble() : 0.0;
|
||||
_atlasXSpin.Value = es.HasSetting("ca_generator/atlas_x") ? es.GetSetting("ca_generator/atlas_x").AsDouble() : 0.0;
|
||||
_atlasYSpin.Value = es.HasSetting("ca_generator/atlas_y") ? es.GetSetting("ca_generator/atlas_y").AsDouble() : 0.0;
|
||||
|
||||
_densityLabel.Text = $"{(int)_densitySlider.Value}%";
|
||||
_smoothPassLabel.Text = $"{(int)_smoothPassSlider.Value}";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user