(client) feat:实现摄像机跟踪与移动,实现任意位置生成实体,实现更安全的资源加载方式(指定unity内部加载资源) (#42)

Co-authored-by: zzdxxz <2079238449@qq.com>
Co-committed-by: zzdxxz <2079238449@qq.com>
This commit is contained in:
2025-08-07 16:44:43 +08:00
committed by TheRedApricot
parent 82dc89c890
commit 670f778eee
143 changed files with 9706 additions and 8122 deletions

View File

@ -48,33 +48,17 @@ namespace AI
return null;
}
}
public class SequentialAI : Selector
public class SequenceAI : AIBase
{
private int currentIndex = 0; // 当前正在尝试的子节点索引
public override JobBase GetJob(Entity.Entity target)
{
// 如果当前索引超出了子节点范围,重置索引并返回 null
if (currentIndex >= children.Count)
foreach (var aiBase in children)
{
ResetIndex();
return null;
var job = aiBase.GetJob(target);
if (job == null)
return null; // 如果某个子节点返回 null则整个序列失败
}
// 获取当前子节点的任务
var currentChild = children[currentIndex];
var job = currentChild.GetJob(target);
// 移动到下一个子节点
currentIndex++;
return job;
}
// 重置当前索引(用于重新开始遍历)
private void ResetIndex()
{
currentIndex = 0;
return null; // 所有子节点完成时返回 null
}
}
public class ContinuousMove : AIBase
@ -89,7 +73,7 @@ namespace AI
{
public override JobBase GetJob(Entity.Entity target)
{
throw new NotImplementedException();
return new TrackPlayerJob();
}
}
public class RandomWander : AIBase
@ -99,5 +83,11 @@ namespace AI
return new WanderJob();
}
}
public class Idel : AIBase
{
public override JobBase GetJob(Entity.Entity target)
{
return new IdleJob();
}
}
}

View File

@ -1,4 +1,6 @@
using System.Collections.Generic;
using Base;
using Prefab;
using Unity.VisualScripting;
using UnityEngine;
@ -8,25 +10,21 @@ namespace AI
{
public Entity.Entity entity;
protected int timeoutTicks = 300;
public bool Running=>timeoutTicks > 0;
public virtual void StartJob(Entity.Entity target)
{
entity = target;
}
public bool Running => timeoutTicks > 0;
protected abstract void UpdateJob();
public virtual void StartJob(Entity.Entity target)
{
entity = target;
}
public bool Update()
{
if(!Running)
if (!Running)
return false;
UpdateJob();
timeoutTicks--;
if (timeoutTicks <= 0)
{
StopJob();
}
return true;
}
public virtual void StopJob()
@ -39,8 +37,8 @@ namespace AI
public override void StartJob(Entity.Entity target)
{
base.StartJob(target);
Vector3 move=new(Random.Range(-10,10), Random.Range(-10,10));
var targetPosition=entity.transform.position+move;
Vector3 move = new(Random.Range(-10, 10), Random.Range(-10, 10));
var targetPosition = entity.transform.position + move;
entity.SetTarget(targetPosition);
entity.IsChase = false;
}
@ -55,9 +53,9 @@ namespace AI
base.StopJob();
entity.IsChase = true;
}
}
public class IdleJob:JobBase
public class IdleJob : JobBase
{
override public void StartJob(Entity.Entity target)
{
@ -75,4 +73,223 @@ namespace AI
entity.TryMove();
}
}
public class TrackPlayerJob : JobBase
{
private EntityPrefab currentTarget; // 当前追踪的目标玩家
private List<EntityPrefab> players; // 玩家实体列表
public override void StartJob(Entity.Entity target)
{
base.StartJob(target);
UpdateTarget();
}
protected override void UpdateJob()
{
if (currentTarget == null || currentTarget.entity.IsDead)
{
// 如果当前目标无效,则重新查找最近的玩家
UpdateTarget();
}
if (currentTarget != null)
{
var targetPosition = new Vector3(currentTarget.Position.x, currentTarget.Position.y, 0);
entity.SetTarget(targetPosition);
entity.TryMove();
}
}
private void UpdateTarget()
{
players = Managers.EntityManage.Instance.FindEntitiesByFaction("Player");
if (players == null || players.Count == 0)
{
currentTarget = null;
StopJob();
return;
}
currentTarget = GetNearestPlayer(players);
}
private EntityPrefab GetNearestPlayer(List<EntityPrefab> players)
{
EntityPrefab nearestPlayer = null;
float minDistance = float.MaxValue;
foreach (var player in players)
{
if (player.entity.IsDead) continue; // 跳过无效玩家
float distance = Vector3.Distance(
new Vector3(player.Position.x, player.Position.y, 0),
new Vector3(entity.Position.x, entity.Position.y, 0)
);
if (distance < minDistance)
{
minDistance = distance;
nearestPlayer = player;
}
}
return nearestPlayer;
}
}
public class AttackPlayerJob : JobBase
{
private EntityPrefab player;
protected override void UpdateJob()
{
if (player == null || !IsPlayerInRange())
{
StopJob(); // 如果玩家不在范围内,停止攻击工作
return;
}
entity.TryAttack();
}
private bool IsPlayerInRange()
{
float distance = Vector3.Distance(
new Vector3(player.Position.x, player.Position.y, 0),
new Vector3(entity.Position.x, entity.Position.y, 0)
);
return distance <= entity.attributes.attackRange;
}
public override void StartJob(Entity.Entity target)
{
base.StartJob(target);
// 查找最近的玩家作为目标
List<EntityPrefab> players = Managers.EntityManage.Instance.FindEntitiesByFaction("Player");
player = GetNearestPlayer(players);
}
private EntityPrefab GetNearestPlayer(List<EntityPrefab> players)
{
EntityPrefab nearestPlayer = null;
float minDistance = float.MaxValue;
foreach (var player in players)
{
if (!IsPlayerValid(player)) continue;
float distance = Vector3.Distance(
new Vector3(player.Position.x, player.Position.y, 0),
new Vector3(entity.Position.x, entity.Position.y, 0)
);
if (distance < minDistance)
{
minDistance = distance;
nearestPlayer = player;
}
}
return nearestPlayer;
}
private bool IsPlayerValid(EntityPrefab player)
{
return player != null && !player.entity.IsDead;
}
}
public class RangedAttackJob : JobBase
{
private EntityPrefab player;
protected override void UpdateJob()
{
if (player == null || !IsPlayerValid(player))
{
StopJob(); // 如果当前目标无效,停止工作
return;
}
float distance = Vector3.Distance(
new Vector3(player.Position.x, player.Position.y, 0),
new Vector3(entity.Position.x, entity.Position.y, 0)
);
if (distance <= entity.attributes.attackRange)
{
// 如果在攻击范围内,进行攻击
entity.TryAttack();
}
else if (distance > entity.attributes.attackRange && distance < MaxTrackDistance)
{
// 如果距离过远,靠近目标
MoveTowardsPlayer();
}
else if (distance < entity.attributes.attackRange)
{
// 如果距离过近,远离目标
MoveAwayFromPlayer();
}
}
private void MoveTowardsPlayer()
{
Vector3 targetPosition = new Vector3(player.Position.x, player.Position.y, 0);
entity.SetTarget(targetPosition);
entity.TryMove();
}
private void MoveAwayFromPlayer()
{
Vector3 direction = entity.Position - player.Position;
direction.Normalize();
Vector3 targetPosition = entity.Position + direction * RetreatDistance;
entity.SetTarget(targetPosition);
entity.TryMove();
}
public override void StartJob(Entity.Entity target)
{
base.StartJob(target);
// 查找最近的玩家作为目标
List<EntityPrefab> players = Managers.EntityManage.Instance.FindEntitiesByFaction("Player");
player = GetNearestPlayer(players);
}
private EntityPrefab GetNearestPlayer(List<EntityPrefab> players)
{
EntityPrefab nearestPlayer = null;
float minDistance = float.MaxValue;
foreach (var player in players)
{
if (!IsPlayerValid(player)) continue;
float distance = Vector3.Distance(
new Vector3(player.Position.x, player.Position.y, 0),
new Vector3(entity.Position.x, entity.Position.y, 0)
);
if (distance < minDistance)
{
minDistance = distance;
nearestPlayer = player;
}
}
return nearestPlayer;
}
private bool IsPlayerValid(EntityPrefab player)
{
return player != null && !player.entity.IsDead;
}
private const float MaxTrackDistance = 20f; // 最大追踪距离
private const float RetreatDistance = 3f; // 后退距离
}
}

