Refactoring the top layer algorithm of the map generator and updating the readme.

重构地图生成器的顶层算法,更新readme。
This commit is contained in:
Cold-Mint 2024-05-19 20:29:32 +08:00
parent ed91d5a03b
commit e902974a14
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
22 changed files with 323 additions and 160 deletions

View File

@ -4,7 +4,7 @@ English [简体中文](README_ZH.md) [にほんご](README_JP.md)
Mint's new game.
A pixel cross-platform Roguelite game.
A pixel cross-platform roguelite game.
## Recent Development progress
@ -14,6 +14,16 @@ A pixel cross-platform Roguelite game.
| loot | await |
| Support still out of the knapsack system | await |
## Screenshot
Game scene
![](screenshot\0.0.1\game_page.png)
Level graph editor
![](screenshot\0.0.1\level_Graph_Editor.png)
## Run the project locally
#### Download engine

View File

@ -1,4 +1,4 @@
[English](README.md) [简体中文](README_ZH) にほんご
[English](README.md) [简体中文](README_ZH.md) にほんご
## こくじ
@ -13,7 +13,15 @@
| マップをランダムに生成します | 進行中です |
| 戦利品 | すたんばい |
| バックパックのシステムをサポートしています | すたんばい |
## スクリーンショットです
ゲームのシーンです
![](screenshot\0.0.1\game_page.png)
ステージエディター
![](screenshot\0.0.1\level_Graph_Editor.png)
## 地元でプロジェクトを進めています
#### ダウンロードエンジンです

View File

@ -14,6 +14,16 @@
| 战利品 | 等待 |
| 支持仍出的背包系统 | 等待 |
## 屏幕截图
游戏场景
![](screenshot\0.0.1\game_page.png)
关卡编辑器
![](screenshot\0.0.1\level_Graph_Editor.png)
## 在本地运行项目
#### 下载引擎

View File

