(client) feat: Improve map generation logic (#36)
Co-authored-by: zzdxxz <2079238449@qq.com> Co-committed-by: zzdxxz <2079238449@qq.com>
This commit is contained in:
@ -258,5 +258,31 @@ namespace Configs
|
||||
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// 用于加载指定路径下的所有资源,并返回资源名称和加载好的资源的键值对。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="path">资源路径(相对于 Resources 文件夹)</param>
|
||||
/// <returns>字典,键为资源名称,值为加载好的资源</returns>
|
||||
public static Dictionary<string, T> LoadResources<T>(string path) where T : UnityEngine.Object
|
||||
{
|
||||
// 创建一个字典来存储资源名称和加载好的资源
|
||||
Dictionary<string, T> resourceDict = new Dictionary<string, T>();
|
||||
|
||||
// 加载指定路径下的所有资源
|
||||
T[] resources = Resources.LoadAll<T>(path);
|
||||
|
||||
foreach (T resource in resources)
|
||||
{
|
||||
if (resource != null)
|
||||
{
|
||||
// 获取资源名称并存入字典
|
||||
string resourceName = resource.name;
|
||||
resourceDict[resourceName] = resource;
|
||||
}
|
||||
}
|
||||
|
||||
return resourceDict;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@ -12,29 +11,30 @@ namespace Data
|
||||
public abstract class Define
|
||||
{
|
||||
public string defName;
|
||||
public string label;
|
||||
public string description;
|
||||
public string label;
|
||||
public string packID;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化方法,根据传入的 XML 元素 (<paramref name="xmlDef"/>) 进行处理。
|
||||
/// 初始化方法,根据传入的 XML 元素 (<paramref name="xmlDef" />) 进行处理。
|
||||
/// </summary>
|
||||
/// <param name="xmlDef">包含定义信息的 XML 元素。</param>
|
||||
/// <returns>
|
||||
/// 返回一个布尔值:
|
||||
/// - 如果返回 <c>false</c>,表示按照默认方式处理(依次对 XML 进行变量名识别和赋值)。
|
||||
/// - 如果返回 <c>true</c>,表示使用自定义方式处理,不进行额外的默认处理。
|
||||
/// 返回一个布尔值:
|
||||
/// - 如果返回 <c>false</c>,表示按照默认方式处理(依次对 XML 进行变量名识别和赋值)。
|
||||
/// - 如果返回 <c>true</c>,表示使用自定义方式处理,不进行额外的默认处理。
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 该方法的基类实现会自动识别并处理以下三个变量:
|
||||
/// - <c>defName</c>
|
||||
/// - <c>label</c>
|
||||
/// - <c>description</c>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 如果需要覆盖默认行为,可以在派生类中重写此方法,并返回 <c>true</c>
|
||||
/// 以指示框架跳过默认处理逻辑。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 该方法的基类实现会自动识别并处理以下三个变量:
|
||||
/// - <c>defName</c>
|
||||
/// - <c>label</c>
|
||||
/// - <c>description</c>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 如果需要覆盖默认行为,可以在派生类中重写此方法,并返回 <c>true</c>
|
||||
/// 以指示框架跳过默认处理逻辑。
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public virtual bool Init(XElement xmlDef)
|
||||
{
|
||||
@ -59,10 +59,7 @@ namespace Data
|
||||
if (value is IList list && list.Count > 0) // 如果是列表类型
|
||||
{
|
||||
sb.AppendLine($"{name}:");
|
||||
foreach (var item in list)
|
||||
{
|
||||
sb.AppendLine($" - {FormatValue(item)}");
|
||||
}
|
||||
foreach (var item in list) sb.AppendLine($" - {FormatValue(item)}");
|
||||
}
|
||||
else if (value is Define defineObject) // 如果是继承自 Define 的子类
|
||||
{
|
||||
@ -89,8 +86,7 @@ namespace Data
|
||||
{
|
||||
var type = obj.GetType();
|
||||
return type.GetFields(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Cast<MemberInfo>()
|
||||
.Concat(type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Cast<MemberInfo>());
|
||||
.Concat(type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Cast<MemberInfo>());
|
||||
}
|
||||
|
||||
private static object GetValue(MemberInfo member, object obj)
|
||||
@ -117,4 +113,17 @@ namespace Data
|
||||
}
|
||||
}
|
||||
|
||||
public class DefineReference : Define
|
||||
{
|
||||
public Define def;
|
||||
public string className;
|
||||
public string fieldName;
|
||||
|
||||
public DefineReference(string className, string defName, string fieldName)
|
||||
{
|
||||
this.defName = defName;
|
||||
this.className = className;
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
}
|
||||
}
|
@ -141,19 +141,34 @@ namespace Data
|
||||
var className = element.Name.ToString();
|
||||
if (string.IsNullOrEmpty(className))
|
||||
continue;
|
||||
// Debug.Log("1");
|
||||
var def = LoadDefineClass(element,element.Name.ToString());
|
||||
if (def == null)
|
||||
continue;
|
||||
// Debug.Log("2");
|
||||
def.packID = packID;
|
||||
if (!defines.ContainsKey(className))
|
||||
defines.Add(className, new List<Define>());
|
||||
defines[className].Add(def);
|
||||
// Debug.Log($"插入{className},{def.defName}");
|
||||
}
|
||||
}
|
||||
|
||||
private static Define LoadDefineClass(XElement defineDoc,string className)
|
||||
/// <summary>
|
||||
/// 根据指定的 XML 元素 (<paramref name="defineDoc"/>) 和类名 (<paramref name="className"/>),
|
||||
/// 动态加载并初始化一个继承自 <see cref="Define"/> 的类实例。
|
||||
/// </summary>
|
||||
/// <param name="defineDoc">包含类定义的 XML 元素 (<see cref="XElement"/>)。</param>
|
||||
/// <param name="className">目标类的全限定名或简短名称。</param>
|
||||
/// <returns>
|
||||
/// 如果成功加载并初始化,则返回对应的 <see cref="Define"/> 类实例;
|
||||
/// 否则返回 null。
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// 如果 <paramref name="defineDoc"/> 或 <paramref name="className"/> 为 null 或空字符串,则抛出此异常。
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// 该方法通过反射动态加载指定类,并检查其是否继承自 <see cref="Define"/>。
|
||||
/// 如果类存在且满足条件,则尝试调用其 <see cref="Define.Init(XElement)"/> 方法进行初始化。
|
||||
/// 如果初始化失败,则使用默认初始化方法 (<see cref="DefaultInitDefine(Define, XElement, Type)"/>)。
|
||||
/// </remarks>
|
||||
public static Define LoadDefineClass(XElement defineDoc,string className)
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
@ -210,7 +225,22 @@ namespace Data
|
||||
|
||||
return define;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化指定的 <paramref name="define"/> 对象,根据 <paramref name="defineDoc"/> 中的 XML 元素内容,
|
||||
/// 将对应的字段值赋给 <paramref name="define"/> 对象。
|
||||
/// </summary>
|
||||
/// <param name="define">需要初始化的对象实例。</param>
|
||||
/// <param name="defineDoc">包含字段定义的 XML 元素 (<see cref="XElement"/>)。</param>
|
||||
/// <param name="defineType">目标对象的类型 (<see cref="Type"/>)。</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// 如果 <paramref name="define"/>、<paramref name="defineDoc"/> 或 <paramref name="defineType"/> 为 null,则抛出此异常。
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// 该方法会遍历 <paramref name="defineType"/> 的所有字段(包括公共和非公共字段),
|
||||
/// 并尝试从 <paramref name="defineDoc"/> 中找到与字段名称匹配的子元素。
|
||||
/// 如果找到匹配的子元素,则将其值转换为字段的类型并赋值给字段。
|
||||
/// 如果字段类型继承自 <see cref="Define"/>,则递归调用 <see cref="LoadDefineClass(XElement, string)"/> 方法进行加载。
|
||||
/// </remarks>
|
||||
public static void DefaultInitDefine(Define define,XElement defineDoc,Type defineType)
|
||||
{
|
||||
var fields = defineType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
@ -225,7 +255,17 @@ namespace Data
|
||||
{
|
||||
Object value;
|
||||
if (IsFieldTypeInheritedFrom(field, typeof(Define)))
|
||||
value = LoadDefineClass(element, field.FieldType.Name);
|
||||
{
|
||||
if (element.HasElements)
|
||||
{
|
||||
value = LoadDefineClass(element, field.FieldType.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = new DefineReference(field.FieldType.Name, element.Value, field.Name);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
value = Convert.ChangeType(element.Value, field.FieldType);
|
||||
field.SetValue(define, value);
|
||||
@ -303,5 +343,23 @@ namespace Data
|
||||
// 如果字段的类型直接是基类或其派生类,则返回 true
|
||||
return fieldType != baseType && baseType.IsAssignableFrom(fieldType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查字段的类型是否继承自指定的类
|
||||
/// </summary>
|
||||
/// <param name="field">字段信息</param>
|
||||
/// <param name="baseType">要检查的基类类型</param>
|
||||
/// <returns>如果字段的类型是基类或其派生类,则返回 true</returns>
|
||||
public static bool IsFieldTypeInheritedFrom(FieldInfo field, Type baseType)
|
||||
{
|
||||
// 获取字段的类型
|
||||
var fieldType = field.FieldType;
|
||||
// 如果字段的类型为 null 或不是基类的派生类,则返回 false
|
||||
if (!baseType.IsAssignableFrom(fieldType))
|
||||
return false;
|
||||
|
||||
// 如果字段的类型直接是基类或其派生类,则返回 true
|
||||
return fieldType != baseType && baseType.IsAssignableFrom(fieldType);
|
||||
}
|
||||
}
|
||||
}
|
50
Client/Assets/Scripts/Data/MapDefine.cs
Normal file
50
Client/Assets/Scripts/Data/MapDefine.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace Data
|
||||
{
|
||||
public class TileDef : Define
|
||||
{
|
||||
public string texturePath = "";
|
||||
public string name = "";
|
||||
|
||||
public override bool Init(XElement xmlDef)
|
||||
{
|
||||
base.Init(xmlDef);
|
||||
name = defName;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class TileMappingTableDef : Define
|
||||
{
|
||||
public Dictionary<string, string> tileDict = new();
|
||||
public override bool Init(XElement xmlDef)
|
||||
{
|
||||
base.Init(xmlDef);
|
||||
// 清空字典以确保没有遗留数据
|
||||
tileDict.Clear();
|
||||
// 检查 xmlDef 是否为空
|
||||
if (xmlDef == null)
|
||||
return false;
|
||||
foreach (var element in xmlDef.Elements())
|
||||
{
|
||||
// 获取子元素的名称作为键
|
||||
var key = element.Name.LocalName;
|
||||
// 获取子元素的 value 属性作为值
|
||||
var value = element.Attribute("value")?.Value;
|
||||
// 检查 value 是否存在
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
tileDict[key] = value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
3
Client/Assets/Scripts/Data/MapDefine.cs.meta
Normal file
3
Client/Assets/Scripts/Data/MapDefine.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67d4d2ac30e641189cdf018ad6769f0e
|
||||
timeCreated: 1752496329
|
@ -1,18 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Data;
|
||||
using UnityEngine;
|
||||
using Utils;
|
||||
|
||||
namespace Managers
|
||||
{
|
||||
public class DefineManager : Singleton<DefineManager>
|
||||
{
|
||||
private const string coreNamespace = "Data";
|
||||
private static readonly string[] dataSetFilePath = { "Data", "Mod" };
|
||||
|
||||
public Dictionary<string, Dictionary<string, Define>> defines = new();
|
||||
public Dictionary<string, DefinePack> packs = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化定义管理器,加载所有定义包并构建定义字典。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法执行以下操作:
|
||||
/// 1. 获取指定路径下的所有子文件夹,每个子文件夹代表一个定义包。
|
||||
/// 2. 遍历每个定义包,尝试加载其中的定义数据。
|
||||
/// 3. 将加载的定义数据按类型分类,并存储到定义字典中。
|
||||
/// </remarks>
|
||||
public void Init()
|
||||
{
|
||||
var packFolder = Configs.ConfigProcessor.GetSubFolders(new(dataSetFilePath));
|
||||
@ -21,7 +30,7 @@ namespace Managers
|
||||
var pack = new DefinePack();
|
||||
if (pack.LoadPack(folder)) packs.Add(pack.packID, pack);
|
||||
}
|
||||
|
||||
List<Tuple<Define,DefineReference>> defineRefs = new();
|
||||
foreach (var pack in packs)
|
||||
{
|
||||
foreach (var define in pack.Value.defines)
|
||||
@ -33,6 +42,37 @@ namespace Managers
|
||||
foreach (var def in defList)
|
||||
{
|
||||
defines[typeName][def.defName] = def;
|
||||
if (def is DefineReference reference)
|
||||
{
|
||||
defineRefs.Add(new(def,reference));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var defineRef in defineRefs)
|
||||
{
|
||||
var define = defineRef.Item1;
|
||||
var reference = defineRef.Item2;
|
||||
var referenceDef=FindDefine(reference.className,define.defName);
|
||||
|
||||
var property = define.GetType().GetProperty(reference.fieldName);
|
||||
if (property != null && property.CanWrite)
|
||||
{
|
||||
property.SetValue(define, referenceDef);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果是字段而不是属性
|
||||
var field = define.GetType().GetField(reference.fieldName);
|
||||
if (field != null)
|
||||
{
|
||||
field.SetValue(define, referenceDef);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理找不到成员的情况
|
||||
Debug.LogError($"Could not find field or property '{reference.fieldName}' in type {define.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
@ -7,15 +9,24 @@ namespace Map
|
||||
{
|
||||
public Tilemap dataLevel;
|
||||
public Tilemap textureLevel;
|
||||
|
||||
public Dictionary<string, TileBase> tileDict = new();
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
tileDict = Configs.ConfigProcessor.LoadResources<TileBase>("TileMap");
|
||||
var tile= tileDict.Values;
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
for (int j = 0; j < 100; j++)
|
||||
{
|
||||
textureLevel.SetTile(new(i,j),tile.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TileMappingTable:Utils.Singleton<TileMappingTable>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
133
Client/Assets/Scripts/Utils/PerlinNoise.cs
Normal file
133
Client/Assets/Scripts/Utils/PerlinNoise.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
public class PerlinNoise
|
||||
{
|
||||
private readonly int[] _p; // 混淆表
|
||||
|
||||
// 构造函数:初始化混淆表
|
||||
public PerlinNoise(int seed)
|
||||
{
|
||||
// 初始化为0-255的随机排列
|
||||
_p = new int[512]; // 混淆表加倍以方便使用
|
||||
var permutation = new int[256];
|
||||
var random = new Random(seed);
|
||||
|
||||
// 填充数组为0-255
|
||||
for (var i = 0; i < 256; i++) permutation[i] = i;
|
||||
|
||||
// 使用Fisher-Yates算法打乱数组
|
||||
for (var i = 0; i < 256; i++)
|
||||
{
|
||||
var swapIndex = random.Next(256);
|
||||
var temp = permutation[i];
|
||||
permutation[i] = permutation[swapIndex];
|
||||
permutation[swapIndex] = temp;
|
||||
}
|
||||
|
||||
// 将打乱后的数组复制两次,生成512个元素的混淆表
|
||||
for (var i = 0; i < 256; i++)
|
||||
{
|
||||
_p[i] = permutation[i];
|
||||
_p[i + 256] = permutation[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 平滑函数 (6t^5 - 15t^4 + 10t^3)
|
||||
private double Fade(double t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
// 线性插值
|
||||
private double Lerp(double t, double a, double b)
|
||||
{
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
// 计算梯度向量和距离向量的点积
|
||||
private double Grad(int hash, double x, double y, double z)
|
||||
{
|
||||
// 根据hash值确定使用哪个梯度向量
|
||||
// 12个梯度向量由以下组合构成:(+/-1, +/-1, 0), (+/-1, 0, +/-1), (0, +/-1, +/-1)
|
||||
switch (hash & 0xF) // 取hash值的最后4位
|
||||
{
|
||||
case 0x0: return x + y;
|
||||
case 0x1: return -x + y;
|
||||
case 0x2: return x - y;
|
||||
case 0x3: return -x - y;
|
||||
case 0x4: return x + z;
|
||||
case 0x5: return -x + z;
|
||||
case 0x6: return x - z;
|
||||
case 0x7: return -x - z;
|
||||
case 0x8: return y + z;
|
||||
case 0x9: return -y + z;
|
||||
case 0xA: return y - z;
|
||||
case 0xB: return -y - z;
|
||||
case 0xC: return y + x; // 这四个是重复的,但Ken Perlin的原始实现中包含它们。
|
||||
case 0xD: return -y + x; // 它们对噪声质量影响不大,但保持了表格的一致性。
|
||||
case 0xE: return y - x;
|
||||
case 0xF: return -y - x;
|
||||
default: return 0; // 不应该发生
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为给定的(x, y, z)坐标生成3D Perlin噪声。
|
||||
/// 输出值通常在-1到1之间。
|
||||
/// </summary>
|
||||
public double Noise(double x, double y, double z)
|
||||
{
|
||||
// 找到包含该点的单位立方体
|
||||
var X = (int)Math.Floor(x) & 255;
|
||||
var Y = (int)Math.Floor(y) & 255;
|
||||
var Z = (int)Math.Floor(z) & 255;
|
||||
|
||||
// 找到该点在立方体内的相对x, y, z坐标
|
||||
x -= Math.Floor(x);
|
||||
y -= Math.Floor(y);
|
||||
z -= Math.Floor(z);
|
||||
|
||||
// 计算x, y, z的平滑曲线
|
||||
var u = Fade(x);
|
||||
var v = Fade(y);
|
||||
var w = Fade(z);
|
||||
|
||||
// 对立方体的8个角进行哈希计算
|
||||
var A = _p[X] + Y;
|
||||
var AA = _p[A] + Z;
|
||||
var AB = _p[A + 1] + Z;
|
||||
var B = _p[X + 1] + Y;
|
||||
var BA = _p[B] + Z;
|
||||
var BB = _p[B + 1] + Z;
|
||||
|
||||
// 获取所有8个角的哈希值
|
||||
var H000 = _p[AA];
|
||||
var H100 = _p[BA];
|
||||
var H010 = _p[AB];
|
||||
var H110 = _p[BB];
|
||||
var H001 = _p[AA + 1];
|
||||
var H101 = _p[BA + 1];
|
||||
var H011 = _p[AB + 1];
|
||||
var H111 = _p[BB + 1];
|
||||
|
||||
// 计算所有8个角的点积并插值
|
||||
double x0, x1, y0, y1;
|
||||
|
||||
x0 = Lerp(u, Grad(H000, x, y, z), // (0,0,0)
|
||||
Grad(H100, x - 1, y, z)); // (1,0,0)
|
||||
x1 = Lerp(u, Grad(H010, x, y - 1, z), // (0,1,0)
|
||||
Grad(H110, x - 1, y - 1, z)); // (1,1,0)
|
||||
y0 = Lerp(v, x0, x1);
|
||||
|
||||
x0 = Lerp(u, Grad(H001, x, y, z - 1), // (0,0,1)
|
||||
Grad(H101, x - 1, y, z - 1)); // (1,0,1)
|
||||
x1 = Lerp(u, Grad(H011, x, y - 1, z - 1), // (0,1,1)
|
||||
Grad(H111, x - 1, y - 1, z - 1)); // (1,1,1)
|
||||
y1 = Lerp(v, x0, x1);
|
||||
|
||||
return Lerp(w, y0, y1);
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Utils/PerlinNoise.cs.meta
Normal file
3
Client/Assets/Scripts/Utils/PerlinNoise.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76fe380e58f0420db22ca4d403d17978
|
||||
timeCreated: 1752507440
|
Reference in New Issue
Block a user