View File

@ -8,182 +8,174 @@ using Object = UnityEngine.Object;
namespace Base
{
/// <summary>
/// UI窗口输入控制和管理类
/// 负责根据输入显示/隐藏UI并根据UI状态管理游戏暂停。
/// </summary>
public class UIInputControl : Utils.MonoSingleton<UIInputControl>, ITickUI
{
// 存储窗口及其激活键的字典
public Dictionary<KeyCode, UIBase> UIwindowKeys = new();
// 存储没有激活键的窗口列表
private List<UIBase> noKeyWindows = new();
// 存储场景中所有UIBase的实例
private List<UIBase> _allWindows = new List<UIBase>();
// 缓存当前可见的窗口
private readonly List<UIBase> _visibleWindows = new List<UIBase>();
// 每帧更新逻辑
private bool needUpdate = false;
/// <summary>
/// 查找并注册场景中所有的UI窗口包括非激活状态的
/// </summary>
private void RegisterAllWindows()
{
_allWindows.Clear();
// 获取当前活动场景中的所有 GameObject
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
if (!activeScene.isLoaded)
{
Debug.LogWarning("当前场景未加载,无法注册窗口!");
return;
}
// 遍历场景中的所有根对象,并查找其子对象中的 UIBase
foreach (var rootGameObject in activeScene.GetRootGameObjects())
{
var windows = rootGameObject.GetComponentsInChildren<UIBase>(true);
_allWindows.AddRange(windows);
}
// 去重(如果有重复的窗口)
_allWindows = _allWindows.Distinct().ToList();
// 初始化所有窗口为隐藏状态
foreach (var window in _allWindows)
{
if (window != null && window.gameObject != null)
{
window.Hide();
}
}
needUpdate = true;
Debug.Log($"窗口数量{_allWindows.Count}");
}
/// <summary>
/// UI逻辑更新循环需要被外部的某个管理器在Update中调用
/// </summary>
public void TickUI()
{
foreach (var kvp in UIwindowKeys)
//使用这个是为了让输入独占窗口关闭自己后不会立即激活其他窗口的按键,延迟一帧
if (needUpdate)
{
if (Input.GetKeyDown(kvp.Key))
{
HandleWindowActivation(kvp.Value);
break;
}
// 更新可见窗口缓存和暂停状态
UpdateVisibleWindowsCache();
UpdatePauseState();
needUpdate = false;
return;
}
}
private void Init()
{
UIwindowKeys.Clear();
noKeyWindows.Clear();
var uiInstances = Resources.FindObjectsOfTypeAll<UIBase>();
foreach (var uiBase in uiInstances)
if(_visibleWindows.Any(window => window.isInputOccupied))
return;
foreach (var window in _allWindows)
{
var key = uiBase.actionButton;
if (key == KeyCode.None)
// 检查窗口是否设置了有效的激活按键,并且该按键在本帧被按下
if (window.actionButton == KeyCode.None || !Input.GetKeyDown(window.actionButton)) continue;
if (window.IsVisible)
{
noKeyWindows.Add(uiBase);
uiBase.Hide();
continue;
}
if (UIwindowKeys.ContainsKey(key))
{
Debug.LogWarning($"Key '{key}' is already assigned to another window. Skipping...");
continue;
}
UIwindowKeys[key] = uiBase;
uiBase.Hide();
}
}
private void HandleWindowActivation(UIBase targetWindow, bool isFunctionCall = false)
{
bool wasTargetVisible = targetWindow.IsVisible;
bool anyOtherWindowOpen = false;
// 遍历所有窗口(包括有键和无键窗口)
foreach (var kvp in UIwindowKeys.Concat(noKeyWindows.Select(w => new KeyValuePair<KeyCode, UIBase>(KeyCode.None, w))))
{
if (kvp.Value == targetWindow)
{
continue;
}
if (kvp.Value.IsVisible)
{
if (!wasTargetVisible || isFunctionCall) // 只在目标窗口要打开时才关闭其他窗口
// 如果窗口当前是可见的,且未占用输入,则通过按键隐藏它
if (!window.isInputOccupied)
{
kvp.Value.Hide();
}
else
{
anyOtherWindowOpen = true; // 记录是否有其他窗口打开
Hide(window);
}
}
}
if (wasTargetVisible)
{
targetWindow.Hide();
}
else
{
targetWindow.Show();
}
bool currentWindowState = !wasTargetVisible || anyOtherWindowOpen;
if (Base.Clock.Instance.Pause != currentWindowState)
{
Base.Clock.Instance.Pause = currentWindowState;
else
{
// 如果窗口当前是隐藏的,则显示它
Show(window);
}
}
}
/// <summary>
/// 模拟按键输入切换窗口
/// 公开的显示窗口方法
/// </summary>
/// <param name="keyCode">要模拟的按键</param>
public void SimulateKeyPress(KeyCode keyCode)
/// <param name="windowToShow">要显示的窗口</param>
public void Show(UIBase windowToShow)
{
if (UIwindowKeys.TryGetValue(keyCode, out UIBase targetWindow))
if (!windowToShow || windowToShow.IsVisible) return;
// 如果窗口是独占的,隐藏所有其他窗口
if (windowToShow.exclusive)
{
HandleWindowActivation(targetWindow); // 调用内部逻辑处理
}
else
{
Debug.LogWarning($"No window is assigned to the key '{keyCode}'.");
List<UIBase> windowsToHide = new List<UIBase>(_visibleWindows);
foreach (var visibleWindow in windowsToHide)
{
Hide(visibleWindow);
}
}
// 显示目标窗口并更新缓存与暂停状态
windowToShow.Show();
var itick=windowToShow as ITickUI;
if (itick != null)
Base.Clock.AddTickUI(itick);
needUpdate = true;
}
/// <summary>
/// 打开指定的窗口(无论是否有激活键)
/// 公开的隐藏窗口方法
/// </summary>
/// <param name="window">要打开的窗口</param>
public void OpenWindow(UIBase window)
/// <param name="windowToHide">要隐藏的窗口</param>
public void Hide(UIBase windowToHide)
{
if (window == null || !(UIwindowKeys.ContainsValue(window) || noKeyWindows.Contains(window)))
{
Debug.LogWarning("Cannot open the specified window as it is not registered.");
return;
}
if (!windowToHide || !windowToHide.IsVisible) return;
HandleWindowActivation(window, true); // 调用内部逻辑处理,标记为函数调用
// 隐藏目标窗口并更新缓存与暂停状态
windowToHide.Hide();
needUpdate = true;
}
/// <summary>
/// 关闭指定的窗口(无论是否有激活键)
/// 根据当前所有可见窗口的 needPause 属性来更新游戏时钟的暂停状态
/// </summary>
/// <param name="window">要关闭的窗口</param>
public void CloseWindow(UIBase window)
private void UpdatePauseState()
{
if (window == null || !(UIwindowKeys.ContainsValue(window) || noKeyWindows.Contains(window)))
bool shouldPause = _visibleWindows.Any(w => w.needPause);
if (Base.Clock.Instance.Pause != shouldPause)
{
Debug.LogWarning("Cannot close the specified window as it is not registered.");
return;
Base.Clock.Instance.Pause = shouldPause;
}
HandleWindowActivation(window, true); // 调用内部逻辑处理,标记为函数调用
}
/// <summary>
/// 切换指定窗口的显示状态(无论是否有激活键)
/// 更新当前可见窗口的缓存列表
/// </summary>
/// <param name="window">要切换的窗口</param>
public void ToggleWindow(UIBase window)
private void UpdateVisibleWindowsCache()
{
if (window == null || !(UIwindowKeys.ContainsValue(window) || noKeyWindows.Contains(window)))
_visibleWindows.Clear();
foreach (var window in _allWindows)
{
Debug.LogWarning("Cannot toggle the specified window as it is not registered.");
return;
if (window.IsVisible)
{
_visibleWindows.Add(window);
}
}
HandleWindowActivation(window, true); // 调用内部逻辑处理,标记为函数调用
}
/// <summary>
/// 在对象销毁时清理事件监听
/// </summary>
private void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
/// <summary>
/// 在对象启动时初始化
/// </summary>
protected override void OnStart()
{
// 注册场景加载事件
SceneManager.sceneLoaded += OnSceneLoaded;
// 初始化时调用一次
Init();
// RegisterAllWindows();
}
/// <summary>
/// 场景加载完成后重新初始化
/// </summary>
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 场景加载完成后调用 Init 方法
Init();
RegisterAllWindows();
}
}
}