@ -5,3 +5,5 @@ room_root_node_must_be_node2d,房间根节点必须是 Node2D。,Room root node
width_or_height_of_room_slot_must_be_1,房间槽的宽度或高度必须为1。,The width or height of the room slot must be 1.,部屋の溝の幅または高さは1でなければなりません。
connected_room_timeout,连接房间超时。,Connecting the room timed out.,接続部屋はタイムアウトです。
projectiles_is_empty,未设置抛射体。,The projectile is not set.,射出体は設置されていません。
map_generator_missing_parameters,地图生成器缺少参数。,Map generator missing parameters.,マップジェネレータが不足しています。
map_generator_attempts_to_parse_empty_layout_diagrams,地图生成器尝试解析空的布局图。,Map generator attempts to parse empty layout diagrams.,マップジェネレータは空のレイアウト図を解析しようとしています。
1 id zh en jp
5 width_or_height_of_room_slot_must_be_1 房间槽的宽度或高度必须为1。 The width or height of the room slot must be 1. 部屋の溝の幅または高さは1でなければなりません。
6 connected_room_timeout 连接房间超时。 Connecting the room timed out. 接続部屋はタイムアウトです。
7 projectiles_is_empty 未设置抛射体。 The projectile is not set. 射出体は設置されていません。
8 map_generator_missing_parameters 地图生成器缺少参数。 Map generator missing parameters. マップジェネレータが不足しています。
9 map_generator_attempts_to_parse_empty_layout_diagrams 地图生成器尝试解析空的布局图。 Map generator attempts to parse empty layout diagrams. マップジェネレータは空のレイアウト図を解析しようとしています。

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b4r8qekdrtgbv"
path="res://.godot/imported/game_page.png-f8f5cdcc23f5b71be114ec8661e808ce.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://screenshot/0.0.1/game_page.png"
dest_files=["res://.godot/imported/game_page.png-f8f5cdcc23f5b71be114ec8661e808ce.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dyiarx2q4r6y6"
path="res://.godot/imported/level_Graph_Editor.png-2a8ba6f35e054e058aaed64bd42cfc40.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://screenshot/0.0.1/level_Graph_Editor.png"
dest_files=["res://.godot/imported/level_Graph_Editor.png-2a8ba6f35e054e058aaed64bd42cfc40.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -0,0 +1,39 @@
using System.Threading.Tasks;
using ColdMint.scripts.levelGraphEditor;
namespace ColdMint.scripts.map;
/// <summary>
/// <para>Layout parsing strategy</para>
/// <para>布局图解析策略</para>
/// </summary>
public interface ILayoutParsingStrategy
{
/// <summary>
/// <para>Sets the layout diagram to parse</para>
/// <para>设置要解析的布局图</para>
/// </summary>
/// <param name="levelGraphEditorSaveData"></param>
public void SetLevelGraph(LevelGraphEditorSaveData levelGraphEditorSaveData);
/// <summary>
/// <para>Gets the next room to place</para>
/// <para>获取下一个要放置的房间</para>
/// </summary>
/// <returns></returns>
public Task<RoomNodeData?> Next();
/// <summary>
/// <para>Gets the ID of the next parent node to place</para>
/// <para>获取下一个要放置的父节点ID</para>
/// </summary>
/// <returns></returns>
public Task<string?> GetNextParentNodeId();
/// <summary>
/// <para>Is there another room that needs to be placed</para>
/// <para>是否还有下一个需要放置的房间</para>
/// </summary>
/// <returns></returns>
public Task<bool> HasNext();
}

View File

@ -0,0 +1,10 @@
namespace ColdMint.scripts.map;
/// <summary>
/// <para>Room injection strategy</para>
/// <para>房间注入策略</para>
/// </summary>
public interface IRoomInjectionStrategy
{
}

View File

@ -1,5 +1,7 @@
using System.Threading.Tasks;
using ColdMint.scripts.levelGraphEditor;
using System.Collections.Generic;
using System.Threading.Tasks;
using ColdMint.scripts.debug;
using ColdMint.scripts.map.interfaces;
namespace ColdMint.scripts.map;
@ -19,6 +21,31 @@ public static class MapGenerator
/// </summary>
private static ILayoutStrategy? _layoutStrategy;
/// <summary>
/// <para>Room placement strategy</para>
/// <para>房间的放置策略</para>
/// </summary>
private static IRoomPlacementStrategy? _roomPlacementStrategy;
/// <summary>
/// <para>Layout diagram parsing policy</para>
/// <para>布局图解析策略</para>
/// </summary>
private static ILayoutParsingStrategy? _layoutParsingStrategy;
public static ILayoutParsingStrategy? LayoutParsingStrategy
{
get => _layoutParsingStrategy;
set => _layoutParsingStrategy = value;
}
public static IRoomPlacementStrategy? RoomPlacementStrategy
{
get => _roomPlacementStrategy;
set => _roomPlacementStrategy = value;
}
public static ILayoutStrategy? LayoutStrategy
{
get => _layoutStrategy;
@ -31,30 +58,62 @@ public static class MapGenerator
/// </summary>
public static async Task GenerateMap()
{
if (_layoutStrategy == null)
if (_layoutStrategy == null || _roomPlacementStrategy == null || _layoutParsingStrategy == null)
{
LogCat.LogError("map_generator_missing_parameters");
return;
}
//Get the layout data
//拿到布局图数据
var levelGraphEditorSaveData = await _layoutStrategy.GetLayout();
//Finding the starting room
//查找起点房间
if (levelGraphEditorSaveData.RoomNodeDataList == null || levelGraphEditorSaveData.RoomNodeDataList.Count == 0)
{
LogCat.LogError("map_generator_attempts_to_parse_empty_layout_diagrams");
return;
}
var startRoomNodeData = levelGraphEditorSaveData.RoomNodeDataList.Find(roomNodeData =>
roomNodeData.HasTag(Config.RoomDataTag.StartingRoom));
if (startRoomNodeData == null)
_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>();
while (await _layoutParsingStrategy.HasNext())
{
//Can't find the starting room
//找不到起点房间
return;
//When a new room needs to be placed
//当有新的房间需要放置时
var roomNodeData = await _layoutParsingStrategy.Next();
if (roomNodeData == null)
{
continue;
}
//The starting room is regarded as the root node, and the map is generated from the root node to the leaf node like the tree structure.
//TODO:将起点房间看作根节点,像树结构一样,从根节点到叶节点生成地图。
var nextParentNodeId = await _layoutParsingStrategy.GetNextParentNodeId();
IRoom? 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];
}
var roomPlacementData =
await _roomPlacementStrategy.CalculateNewRoomPlacementData(parentRoomNode, roomNodeData);
if (roomPlacementData == null)
{
continue;
}
if (!await _roomPlacementStrategy.PlaceRoom(roomPlacementData))
{
continue;
}
if (!string.IsNullOrEmpty(roomNodeData.Id) && roomPlacementData.Room != null)
{
roomDictionary.Add(roomNodeData.Id, roomPlacementData.Room);
}
}
//All rooms have been placed.
//所有房间已放置完毕。
}
}

