(client) feat:实现热重载,实现多维度,实现武器,实现掉落物,实现状态UI,实现攻击AI (#44)
Co-authored-by: zzdxxz <2079238449@qq.com> Co-committed-by: zzdxxz <2079238449@qq.com>
This commit is contained in:
@ -1,12 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Data;
|
||||
using Managers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Item
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示游戏中的一个物品资源,包含了物品的各项基本属性和数据。
|
||||
/// 这是一个只读的数据结构,用于存储物品的定义信息。
|
||||
/// </summary>
|
||||
public class ItemResource
|
||||
{
|
||||
public string name;
|
||||
public string description;
|
||||
|
||||
public Sprite icon;
|
||||
/// <summary>
|
||||
/// 构造函数:通过 ItemDef 对象初始化 ItemResource。
|
||||
/// </summary>
|
||||
/// <param name="def">物品的定义对象。</param>
|
||||
/// <exception cref="ArgumentNullException">如果传入的 ItemDef 为 null,则抛出此异常。</exception>
|
||||
public ItemResource(ItemDef def)
|
||||
{
|
||||
// 参数校验:在构造函数中进行参数非空检查至关重要,避免空引用异常。
|
||||
if (def == null) throw new ArgumentNullException(nameof(def), "创建 ItemResource 时,ItemDef 不能为 null。");
|
||||
|
||||
// 从 ItemDef 对象中直接赋值 ItemResource 的所有属性。
|
||||
// 这将创建 ItemResource 的逻辑完全封装在类内部,外部调用方无需关心具体属性的提取过程。
|
||||
DefName = def.defName;
|
||||
Name = def.label;
|
||||
Description = def.description;
|
||||
Rarity = def.rarity;
|
||||
MaxStack = def.maxStack;
|
||||
IsEquippable = def.ssEquippable;
|
||||
FPS = def.FPS;
|
||||
|
||||
var sprites = new List<Sprite>();
|
||||
var packID = def.packID;
|
||||
|
||||
// 逻辑修改:添加对 def.textures 的非空检查,避免 NullReferenceException
|
||||
if (def.textures != null)
|
||||
{
|
||||
foreach (var texture in def.textures)
|
||||
{
|
||||
var spr = PackagesImageManager.Instance.GetSprite(packID, texture);
|
||||
if (spr)
|
||||
sprites.Add(spr);
|
||||
else
|
||||
// 统一日志信息,说明哪个纹理加载失败
|
||||
Debug.LogWarning(
|
||||
$"ItemResource: Failed to load sprite for texture '{texture}' for item '{def.defName}'.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 仅当 textures 为 null 时警告,因为它可能是预期的数据状态(无图标)
|
||||
Debug.LogWarning(
|
||||
$"ItemResource: ItemDef '{def.defName}' has a null textures array. No icons will be loaded.");
|
||||
}
|
||||
|
||||
// 逻辑修改:Icon 属性现在是 IReadOnlyList<Sprite> 类型,确保外部只读访问
|
||||
Icon = sprites.AsReadOnly(); // 使用 AsReadOnly() 获得一个只读包装器
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物品的定义名称,通常作为其唯一标识符。
|
||||
/// </summary>
|
||||
public string DefName { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 物品的显示名称(例如,在UI中显示的名称)。
|
||||
/// </summary>
|
||||
public string Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 物品的描述文本。
|
||||
/// </summary>
|
||||
public string Description { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 物品的图标精灵集合。提供只读访问以保持数据结构不变性。
|
||||
/// </summary>
|
||||
// 逻辑修改:Icon 属性更改为 IReadOnlyList<Sprite>
|
||||
public IReadOnlyList<Sprite> Icon { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 物品的稀有度。
|
||||
/// </summary>
|
||||
public ItemRarity Rarity { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 物品的最大堆叠数量。
|
||||
/// </summary>
|
||||
public int MaxStack { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 指示物品是否可以被装备。
|
||||
/// </summary>
|
||||
public bool IsEquippable { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 物品的帧率(如果适用)。
|
||||
/// </summary>
|
||||
public float FPS { get; protected set; }
|
||||
|
||||
public static ItemResource GetDefault()
|
||||
{
|
||||
ItemDef defaultDef = new()
|
||||
{
|
||||
defName = "default",
|
||||
label = "错误物品",
|
||||
description = "你看到这个物品表示加载出错了",
|
||||
rarity = ItemRarity.Uncommon,
|
||||
maxStack = 1,
|
||||
ssEquippable = false
|
||||
};
|
||||
return new ItemResource(defaultDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
239
Client/Assets/Scripts/Item/WeaponResource.cs
Normal file
239
Client/Assets/Scripts/Item/WeaponResource.cs
Normal file
@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq; // Added for LINQ
|
||||
using Base;
|
||||
using Data;
|
||||
using Entity;
|
||||
using Prefab;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Item
|
||||
{
|
||||
public class WeaponResource : ItemResource
|
||||
{
|
||||
public Attributes Attributes { get; private set; }
|
||||
public BulletDef Bullet { get; private set; }
|
||||
public WeaponType Type { get; private set; }
|
||||
|
||||
public DrawNodeDef AttackAnimationDef { get; private set; }
|
||||
public float AttackAnimationTime{get; private set;}
|
||||
|
||||
public float AttackDetectionTime{get;private set;}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:通过 WeaponDef 对象初始化 WeaponResource。
|
||||
/// </summary>
|
||||
/// <param name="def">武器的定义对象。</param>
|
||||
/// <exception cref="ArgumentNullException">如果传入的 WeaponDef 为 null,则抛出此异常。</exception>
|
||||
public WeaponResource(WeaponDef def)
|
||||
: base(def) // 调用基类 ItemResource 的构造函数,直接传入 WeaponDef。
|
||||
{
|
||||
// 参数校验:确保 WeaponDef 对象不为空,避免空引用异常。
|
||||
if (def == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(def), "创建 WeaponResource 时,WeaponDef 不能为 null。");
|
||||
}
|
||||
|
||||
Bullet = def.bullet;
|
||||
// 初始化武器的属性。Attributes 对象通过 WeaponDef 中的属性数据进行构建。
|
||||
Attributes = new Attributes(def.attributes);
|
||||
// 初始化武器类型,直接从 WeaponDef 定义中获取。
|
||||
Type = def.type;
|
||||
AttackDetectionTime = def.attackDetectionTime;
|
||||
|
||||
// 逻辑修改说明 1:存储 DrawNodeDef,而不是直接创建 GameObject。
|
||||
AttackAnimationDef = def.attackAnimation;
|
||||
AttackAnimationTime = AttackAnimationDef?.GetAnimationCycleDuration() ?? 0.1f;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逻辑修改说明 2:新增公共方法:根据 WeaponResource 的定义创建攻击动画的运行时 GameObject 实例。
|
||||
/// 这是 WeaponResource 的工厂方法,用于按需创建动画对象。
|
||||
/// </summary>
|
||||
/// <param name="parent">动画 GameObject 的父 Transform。</param>
|
||||
/// <returns>包含动画根 GameObject 和所有 ITick 组件的元组。</returns>
|
||||
public (GameObject root, ITick[] animationComponents) InstantiateAttackAnimation(Transform parent)
|
||||
{
|
||||
if (AttackAnimationDef == null)
|
||||
{
|
||||
Debug.LogWarning($"WeaponResource: 尝试实例化攻击动画但 _attackAnimationDef 为 null. 武器类型: {Type}");
|
||||
return (null, Array.Empty<ITick>());
|
||||
}
|
||||
|
||||
// 逻辑修改说明 4:在此处统一加载 ImagePrefab 和 SpriteAnimator,避免性能损耗。
|
||||
var imagePrefab = Resources.Load<ImagePrefab>("Prefab/Image");
|
||||
var animatorPrefab = Resources.Load<SpriteAnimator>("Prefab/Animation");
|
||||
|
||||
if (imagePrefab == null)
|
||||
{
|
||||
Debug.LogError("InstantiateAttackAnimation: 无法加载 Prefab/Image.");
|
||||
return (null, Array.Empty<ITick>());
|
||||
}
|
||||
if (animatorPrefab == null)
|
||||
{
|
||||
Debug.LogError("InstantiateAttackAnimation: 无法加载 Prefab/Animation.");
|
||||
return (null, Array.Empty<ITick>());
|
||||
}
|
||||
|
||||
// 逻辑修改说明 2:调用修改后的私有辅助方法来创建 GameObject 层次结构。
|
||||
var animationRoot = _CreateBodyPartGameObject(
|
||||
AttackAnimationDef,
|
||||
parent,
|
||||
imagePrefab,
|
||||
animatorPrefab);
|
||||
|
||||
if (animationRoot == null)
|
||||
{
|
||||
Debug.LogWarning($"WeaponResource: 创建攻击动画根节点失败. 武器类型: {Type}");
|
||||
return (null, Array.Empty<ITick>());
|
||||
}
|
||||
|
||||
// 逻辑修改说明 3:收集所有实现了 ITick 接口的组件。
|
||||
var tickComponents = animationRoot.GetComponentsInChildren<ITick>(true);
|
||||
// GetComponentsInChildren(true) 会查找包括自身在内的所有子对象上的组件,即使它们是处于非活动状态。
|
||||
|
||||
return (animationRoot, tickComponents);
|
||||
}
|
||||
|
||||
// 逻辑修改说明 4:修改 InitBodyPart 为 _CreateBodyPartGameObject,并设为 private static 方法。
|
||||
// 它现在接收 ImagePrefab 和 SpriteAnimator 作为参数,不再在内部加载。
|
||||
private static GameObject _CreateBodyPartGameObject(DrawNodeDef drawNode, Transform parent, ImagePrefab imagePrefab, SpriteAnimator animatorPrefab)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 参数验证
|
||||
if (drawNode == null)
|
||||
{
|
||||
Debug.LogWarning("CreateBodyPartGameObject: drawNode参数为null");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
// 逻辑修改说明:这里直接返回 null 更合适,因为没有父节点无法创建实例。
|
||||
Debug.LogWarning($"CreateBodyPartGameObject: 父节点为null (节点名: {drawNode.nodeName ?? "Unnamed DrawNode"})");
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject nodeObject = null;
|
||||
// 根据纹理数量创建不同类型的节点
|
||||
switch (drawNode.textures?.Count ?? 0)
|
||||
{
|
||||
case 0:
|
||||
// 无纹理节点
|
||||
nodeObject = new GameObject(drawNode.nodeName);
|
||||
nodeObject.transform.SetParent(parent.transform, false);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// 单纹理节点
|
||||
// imagePrefab 已作为参数传入,无需再次加载
|
||||
nodeObject = Object.Instantiate(imagePrefab.gameObject, parent.transform);
|
||||
nodeObject.name = drawNode.nodeName ?? "UnnamedNode"; // 提前设置名称以在警告中显示
|
||||
|
||||
var texture =
|
||||
Managers.PackagesImageManager.Instance?.GetSprite(drawNode.packID,
|
||||
drawNode.textures[0]);
|
||||
|
||||
if (!texture)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"CreateBodyPartGameObject: 无法获取纹理 (节点名: {nodeObject.name}, 纹理ID: {drawNode.textures[0]})");
|
||||
}
|
||||
|
||||
var imagePrefabCom = nodeObject.GetComponent<ImagePrefab>();
|
||||
if (imagePrefabCom != null)
|
||||
{
|
||||
imagePrefabCom.SetSprite(texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"CreateBodyPartGameObject: 无法获取ImagePrefab组件 (节点名: {nodeObject.name})");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// 多纹理动画节点
|
||||
// animatorPrefab 已作为参数传入,无需再次加载
|
||||
nodeObject = Object.Instantiate(animatorPrefab.gameObject, parent.transform);
|
||||
nodeObject.name = drawNode.nodeName ?? "UnnamedNode"; // 提前设置名称以在警告中显示
|
||||
|
||||
var animator = nodeObject.GetComponent<SpriteAnimator>();
|
||||
|
||||
if (animator == null)
|
||||
{
|
||||
Debug.LogWarning($"CreateBodyPartGameObject: 无法获取SpriteAnimator组件 (节点名: {nodeObject.name})");
|
||||
break;
|
||||
}
|
||||
|
||||
animator.SetFPS(drawNode.FPS);
|
||||
var animatedSprites = new List<Sprite>();
|
||||
foreach (var textureId in drawNode.textures)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sprite =
|
||||
Managers.PackagesImageManager.Instance?.GetSprite(drawNode.packID, textureId);
|
||||
if (sprite != null)
|
||||
{
|
||||
animatedSprites.Add(sprite);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"CreateBodyPartGameObject: 无法获取动画纹理 (节点名: {nodeObject.name}, 纹理ID: {textureId})");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"CreateBodyPartGameObject: 加载动画纹理时出错 (节点名: {nodeObject.name}, 纹理ID: {textureId}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (animatedSprites.Count > 0)
|
||||
{
|
||||
animator.SetSprites(animatedSprites.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"CreateBodyPartGameObject: 没有有效的动画纹理 (节点名: {nodeObject.name})");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// 设置节点属性 (对于case 1, default 已经在上面设置了名称,这里可以作为 fallback 或统一处理)
|
||||
if (nodeObject == null) return null; // 如果nodeObject在上面某些分支没有成功创建,提前返回
|
||||
|
||||
nodeObject.transform.localPosition = drawNode.position;
|
||||
// nodeObject.name = drawNode.nodeName ?? "UnnamedNode"; // 不再需要,已在上面处理
|
||||
|
||||
// 递归初始化子节点
|
||||
if (drawNode.nodes == null) return nodeObject;
|
||||
foreach (var child in drawNode.nodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 逻辑修改说明 4:递归调用时,传递 prefab 参数。
|
||||
_CreateBodyPartGameObject(child, nodeObject.transform, imagePrefab, animatorPrefab);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"CreateBodyPartGameObject: 初始化子节点失败 (父节点: {nodeObject.name ?? drawNode.nodeName ?? "Unnamed Parent"}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return nodeObject;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"CreateBodyPartGameObject: 初始化节点时发生未处理的异常 (节点名: {drawNode?.nodeName ?? "Unknown DrawNode"}): {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Item/WeaponResource.cs.meta
Normal file
3
Client/Assets/Scripts/Item/WeaponResource.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa734e2254034fa4a59fb1e0cdea250f
|
||||
timeCreated: 1756193577
|
Reference in New Issue
Block a user