View File

@ -1,13 +1,85 @@
using Base;
using UnityEngine;
namespace CameraControl
{
public class CameraControl:MonoBehaviour,Base.ITick
public class CameraControl : Utils.MonoSingleton<CameraControl>, ITick, ITickUI
{
public Entity.Entity focusedEntity=null;
// 当前被聚焦的目标实体
public Entity.Entity focusedEntity = null;
// Camera movement variables
private Vector3 _dragOrigin;
private bool _isDragging = false;
private float _zoomSpeed = 5f;
private float _minZoom = 2f;
private float _maxZoom = 20f;
private Camera _camera;
protected override void OnStart()
{
_camera = Camera.main;
if (_camera == null)
{
_camera = FindFirstObjectByType<Camera>();
}
}
public void Tick()
{
if (focusedEntity)
{
// Follow the focused entity's position
var targetPosition = new Vector3(
focusedEntity.Position.x,
focusedEntity.Position.y,
_camera.transform.position.z);
_camera.transform.position = Vector3.Lerp(
_camera.transform.position,
targetPosition,
Time.fixedDeltaTime * 5f);
}
}
public void TickUI()
{
HandleMiddleMouseDrag();
HandleMouseZoom();
}
private void HandleMiddleMouseDrag()
{
// Start drag
if (Input.GetMouseButtonDown(2)) // Middle mouse button
{
_dragOrigin = _camera.ScreenToWorldPoint(Input.mousePosition);
_isDragging = true;
focusedEntity = null; // Clear focus when manually moving camera
}
// During drag
if (Input.GetMouseButton(2) && _isDragging)
{
var difference = _dragOrigin - _camera.ScreenToWorldPoint(Input.mousePosition);
_camera.transform.position += difference;
}
// End drag
if (Input.GetMouseButtonUp(2))
{
_isDragging = false;
}
}
private void HandleMouseZoom()
{
var scroll = Input.GetAxis("Mouse ScrollWheel");
if (scroll == 0) return;
var newSize = _camera.orthographicSize - scroll * _zoomSpeed;
_camera.orthographicSize = Mathf.Clamp(newSize, _minZoom, _maxZoom);
}
}
}

View File

@ -0,0 +1,7 @@
namespace Data
{
public class AffiliationDef : Define
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bdca0abb717148269b84fe8884ebbf02
timeCreated: 1754545405

View File

@ -1,30 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Data
{
public class PawnDef : Define
{
public AttributesDef attributes;
public DrawingOrderDef drawingOrder;
public BehaviorTreeDef behaviorTree;
public string affiliation;
}
public class MonsterDef:PawnDef
{
}
public class BehaviorTreeDef : Define
{
public BehaviorTreeDef[] childTree;
public string className="Selector";
public string condition;
public string value;
public override bool Init(XElement xmlDef)
@ -33,7 +17,7 @@ namespace Data
// 从当前节点获取className和condition属性
className = xmlDef.Attribute("className")?.Value ?? className;
condition = xmlDef.Attribute("condition")?.Value;
value = xmlDef.Attribute("value")?.Value;
var nodes = xmlDef.Elements("Node");
if (!nodes.Any())
@ -53,8 +37,4 @@ namespace Data
}
}
public class AffiliationDef : Define
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8513d8eee75e4b9f92179e29f88221b3
timeCreated: 1754545380

View File

@ -0,0 +1,7 @@
namespace Data
{
public class BuildingDef:PawnDef
{
public bool IsBlocked = true;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3855e1d8b2ab4cae904771195fa46cc5
timeCreated: 1753703020

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UnityEngine;
namespace Data
{
public class CharacterDef : PawnDef
{
}
}

View File

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
namespace Data
@ -21,11 +19,6 @@ namespace Data
Image,
Animation
}
public class CharacterDef : PawnDef
{
}
public class DrawingOrderDef : Define
{
public DrawNodeDef drawingOrder_down;
@ -33,6 +26,7 @@ namespace Data
public DrawNodeDef drawingOrder_left;
public DrawNodeDef drawingOrder_right;
public string texturePath;
public int pixelsPerUnit = 16;
public DrawNodeDef GetDrawingOrder(Orientation orientation)
{
@ -74,7 +68,7 @@ namespace Data
public DrawNodeType drawNodeType = DrawNodeType.Image;
public string nodeName;
public Vector2 position = new(0, 0);
public float FPS = 1;
public float FPS = 0.5f;
public override bool Init(XElement xmlDef)
{
@ -143,5 +137,4 @@ namespace Data
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6360fb04b7a9454d9ac6d8f4864ee31f
timeCreated: 1754545254

View File

@ -1,8 +1,5 @@
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using UnityEditor.Animations;
using UnityEngine.Tilemaps;
namespace Data
{

View File

@ -0,0 +1,7 @@
namespace Data
{
public class MonsterDef:PawnDef
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eeac50195bd94af4a21039131cdb77d6
timeCreated: 1754545363

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Data
{
public class PawnDef : Define
{
public AttributesDef attributes;
public DrawingOrderDef drawingOrder;
public BehaviorTreeDef behaviorTree;
public string affiliation;
}
}

View File

@ -0,0 +1,12 @@
using Data;
namespace Entity
{
public class BuildingEntity:Entity
{
void Init(BuildingDef def)
{
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 703125e2813a463d9b714841a3f9995f
timeCreated: 1753702932

View File

@ -9,19 +9,20 @@ using UnityEngine.Serialization;
namespace Entity
{
public class Entity:MonoBehaviour,ITick
public class Entity : MonoBehaviour, ITick
{
public SpriteAnimator animatorPrefab;
public ImagePrefab imagePrefab;
public AIBase aiTree;
public JobBase currentJob;
public AttributesDef attributes=new();
public AttributesDef attributes = new();
public Vector3 direction;
public GameObject body;
public string affiliation;
public bool canSelect = true;
public bool IsChase { set; get; } = true;
public bool PlayerControlled
{
@ -36,15 +37,16 @@ namespace Entity
}
get => _isPlayerControlled;
}
public Vector3 Position => transform.position;
public bool IsDead => attributes.health <= 0;
private bool _isPlayerControlled = false;
private bool _warning = false;
private Dictionary<Orientation,List<ITick>> bodyAnimationNode=new();
public Dictionary<Orientation, List<ITick>> bodyAnimationNode = new();
private Dictionary<Orientation, GameObject> bodyNodes = new();
private Orientation currentOrientation = Orientation.Down;
public virtual void Init(PawnDef pawnDef)
@ -70,7 +72,7 @@ namespace Entity
if (drawNode == null) continue;
var directionRoot = new GameObject(orientation.ToString());
directionRoot.transform.SetParent(body.transform, false);
InitBodyPart(drawNode, directionRoot,drawingOrder.texturePath);
InitBodyPart(drawNode, directionRoot, drawingOrder.texturePath);
bodyNodes[orientation] = directionRoot;
}
currentOrientation = Orientation.Down;
@ -83,9 +85,9 @@ namespace Entity
}
// 递归初始化单个绘图节点及其子节点
public virtual void InitBodyPart(DrawNodeDef drawNode, GameObject parent,string folderPath)
public virtual void InitBodyPart(DrawNodeDef drawNode, GameObject parent, string folderPath)
{
if(drawNode==null) return;
if (drawNode == null) return;
GameObject nodeObject;
if (drawNode.nodeName == "noName")
@ -102,8 +104,11 @@ namespace Entity
var texture =
Managers.PackagesImageManager.Instance.FindBodyTextures(drawNode.packID, folderPath,
$"{drawNode.nodeName}_{currentOrientation}");
var image = nodeObject.GetComponent<ImagePrefab>();
image.SetSprite(texture[0]);
if (texture.Length > 0)
{
var image = nodeObject.GetComponent<ImagePrefab>();
image.SetSprite(texture[0]);
}
break;
case DrawNodeType.Animation:
@ -126,7 +131,7 @@ namespace Entity
// 递归初始化子节点
foreach (var child in drawNode.children)
{
InitBodyPart(child, nodeObject,folderPath);
InitBodyPart(child, nodeObject, folderPath);
}
}
public void Tick()
@ -139,7 +144,6 @@ namespace Entity
{
AutoBehave();
}
if (bodyAnimationNode.TryGetValue(currentOrientation, out var ticks))
{
foreach (var tick in ticks)
@ -149,9 +153,9 @@ namespace Entity
}
}
public virtual void TryAttck()
public virtual void TryAttack()
{
}
public virtual void SetOrientation(Orientation orientation)
@ -174,7 +178,7 @@ namespace Entity
if (hit < 0)
hit = from.attributes.attack / 100;
attributes.health -= hit;
currentJob.StopJob();
}
@ -190,7 +194,7 @@ namespace Entity
private void AutoBehave()
{
if(aiTree == null)
if (aiTree == null)
return;
if (currentJob == null || !currentJob.Running)
{
@ -206,7 +210,7 @@ namespace Entity
}
currentJob.StartJob(this);
}
currentJob.Update();
}
@ -277,7 +281,7 @@ namespace Entity
return (AIBase)Activator.CreateInstance(typeof(AIBase));
}
// 定义可能的命名空间列表
var possibleNamespaces = new[] { "AI"};
var possibleNamespaces = new[] { "AI" };
foreach (var ns in possibleNamespaces)
{
@ -305,4 +309,5 @@ namespace Entity
throw new InvalidOperationException($"无法找到类型 {className} 或该类型不是 AIBase 的子类");
}
}
}

View File

@ -9,14 +9,4 @@ namespace Entity
}
}
public class MonsterAttributes
{
public int health = 10;
public int moveSpeed = 1;
public int attack = 1;
public int defense = 0;
public int attackSpeed = 2;
public int attackRange = 3;
public int attackTargetCount = 1;
}
}