View File

@ -0,0 +1,22 @@
using ColdMint.scripts.map.interfaces;
using Godot;
namespace ColdMint.scripts.map.dateBean;
/// <summary>
/// <para>Room placement information</para>
/// <para>房间放置信息</para>
/// </summary>
public class RoomPlacementData
{
/// <summary>
/// <para>the location of placement</para>
/// <para>放置的位置</para>
/// </summary>
public Vector2? Position { get; set; }
/// <summary>
/// <para>Place the room template</para>
/// <para>放置的房间模板</para>
/// </summary>
public IRoom? Room { get; set; }
}

View File

@ -1,18 +0,0 @@
namespace ColdMint.scripts.map.interfaces;
/// <summary>
/// <para>Represents a branch on the map.</para>
/// <para>表示地图上的一个分支。</para>
/// </summary>
public interface IBranch
{
/// <summary>
/// <para>Master branch or not</para>
/// <para>是否为主分支</para>
/// </summary>
bool IsMasterBranch
{
get;
set;
}
}

View File

@ -15,6 +15,10 @@ public interface IRoom
/// </summary>
PackedScene? RoomScene { get; set; }
/// <summary>
/// <para>Tile map</para>
/// <para>瓦片地图</para>
/// </summary>
TileMap? TileMap { get; set; }
/// <summary>

View File

@ -0,0 +1,41 @@
using System.Threading.Tasks;
using ColdMint.scripts.levelGraphEditor;
using ColdMint.scripts.map.dateBean;
namespace ColdMint.scripts.map.interfaces;
/// <summary>
/// <para>Room placement strategy</para>
/// <para>房间放置策略</para>
/// </summary>
public interface IRoomPlacementStrategy
{
/// <summary>
/// <para>Place the room in the designated location</para>
/// <para>在指定的位置放置房间</para>
/// </summary>
/// <param name="roomPlacementData">
///<para>Room placement information</para>
///<para>房间放置信息</para>
/// </param>
/// <returns>
///<para>Placement success or not</para>
///<para>是否放置成功</para>
/// </returns>
public Task<bool> PlaceRoom(RoomPlacementData roomPlacementData);
/// <summary>
/// <para>Calculate new room placement information</para>
/// <para>计算新的房间放置信息</para>
/// </summary>
/// <param name="parentRoomNode">
///<para>Parent room node</para>
///<para>父房间节点</para>
/// </param>
/// <param name="newRoomNodeData">
///<para>New room data to be placed</para>
///<para>欲放置的新房间数据</para>
/// </param>
/// <returns></returns>
public Task<RoomPlacementData?> CalculateNewRoomPlacementData(IRoom? parentRoomNode, RoomNodeData newRoomNodeData);
}

View File

@ -1,40 +0,0 @@
using System.Threading.Tasks;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.RoomPlacer;
using Godot;
namespace ColdMint.scripts.map.interfaces;
/// <summary>
/// <para>Room placer</para>
/// <para>房间放置器</para>
/// </summary>
/// <remarks>
///<para>Responsible for arranging the rooms on the map</para>
///<para>负责在地图中摆放房间</para>
/// </remarks>
public interface IRoomPlacer
{
/// <summary>
/// <para>Place the room in the designated location</para>
/// <para>在指定的位置放置房间</para>
/// </summary>
/// <param name="position"></param>
/// <param name="room"></param>
/// <returns></returns>
public Task<bool> PlaceRoom(Vector2 position, IRoom room);
/// <summary>
/// <para>Pass into two rooms and calculate the location of the new room</para>
/// <para>传入两个房间,计算新房间的位置</para>
/// </summary>
/// <param name="mainRoom"></param>
/// <param name="newRoom"></param>
/// <param name="mainRoomSlot"></param>
/// <param name="newRoomSlot"></param>
/// <param name="roomPlacerConfig"></param>
/// <returns></returns>
public Task<Vector2> CalculatedPosition(IRoom mainRoom, IRoom newRoom, RoomSlot? mainRoomSlot, RoomSlot? newRoomSlot,
RoomPlacerConfig roomPlacerConfig);
}

