Adjust the map generator.

调整地图生成器。
This commit is contained in:
Cold-Mint 2024-05-20 22:38:41 +08:00
parent dd2577cfd0
commit 529d5e95b5
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
16 changed files with 191 additions and 245 deletions

View File

@ -1,22 +1,15 @@
using System.Threading.Tasks;
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.map;
using ColdMint.scripts.map.LayoutParsingStrategy;
using ColdMint.scripts.map.layoutStrategy;
using ColdMint.scripts.map.room;
using ColdMint.scripts.map.roomHolder;
using ColdMint.scripts.map.RoomPlacer;
using ColdMint.scripts.map.RoomProvider;
using ColdMint.scripts.map.slotsMatcher;
using Godot;
namespace ColdMint.scripts.loader.sceneLoader;
public partial class GameSceneLoader : SceneLoaderTemplate
{
public override Task InitializeData()
{
//加载血条场景
@ -36,7 +29,10 @@ public partial class GameSceneLoader : SceneLoaderTemplate
public override async Task LoadScene()
{
MapGenerator.MapRoot = GetNode<Node>("MapRoot");
MapGenerator.LayoutStrategy = new TestLayoutStrategy();
MapGenerator.LayoutParsingStrategy = new SequenceLayoutParsingStrategy();
MapGenerator.RoomPlacementStrategy = new PatchworkRoomPlacementStrategy();
await MapGenerator.GenerateMap();
}
}

View File

@ -4,6 +4,8 @@ using ColdMint.scripts.debug;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.LayoutParsingStrategy;
using ColdMint.scripts.map.layoutStrategy;
using ColdMint.scripts.map.room;
using Godot;
namespace ColdMint.scripts.map;
@ -23,6 +25,12 @@ public static class MapGenerator
/// </summary>
private static ILayoutStrategy? _layoutStrategy;
/// <summary>
/// <para>Map root node</para>
/// <para>地图根节点</para>
/// </summary>
private static Node? _mapRoot;
/// <summary>
/// <para>Room placement strategy</para>
/// <para>房间的放置策略</para>
@ -36,6 +44,12 @@ public static class MapGenerator
/// </summary>
private static ILayoutParsingStrategy? _layoutParsingStrategy;
public static Node? MapRoot
{
get => _mapRoot;
set => _mapRoot = value;
}
public static ILayoutParsingStrategy? LayoutParsingStrategy
{
get => _layoutParsingStrategy;
@ -60,7 +74,8 @@ public static class MapGenerator
/// </summary>
public static async Task GenerateMap()
{
if (_layoutStrategy == null || _roomPlacementStrategy == null || _layoutParsingStrategy == null)
if (_layoutStrategy == null || _roomPlacementStrategy == null || _layoutParsingStrategy == null ||
_mapRoot == null)
{
LogCat.LogError("map_generator_missing_parameters");
return;
@ -79,7 +94,7 @@ public static class MapGenerator
_layoutParsingStrategy.SetLevelGraph(levelGraphEditorSaveData);
//Save the dictionary, put the ID in the room data, corresponding to the successful placement of the room.
//保存字典将房间数据内的ID对应放置成功的房间。
var roomDictionary = new Dictionary<string, IRoom>();
var roomDictionary = new Dictionary<string, Room>();
while (await _layoutParsingStrategy.HasNext())
{
//When a new room needs to be placed
@ -91,12 +106,15 @@ public static class MapGenerator
}
var nextParentNodeId = await _layoutParsingStrategy.GetNextParentNodeId();
IRoom? parentRoomNode = null;
Room? parentRoomNode = null;
if (nextParentNodeId != null)
{
//If the new room has the parent's ID, then we pass the parent's room into the compute function.
//如果新房间有父节点的ID那么我们将父节点的房间传入到计算函数内。
parentRoomNode = roomDictionary[nextParentNodeId];
if (roomDictionary.TryGetValue(nextParentNodeId, out var value))
{
parentRoomNode = value;
}
}
var roomPlacementData =
@ -106,7 +124,7 @@ public static class MapGenerator
continue;
}
if (!await _roomPlacementStrategy.PlaceRoom(roomPlacementData))
if (!await _roomPlacementStrategy.PlaceRoom(_mapRoot, roomPlacementData))
{
continue;
}

View File

@ -1,46 +0,0 @@
using ColdMint.scripts.debug;
using ColdMint.scripts.map.interfaces;
using Godot;
namespace ColdMint.scripts.map;
public class MapGeneratorConfig : IMapGeneratorConfig
{
/// <summary>
/// <para>At least how many rooms are generated</para>
/// <para>至少生成多少个房间</para>
/// </summary>
public const int MinRoomCount = 15;
/// <summary>
/// <para>Maximum number of rooms generated</para>
/// <para>最多生成多少个房间</para>
/// </summary>
public const int MaxRoomCount = 30;
public const int MinBranchCount = 3;
public const int MaxBranchCount = 5;
private int _roomCount;
private int _branchCount;
public MapGeneratorConfig(Node2D mapRoot, ulong seed)
{
MapRoot = mapRoot;
Seed = seed;
RandomNumberGenerator = new RandomNumberGenerator();
RandomNumberGenerator.Seed = seed;
_roomCount = RandomNumberGenerator.RandiRange(MinRoomCount, MaxRoomCount);
_branchCount = RandomNumberGenerator.RandiRange(MinBranchCount, MaxBranchCount);
LogCat.Log("Seed:" + seed + " RoomCount:" + _roomCount);
}
public Node2D MapRoot { get; }
public int RoomCount => _roomCount;
public int BranchCount => _branchCount;
public ulong Seed { get; }
public RandomNumberGenerator RandomNumberGenerator { get; }
}

View File

@ -1,53 +0,0 @@
using System.Collections.Generic;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.room;
namespace ColdMint.scripts.map.RoomProvider;
public class RoomProvider : IRoomProvider
{
private List<RoomTemplate> _roomTemplates;
public RoomProvider()
{
_roomTemplates = new List<RoomTemplate>();
}
/// <summary>
/// <para>AddRoom</para>
/// <para>添加房间</para>
/// </summary>
/// <remarks>
///<para>If the initial room is not set, the first room added will be automatically set as the initial room</para>
///<para>若未设置初始房间,那么第一次添加的房间将被自动设置为初始房间</para>
/// </remarks>
/// <param name="roomTemplate"></param>
public void AddRoom(RoomTemplate roomTemplate)
{
if (InitialRoom == null)
{
InitialRoom = roomTemplate;
return;
}
_roomTemplates.Add(roomTemplate);
}
public IRoomTemplate? InitialRoom { get; set; }
public IRoomTemplate GetRoomRes(int index, IMapGeneratorConfig config)
{
var indexInList = config.RandomNumberGenerator.RandiRange(0, _roomTemplates.Count - 1);
IRoomTemplate result = _roomTemplates[indexInList];
//添加一次使用次数,当模板不能再次使用时,从列表内移除。
result.AddUsedNumber();
if (!result.CanUse)
{
_roomTemplates.RemoveAt(indexInList);
}
return result;
}
}

View File

@ -1,4 +1,4 @@
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.room;
using Godot;
namespace ColdMint.scripts.map.dateBean;
@ -18,5 +18,5 @@ public class RoomPlacementData
/// <para>Place the room template</para>
/// <para>放置的房间模板</para>
/// </summary>
public IRoom? Room { get; set; }
public Room? Room { get; set; }
}

View File

@ -1,36 +0,0 @@
using Godot;
namespace ColdMint.scripts.map.interfaces;
/// <summary>
/// <para>IMapGeneratorConfig</para>
/// <para>房间生成器配置</para>
/// </summary>
public interface IMapGeneratorConfig
{
Node2D MapRoot { get; }
/// <summary>
/// <para>How many rooms do we need to generate</para>
/// <para>我们需要生成多少个房间</para>
/// </summary>
int RoomCount { get; }
/// <summary>
/// <para>The number of forks in this map</para>
/// <para>这个地图的分叉数量</para>
/// </summary>
int BranchCount { get; }
/// <summary>
/// <para>seed</para>
/// <para>种子</para>
/// </summary>
ulong Seed { get; }
/// <summary>
/// <para>RandomNumberGenerator</para>
/// <para>随机数生成器</para>
/// </summary>
RandomNumberGenerator RandomNumberGenerator { get; }
}

View File

@ -1,35 +0,0 @@
using ColdMint.scripts.map.dateBean;
using Godot;
namespace ColdMint.scripts.map.interfaces;
/// <summary>
/// <para>IRoom</para>
/// <para>表示房间</para>
/// </summary>
public interface IRoom
{
/// <summary>
/// <para>Set room scene</para>
/// <para>设置房间场景</para>
/// </summary>
PackedScene? RoomScene { get; set; }
/// <summary>
/// <para>Tile map</para>
/// <para>瓦片地图</para>
/// </summary>
TileMap? TileMap { get; set; }
/// <summary>
/// <para>Gets the root node of the room</para>
/// <para>获取房间的根节点</para>
/// </summary>
Node2D? RootNode { get; }
/// <summary>
/// <para>The room holds the corresponding slot data</para>
/// <para>房间持有对应的插槽数据</para>
/// </summary>
RoomSlot?[]? RoomSlots { get; }
}

View File

@ -1,4 +1,6 @@
namespace ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.room;
namespace ColdMint.scripts.map.interfaces;
/// <summary>
/// <para>Room holder</para>
@ -10,13 +12,13 @@
/// </remarks>
public interface IRoomHolder
{
bool AddRoom(IRoom room);
bool AddRoom(Room room);
/// <summary>
/// <para>LastRoom</para>
/// <para>最后添加的房间</para>
/// </summary>
IRoom? LastRoom { get; }
Room? LastRoom { get; }
/// <summary>
/// <para>Number of rooms that have been placed</para>

View File

@ -1,6 +1,8 @@
using System.Threading.Tasks;
using ColdMint.scripts.levelGraphEditor;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.room;
using Godot;
namespace ColdMint.scripts.map.interfaces;
@ -14,15 +16,16 @@ public interface IRoomPlacementStrategy
/// <para>Place the room in the designated location</para>
/// <para>在指定的位置放置房间</para>
/// </summary>
/// <param name="mapRoot"></param>
/// <param name="roomPlacementData">
///<para>Room placement information</para>
///<para>房间放置信息</para>
/// <para>Room placement information</para>
/// <para>房间放置信息</para>
/// </param>
/// <returns>
///<para>Placement success or not</para>
///<para>是否放置成功</para>
/// <para>Placement success or not</para>
/// <para>是否放置成功</para>
/// </returns>
public Task<bool> PlaceRoom(RoomPlacementData roomPlacementData);
public Task<bool> PlaceRoom(Node mapRoot, RoomPlacementData roomPlacementData);
/// <summary>
/// <para>Calculate new room placement information</para>
@ -37,5 +40,5 @@ public interface IRoomPlacementStrategy
///<para>欲放置的新房间数据</para>
/// </param>
/// <returns></returns>
public Task<RoomPlacementData?> CalculateNewRoomPlacementData(IRoom? parentRoomNode, RoomNodeData newRoomNodeData);
public Task<RoomPlacementData?> CalculateNewRoomPlacementData(Room? parentRoomNode, RoomNodeData newRoomNodeData);
}

View File

@ -1,28 +0,0 @@
namespace ColdMint.scripts.map.interfaces;
/// <summary>
/// <para>Room provider</para>
/// <para>房间提供者</para>
/// </summary>
/// <remarks>
///<para>Responsible for providing room templates for map generator.</para>
///<para>负责为地图生成器提供房间模板。</para>
/// </remarks>
public interface IRoomProvider
{
/// <summary>
/// <para>Initial room</para>
/// <para>初始房间</para>
/// </summary>
IRoomTemplate? InitialRoom { get; set; }
/// <summary>
/// <para>Acquire room assets</para>
/// <para>获取房间资产</para>
/// </summary>
/// <param name="index"></param>
/// <param name="config"></param>
/// <returns></returns>
IRoomTemplate? GetRoomRes(int index, IMapGeneratorConfig config);
}

View File

@ -1,5 +1,6 @@
using System.Threading.Tasks;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.room;
namespace ColdMint.scripts.map.interfaces;
@ -16,7 +17,7 @@ public interface IRoomSlotsMatcher
/// <param name="mainRoom"></param>
/// <param name="newRoom"></param>
/// <returns></returns>
Task<bool> IsMatch(IRoom? mainRoom, IRoom newRoom);
Task<bool> IsMatch(Room? mainRoom, Room newRoom);
/// <summary>

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using ColdMint.scripts.debug;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.utils;
using Godot;
@ -16,7 +15,7 @@ namespace ColdMint.scripts.map.room;
///<para>The room template is like a jigsaw puzzle and participates in the map building process.</para>
///<para>房间模板就像一个拼图,参与到地图的构建过程中。</para>
/// </remarks>
public class Room : IRoom
public class Room
{
private Node2D? _rootNode;
private RoomSlot?[]? _roomSlots;

View File

@ -1,4 +1,4 @@
using ColdMint.scripts.map.interfaces;
using System.Collections.Generic;
using Godot;
namespace ColdMint.scripts.map.room;
@ -9,14 +9,67 @@ namespace ColdMint.scripts.map.room;
/// </summary>
public static class RoomFactory
{
/// <summary>
/// <para>A room template sets a path to a room resource</para>
/// <para>房间模板集转房间资源路径</para>
/// </summary>
/// <param name="roomTemplateSet"></param>
/// <returns>
/// <para>Returned value Checked for the existence of the file.</para>
/// <para>返回值已检验文件是否存在。</para>
/// </returns>
public static string[] RoomTemplateSetToRoomRes(string[] roomTemplateSet)
{
var resList = new List<string>();
foreach (var roomTemplate in roomTemplateSet)
{
//Detects whether it is a folder
//检测是否为文件夹
if (DirAccess.DirExistsAbsolute(roomTemplate))
{
using var dir = DirAccess.Open(roomTemplate);
if (dir != null)
{
dir.ListDirBegin();
var fileName = dir.GetNext();
while (!string.IsNullOrEmpty(fileName))
{
if (!dir.CurrentIsDir())
{
resList.Add(fileName);
}
fileName = dir.GetNext();
}
}
}
if (FileAccess.FileExists(roomTemplate))
{
resList.Add(roomTemplate);
}
}
return resList.ToArray();
}
/// <summary>
/// <para>CreateRoom</para>
/// <para>创建房间模板</para>
/// </summary>
/// <param name="resPath"></param>
/// <returns></returns>
public static IRoom CreateRoom(string resPath)
public static Room? CreateRoom(string resPath)
{
//If the file does not exist, null is returned
//如果文件不存在则返回null
var exists = FileAccess.FileExists(resPath);
if (!exists)
{
return null;
}
var room = new Room
{
RoomScene = GD.Load<PackedScene>(resPath)

View File

@ -1,19 +1,20 @@
using System.Collections.Generic;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.room;
namespace ColdMint.scripts.map.roomHolder;
public class RoomHolder : IRoomHolder
{
private readonly List<IRoom> _rooms = new List<IRoom>();
private readonly List<Room> _rooms = new List<Room>();
public bool AddRoom(IRoom room)
public bool AddRoom(Room room)
{
_rooms.Add(room);
return true;
}
public IRoom? LastRoom
public Room? LastRoom
{
get
{

View File

@ -2,6 +2,7 @@
using ColdMint.scripts.levelGraphEditor;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.room;
using Godot;
namespace ColdMint.scripts.map.RoomPlacer;
@ -16,19 +17,88 @@ namespace ColdMint.scripts.map.RoomPlacer;
/// </remarks>
public class PatchworkRoomPlacementStrategy : IRoomPlacementStrategy
{
public Task<bool> PlaceRoom(RoomPlacementData roomPlacementData)
private readonly Vector2 _halfCell = new Vector2(Config.CellSize / 2f, Config.CellSize / 2f);
public Task<bool> PlaceRoom(Node mapRoot, RoomPlacementData roomPlacementData)
{
throw new System.NotImplementedException();
if (roomPlacementData.Room == null || roomPlacementData.Position == null)
{
return Task.FromResult(false);
}
public Task<RoomPlacementData?> GetStartRoomPlacementData(RoomNodeData startRoomNodeData)
if (roomPlacementData.Room.RootNode == null)
{
throw new System.NotImplementedException();
return Task.FromResult(false);
}
var rootNode = roomPlacementData.Room.RootNode;
rootNode.Reparent(mapRoot);
rootNode.Position = roomPlacementData.Position.Value;
return Task.FromResult(true);
}
public Task<RoomPlacementData?> CalculateNewRoomPlacementData(IRoom parentRoomNode, RoomNodeData newRoomNodeData)
public Task<RoomPlacementData?> CalculateNewRoomPlacementData(Room? parentRoomNode, RoomNodeData newRoomNodeData)
{
throw new System.NotImplementedException();
if (newRoomNodeData.RoomTemplateSet == null || newRoomNodeData.RoomTemplateSet.Length == 0)
{
return Task.FromResult<RoomPlacementData?>(null);
}
var roomResArray = RoomFactory.RoomTemplateSetToRoomRes(newRoomNodeData.RoomTemplateSet);
if (parentRoomNode == null)
{
//No parent node is set, which we think is the starting room.
//没有设置父节点,我们认为是起点房间。
//TODO:在这里兼容世界种子。
var roomPlacementData = new RoomPlacementData
{
Room = RoomFactory.CreateRoom(roomResArray[0]),
Position = Vector2.Zero
};
return Task.FromResult<RoomPlacementData?>(roomPlacementData);
}
else
{
//TODO:在这里实现房间的放置策略。
return Task.FromResult<RoomPlacementData?>(null);
}
}
private Task<Vector2?> CalculatedPosition(Room mainRoom, Room newRoom, RoomSlot? mainRoomSlot,
RoomSlot? newRoomSlot,bool roomSlotOverlap)
{
if (mainRoom.RootNode == null || mainRoom.TileMap == null || newRoom.TileMap == null || mainRoomSlot == null ||
newRoomSlot == null)
{
return Task.FromResult<Vector2?>(null);
}
//计算主插槽中点在世界中的位置。
//mainRoom.RootNode.Position意为房间所在的世界位置
//mainRoom.TileMap.MapToLocal(mainRoomSlot.StartPosition)意为主插槽在房间中的位置
var result = mainRoom.RootNode.Position + mainRoom.TileMap.MapToLocal(mainRoomSlot.StartPosition);
if (roomSlotOverlap)
{
//执行减法,从槽中点偏移到左上角
result -= _halfCell;
}
else
{
//执行减法,从槽中点偏移到右下角
result += _halfCell;
}
//我们不能将新房间的原点设置在主房间槽的左上角或右下角,这会导致插槽不对应。
//竖直槽,我们需要在同一水平上。
if (mainRoomSlot.IsHorizontal)
{
result += newRoom.TileMap.MapToLocal(new Vector2I(newRoomSlot.EndPosition.X, 0)) - _halfCell;
}
else
{
result -= newRoom.TileMap.MapToLocal(new Vector2I(0, newRoomSlot.EndPosition.Y)) - _halfCell;
}
return Task.FromResult<Vector2?>(result);
}
}

View File

@ -1,6 +1,7 @@
using System.Threading.Tasks;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.room;
namespace ColdMint.scripts.map.slotsMatcher;
@ -9,7 +10,7 @@ public class RoomSlotsMatcher : IRoomSlotsMatcher
private RoomSlot? _lastMatchedMainSlot;
private RoomSlot? _lastMatchedMinorSlot;
public Task<bool> IsMatch(IRoom? mainRoom, IRoom newRoom)
public Task<bool> IsMatch(Room? mainRoom, Room newRoom)
{
if (mainRoom == null)
{