View File

@ -14,6 +14,8 @@ namespace Entity
public Entity entity;
private bool _select = false;
public static Vector3 minimum=new(0.5f,0.5f,0.5f);
public void Init()
{
@ -42,20 +44,32 @@ namespace Entity
/// </returns>
public Vector3 GetSize()
{
// 获取所有子对象的 Renderer 组件
var renderers = body.GetComponentsInChildren<Renderer>();
// 如果没有找到任何 Renderer返回一个默认值 (-1, -1, -1)
if (renderers.Length == 0)
{
return new(-1, -1);
return minimum;
}
// 初始化 totalBounds 为第一个 Renderer 的 bounds
var totalBounds = renderers[0].bounds;
// 遍历剩余的 Renderer将它们的 bounds 合并到 totalBounds 中
for (var i = 1; i < renderers.Length; i++)
{
totalBounds.Encapsulate(renderers[i].bounds);
}
// 获取合并后的包围盒的大小
var size = totalBounds.size;
// 确保每个维度的大小都不小于 0.5
size.x = Mathf.Max(size.x, 0.5f);
size.y = Mathf.Max(size.y, 0.5f);
size.z = Mathf.Max(size.z, 0.5f);
return size;
}
@ -78,7 +92,7 @@ namespace Entity
{
var rightMenu = Prefab.RightMenuPrefab.Instance;
rightMenu.Init(GetMenu());
rightMenu.transform.position=Input.mousePosition;
rightMenu.transform.position = Input.mousePosition;
rightMenu.Show();
}
}
@ -86,12 +100,31 @@ namespace Entity
private List<(string name, UnityAction callback)> GetMenu()
{
var result = new List<(string name, UnityAction callback)>();
if(entity.PlayerControlled)
result.Add(("结束操控",()=>entity.PlayerControlled=false));
if (entity.PlayerControlled)
result.Add(("结束操控", EndControl));
else
result.Add(("手动操控",()=>entity.PlayerControlled=true));
result.Add(("杀死",()=>entity.Kill()));
result.Add(("手动操控", StartControl));
result.Add(("杀死", () => entity.Kill()));
result.Add(("变成笨蛋", BecomeDefault));
return result;
}
private void BecomeDefault()
{
entity.Kill();
Managers.EntityManage.Instance.GenerateDefaultEntity(entity.Position);
}
private void StartControl()
{
entity.PlayerControlled = true;
CameraControl.CameraControl.Instance.focusedEntity=entity;
}
private void EndControl()
{
entity.PlayerControlled = false;
CameraControl.CameraControl.Instance.focusedEntity=null;
}
}
}