View File

@ -0,0 +1,34 @@
using System.Threading.Tasks;
using ColdMint.scripts.levelGraphEditor;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.interfaces;
using Godot;
namespace ColdMint.scripts.map.RoomPlacer;
/// <summary>
/// <para>Patchwork room placement strategy</para>
/// <para>拼接的房间放置策略</para>
/// </summary>
/// <remarks>
///<para>Under this strategy, think of each room template as a puzzle piece, find their "slots", and then connect them together.</para>
///<para>在此策略下,将每个房间模板看作是一块拼图,找到他们的“槽”,然后将其连接在一起。</para>
/// </remarks>
public class PatchworkRoomPlacementStrategy : IRoomPlacementStrategy
{
public Task<bool> PlaceRoom(RoomPlacementData roomPlacementData)
{
throw new System.NotImplementedException();
}
public Task<RoomPlacementData?> GetStartRoomPlacementData(RoomNodeData startRoomNodeData)
{
throw new System.NotImplementedException();
}
public Task<RoomPlacementData?> CalculateNewRoomPlacementData(IRoom parentRoomNode, RoomNodeData newRoomNodeData)
{
throw new System.NotImplementedException();
}
}

View File

@ -1,72 +0,0 @@
using System.Threading.Tasks;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.interfaces;
using Godot;
using static ColdMint.scripts.Config;
namespace ColdMint.scripts.map.RoomPlacer;
public class RoomPlacer : RoomPlacerTemplate
{
public IMapGeneratorConfig? MapGeneratorConfig { get; set; }
private readonly Vector2 _halfCell = new Vector2(CellSize / 2f, CellSize / 2f);
public override Task<bool> PlaceRoom(Vector2 position, IRoom room)
{
if (MapGeneratorConfig == null)
{
return Task.FromResult(false);
}
var node = room.RootNode;
MapGeneratorConfig.MapRoot.AddChild(node);
if (node is { } node2D)
{
//If the Node is not empty and is a 2D node.
//如果Node不是空的且是2D节点。
node2D.Position = position;
return Task.FromResult(true);
}
return Task.FromResult(false);
}
public override Task<Vector2> CalculatedPosition(IRoom mainRoom, IRoom newRoom, RoomSlot? mainRoomSlot,
RoomSlot? newRoomSlot,
RoomPlacerConfig roomPlacerConfig)
{
if (mainRoom.RootNode == null || mainRoom.TileMap == null || newRoom.TileMap == null || mainRoomSlot == null ||
newRoomSlot == null)
{
return Task.FromResult(Vector2.Zero);
}
//计算主插槽中点在世界中的位置。
//mainRoom.RootNode.Position意为房间所在的世界位置
//mainRoom.TileMap.MapToLocal(mainRoomSlot.StartPosition)意为主插槽在房间中的位置
var result = mainRoom.RootNode.Position + mainRoom.TileMap.MapToLocal(mainRoomSlot.StartPosition);
if (roomPlacerConfig.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(result);
}
}

View File

@ -1,14 +0,0 @@
using System.Threading.Tasks;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.interfaces;
using Godot;
namespace ColdMint.scripts.map.RoomPlacer;
public abstract class RoomPlacerTemplate : IRoomPlacer
{
public abstract Task<bool> PlaceRoom(Vector2 position, IRoom room);
public abstract Task<Vector2> CalculatedPosition(IRoom mainRoom, IRoom newRoom, RoomSlot? mainRoomSlot, RoomSlot? newRoomSlot,
RoomPlacerConfig roomPlacerConfig);
}