(client) feat:窗口新增独占、占用输入属性,添加部分行为树节点和工作类

This commit is contained in:
m0_75251201
2025-07-31 17:45:50 +08:00
parent 82dc89c890
commit e1ff66ff28
41 changed files with 2418 additions and 7175 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
@ -99,5 +83,5 @@ namespace AI
return new WanderJob();
}
}
}

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

@ -10,12 +10,10 @@ namespace Base
{
public class UIInputControl : Utils.MonoSingleton<UIInputControl>, ITickUI
{
// 存储窗口及其激活键的字典
public Dictionary<KeyCode, UIBase> UIwindowKeys = new();
// 存储没有激活键的窗口列表
private List<UIBase> noKeyWindows = new();
private List<UIBase> allWindows = new(); // 新增:所有窗口的集合
// 每帧更新逻辑
public void TickUI()
{
foreach (var kvp in UIwindowKeys)
@ -27,16 +25,19 @@ namespace Base
}
}
}
private void Init()
{
UIwindowKeys.Clear();
noKeyWindows.Clear();
allWindows.Clear(); // 清空所有窗口集合
var uiInstances = Resources.FindObjectsOfTypeAll<UIBase>();
foreach (var uiBase in uiInstances)
{
allWindows.Add(uiBase); // 添加到所有窗口集合
var key = uiBase.actionButton;
if (key == KeyCode.None)
{
@ -44,42 +45,55 @@ namespace Base
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)
private void HandleWindowActivation(UIBase targetWindow)
{
bool wasTargetVisible = targetWindow.IsVisible;
bool anyOtherWindowOpen = false;
bool shouldCloseExclusive = false;
bool exclusiveWindowWasOpen = false;
// 遍历所有窗口(包括有键和无键窗口)
foreach (var kvp in UIwindowKeys.Concat(noKeyWindows.Select(w => new KeyValuePair<KeyCode, UIBase>(KeyCode.None, w))))
// 第一次遍历:检查是否有需要关闭的独占窗口
foreach (var window in allWindows)
{
if (kvp.Value == targetWindow)
{
continue;
}
if (window == targetWindow) continue;
if (kvp.Value.IsVisible)
if (window.IsVisible && window.exclusive)
{
if (!wasTargetVisible || isFunctionCall) // 只在目标窗口打开时才关闭其他窗口
// 记录有独占窗口打开且需要关闭
shouldCloseExclusive = true;
exclusiveWindowWasOpen = true;
break;
}
}
// 第二次遍历:根据条件关闭窗口
foreach (var window in allWindows)
{
if (window == targetWindow) continue;
if (window.IsVisible)
{
// 关闭所有独占窗口(无论是新窗口打开还是关闭)
// 或当目标窗口是独占窗口时关闭所有其他窗口
if (window.exclusive || targetWindow.exclusive || shouldCloseExclusive)
{
kvp.Value.Hide();
}
else
{
anyOtherWindowOpen = true; // 记录是否有其他窗口打开
window.Hide();
}
}
}
// 切换目标窗口状态
if (wasTargetVisible)
{
targetWindow.Hide();
@ -89,10 +103,27 @@ namespace Base
targetWindow.Show();
}
bool currentWindowState = !wasTargetVisible || anyOtherWindowOpen;
if (Base.Clock.Instance.Pause != currentWindowState)
// 更新暂停状态(优化版)
UpdatePauseState(exclusiveWindowWasOpen, targetWindow);
}
private void UpdatePauseState(bool exclusiveWindowWasOpen, UIBase targetWindow)
{
bool needPause = false;
foreach (var window in allWindows)
{
Base.Clock.Instance.Pause = currentWindowState;
if (window.IsVisible && window.needPause)
{
needPause = true;
break;
}
}
// 只在状态改变时更新
if (Base.Clock.Instance.Pause != needPause)
{
Base.Clock.Instance.Pause = needPause;
}
}
@ -124,7 +155,7 @@ namespace Base
return;
}
HandleWindowActivation(window, true); // 调用内部逻辑处理,标记为函数调用
HandleWindowActivation(window); // 调用内部逻辑处理,标记为函数调用
}
/// <summary>
@ -139,7 +170,7 @@ namespace Base
return;
}
HandleWindowActivation(window, true); // 调用内部逻辑处理,标记为函数调用
HandleWindowActivation(window); // 调用内部逻辑处理,标记为函数调用
}
/// <summary>
@ -154,7 +185,7 @@ namespace Base
return;
}
HandleWindowActivation(window, true); // 调用内部逻辑处理,标记为函数调用
HandleWindowActivation(window); // 调用内部逻辑处理,标记为函数调用
}
/// <summary>
@ -186,4 +217,5 @@ namespace Base
Init();
}
}
}

View File

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

View File

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

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
namespace Data

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

@ -24,7 +24,7 @@ namespace Data
{
public BehaviorTreeDef[] childTree;
public string className="Selector";
public string condition;
public string value;
public override bool Init(XElement xmlDef)
@ -33,7 +33,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())

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

@ -91,7 +91,13 @@ namespace Entity
else
result.Add(("手动操控",()=>entity.PlayerControlled=true));
result.Add(("杀死",()=>entity.Kill()));
result.Add(("变成笨蛋",()=>BecomeDefault()));
return result;
}
private void BecomeDefault()
{
entity.Kill();
Managers.EntityManage.Instance.GenerateDefaultEntity(entity.Position);
}
}

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

@ -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

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

View File

@ -12,21 +12,21 @@ namespace UI
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 = "事件菜单";
@ -38,7 +38,7 @@ namespace UI
}
void InitCharacter()
private void InitCharacter()
{
var title = InstantiatePrefab(textTemplate, menuContent.transform);
title.Label = "生成人物";
@ -52,6 +52,22 @@ namespace UI
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 +75,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)
{
@ -81,7 +97,7 @@ namespace UI
return instantiatedComponent;
}
void GenerateEntityCallback(PawnDef pawnDef)
private void GenerateEntityCallback(PawnDef pawnDef)
{
Managers.EntityManage.Instance.GenerateEntity(pawnDef, new(0, 0));
}

View File

@ -0,0 +1,26 @@
using Base;
using UnityEngine;
using UnityEngine.Events;
namespace UI
{
public class EntityPlacementUI:UIBase,ITickUI
{
public UnityAction currentAction = null;
public void TickUI()
{
if (!IsVisible||currentAction==null)
return;
if (Input.GetMouseButton(0))
{
currentAction.Invoke();
}
if (Input.GetKeyDown(KeyCode.Escape))
{
Hide();
}
}
}
}

View File

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

View File

@ -1,3 +1,5 @@
using UnityEngine;
namespace UI
{
public class EscUI:UIBase

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;