(client) feat:支持定义实体的碰撞体大小和偏移;建筑支持定义实体建筑和瓦片建筑,建筑支持指定按钮回调;添加存档管理器;Dev支持设置是否暂停;实体允许定义事件组;添加基地界面 (#57)

Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com>
Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/57
This commit is contained in:
2025-09-28 15:02:57 +08:00
parent 87a8abe86c
commit aff747be17
232 changed files with 39203 additions and 4161 deletions

View File

@@ -1,9 +1,21 @@
using Data;
using Managers;
using UnityEngine;
namespace Entity
{
public class Building : CombatantEntity
{
private BuildingOutline buildingOutline;
private BuildingDef buildingDef;
public override void Init(EntityDef entityDef)
{
base.Init(entityDef);
buildingDef = entityDef as BuildingDef;
buildingOutline = entityPrefab.outline as BuildingOutline;
}
public override void SetTarget(Vector3 pos)
{
}
@@ -34,5 +46,18 @@ namespace Entity
transform.position += Vector3.right;
}
}
public override void Tick()
{
base.Tick();
if (buildingDef.triggerEvents != null && buildingOutline.PlayerOnGround &&
Input.GetKeyDown(buildingDef.activateKey))
{
foreach (var eventDef in buildingDef.triggerEvents)
{
EventManager.Instance.Action(eventDef, this);
}
}
}
}
}

View File

@@ -1,3 +1,7 @@
using System;
using Base;
using Data;
using TMPro;
using UnityEngine;
namespace Entity
@@ -5,16 +9,55 @@ namespace Entity
public class BuildingOutline : Outline
{
public BoxCollider2D boxCollider;
public CircleCollider2D trigger;
public TMP_Text tip;
public bool PlayerOnGround { get; private set; }
override public void Init()
{
var size = GetSize();
outlineRenderer.size = size;
boxCollider.size = size;
var textureSize = GetBodyTextureSize();
outlineRenderer.size = textureSize;
var colliderSize = GetColliderSize();
boxCollider.size = colliderSize;
boxCollider.offset = GetOffset();
if (progressBarPrefab)
{
progressBarPrefab.transform.localPosition += new Vector3(0f, size.y * 2 / 3, 0f);
progressBarPrefab.transform.localScale = new Vector3(size.x, 1f / 10f, 1);
progressBarPrefab.transform.localPosition += new Vector3(0f, textureSize.y * 2 / 3, 0f);
progressBarPrefab.transform.localScale = new Vector3(textureSize.x, 1f / 10f, 1);
}
if (entity.entityDef is not BuildingDef buildingDef || buildingDef.triggerEvents == null)
{
trigger.enabled = false;
return;
}
trigger.radius = buildingDef.detectionRadius;
if (!string.IsNullOrEmpty(buildingDef.triggerPosition))
{
var position = Utils.StringUtils.StringToVector2(buildingDef.triggerPosition);
trigger.offset = position;
}
tip.transform.localPosition += new Vector3(0f, textureSize.y * 2 / 3, 0f);
tip.text = $"按{buildingDef.activateKey}打开{buildingDef.label}\n{buildingDef.description}";
tip.gameObject.SetActive(false);
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player"))
{
tip.gameObject.SetActive(true);
PlayerOnGround = true;
}
}
private void OnTriggerExit2D(Collider2D other)
{
tip.gameObject.SetActive(false);
PlayerOnGround = false;
}
}
}

View File

