(client) feat:窗口新增独占、占用输入属性,添加部分行为树节点和工作类
This commit is contained in:
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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; // 后退距离
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
7
Client/Assets/Scripts/Data/BuildingDef.cs
Normal file
7
Client/Assets/Scripts/Data/BuildingDef.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Data
|
||||
{
|
||||
public class BuildingDef:PawnDef
|
||||
{
|
||||
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Data/BuildingDef.cs.meta
Normal file
3
Client/Assets/Scripts/Data/BuildingDef.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3855e1d8b2ab4cae904771195fa46cc5
|
||||
timeCreated: 1753703020
|
@ -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
|
||||
|
@ -1,8 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace Data
|
||||
{
|
||||
|
@ -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())
|
||||
|
12
Client/Assets/Scripts/Entity/BuildingEntity.cs
Normal file
12
Client/Assets/Scripts/Entity/BuildingEntity.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Data;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class BuildingEntity:Entity
|
||||
{
|
||||
void Init(BuildingDef def)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Entity/BuildingEntity.cs.meta
Normal file
3
Client/Assets/Scripts/Entity/BuildingEntity.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 703125e2813a463d9b714841a3f9995f
|
||||
timeCreated: 1753702932
|
@ -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 的子类");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
91
Client/Assets/Scripts/Logging/LogCapturer.cs
Normal file
91
Client/Assets/Scripts/Logging/LogCapturer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Logging/LogCapturer.cs.meta
Normal file
3
Client/Assets/Scripts/Logging/LogCapturer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9095bd039dd74398a68ce9b93da74df0
|
||||
timeCreated: 1753456559
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ public class Program : MonoBehaviour
|
||||
{
|
||||
UnityLogger.Init();
|
||||
Managers.DefineManager.Instance.Init();
|
||||
Managers.PackagesImageManager.Instance.Init();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
|
@ -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));
|
||||
}
|
||||
|
26
Client/Assets/Scripts/UI/EntityPlacementUI.cs
Normal file
26
Client/Assets/Scripts/UI/EntityPlacementUI.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/EntityPlacementUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/EntityPlacementUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22c2554b0c0949aab37618f3a80ffe5a
|
||||
timeCreated: 1753505464
|
@ -1,3 +1,5 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class EscUI:UIBase
|
||||
|
100
Client/Assets/Scripts/UI/LogUI.cs
Normal file
100
Client/Assets/Scripts/UI/LogUI.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/LogUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/LogUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84d93a37c40b425890e954f2ddb9344c
|
||||
timeCreated: 1753457075
|
@ -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;
|
||||
|
Reference in New Issue
Block a user