(client) feat:实现实体动态创建,实体右键菜单
Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com> Reviewed-on: #41
This commit is contained in:
10
Client/Assets/Scripts/Entity/BuildingBase.cs
Normal file
10
Client/Assets/Scripts/Entity/BuildingBase.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Entity
|
||||
{
|
||||
public class BuildingBase:Entity
|
||||
{
|
||||
public override void TryMove()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Entity/BuildingBase.cs.meta
Normal file
3
Client/Assets/Scripts/Entity/BuildingBase.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb881a08fe004eb4ab0a6fa9d5d86d33
|
||||
timeCreated: 1753100586
|
@ -10,12 +10,11 @@ namespace Entity
|
||||
public class Character : Entity
|
||||
{
|
||||
public CharacterDef characterDef;
|
||||
public GameObject body;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
aiTree = new RandomWander();
|
||||
runtimeAttributes = new AttributesDef();
|
||||
attributes = new AttributesDef();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
|
@ -1,18 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AI;
|
||||
using Base;
|
||||
using Data;
|
||||
using Prefab;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public abstract class Entity:MonoBehaviour,ITick
|
||||
public class Entity:MonoBehaviour,ITick
|
||||
{
|
||||
public SpriteAnimator animatorPrefab;
|
||||
public ImagePrefab imagePrefab;
|
||||
|
||||
public AIBase aiTree;
|
||||
public JobBase currentJob;
|
||||
public AttributesDef runtimeAttributes;
|
||||
public AttributesDef attributes=new();
|
||||
public Vector3 direction;
|
||||
public GameObject body;
|
||||
public string affiliation;
|
||||
|
||||
public bool canSelect = true;
|
||||
public bool IsChase { set; get; } = true;
|
||||
@ -20,23 +27,111 @@ namespace Entity
|
||||
{
|
||||
set
|
||||
{
|
||||
if (!value)
|
||||
if (value)
|
||||
{
|
||||
IsChase = true;
|
||||
currentJob = null;
|
||||
}
|
||||
_isPlayerControlled = value;
|
||||
}
|
||||
get => _isPlayerControlled;
|
||||
}
|
||||
|
||||
|
||||
public bool IsDead => attributes.health <= 0;
|
||||
|
||||
private bool _isPlayerControlled = false;
|
||||
private bool _warning = false;
|
||||
|
||||
private Dictionary<Orientation,List<ITick>> bodyAnimationNode=new();
|
||||
private Dictionary<Orientation, GameObject> bodyNodes = new();
|
||||
|
||||
private const int WarningInterval = 5000;
|
||||
private int _warningTicks = 0;
|
||||
|
||||
private Orientation currentOrientation = Orientation.Down;
|
||||
|
||||
public virtual void Init(PawnDef pawnDef)
|
||||
{
|
||||
attributes = pawnDef.attributes.Clone();
|
||||
aiTree = ConvertToAIBase(pawnDef.behaviorTree);
|
||||
affiliation = pawnDef.affiliation;
|
||||
InitBody(pawnDef.drawingOrder);
|
||||
}
|
||||
|
||||
public virtual void InitBody(DrawingOrderDef drawingOrder)
|
||||
{
|
||||
// 定义方向枚举和对应的 GetDrawingOrder 调用
|
||||
Orientation[] orientations = { Orientation.Down, Orientation.Up, Orientation.Left, Orientation.Right };
|
||||
|
||||
foreach (var orientation in orientations)
|
||||
{
|
||||
currentOrientation = orientation;
|
||||
bodyAnimationNode[orientation] = new();
|
||||
// 获取当前方向的绘图节点
|
||||
var drawNode = drawingOrder.GetDrawingOrder(orientation);
|
||||
|
||||
if (drawNode == null) continue;
|
||||
var directionRoot = new GameObject(orientation.ToString());
|
||||
directionRoot.transform.SetParent(body.transform, false);
|
||||
InitBodyPart(drawNode, directionRoot,drawingOrder.texturePath);
|
||||
bodyNodes[orientation] = directionRoot;
|
||||
}
|
||||
currentOrientation = Orientation.Down;
|
||||
|
||||
foreach (var bodyNode in bodyNodes)
|
||||
{
|
||||
bodyNode.Value.SetActive(false);
|
||||
}
|
||||
SetOrientation(Orientation.Down);
|
||||
}
|
||||
|
||||
// 递归初始化单个绘图节点及其子节点
|
||||
public virtual void InitBodyPart(DrawNodeDef drawNode, GameObject parent,string folderPath)
|
||||
{
|
||||
if(drawNode==null) return;
|
||||
|
||||
GameObject nodeObject;
|
||||
if (drawNode.nodeName == "noName")
|
||||
{
|
||||
nodeObject = new();
|
||||
nodeObject.transform.SetParent(parent.transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (drawNode.drawNodeType)
|
||||
{
|
||||
case DrawNodeType.Image:
|
||||
nodeObject = Instantiate(imagePrefab.gameObject, parent.transform);
|
||||
var texture =
|
||||
Managers.PackagesImageManager.Instance.FindBodyTextures(drawNode.packID, folderPath,
|
||||
$"{drawNode.nodeName}_{currentOrientation}");
|
||||
var image = nodeObject.GetComponent<ImagePrefab>();
|
||||
image.SetSprite(texture[0]);
|
||||
break;
|
||||
|
||||
case DrawNodeType.Animation:
|
||||
nodeObject = Instantiate(animatorPrefab.gameObject, parent.transform);
|
||||
ITick tick = nodeObject.GetComponent<SpriteAnimator>();
|
||||
if (tick != null)
|
||||
bodyAnimationNode[currentOrientation].Add(tick);
|
||||
var textures = Managers.PackagesImageManager.Instance.FindBodyTextures(drawNode.packID,
|
||||
folderPath,
|
||||
$"{drawNode.nodeName}_{currentOrientation}");
|
||||
var animator = nodeObject.GetComponent<SpriteAnimator>();
|
||||
animator.SetSprites(textures);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
nodeObject.transform.localPosition = drawNode.position;
|
||||
nodeObject.name = drawNode.nodeName;
|
||||
// 递归初始化子节点
|
||||
foreach (var child in drawNode.children)
|
||||
{
|
||||
InitBodyPart(child, nodeObject,folderPath);
|
||||
}
|
||||
}
|
||||
public void Tick()
|
||||
{
|
||||
if (PlayerControlled)
|
||||
if (_isPlayerControlled)
|
||||
{
|
||||
UpdatePlayerControls();
|
||||
}
|
||||
@ -44,26 +139,41 @@ namespace Entity
|
||||
{
|
||||
AutoBehave();
|
||||
}
|
||||
|
||||
if (bodyAnimationNode.TryGetValue(currentOrientation, out var ticks))
|
||||
{
|
||||
foreach (var tick in ticks)
|
||||
{
|
||||
tick.Tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void TryAttck()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void SetOrientation(Orientation orientation)
|
||||
{
|
||||
bodyNodes[currentOrientation]?.SetActive(false);
|
||||
currentOrientation = orientation;
|
||||
bodyNodes[orientation]?.SetActive(true);
|
||||
}
|
||||
/// <summary>
|
||||
/// 往对应朝向移动moveSpeed*deltaTime的距离
|
||||
/// </summary>
|
||||
public virtual void TryMove()
|
||||
{
|
||||
transform.position+=direction * (runtimeAttributes.moveSpeed * Time.deltaTime * (IsChase ? 1 : 0.5f));
|
||||
transform.position += direction * (attributes.moveSpeed * Time.deltaTime * (IsChase ? 1 : 0.5f));
|
||||
}
|
||||
|
||||
public virtual void OnHit(Entity from)
|
||||
{
|
||||
var hit = from.runtimeAttributes.attack - runtimeAttributes.defense;
|
||||
var hit = from.attributes.attack - attributes.defense;
|
||||
if (hit < 0)
|
||||
hit = from.runtimeAttributes.attack / 100;
|
||||
runtimeAttributes.health -= hit;
|
||||
hit = from.attributes.attack / 100;
|
||||
attributes.health -= hit;
|
||||
|
||||
currentJob.StopJob();
|
||||
}
|
||||
@ -73,25 +183,25 @@ namespace Entity
|
||||
direction = (pos - transform.position).normalized;
|
||||
}
|
||||
|
||||
public virtual void Kill(float delay = 0)
|
||||
public virtual void Kill()
|
||||
{
|
||||
Destroy(gameObject,delay);
|
||||
attributes.health = 0;
|
||||
}
|
||||
|
||||
private void AutoBehave()
|
||||
{
|
||||
if(aiTree == null)
|
||||
return;
|
||||
if (currentJob == null || !currentJob.Running)
|
||||
{
|
||||
currentJob = aiTree.GetJob(this);
|
||||
if (currentJob == null)
|
||||
{
|
||||
if (_warningTicks<=0)
|
||||
if (!_warning)
|
||||
{
|
||||
Debug.LogWarning($"{GetType().Name}类型的{name}没有分配到任何工作,给行为树末尾添加等待行为,避免由于没有工作导致无意义的反复查找工作导致性能问题");
|
||||
_warningTicks += WarningInterval;
|
||||
_warning = true;
|
||||
}
|
||||
|
||||
_warningTicks--;
|
||||
return;
|
||||
}
|
||||
currentJob.StartJob(this);
|
||||
@ -102,42 +212,97 @@ namespace Entity
|
||||
|
||||
private void UpdatePlayerControls()
|
||||
{
|
||||
// 获取当前键盘输入状态
|
||||
var inputDirection = new Vector3();
|
||||
// 检测 Shift 键状态
|
||||
var isHoldingShift = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
|
||||
IsChase = !isHoldingShift; // 按住 Shift 时 IsChase = false,否则 true
|
||||
// 获取当前键盘输入状态(2D 移动,只使用 X 和 Y 轴)
|
||||
var inputDirection = Vector2.zero;
|
||||
|
||||
// 检测 WASD 输入
|
||||
// 检测 WASD 或方向键输入
|
||||
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
|
||||
{
|
||||
inputDirection += Vector3.forward; // 向前移动
|
||||
inputDirection += Vector2.up; // 向上移动(Y 轴正方向)
|
||||
}
|
||||
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
|
||||
{
|
||||
inputDirection += Vector3.back; // 向后移动
|
||||
inputDirection += Vector2.down; // 向下移动(Y 轴负方向)
|
||||
}
|
||||
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
|
||||
{
|
||||
inputDirection += Vector3.left; // 向左移动
|
||||
inputDirection += Vector2.left; // 向左移动(X 轴负方向)
|
||||
}
|
||||
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
|
||||
{
|
||||
inputDirection += Vector3.right; // 向右移动
|
||||
inputDirection += Vector2.right; // 向右移动(X 轴正方向)
|
||||
}
|
||||
|
||||
// 如果有输入方向,则设置目标位置并尝试移动
|
||||
if (inputDirection != Vector3.zero)
|
||||
if (inputDirection == Vector2.zero) return;
|
||||
// 归一化方向向量,确保对角线移动速度一致
|
||||
inputDirection = inputDirection.normalized;
|
||||
|
||||
// 设置目标位置(2D 移动,Z 轴保持不变)
|
||||
var targetPosition = transform.position + new Vector3(inputDirection.x, inputDirection.y, 0);
|
||||
|
||||
// 调用 SetTarget 方法设置目标位置
|
||||
SetTarget(targetPosition);
|
||||
|
||||
// 调用 TryMove 方法处理实际移动逻辑
|
||||
TryMove();
|
||||
}
|
||||
public static AIBase ConvertToAIBase(BehaviorTreeDef behaviorTreeDef)
|
||||
{
|
||||
if (behaviorTreeDef == null)
|
||||
return null;
|
||||
var aiBase = CreateAIBaseInstance(behaviorTreeDef.className);
|
||||
if (behaviorTreeDef.childTree != null)
|
||||
{
|
||||
// 归一化方向向量,确保对角线移动速度一致
|
||||
inputDirection = inputDirection.normalized;
|
||||
|
||||
// 设置目标位置(假设当前位置为 transform.position)
|
||||
Vector3 targetPosition = transform.position + inputDirection;
|
||||
|
||||
// 调用 SetTarget 方法设置目标位置
|
||||
SetTarget(targetPosition);
|
||||
|
||||
// 调用 TryMove 方法处理实际移动逻辑
|
||||
TryMove();
|
||||
foreach (var child in behaviorTreeDef.childTree)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
aiBase.children.Add(ConvertToAIBase(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
return aiBase;
|
||||
}
|
||||
// 使用反射根据 className 创建具体的 AIBase 子类实例
|
||||
private static AIBase CreateAIBaseInstance(string className)
|
||||
{
|
||||
if (string.IsNullOrEmpty(className))
|
||||
throw new ArgumentException("className 不能为空");
|
||||
if (className.Equals("AIBase", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (AIBase)Activator.CreateInstance(typeof(AIBase));
|
||||
}
|
||||
// 定义可能的命名空间列表
|
||||
var possibleNamespaces = new[] { "AI"};
|
||||
|
||||
foreach (var ns in possibleNamespaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取当前程序集
|
||||
var assembly = typeof(AIBase).Assembly;
|
||||
|
||||
// 尝试查找类型
|
||||
var type = assembly.GetType($"{ns}.{className}");
|
||||
|
||||
if (type != null && typeof(AIBase).IsAssignableFrom(type))
|
||||
{
|
||||
// 如果找到合适的类型,则创建实例并返回
|
||||
return (AIBase)Activator.CreateInstance(type);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略单个命名空间的错误,继续尝试下一个命名空间
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有命名空间都未找到对应的类型,抛出异常
|
||||
throw new InvalidOperationException($"无法找到类型 {className} 或该类型不是 AIBase 的子类");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class Outline:MonoBehaviour
|
||||
public class Outline : MonoBehaviour
|
||||
{
|
||||
public GameObject body;
|
||||
public SpriteRenderer outlineRenderer;
|
||||
public CapsuleCollider2D outlineCollider;
|
||||
|
||||
public Entity entity;
|
||||
|
||||
private bool _select = false;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var size = GetSize();
|
||||
@ -15,15 +22,17 @@ namespace Entity
|
||||
outlineCollider.direction = size.x > size.y ? CapsuleDirection2D.Horizontal : CapsuleDirection2D.Vertical;
|
||||
outlineCollider.size = size;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
outlineRenderer.gameObject.SetActive(true);
|
||||
outlineRenderer.enabled = true;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
outlineRenderer.gameObject.SetActive(false);
|
||||
outlineRenderer.enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定对象及其所有子对象组成的图像的大小。
|
||||
/// </summary>
|
||||
@ -37,15 +46,52 @@ namespace Entity
|
||||
|
||||
if (renderers.Length == 0)
|
||||
{
|
||||
return new(-1,-1);
|
||||
return new(-1, -1);
|
||||
}
|
||||
|
||||
var totalBounds = renderers[0].bounds;
|
||||
for (var i = 1; i < renderers.Length; i++)
|
||||
{
|
||||
totalBounds.Encapsulate(renderers[i].bounds);
|
||||
}
|
||||
|
||||
var size = totalBounds.size;
|
||||
return size;
|
||||
}
|
||||
|
||||
private void OnMouseEnter()
|
||||
{
|
||||
Show();
|
||||
_select = true;
|
||||
}
|
||||
|
||||
private void OnMouseExit()
|
||||
{
|
||||
Hide();
|
||||
_select = false;
|
||||
}
|
||||
|
||||
private void OnMouseOver()
|
||||
{
|
||||
// 检测是否按下的是鼠标右键
|
||||
if (Input.GetMouseButtonDown(1)) // 鼠标右键对应的是按钮索引 1
|
||||
{
|
||||
var rightMenu = Prefab.RightMenuPrefab.Instance;
|
||||
rightMenu.Init(GetMenu());
|
||||
rightMenu.transform.position=Input.mousePosition;
|
||||
rightMenu.Show();
|
||||
}
|
||||
}
|
||||
|
||||
private List<(string name, UnityAction callback)> GetMenu()
|
||||
{
|
||||
var result = new List<(string name, UnityAction callback)>();
|
||||
if(entity.PlayerControlled)
|
||||
result.Add(("结束操控",()=>entity.PlayerControlled=false));
|
||||
else
|
||||
result.Add(("手动操控",()=>entity.PlayerControlled=true));
|
||||
result.Add(("杀死",()=>entity.Kill()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user