@@ -5,9 +5,9 @@ using Item;
using Managers;
using Prefab;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Parsing;
using UnityEngine;
using Utils;
@@ -19,18 +19,7 @@ namespace Entity
/// </summary>
public class Entity : MonoBehaviour, ITick
{
/// <summary>
/// 动画预制体,用于管理实体的动画逻辑。
/// </summary>
public SpriteAnimator animatorPrefab;
/// <summary>
/// 图像预制体,用于管理实体的静态图像显示。
/// </summary>
public ImagePrefab imagePrefab;
public ProgressBarPrefab healthBarPrefab;
public EntityPrefab entityPrefab;
public EntityDef entityDef;
/// <summary>
@@ -40,7 +29,7 @@ namespace Entity
public SpriteAnimator weaponItem;
public SpriteAnimator weaponAttackAnimation;
/// <summary>
/// 人工智能行为树,定义实体的行为逻辑。
/// </summary>
@@ -96,7 +85,7 @@ namespace Entity
public bool canSelect = true;
public string currentDimensionId = null;
public string currentDimensionId;
@@ -110,18 +99,18 @@ namespace Entity
if (value)
{
currentJob = null;
// 逻辑修改只有当存在一个不同的焦点实体时才将其PlayerControlled设为false
if (Program.Instance.FocusedEntity && Program.Instance.FocusedEntity != this)
{
Program.Instance.FocusedEntity.PlayerControlled = false;
}
Program.Instance.SetFocusedEntity(this);
gameObject.tag="Player";
}
// 逻辑修改:确保只有当自身是焦点实体时才取消焦点,避免不必要的逻辑执行
else if (Program.Instance.FocusedEntity == this)
{
Program.Instance.SetFocusedEntity(null);
gameObject.tag = "Untagged";
}
}
get => Program.Instance.FocusedEntity == this;
@@ -142,8 +131,8 @@ namespace Entity
public bool IsShowingHealthBarUI => _hitBarUIShowTimer > 0;
public bool IsAttacking => _attackTimer > 0;
private float _attackTimer = 0;
private float _attackDetectionTime = 0;
private float _attackTimer;
private float _attackDetectionTime;
private WeaponResource currentAttackWeapon;
/// <summary>
@@ -158,11 +147,8 @@ namespace Entity
/// </summary>
public event Action<Entity> OnEntityDied;
private bool _warning = false;
private bool _warning;
private GameObject wearponAttackAnimationNodeRoot = null;
/// <summary>
/// 存储不同朝向下的身体节点对象。
/// </summary>
@@ -171,19 +157,38 @@ namespace Entity
/// <summary>
/// 当前实体的朝向。
/// </summary>
private Orientation _currentOrientation = Orientation.Down;
public Orientation CurrentOrientation { get; private set; } = Orientation.Down;
/// <summary>
/// 当前实体的状态
/// </summary>
private EntityState _currentState = EntityState.Idle;
public EntityState CurrentState { get; private set; } = EntityState.Idle;
public AudioSource Audio;
[SerializeField] private float _hitBarUIShowTime = 5;
private float _hitBarUIShowTimer = 0;
private int _walkingTimer = 0;
private float _hitBarUIShowTimer;
private int _walkingTimer;
private List<(Func<Entity, bool>,string)> _conditionalEvents=new();
/// <summary>
/// 初始化实体的基本属性和行为树。
@@ -192,13 +197,14 @@ namespace Entity
public virtual void Init(EntityDef entityDef)
{
attributes = new Attributes(entityDef.attributes);
aiTree = AI.BehaviorTree.ConvertToAIBase(entityDef.behaviorTree);
aiTree = BehaviorTree.ConvertToAIBase(entityDef.behaviorTree);
affiliation = entityDef.affiliation?.defName;
InitBody(entityDef.drawingOrder);
this.entityDef = entityDef;
HideHealthBar();
InitWeaponAnimator();
InitConditionalEvents(entityDef.eventDef?.GetAllConditionalEvents());
}
protected virtual void InitWeaponAnimator()
@@ -231,7 +237,9 @@ namespace Entity
protected virtual void InitBody(DrawingOrderDef drawingOrder)
{
// 预缓存枚举值(避免每次循环重复调用 Enum.GetValues
var states = Enum.GetValues(typeof(EntityState)).Cast<EntityState>().ToArray();
var states = Enum.GetValues(typeof(EntityState)).Cast<EntityState>().ToList();
states.Remove(EntityState.Death);
var orientations = Enum.GetValues(typeof(Orientation)).Cast<Orientation>().ToArray();
// 预初始化字典结构(减少内层循环的字典检查)
foreach (var state in states)
@@ -250,15 +258,15 @@ namespace Entity
GameObject targetObj;
if (nodeDef == null)
{
if (imagePrefab && Managers.PackagesImageManager.Instance.defaultSprite != null)
if (PackagesImageManager.Instance.defaultSprite)
{
targetObj = Instantiate(imagePrefab.gameObject, body.transform);
targetObj = Instantiate(GameObjectCreate.ImagePrefab.gameObject, body.transform);
targetObj.name = $"{state}_{orientation}_Default";
targetObj.transform.localPosition = Vector3.zero;
var imagePrefabCom = targetObj.GetComponent<ImagePrefab>();
if (imagePrefabCom)
{
imagePrefabCom.SetSprite(Managers.PackagesImageManager.Instance.defaultSprite);
imagePrefabCom.SetSprite(PackagesImageManager.Instance.defaultSprite);
}
else
{
@@ -313,21 +321,37 @@ namespace Entity
}
protected void InitConditionalEvents(ConditionalEvent[] conditionalEvents)
{
if(conditionalEvents==null)return;
_conditionalEvents.Clear();
foreach (var conditionalEvent in conditionalEvents)
{
var condition = ConditionDelegateFactory.CreateConditionDelegate(conditionalEvent.parameter,
typeof(Entity), typeof(ConditionFunctions));
if(condition==null)continue;
_conditionalEvents.Add((condition, conditionalEvent.defName));
}
}
/// <summary>
/// 更新实体的逻辑,包括玩家控制和自动行为。
/// </summary>
public virtual void Tick()
{
//行走动画切换
if (_walkingTimer > 0)
{
_walkingTimer -= 1;
if (_walkingTimer <= 0)
{
SetBodyTexture(EntityState.Idle, _currentOrientation);
SetBodyTexture(EntityState.Idle, CurrentOrientation);
}
}
//行为控制
if (PlayerControlled)
{
UpdatePlayerControls();
@@ -336,7 +360,7 @@ namespace Entity
{
AutoBehave();
}
//血条显示
if (IsShowingHealthBarUI)
{
_hitBarUIShowTimer -= Time.deltaTime;
@@ -345,7 +369,7 @@ namespace Entity
HideHealthBar();
}
}
//攻击控制
if (_attackTimer > 0)
{
_attackTimer -= Time.deltaTime;
@@ -366,7 +390,15 @@ namespace Entity
if (_attackTimer <= 0)
{
SetBodyTexture(EntityState.Idle, _currentOrientation);
SetBodyTexture(EntityState.Idle, CurrentOrientation);
}
}
//条件事件控制
foreach (var conditionalEvent in _conditionalEvents)
{
if (conditionalEvent.Item1(this))
{
EventManager.Instance.Action(conditionalEvent.Item2, this);
}
}
}
@@ -407,7 +439,7 @@ namespace Entity
{
SetBodyTexture(
weaponResource.Type == WeaponType.Melee ? EntityState.MeleeAttack : EntityState.RangedAttack,
_currentOrientation);
CurrentOrientation);
}
else
{
@@ -430,14 +462,14 @@ namespace Entity
}
}
_currentState = state;
_currentOrientation = orientation;
CurrentState = state;
CurrentOrientation = orientation;
}
public void HideCurrentBodyTexture()
{
if (!bodyNodes.TryGetValue(_currentState, out var stateNode)) return;
if (stateNode.TryGetValue(_currentOrientation, out var node))
if (!bodyNodes.TryGetValue(CurrentState, out var stateNode)) return;
if (stateNode.TryGetValue(CurrentOrientation, out var node))
{
node.SetActive(false);
}
@@ -451,7 +483,7 @@ namespace Entity
// if (IsAttacking)
// return;
transform.position += direction * (attributes.moveSpeed * Time.deltaTime);
SetBodyTexture(EntityState.Walking, _currentOrientation);
SetBodyTexture(EntityState.Walking, CurrentOrientation);
_walkingTimer = 2;
}
@@ -551,7 +583,7 @@ namespace Entity
ori = direction.x > 0 ? Orientation.Right : Orientation.Left;
}
SetBodyTexture(_currentState, ori);
SetBodyTexture(CurrentState, ori);
if (!PlayerControlled)
{
attackDirection=direction;
@@ -598,7 +630,7 @@ namespace Entity
if (weaponItem)
RotateTool.RotateTransformToDirection(weaponItem.transform, attackDirection);
}
if (Input.GetKeyDown(KeyCode.V))
{
weaponItem.gameObject.SetActive(!weaponItem.gameObject.activeSelf);
@@ -631,7 +663,6 @@ namespace Entity
{
TryAttack();
}
// 如果有输入方向,则设置目标位置并尝试移动
if (inputDirection == Vector2.zero) return;
// 归一化方向向量,确保对角线移动速度一致
@@ -698,9 +729,9 @@ namespace Entity
// 假设 EntityManage.Instance.GenerateBulletEntity 方法存在
// (需要一个 EntityManage 单例来实现子弹生成)
if (EntityManage.Instance != null && Program.Instance != null)
if (EntityManager.Instance != null && Program.Instance != null)
{
EntityManage.Instance.GenerateBulletEntity(
EntityManager.Instance.GenerateBulletEntity(
Program.Instance.FocusedDimensionId,
weapon.Bullet,
transform.position, // 子弹的生成位置
@@ -718,5 +749,15 @@ namespace Entity
{
return null;
}
public void ExecuteEvent(EntityEventType type)
{
var eventDefs = entityDef?.eventDef?.GetEventsByType(type);
if (eventDefs == null || eventDefs.Length == 0) return;
foreach (var eventDef in eventDefs)
{
EventManager.Instance.Action(eventDef,this);
}
}
}
}

View File

@@ -39,9 +39,7 @@ namespace Entity
var defAttributes = base.baseAttributes;
var hediffOffset = new AttributesOffsetDef();
// 这里假设 SaveManager.Instance.baseHediffs 指的是“所有实体共通的基础Hediff”
// 并且这些基础 Hediff 也会影响 baseAttributes
foreach (var hediff in SaveManager.Instance.baseHediffs)
foreach (var hediff in HediffManager.Instance.BaseHediffs)
{
hediffOffset += hediff.CurrentTotalAttributesOffset;
}

View File

@@ -1,6 +1,7 @@
using Managers;
using Prefab;
using System.Collections.Generic;
using Base;
using UnityEngine;
using UnityEngine.Events;
@@ -19,12 +20,15 @@ namespace Entity
public static Vector3 minimum = new(0.5f, 0.5f, 0.5f);
public bool CanShow => Setting.Instance.CurrentSettings.developerMode && entity.canSelect;
public virtual void Init()
{
var size = GetSize();
outlineRenderer.size = size;
outlineRenderer.size = GetBodyTextureSize();
var size = GetColliderSize();
outlineCollider.direction = size.x > size.y ? CapsuleDirection2D.Horizontal : CapsuleDirection2D.Vertical;
outlineCollider.size = size;
outlineCollider.offset = GetOffset();
if (progressBarPrefab)
{
progressBarPrefab.transform.localPosition += new Vector3(0f, size.y * 2 / 3, 0f);
@@ -49,7 +53,14 @@ namespace Entity
/// 返回一个 Vector3 对象,表示对象在世界空间中的总大小(宽度、高度、深度)。
/// 如果没有找到任何渲染器,则返回 (-1, -1, -1) 表示无效大小。
/// </returns>
public Vector3 GetSize()
public Vector2 GetColliderSize()
{
return !string.IsNullOrEmpty(entity.entityDef.colliderSize)
? Utils.StringUtils.StringToVector2(entity.entityDef.colliderSize)
: GetBodyTextureSize();
}
public Vector2 GetBodyTextureSize()
{
// 获取所有子对象的 Renderer 组件
var renderers = body.GetComponentsInChildren<Renderer>();
@@ -80,28 +91,37 @@ namespace Entity
return size;
}
private void OnMouseEnter()
public Vector2 GetOffset()
{
return string.IsNullOrEmpty(entity.entityDef.colliderPosition)
? Vector2.zero
: Utils.StringUtils.StringToVector2(entity.entityDef.colliderPosition);
}
protected virtual void OnMouseEnter()
{
if (!CanShow)
return;
Show();
}
private void OnMouseExit()
protected virtual void OnMouseExit()
{
Hide();
}
private void OnMouseOver()
protected virtual void OnMouseOver()
{
if (!entity.canSelect)
if (!Program.Instance.CanOpenRightMenu || !CanShow)
return;
// 检测是否按下的是鼠标右键
if (Input.GetMouseButtonDown(1)) // 鼠标右键对应的是按钮索引 1
if (Input.GetMouseButtonDown(1))
{
RightMenuManager.GenerateRightMenu(GetMenu(), Input.mousePosition);
}
}
private List<(string name, UnityAction callback)> GetMenu()
protected virtual List<(string name, UnityAction callback)> GetMenu()
{
var result = new List<(string name, UnityAction callback)>();
if (entity.PlayerControlled)
@@ -113,18 +133,18 @@ namespace Entity
return result;
}
private void BecomeDefault()
protected void BecomeDefault()
{
entity.Kill();
EntityManage.Instance.GenerateDefaultEntity(Program.Instance.FocusedDimensionId, entity.Position);
EntityManager.Instance.GenerateDefaultEntity(Program.Instance.FocusedDimensionId, entity.Position);
}
private void StartControl()
protected void StartControl()
{
entity.PlayerControlled = true;
}
private void EndControl()
protected void EndControl()
{
entity.PlayerControlled = false;
}

View File

@@ -36,7 +36,7 @@ namespace Entity
var texture = itemResource.Icon;
if (texture.Count == 1)
{
var imageObj = Instantiate(imagePrefab.gameObject, body.transform);
var imageObj = Instantiate(Utils.GameObjectCreate.ImagePrefab.gameObject, body.transform);
imageObj.transform.localPosition = Vector3.zero;
bodyNodes[EntityState.Idle][Orientation.Down] = imageObj;
var image = imageObj.GetComponent<ImagePrefab>();
@@ -44,7 +44,7 @@ namespace Entity
}
else if (texture.Count > 1)
{
var animatorObj = Instantiate(animatorPrefab.gameObject, body.transform);
var animatorObj = Instantiate(Utils.GameObjectCreate.AnimatorPrefab.gameObject, body.transform);
animatorObj.transform.localPosition = Vector3.zero;
bodyNodes[EntityState.Idle][Orientation.Down] = animatorObj;
var animator = animatorObj.GetComponent<SpriteAnimator>();