View File

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Logging
{
public static class LogCapturer
{
// 日志条目结构
public struct LogEntry
{
public DateTime Timestamp;
public LogType Type;
public string Message;
public string StackTrace;
public override string ToString() =>
$"[{Timestamp:HH:mm:ss}] [{Type}] {Message}" +
(Type == LogType.Exception ? $"\n{StackTrace}" : "");
}
private static readonly Queue<LogEntry> _logs = new Queue<LogEntry>();
private static readonly object _lock = new object(); // 线程锁
private static int _maxLogs = 1000; // 默认容量
// 最大日志容量属性
public static int MaxLogs
{
get => _maxLogs;
set {
lock (_lock) {
_maxLogs = Mathf.Max(value, 1); // 最小值为1
TrimExcess();
}
}
}
static LogCapturer()
{
// 注册全局日志回调
Application.logMessageReceivedThreaded += HandleLog;
}
// 日志处理回调
private static void HandleLog(string message, string stackTrace, LogType type)
{
lock (_lock)
{
var entry = new LogEntry
{
Timestamp = DateTime.Now,
Type = type,
Message = message,
StackTrace = stackTrace
};
_logs.Enqueue(entry);
TrimExcess();
}
}
// 日志队列修剪
private static void TrimExcess()
{
while (_logs.Count > _maxLogs)
{
_logs.Dequeue();
}
}
// 获取当前所有日志(倒序:最新在前)
public static List<LogEntry> GetLogs(bool reverseOrder = true)
{
lock (_lock)
{
var list = new List<LogEntry>(_logs);
if (reverseOrder) list.Reverse();
return list;
}
}
// 清空日志
public static void Clear()
{
lock (_lock)
{
_logs.Clear();
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9095bd039dd74398a68ce9b93da74df0
timeCreated: 1753456559

View File

@ -15,12 +15,19 @@ namespace Managers
public EntityPrefab entityPrefab;
public EntityPrefab defaultEntityPrefab;
public List<EntityPrefab> FindEntitiesByFaction(string factionKey)
{
if (factionEntities.TryGetValue(factionKey, out var entities))
{
return entities; // 如果找到,返回对应的实体列表
}
return new List<EntityPrefab>(); // 如果未找到,返回一个空列表
}
public void Tick()
{
foreach (var faction in factionEntities)
{
List<EntityPrefab> entitiesToRemove = new List<EntityPrefab>();
var entitiesToRemove = new List<EntityPrefab>();
foreach (var entityPrefab in faction.Value)
{
@ -34,7 +41,6 @@ namespace Managers
itike.Tick();
}
}
// 删除所有标记为死亡的实体
foreach (var entityToRemove in entitiesToRemove)
{
@ -56,7 +62,7 @@ namespace Managers
/// </remarks>
public void GenerateEntity(Data.PawnDef pawnDef, Vector3 pos)
{
// 检查entityPrefab是否为空
// 检查 entityPrefab 是否为空
if (entityPrefab == null)
{
Debug.LogError("Error: entityPrefab is null. Please assign a valid prefab.");
@ -64,7 +70,7 @@ namespace Managers
return;
}
// 检查pawnDef是否为空
// 检查 pawnDef 是否为空
if (pawnDef == null)
{
Debug.LogError("Error: PawnDef is null. Cannot generate entity without a valid PawnDef.");
@ -72,26 +78,27 @@ namespace Managers
return;
}
GameObject instantiatedEntity = null; // 用于跟踪已实例化的对象
try
{
// 实例化实体对象
var entity = Instantiate(entityPrefab.gameObject, pos, Quaternion.identity, entityLevel.transform);
instantiatedEntity = Instantiate(entityPrefab.gameObject, pos, Quaternion.identity, entityLevel.transform);
// 获取EntityPrefab组件
var entityComponent = entity.GetComponent<EntityPrefab>();
// 获取 EntityPrefab 组件
var entityComponent = instantiatedEntity.GetComponent<EntityPrefab>();
// 检查EntityPrefab组件是否存在
// 检查 EntityPrefab 组件是否存在
if (entityComponent == null)
{
Debug.LogError($"Error: EntityPrefab component not found on the instantiated object: {entity.name}");
GenerateDefaultEntity(pos);
return;
throw new InvalidOperationException($"Error: EntityPrefab component not found on the instantiated object: {instantiatedEntity.name}");
}
// 初始化实体组件
entityComponent.Init(pawnDef);
// 确保派系键存在,并初始化对应的列表
var factionKey = pawnDef.attributes.label == null ? "default" : pawnDef.attributes.label;
var factionKey = pawnDef.attributes.label ?? "default"; // 使用 null 合并运算符简化代码
if (!factionEntities.ContainsKey(factionKey))
{
factionEntities[factionKey] = new List<EntityPrefab>();
@ -100,21 +107,30 @@ namespace Managers
}
catch (System.Exception ex)
{
// 如果有已实例化的对象,则销毁它
if (instantiatedEntity != null)
{
Destroy(instantiatedEntity); // 删除已创建的对象
}
// 捕获并记录任何异常
Debug.LogError($"An error occurred while generating the entity: {ex.Message}\nStack Trace: {ex.StackTrace}");
// 调用默认生成方法
GenerateDefaultEntity(pos);
}
}
public void GenerateDefaultEntity(Vector3 pos)
{
var entity = Instantiate(entityPrefab.gameObject, pos, Quaternion.identity, entityLevel.transform);
var entity = Instantiate(defaultEntityPrefab.gameObject, pos, Quaternion.identity, entityLevel.transform);
var entityComponent = entity.GetComponent<EntityPrefab>();
const string factionKey = "default";
if (!factionEntities.ContainsKey(factionKey))
{
factionEntities[factionKey] = new List<EntityPrefab>();
}
entityComponent.DefaultInit();
factionEntities[factionKey].Add(entityComponent);
}
protected override void OnStart()

View File

@ -25,7 +25,6 @@ namespace Managers
defaultSprite = Resources.Load<Sprite>("Default/DefaultImage");
InitImageDef();
InitDrawOrder();
packagesImages = null;
}
public void InitImageDef()
@ -54,39 +53,153 @@ namespace Managers
public void InitDrawOrder()
{
var drawOrderDef = Managers.DefineManager.Instance.QueryDefinesByType<DrawingOrderDef>();
if (drawOrderDef == null || drawOrderDef.Length == 0)
try
{
return;
}
Dictionary<string, string> packRootSite = new();
foreach (var drawOrder in drawOrderDef)
{
if (string.IsNullOrEmpty(drawOrder.texturePath) || string.IsNullOrEmpty(drawOrder.packID))
continue;
if (!packRootSite.ContainsKey(drawOrder.packID))
packRootSite[drawOrder.packID] = Managers.DefineManager.Instance.GetPackagePath(drawOrder.packID);
var rootPath= packRootSite[drawOrder.packID];
var folderPath=Path.Combine(rootPath, drawOrder.texturePath);
var imagePath = Configs.ConfigProcessor.GetFilesByExtensions(folderPath,
new[]
{
"jpg", "jpeg", "png", "tga", "tif", "tiff", "psd", "bmp"
});
foreach (var path in imagePath)
// 查询绘制顺序定义
var drawOrderDef = Managers.DefineManager.Instance.QueryDefinesByType<DrawingOrderDef>();
if (drawOrderDef == null || drawOrderDef.Length == 0)
{
var image=Configs.ConfigProcessor.LoadTextureByIO(path);
if (image == null)
continue;
var spr=Sprite.Create(
image,
new Rect(0, 0, image.width, image.height),
new Vector2(0.5f, 0.5f) // 中心点
);
var name=Path.GetFileNameWithoutExtension(path);
InsertBodyTexture(drawOrder.packID, drawOrder.texturePath, name, spr);
Debug.LogWarning("No DrawingOrderDef found.");
return;
}
// 初始化包路径字典
Dictionary<string, string> packRootSite = new();
foreach (var drawOrder in drawOrderDef)
{
// 检查必要字段是否为空
if (string.IsNullOrEmpty(drawOrder.texturePath) || string.IsNullOrEmpty(drawOrder.packID))
{
Debug.LogWarning(
$"Skipping invalid drawOrder: texturePath or packID is null or empty. PackID: {drawOrder.packID}");
continue;
}
// 获取包路径
if (!packRootSite.ContainsKey(drawOrder.packID))
{
var packagePath = Managers.DefineManager.Instance.GetPackagePath(drawOrder.packID);
if (string.IsNullOrEmpty(packagePath))
{
Debug.LogError($"Package path not found for packID: {drawOrder.packID}");
continue;
}
packRootSite[drawOrder.packID] = packagePath;
}
// 判断是否为 Unity 资源路径
bool isUnityResource = drawOrder.texturePath.StartsWith("res:", StringComparison.OrdinalIgnoreCase);
string rootPath = packRootSite[drawOrder.packID];
if (isUnityResource)
{
// 移除 "res:" 前缀并适配 Unity 资源路径规则
string resourceFolder = drawOrder.texturePath.Substring(4).TrimStart('/').Replace('\\', '/');
// 加载文件夹下的所有纹理资源
Texture2D[] textures = Resources.LoadAll<Texture2D>(resourceFolder);
if (textures == null || textures.Length == 0)
{
Debug.LogWarning($"No textures found in Unity resource folder: {resourceFolder}");
continue;
}
foreach (var image in textures)
{
if (image == null)
{
Debug.LogWarning(
$"Texture loaded from Unity resource folder: {resourceFolder} is null.");
continue;
}
// 创建精灵
try
{
var spr = Sprite.Create(
image,
new Rect(0, 0, image.width, image.height),
new Vector2(0.5f, 0.5f), // 中心点
drawOrder.pixelsPerUnit
);
var name = image.name;
// 插入纹理
InsertBodyTexture(drawOrder.packID, drawOrder.texturePath, name, spr);
}
catch (Exception ex)
{
Debug.LogError(
$"Failed to create sprite from Unity resource: {image.name}. Error: {ex.Message}");
}
}
}
else
{
// 文件系统路径处理
var folderPath = Path.Combine(rootPath, drawOrder.texturePath);
// 获取图像文件列表
try
{
var imagePath = Configs.ConfigProcessor.GetFilesByExtensions(folderPath,
new[] { "jpg", "jpeg", "png", "tga", "tif", "tiff", "psd", "bmp" });
foreach (var path in imagePath)
{
// 加载纹理
Texture2D image = null;
try
{
image = Configs.ConfigProcessor.LoadTextureByIO(path);
}
catch (Exception ex)
{
Debug.LogError($"Failed to load texture from path: {path}. Error: {ex.Message}");
continue;
}
if (image == null)
{
Debug.LogWarning($"Texture loaded from path: {path} is null.");
continue;
}
// 创建精灵
try
{
var spr = Sprite.Create(
image,
new Rect(0, 0, image.width, image.height),
new Vector2(0.5f, 0.5f), // 中心点
drawOrder.pixelsPerUnit
);
var name = Path.GetFileNameWithoutExtension(path);
// 插入纹理
InsertBodyTexture(drawOrder.packID, drawOrder.texturePath, name, spr);
}
catch (Exception ex)
{
Debug.LogError(
$"Failed to create sprite from texture: {path}. Error: {ex.Message}");
}
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to retrieve files from folder: {folderPath}. Error: {ex.Message}");
}
}
}
}
catch (Exception ex)
{
Debug.LogError($"An unexpected error occurred in InitDrawOrder: {ex.Message}");
}
}

View File

@ -0,0 +1,87 @@
using System.Collections.Generic;
using Data;
using UnityEngine;
using UnityEngine.Tilemaps;
namespace Managers
{
public class TileManager:Utils.Singleton<TileManager>
{
public Dictionary<string,TileBase> tileBaseMapping = new();
public Dictionary<(int, int, int, int), TileBase> tileToTileBaseMapping = new();
public Dictionary<string, int> tileID = new();
public void Init()
{
if (tileToTileBaseMapping.Count > 0)
return;
Managers.PackagesImageManager.Instance.Init();
var imagePack = Managers.PackagesImageManager.Instance;
var tileType = Managers.DefineManager.Instance.QueryDefinesByType<TileDef>();
for (var i = 0; i < tileType.Length; i++)
{
tileID.Add(tileType[i].name, i);
}
var tileTextureMappingDef=Managers.DefineManager.Instance.QueryDefinesByType<TileMappingTableDef>();
foreach (var mappingTableDef in tileTextureMappingDef)
{
foreach (var keyVal in mappingTableDef.tileDict)
{
var key = keyVal.Key;
var val = keyVal.Value;
var parts = key.Split('_');
if (parts.Length != 4)
{
var packName = Managers.DefineManager.Instance.GetDefinePackageName(mappingTableDef);
Debug.LogError($"来自{packName}定义的TileMappingTableDef键值{key}内容不合法!\n应该为[瓦片名称_瓦片名称_瓦片名称_瓦片名称]的格式");
continue;
}
if (!(tileID.TryGetValue(parts[0], out var k1) &&
tileID.TryGetValue(parts[1], out var k2) &&
tileID.TryGetValue(parts[2], out var k3) &&
tileID.TryGetValue(parts[3], out var k4)))
{
var packName = Managers.DefineManager.Instance.GetDefinePackageName(mappingTableDef);
Debug.LogError($"来自{packName}定义的TileMappingTableDef键值{key}中存在未定义的瓦片名称");
continue;
}
var sprite = imagePack.GetSprite(mappingTableDef.packID,val);
if (sprite == null)
{
var packName = Managers.DefineManager.Instance.GetDefinePackageName(mappingTableDef);
Debug.LogError($"来自{packName}定义的TileMappingTableDef键值{val}中存在未定义的图片名称");
continue;
}
if (tileToTileBaseMapping.ContainsKey((k1, k2, k3, k4)))
{
var packName = Managers.DefineManager.Instance.GetDefinePackageName(mappingTableDef);
Debug.LogWarning($"来自{packName}定义的TileMappingTableDef键值{(k1, k2, k3, k4)}存在重复索引,将忽略重复项");
continue;
}
var tile = LoadTile(sprite);
tileToTileBaseMapping[(k1, k2, k3, k4)] = tile;
tileBaseMapping[val] = tile;
}
}
}
public void Reload()
{
tileToTileBaseMapping.Clear();
Init();
}
public TileBase LoadTile(Sprite sprite)
{
var newTile = ScriptableObject.CreateInstance<Tile>();
newTile.sprite = sprite;
newTile.color = Color.white;
newTile.colliderType = Tile.ColliderType.Sprite;
return newTile;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ff3c1eb2791148068c9f03baeb04f96f
timeCreated: 1754375678

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Data;
using Managers;
using UnityEngine;
using UnityEngine.Tilemaps;
@ -9,36 +10,18 @@ namespace Map
public class DoubleMap : MonoBehaviour
{
public List<List<int>> mapData = new();
// public Tilemap dataLevel;
public Tilemap textureLevel;
public Dictionary<string, TileBase> tileDict = new();
private int offsetX = 0; // 地图数据的 X 偏移量
private int offsetY = 0; // 地图数据的 Y 偏移量
void Start()
{
TileManager.Instance.Init();
var mapSize = 10;
float noiseScale = 0.1f;
for (int x = 0; x < mapSize; x++)
{
List<int> col = new();
for (int y = 0; y < mapSize; y++)
{
// 计算柏林噪声值
float noiseValue = Mathf.PerlinNoise(x * noiseScale, y * noiseScale);
if (noiseValue < 0.5f) // 小于 0.5 表示 Dirt
{
col.Add(TileManager.Instance.tileID.GetValueOrDefault("Dirt"));
}
else // 大于等于 0.5 表示 Grass
{
col.Add(TileManager.Instance.tileID.GetValueOrDefault("Grass"));
}
}
mapData.Add(col);
UpdateTexture();
}
UpdateTexture();
}
public void UpdateTexture()
@ -54,33 +37,46 @@ namespace Map
public int GetTile(int x, int y)
{
if (x < 0 || x >= mapData.Count)
// 转换为相对于 mapData 的索引
int relativeX = x - offsetX;
int relativeY = y - offsetY;
if (relativeX < 0 || relativeX >= mapData.Count)
{
return 0;
}
var col = mapData[x];
if (y < 0 || y >= mapData.Count)
var col = mapData[relativeX];
if (relativeY < 0 || relativeY >= mapData.Count)
{
return 0;
}
return col[y];
return col[relativeY];
}
public void SetTile(int x, int y, string tileName)
{
SetTile(x,y,TileManager.Instance.tileID.GetValueOrDefault(tileName));
SetTile(x, y, TileManager.Instance.tileID.GetValueOrDefault(tileName));
}
public void SetTile(int x, int y, int id)
{
mapData[x][y] = id;
UpdateTexture(x, y);
UpdateTexture(x, y-1);
UpdateTexture(x-1, y);
UpdateTexture(x-1, y-1);
// 转换为相对于 mapData 的索引
int relativeX = x - offsetX;
int relativeY = y - offsetY;
if (relativeX >= 0 && relativeX < mapData.Count &&
relativeY >= 0 && relativeY < mapData[relativeX].Count)
{
mapData[relativeX][relativeY] = id;
UpdateTexture(x, y);
UpdateTexture(x, y - 1);
UpdateTexture(x - 1, y);
UpdateTexture(x - 1, y - 1);
}
}
/// <summary>
/// 更新对应坐标的贴图
/// </summary>
@ -89,94 +85,27 @@ namespace Map
/// <returns></returns>
public void UpdateTexture(int x, int y)
{
// 转换为相对于 mapData 的索引
int relativeX = x - offsetX;
int relativeY = y - offsetY;
if (relativeX < 0 || relativeX >= mapData.Count ||
relativeY < 0 || relativeY >= mapData[relativeX].Count)
{
return; // 如果超出范围,直接返回
}
var lt = GetTile(x, y + 1);
var rt = GetTile(x + 1, y + 1);
var lb = GetTile(x, y);
var rb = GetTile(x + 1, y);
if (TileManager.Instance.tileToTileBaseMapping.ContainsKey((lt, rt, lb, rb)))
{
textureLevel.SetTile(new(x,y),TileManager.Instance.tileToTileBaseMapping[(lt, rt, lb, rb)]);
textureLevel.SetTile(new Vector3Int(x, y, 0),
TileManager.Instance.tileToTileBaseMapping[(lt, rt, lb, rb)]);
}
}
}
public class TileManager:Utils.Singleton<TileManager>
{
public Dictionary<string,TileBase> tileBaseMapping = new();
public Dictionary<(int, int, int, int), TileBase> tileToTileBaseMapping = new();
public Dictionary<string, int> tileID = new();
public void Init()
{
if (tileToTileBaseMapping.Count > 0)
return;
Managers.PackagesImageManager.Instance.Init();
var imagePack = Managers.PackagesImageManager.Instance;
var tileType = Managers.DefineManager.Instance.QueryDefinesByType<TileDef>();
for (var i = 0; i < tileType.Length; i++)
{
tileID.Add(tileType[i].name, i);
}
var tileTextureMappingDef=Managers.DefineManager.Instance.QueryDefinesByType<TileMappingTableDef>();
foreach (var mappingTableDef in tileTextureMappingDef)
{
foreach (var keyVal in mappingTableDef.tileDict)
{
var key = keyVal.Key;
var val = keyVal.Value;
var parts = key.Split('_');
if (parts.Length != 4)
{
var packName = Managers.DefineManager.Instance.GetDefinePackageName(mappingTableDef);
Debug.LogError($"来自{packName}定义的TileMappingTableDef键值{key}内容不合法!\n应该为[瓦片名称_瓦片名称_瓦片名称_瓦片名称]的格式");
continue;
}
if (!(tileID.TryGetValue(parts[0], out var k1) &&
tileID.TryGetValue(parts[1], out var k2) &&
tileID.TryGetValue(parts[2], out var k3) &&
tileID.TryGetValue(parts[3], out var k4)))
{
var packName = Managers.DefineManager.Instance.GetDefinePackageName(mappingTableDef);
Debug.LogError($"来自{packName}定义的TileMappingTableDef键值{key}中存在未定义的瓦片名称");
continue;
}
var sprite = imagePack.GetSprite(mappingTableDef.packID,val);
if (sprite == null)
{
var packName = Managers.DefineManager.Instance.GetDefinePackageName(mappingTableDef);
Debug.LogError($"来自{packName}定义的TileMappingTableDef键值{val}中存在未定义的图片名称");
continue;
}
if (tileToTileBaseMapping.ContainsKey((k1, k2, k3, k4)))
{
var packName = Managers.DefineManager.Instance.GetDefinePackageName(mappingTableDef);
Debug.LogWarning($"来自{packName}定义的TileMappingTableDef键值{(k1, k2, k3, k4)}存在重复索引,将忽略重复项");
continue;
}
var tile = LoadTile(sprite);
tileToTileBaseMapping[(k1, k2, k3, k4)] = tile;
tileBaseMapping[val] = tile;
}
}
}
public void Reload()
{
tileToTileBaseMapping.Clear();
Init();
}
public TileBase LoadTile(Sprite sprite)
{
var newTile = ScriptableObject.CreateInstance<Tile>();
newTile.sprite = sprite;
newTile.color = Color.white;
newTile.colliderType = Tile.ColliderType.Sprite;
return newTile;
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using UnityEngine;
using UnityEngine.Tilemaps;
namespace Map
{
public class MapGenerator:MonoBehaviour
{
public DoubleMap baseLevel;
public Tilemap buildLevel;
public Tilemap plantLevel;
public void Start()
{
var perline= Utils.PerlinNoise.Instance;
int size = 100;
for (int i = -size; i <= size; i++)
{
for (int j = -size; j <= size; j++)
{
var val = perline.Noise(i, j);
if (val < 0)
{
}
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 81339b242c794b8d9d28d7111a46bfbf
timeCreated: 1754370253

View File

@ -1,4 +1,5 @@
using System;
using System.Linq;
using AI;
using Base;
using Data;
@ -13,11 +14,34 @@ namespace Prefab
public Entity.Entity entity;
public Outline outline;
public Vector3 Position
{
get
{
return transform.position;
}
set
{
transform.position = value;
}
}
public void Init(Data.PawnDef pawnDef)
{
entity.Init(pawnDef);
outline.Init();
outline.Hide();
}
public void DefaultInit()
{
var animator = GetComponentsInChildren<SpriteAnimator>();
ITick[] inf = animator;
entity.bodyAnimationNode.Add(Orientation.Down,inf.ToList());
entity.bodyAnimationNode.Add(Orientation.Up,inf.ToList());
entity.bodyAnimationNode.Add(Orientation.Left,inf.ToList());
entity.bodyAnimationNode.Add(Orientation.Right,inf.ToList());
outline.Init();
outline.Hide();
}

View File

@ -83,7 +83,24 @@ namespace Prefab
// 外部控制方法
public void SetPaused(bool paused) => _isPaused = paused;
public void SetSprites(Sprite[] newSprites) => _sprites = newSprites;
public void SetSprites(Sprite[] newSprites)
{
_sprites = newSprites;
// 如果有新的精灵数组,则立即显示第一帧
if (_sprites != null && _sprites.Length > 0)
{
_currentFrameIndex = 0; // 重置当前帧索引为第一帧
_renderer.sprite = _sprites[_currentFrameIndex]; // 立即显示第一帧
}
else
{
_renderer.sprite = null; // 如果没有精灵,则清空渲染器
}
// 重置帧计时器,以确保从头开始播放
_frameTimer = 0f;
}
public void SetFPS(float newFPS) => _fps = Mathf.Max(0.1f, newFPS);
public void SetStaticSprite(Sprite sprite) => _staticSprite = sprite;
}

View File

@ -7,6 +7,7 @@ public class Program : MonoBehaviour
{
UnityLogger.Init();
Managers.DefineManager.Instance.Init();
Managers.PackagesImageManager.Instance.Init();
}
private void Start()

View File

@ -10,23 +10,25 @@ namespace UI
{
public GameObject menuContent;
public EntityPlacementUI entityPlacementUI;
public Prefab.TextPrefab textTemplate;
public Prefab.ButtonPrefab buttonTemplate;
void Start()
private void Start()
{
Init();
}
void Init()
private void Init()
{
InitEvent();
InitCharacter();
InitMonster();
}
void InitEvent()
private void InitEvent()
{
var title = InstantiatePrefab(textTemplate, menuContent.transform);
title.Label = "事件菜单";
@ -35,10 +37,10 @@ namespace UI
// var button= InstantiatePrefab(buttonTemplate, menuContent.transform);
// button.text.text = i.ToString();
// }
}
void InitCharacter()
private void InitCharacter()
{
var title = InstantiatePrefab(textTemplate, menuContent.transform);
title.Label = "生成人物";
@ -46,12 +48,28 @@ namespace UI
var defList = Managers.DefineManager.Instance.QueryNamedDefinesByType<Data.CharacterDef>();
foreach (var def in defList)
{
var button=InstantiatePrefab(buttonTemplate, menuContent.transform);
var button = InstantiatePrefab(buttonTemplate, menuContent.transform);
button.Label = def.label;
var pawnDef = def;
button.AddListener(() => GenerateEntityCallback(pawnDef));
}
}
private void InitMonster()
{
var title = InstantiatePrefab(textTemplate, menuContent.transform);
title.Label = "生成怪物";
var defList = Managers.DefineManager.Instance.QueryNamedDefinesByType<Data.MonsterDef>();
foreach (var def in defList)
{
var button = InstantiatePrefab(buttonTemplate, menuContent.transform);
button.Label = def.label;
var pawnDef = def;
button.AddListener(() => GenerateEntityCallback(pawnDef));
}
}
/// <summary>
/// 通用的实例化函数,返回实例化的预制件脚本组件。
/// </summary>
@ -59,7 +77,7 @@ namespace UI
/// <param name="prefab">要实例化的预制件</param>
/// <param name="parent">实例化对象的父对象</param>
/// <returns>实例化的预制件脚本组件</returns>
T InstantiatePrefab<T>(T prefab, Transform parent) where T : Component
private T InstantiatePrefab<T>(T prefab, Transform parent) where T : Component
{
if (prefab == null || parent == null)
{
@ -68,10 +86,10 @@ namespace UI
}
// 实例化预制件
GameObject instance = Instantiate(prefab.gameObject, parent);
var instance = Instantiate(prefab.gameObject, parent);
// 获取实例化对象的脚本组件
T instantiatedComponent = instance.GetComponent<T>();
var instantiatedComponent = instance.GetComponent<T>();
if (instantiatedComponent == null)
{
@ -81,9 +99,18 @@ namespace UI
return instantiatedComponent;
}
void GenerateEntityCallback(PawnDef pawnDef)
private void GenerateEntityCallback(PawnDef pawnDef)
{
Managers.EntityManage.Instance.GenerateEntity(pawnDef, new(0, 0));
entityPlacementUI.currentAction = () =>
{
// 将鼠标屏幕坐标转换为世界坐标,并确保 Z 值为 0
if (!Camera.main) return;
var worldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
worldPosition.z = 0;
Managers.EntityManage.Instance.GenerateEntity(pawnDef, worldPosition);
};
entityPlacementUI.Prompt = $"当前生成器:\n名称{pawnDef.label}\n描述{pawnDef.description}";
Base.UIInputControl.Instance.Show(entityPlacementUI);
}
}

View File

@ -0,0 +1,36 @@
using Base;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
namespace UI
{
public delegate void NonReturnCallback();
public class EntityPlacementUI:UIBase,ITickUI
{
public TMP_Text promptText;
public NonReturnCallback currentAction;
public string Prompt
{
get => promptText.text;
set => promptText.text = value;
}
public void TickUI()
{
if (!IsVisible)
return;
if (Input.GetKeyDown(KeyCode.Escape))
{
Base.UIInputControl.Instance.Hide(this);
}
if (currentAction!=null&&Input.GetMouseButtonDown(0))
{
currentAction();
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 22c2554b0c0949aab37618f3a80ffe5a
timeCreated: 1753505464

View File

@ -1,10 +1,12 @@
using UnityEngine;
namespace UI
{
public class EscUI:UIBase
{
public void ContinueButton()
{
Base.UIInputControl.Instance.CloseWindow(this);
Base.UIInputControl.Instance.Hide(this);
}
public void ExitButton()

View File

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using Prefab;
using TMPro;
using UnityEngine;
namespace UI
{
public class LogUI : UIBase
{
public Transform contentPanel; // 日志内容容器
public TextPrefab textPrefab; // 文本预制体引用
// 日志类型颜色映射
private static readonly Dictionary<LogType, Color> logColors = new Dictionary<LogType, Color>
{
{ LogType.Log, Color.white },
{ LogType.Warning, Color.yellow },
{ LogType.Error, new Color(1f, 0.4f, 0.4f) },
{ LogType.Exception, new Color(1f, 0.2f, 0.2f) },
{ LogType.Assert, new Color(0.8f, 0.4f, 1f) }
};
private List<Transform> _logItems = new List<Transform>(); // 已创建的日志条目
private int _lastLogCount = 0; // 上次显示的日志数量
private void Start()
{
Logging.LogCapturer.Clear();
}
public override void Show()
{
base.Show();
RefreshLogDisplay();
}
private void RefreshLogDisplay()
{
var logs = Logging.LogCapturer.GetLogs();
// 如果日志数量减少,清理多余的条目
if (logs.Count < _lastLogCount)
{
for (int i = logs.Count; i < _lastLogCount; i++)
{
Destroy(_logItems[i].gameObject);
}
_logItems.RemoveRange(logs.Count, _logItems.Count - logs.Count);
}
// 更新现有条目
for (int i = 0; i < Math.Min(logs.Count, _logItems.Count); i++)
{
UpdateLogEntry(_logItems[i], logs[logs.Count - 1 - i]);
}
// 添加新的条目
if (logs.Count > _lastLogCount)
{
for (int i = _lastLogCount; i < logs.Count; i++)
{
CreateLogEntry(logs[logs.Count - 1 - i]);
}
}
_lastLogCount = logs.Count;
}
private void CreateLogEntry(Logging.LogCapturer.LogEntry entry)
{
// 实例化文本预制体
var logItem = Instantiate(textPrefab, contentPanel);
_logItems.Add(logItem.transform);
UpdateLogEntry(logItem.transform, entry);
}
private void UpdateLogEntry(Transform logItemTransform, Logging.LogCapturer.LogEntry entry)
{
var logItem = logItemTransform.GetComponent<TextPrefab>();
// 设置文本内容
logItem.Label = entry.ToString();
// 设置文本颜色(根据日志类型)
if (logColors.TryGetValue(entry.Type, out Color color))
{
logItem.text.color = color;
}
else
{
logItem.text.color = Color.white; // 默认颜色
}
logItem.text.alignment = TextAlignmentOptions.TopLeft;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 84d93a37c40b425890e954f2ddb9344c
timeCreated: 1753457075

View File

@ -2,12 +2,24 @@ using UnityEngine;
namespace UI
{
public abstract class UIBase:MonoBehaviour
public abstract class UIBase : MonoBehaviour
{
public bool exclusive = true;
public bool needPause = true;
public bool isInputOccupied = false;
public KeyCode actionButton = KeyCode.None;
// 显示或隐藏窗口
public virtual void Show() { gameObject.SetActive(true); }
public virtual void Hide() { gameObject.SetActive(false); }
public virtual void Show()
{
gameObject.SetActive(true);
}
public virtual void Hide()
{
gameObject.SetActive(false);
}
// 判断是否可见
public bool IsVisible => gameObject.activeInHierarchy;

View File

@ -2,14 +2,19 @@ using System;
namespace Utils
{
public class PerlinNoise
public class PerlinNoise : Utils.Singleton<PerlinNoise>
{
private readonly int[] _p; // 混淆表
private int[] _p; // 混淆表
private const int DefaultSeed = 0; // 默认种子
// 构造函数:初始化混淆表
public PerlinNoise(int seed)
public PerlinNoise()
{
Initialize(DefaultSeed);
}
// 初始化混淆表
private void Initialize(int seed)
{
// 初始化为0-255的随机排列
_p = new int[512]; // 混淆表加倍以方便使用
var permutation = new int[256];
var random = new Random(seed);
@ -34,67 +39,67 @@ namespace Utils
}
}
// 重新指定种子
public void Reinitialize(int seed)
{
Initialize(seed);
}
// 平滑函数 (6t^5 - 15t^4 + 10t^3)
private double Fade(double t)
private static double Fade(double t)
{
return t * t * t * (t * (t * 6 - 15) + 10);
}
// 线性插值
private double Lerp(double t, double a, double b)
private static double Lerp(double t, double a, double b)
{
return a + t * (b - a);
}
// 计算梯度向量和距离向量的点积
private double Grad(int hash, double x, double y, double z)
private static 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位
return (hash & 0xF) switch // 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; // 不应该发生
}
0x0 => x + y,
0x1 => -x + y,
0x2 => x - y,
0x3 => -x - y,
0x4 => x + z,
0x5 => -x + z,
0x6 => x - z,
0x7 => -x - z,
0x8 => y + z,
0x9 => -y + z,
0xA => y - z,
0xB => -y - z,
0xC => y + x,
0xD => -y + x,
0xE => y - x,
0xF => -y - x,
_ => 0
};
}
/// <summary>
/// 为给定的(x, y, z)坐标生成3D Perlin噪声。
/// 输出值通常在-1到1之间。
/// </summary>
public double Noise(double x, double y, double z)
public double Noise(double x, double y=0, double z = 0)
{
// 找到包含该点的单位立方体
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;
@ -102,7 +107,6 @@ namespace Utils
var BA = _p[B] + Z;
var BB = _p[B + 1] + Z;
// 获取所有8个角的哈希值
var H000 = _p[AA];
var H100 = _p[BA];
var H010 = _p[AB];
@ -112,19 +116,14 @@ namespace Utils
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)
x0 = Lerp(u, Grad(H000, x, y, z), Grad(H100, x - 1, y, z));
x1 = Lerp(u, Grad(H010, x, y - 1, z), Grad(H110, x - 1, y - 1, z));
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)
x0 = Lerp(u, Grad(H001, x, y, z - 1), Grad(H101, x - 1, y, z - 1));
x1 = Lerp(u, Grad(H011, x, y - 1, z - 1), Grad(H111, x - 1, y - 1, z - 1));
y1 = Lerp(v, x0, x1);
return Lerp(w, y0, y1);