(client) feat:实体的手动控制

This commit is contained in:
m0_75251201
2025-07-21 13:54:08 +08:00
parent a48ccca5f4
commit 6b7ddbbd40
14 changed files with 321 additions and 35 deletions

View File

@ -610,7 +610,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &4227482396833377269
RectTransform:
m_ObjectHideFlags: 0
@ -670,6 +670,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: c83e72721411938449d92dd48c76480d, type: 3}
m_Name:
m_EditorClassIdentifier:
actionButton: 0
menuContent: {fileID: 4435203021089737161}
textTemplate: {fileID: 2137672851208466200, guid: 4572fd0db4eb91d4588451064f59c91b, type: 3}
buttonTemplate: {fileID: 3166707847097429176, guid: f0afd08be12de0d43af753af4f618da4, type: 3}

View File

@ -364,7 +364,8 @@ Transform:
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Children:
- {fileID: 788852947}
m_Father: {fileID: 1109852279}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &603423466
@ -450,6 +451,7 @@ GameObject:
- component: {fileID: 661053191}
- component: {fileID: 661053192}
- component: {fileID: 661053193}
- component: {fileID: 661053194}
m_Layer: 0
m_Name: Outline
m_TagString: Untagged
@ -541,6 +543,131 @@ MonoBehaviour:
m_EditorClassIdentifier:
body: {fileID: 382708044}
outlineRenderer: {fileID: 661053192}
outlineCollider: {fileID: 0}
--- !u!70 &661053194
CapsuleCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 661053190}
m_Enabled: 1
serializedVersion: 3
m_Density: 1
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_ForceSendLayers:
serializedVersion: 2
m_Bits: 4294967295
m_ForceReceiveLayers:
serializedVersion: 2
m_Bits: 4294967295
m_ContactCaptureLayers:
serializedVersion: 2
m_Bits: 4294967295
m_CallbackLayers:
serializedVersion: 2
m_Bits: 4294967295
m_IsTrigger: 0
m_UsedByEffector: 0
m_CompositeOperation: 0
m_CompositeOrder: 0
m_Offset: {x: 0, y: 0}
m_Size: {x: 1, y: 1}
m_Direction: 0
--- !u!1 &788852946
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 788852947}
- component: {fileID: 788852948}
m_Layer: 0
m_Name: Square
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &788852947
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 788852946}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 382708045}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &788852948
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 788852946}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1, y: 1}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!1001 &1002040475
PrefabInstance:
m_ObjectHideFlags: 0
@ -685,8 +812,9 @@ GameObject:
m_Component:
- component: {fileID: 1109852279}
- component: {fileID: 1109852280}
- component: {fileID: 1109852281}
m_Layer: 0
m_Name: EntityPrefab
m_Name: CharacterPrefab
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@ -721,7 +849,23 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 757576bb4354ac54da09868e1be02eec, type: 3}
m_Name:
m_EditorClassIdentifier:
playerControlled: 0
direction: {x: 0, y: 0, z: 0}
canSelect: 1
body: {fileID: 382708044}
--- !u!114 &1109852281
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1109852278}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b20b1846b9ef47db83c2ac8c4c4e82cb, type: 3}
m_Name:
m_EditorClassIdentifier:
entity: {fileID: 1109852280}
outline: {fileID: 661053193}
--- !u!1001 &1147017152
PrefabInstance:
m_ObjectHideFlags: 0

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5760fa63a6a454c4ba22c0780f247ef2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -33,8 +33,12 @@ namespace AI
{
}
public class WanderNode : AIBase
public class RandomWander : AIBase
{
public override JobBase GetJob(Entity.Entity target)
{
return new WanderJob();
}
}
public class ConditionalAI : AIBase
{

View File

@ -1,12 +1,13 @@
using Base;
using Unity.VisualScripting;
using UnityEngine;
namespace AI
{
public abstract class JobBase
{
public Entity.Entity entity;
private int timeoutTicks = 1000;
private int timeoutTicks = 100;
public bool Running=>timeoutTicks > 0;
public virtual void StartJob(Entity.Entity target)
@ -33,6 +34,28 @@ namespace AI
timeoutTicks = 0;
}
}
public class WanderJob : JobBase
{
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;
entity.SetTarget(targetPosition);
entity.IsChase = false;
}
protected override void UpdateJob()
{
entity.TryMove();
}
override public void StopJob()
{
base.StopJob();
entity.IsChase = true;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d2497fdaa11d3554287c58d696dab7e9

View File

@ -1,6 +1,6 @@
using UnityEngine;
namespace Camera
namespace CameraControl
{
public class CameraControl:MonoBehaviour,Base.ITick
{

View File

@ -5,7 +5,7 @@ namespace Data
public class AttributesDef : Define
{
public int health = 10;
public int moveSpeed = 1;
public float moveSpeed = 1;
public int attack = 1;
public int defense = 0;
public int attackSpeed = 2;

View File

@ -1,25 +1,28 @@
using System;
using System.Linq;
using AI;
using Base;
using Data;
using UnityEngine;
namespace Entity
{
public class Character : Entity,ITick
public class Character : Entity
{
public CharacterDef characterDef;
public GameObject body;
private void Start()
{
aiTree = new RandomWander();
runtimeAttributes = new AttributesDef();
}
public void Init()
{
if (characterDef == null)
return;
}
public new void Tick()
{
base.Tick();
}
}
}

View File

@ -3,46 +3,60 @@ using AI;
using Base;
using Data;
using UnityEngine;
using UnityEngine.Serialization;
namespace Entity
{
public abstract class Entity:MonoBehaviour,ITick
{
public string name;
public bool playerControlled = false;
public AIBase aiTree;
public JobBase currentJob;
public AttributesDef runtimeAttributes;
public Vector3 direction;
public bool canSelect = true;
public bool IsChase { set; get; } = true;
public bool PlayerControlled
{
set
{
if (!value)
{
IsChase = true;
}
_isPlayerControlled = value;
}
get => _isPlayerControlled;
}
private bool _isPlayerControlled = false;
private const int WarningInterval = 5000;
private int warningTicks = 0;
private int _warningTicks = 0;
public void Tick()
{
if (currentJob == null || !currentJob.Running)
if (PlayerControlled)
{
currentJob = aiTree.GetJob(this);
if (currentJob == null)
{
if (warningTicks<=0)
{
Debug.LogWarning($"{GetType().Name}类型的{name}没有分配到任何工作,给行为树末尾添加等待行为,避免由于没有工作导致无意义的反复查找工作导致性能问题");
warningTicks += WarningInterval;
}
warningTicks--;
return;
}
UpdatePlayerControls();
}
else
{
AutoBehave();
}
currentJob.Update();
}
public virtual void TryAttck()
{
}
/// <summary>
/// 往对应朝向移动moveSpeed*deltaTime的距离
/// </summary>
public virtual void TryMove()
{
transform.position+=direction * (runtimeAttributes.moveSpeed * Time.deltaTime * (IsChase ? 1 : 0.5f));
}
public virtual void OnHit(Entity from)
{
@ -50,6 +64,80 @@ namespace Entity
if (hit < 0)
hit = from.runtimeAttributes.attack / 100;
runtimeAttributes.health -= hit;
currentJob.StopJob();
}
public virtual void SetTarget(Vector3 pos)
{
direction = (pos - transform.position).normalized;
}
public virtual void Kill(float delay = 0)
{
Destroy(gameObject,delay);
}
private void AutoBehave()
{
if (currentJob == null || !currentJob.Running)
{
currentJob = aiTree.GetJob(this);
if (currentJob == null)
{
if (_warningTicks<=0)
{
Debug.LogWarning($"{GetType().Name}类型的{name}没有分配到任何工作,给行为树末尾添加等待行为,避免由于没有工作导致无意义的反复查找工作导致性能问题");
_warningTicks += WarningInterval;
}
_warningTicks--;
return;
}
currentJob.StartJob(this);
}
currentJob.Update();
}
private void UpdatePlayerControls()
{
// 获取当前键盘输入状态
var inputDirection = new Vector3();
// 检测 WASD 输入
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
{
inputDirection += Vector3.forward; // 向前移动
}
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
{
inputDirection += Vector3.back; // 向后移动
}
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
inputDirection += Vector3.left; // 向左移动
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
inputDirection += Vector3.right; // 向右移动
}
// 如果有输入方向,则设置目标位置并尝试移动
if (inputDirection != Vector3.zero)
{
// 归一化方向向量,确保对角线移动速度一致
inputDirection = inputDirection.normalized;
// 设置目标位置(假设当前位置为 transform.position
Vector3 targetPosition = transform.position + inputDirection;
// 调用 SetTarget 方法设置目标位置
SetTarget(targetPosition);
// 调用 TryMove 方法处理实际移动逻辑
TryMove();
}
}
}
}

View File

@ -6,12 +6,18 @@ namespace Entity
{
public GameObject body;
public SpriteRenderer outlineRenderer;
public CapsuleCollider2D outlineCollider;
public void Show()
public void Init()
{
var size = GetSize();
outlineRenderer.gameObject.SetActive(true);
outlineRenderer.size = size;
outlineCollider.direction = size.x > size.y ? CapsuleDirection2D.Horizontal : CapsuleDirection2D.Vertical;
outlineCollider.size = size;
}
public void Show()
{
outlineRenderer.gameObject.SetActive(true);
}
public void Hide()

View File

@ -1,5 +1,6 @@
using System;
using AI;
using Base;
using Data;
using Entity;
using Unity.VisualScripting;
@ -10,10 +11,16 @@ namespace Prefab
public class EntityPrefab : MonoBehaviour
{
public Entity.Entity entity;
public Outline outline;
public void Init(Data.PawnDef pawnDef)
{
entity.runtimeAttributes = pawnDef.attributes.Clone();
entity.aiTree = ConvertToAIBase(pawnDef.behaviorTree);
outline.Init();
outline.Hide();
}
public static AIBase ConvertToAIBase(BehaviorTreeDef behaviorTreeDef)
{