2026-04-29 01:18:39 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 文件:MonsterContral.cs
|
|
|
|
|
|
// 职责:鱼(怪物)实体基类 —— 状态机、路径移动、动画、死亡处理
|
|
|
|
|
|
// 优化点:
|
|
|
|
|
|
// 1. 方向朝向逻辑提取为独立方法 ApplyFacingDirection,消除大量重复 switch
|
|
|
|
|
|
// 2. OnMoveUpdata 按 isyz 分支,各分支内复用 ApplyFacingDirection
|
|
|
|
|
|
// 3. OnDestroy 统一清理三个字典,去除重复判断
|
|
|
|
|
|
// 4. 常量命名使用 PascalCase,符合 C# 规范
|
|
|
|
|
|
// 5. 补充 XML 注释,明确方法职责
|
|
|
|
|
|
// 6. forwordXYZ 空检查改用 string.IsNullOrEmpty
|
|
|
|
|
|
// ============================================================
|
2026-03-31 18:30:49 +08:00
|
|
|
|
|
|
|
|
|
|
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
using Assets.MyGame.Scripts.Net.APIResponses.Enum;
|
2026-04-24 22:51:34 +08:00
|
|
|
|
using Assets.MyGame.Scripts.Net.WebResponses;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
using Spine.Unity;
|
2026-05-06 01:34:23 +08:00
|
|
|
|
using System;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
2026-05-06 01:34:23 +08:00
|
|
|
|
using TMPro;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using static MonsterMoveLine;
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>
|
2026-08-04 14:52:22 +08:00
|
|
|
|
/// 鱼实体基类:封装状态机(移动/待机)、贝塞尔曲线路径跟随、动画播放、死亡逻辑。
|
|
|
|
|
|
/// 具体鱼类型(Boss等)通过继承重写虚方法扩展行为。
|
|
|
|
|
|
/// </summary>
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public class MonsterContral : MonoBehaviour
|
|
|
|
|
|
{
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
// 常量
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>鱼阵路线 key 前缀</summary>
|
2026-06-13 22:34:43 +08:00
|
|
|
|
private const string YzMoveLineName = "鱼阵";
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>普通鱼路线 key 前缀</summary>
|
2026-06-13 22:34:43 +08:00
|
|
|
|
private const string NormalMoveLineName = "曲线";
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
// 公开字段(Inspector 可见 / 外部赋值)
|
|
|
|
|
|
// ----------------------------------------------------------------
|
2026-05-18 18:39:11 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 基础倍率
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public int beilv;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>本鱼唯一标识(服务器下发的 GUID)</summary>
|
2026-03-09 09:25:01 +08:00
|
|
|
|
public string guid;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
2026-08-01 00:49:49 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// ⭐ 是否已完成 Init(路线/guid/moveTime 已赋值)。
|
|
|
|
|
|
/// false=池回收后未初始化,OnEnable 不应执行 OnMoveUpdata,避免旧 moveTime/moveLine 误触发销毁
|
|
|
|
|
|
/// true=Init 已调用,可以正常 Update
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected bool isInitialized = false;
|
|
|
|
|
|
|
2026-07-20 16:30:28 +08:00
|
|
|
|
/// <summary>当前鱼使用的路线 ID(Init 时保存,用于房主把活鱼广播给新加入玩家)</summary>
|
|
|
|
|
|
public string lastMoveLineId;
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>运行时数据(血量、路径、配置等)</summary>
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public MonsterDataBase monsterData = new MonsterDataBase(new MonsterHealthBase());
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>Spine 骨骼动画组件(非 Spine 鱼可为 null)</summary>
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public SkeletonAnimation skeleton;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>是否使用 Spine 骨骼动画</summary>
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public bool isSpine = true;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>鱼类型 ID(对应 FishResourcesData 索引)</summary>
|
2026-04-28 01:53:41 +08:00
|
|
|
|
public int fishTypeID;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>动画状态 → 动画名称 映射</summary>
|
|
|
|
|
|
public Dictionary<CharacterAnimationType, string> dic_AnimKeyAnimName
|
|
|
|
|
|
= new Dictionary<CharacterAnimationType, string>();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>当前状态</summary>
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public CharacterStateType currState;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>血量(目前未使用扣血逻辑,保留供扩展)</summary>
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public int HP = 5;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>死亡粒子特效 Prefab(为 null 时使用 Manager 默认值)</summary>
|
|
|
|
|
|
public GameObject effect_Die;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>死亡金币奖励特效 Prefab</summary>
|
2026-04-23 17:24:31 +08:00
|
|
|
|
public GameObject effect_DieAward;
|
2026-05-06 01:34:23 +08:00
|
|
|
|
public GameObject currEffect_DieAward;
|
|
|
|
|
|
public Action<GameObject> action_effect_DieAward;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>是否处于移动状态(false 时 Update 跳过移动计算)</summary>
|
2026-02-09 10:44:38 +08:00
|
|
|
|
public bool isMove = true;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
2026-06-16 19:12:37 +08:00
|
|
|
|
/// <summary>设置冰冻特效激活状态</summary>
|
|
|
|
|
|
public void SetFreezeEffect(bool active)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (bingDongEffect != null)
|
|
|
|
|
|
bingDongEffect.SetActive(active);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>是否为 3D 鱼(影响朝向轴选取)</summary>
|
2026-03-12 10:48:53 +08:00
|
|
|
|
public bool is3D = false;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 前进方向轴,可选 "X" / "-X" / "Y" / "-Y" / "Z" / "-Z"。
|
|
|
|
|
|
/// 为空时默认使用 up(2D)或 forward(3D)。
|
2026-08-04 14:52:22 +08:00
|
|
|
|
/// ⭐ 运行时不应修改;如需修改请调用 RefreshFacingAxis() 重新解析。
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// </summary>
|
2026-03-18 17:26:14 +08:00
|
|
|
|
public string forwordXYZ;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// forwordXYZ 预解析后的朝向轴枚举(OnEnable 时解析)。
|
|
|
|
|
|
/// 避免 ApplyFacingDirection 每帧每鱼做 string.IsNullOrEmpty + switch(string)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private FacingAxis _facingAxis = FacingAxis.None;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>朝向轴枚举:对应 forwordXYZ 的 7 种取值</summary>
|
|
|
|
|
|
private enum FacingAxis
|
|
|
|
|
|
{
|
|
|
|
|
|
None, // "" / null
|
|
|
|
|
|
X,
|
|
|
|
|
|
NegX, // "-X"
|
|
|
|
|
|
Y,
|
|
|
|
|
|
NegY, // "-Y"
|
|
|
|
|
|
Z,
|
|
|
|
|
|
NegZ // "-Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将 forwordXYZ 字符串解析为枚举。OnEnable 调用一次,运行时直接用枚举 switch。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void RefreshFacingAxis()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(forwordXYZ))
|
|
|
|
|
|
{
|
|
|
|
|
|
_facingAxis = FacingAxis.None;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
switch (forwordXYZ)
|
|
|
|
|
|
{
|
|
|
|
|
|
case "X": _facingAxis = FacingAxis.X; break;
|
|
|
|
|
|
case "-X": _facingAxis = FacingAxis.NegX; break;
|
|
|
|
|
|
case "Y": _facingAxis = FacingAxis.Y; break;
|
|
|
|
|
|
case "-Y": _facingAxis = FacingAxis.NegY; break;
|
|
|
|
|
|
case "Z": _facingAxis = FacingAxis.Z; break;
|
|
|
|
|
|
case "-Z": _facingAxis = FacingAxis.NegZ; break;
|
|
|
|
|
|
default: _facingAxis = FacingAxis.None; break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 缓存 yzID 前3字符前缀。Init 时调用一次(普通鱼在 base.Init,Boss 在自己 Init)。
|
|
|
|
|
|
/// 避免 UpdateYZMovement 每帧每鱼 yzID.Substring(0,3) 产生字符串 GC 分配。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected void SetYzPrefix(string id)
|
|
|
|
|
|
{
|
|
|
|
|
|
_yzPrefix = (!string.IsNullOrEmpty(id) && id.Length >= 3) ? id.Substring(0, 3) : "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>鱼类型枚举(缓存,避免反复类型转换)</summary>
|
2026-04-25 00:12:55 +08:00
|
|
|
|
public FishTypeEnum fishenum;
|
2026-02-09 10:44:38 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>路径已行进的时间(秒),用于贝塞尔曲线插值;可由外部预设偏移实现同步追帧。</summary>
|
|
|
|
|
|
public float moveTime = 0f;
|
2026-02-09 10:44:38 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>是否为鱼阵成员</summary>
|
|
|
|
|
|
public bool isyz = false;
|
2026-02-09 10:44:38 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>所属鱼阵 ID(空字符串表示普通鱼)</summary>
|
|
|
|
|
|
public string yzID = "";
|
2026-04-25 22:41:38 +08:00
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
// ⭐ 缓存优化字段
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>缓存的子物体(朝向控制用),避免每帧 transform.GetChild(0)</summary>
|
|
|
|
|
|
private Transform _cachedChild;
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
// ⭐ 鱼阵 700/701 直线移动缓存
|
|
|
|
|
|
// 同一鱼阵起点相同,Init 时一次性算好世界坐标 + 方向系数,
|
|
|
|
|
|
// UpdateYZMovement 每帧直接用,避免每帧每鱼 CanvasToWorld + 条件判断。
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
private bool _isYzLineMove; // 是否为 700/701 直线阵
|
|
|
|
|
|
private Vector3 _yzStartWorld; // 直线阵起点世界坐标
|
|
|
|
|
|
private float _yzLineDir; // 直线阵方向系数 (-1 向上 / +1 向下)
|
|
|
|
|
|
private bool _yzLineInited; // 直线阵缓存是否已初始化
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// yzID 前3字符前缀缓存(Init 时 Substring 一次)。
|
|
|
|
|
|
/// 避免 UpdateYZMovement 每帧每鱼 yzID.Substring(0,3) 产生字符串 GC 分配。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private string _yzPrefix = "";
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
// 私有字段
|
|
|
|
|
|
// ----------------------------------------------------------------
|
2026-04-25 22:41:38 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>受击闪烁特效对象(在子物体中查找 "HitEffect")</summary>
|
|
|
|
|
|
private GameObject hurt;
|
2026-04-24 22:51:34 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>UI 根节点 RectTransform(坐标转换用)</summary>
|
|
|
|
|
|
protected RectTransform uirootRect;
|
2026-04-24 22:51:34 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>主相机缓存</summary>
|
|
|
|
|
|
protected Camera mainCamera;
|
|
|
|
|
|
|
2026-05-27 19:47:06 +08:00
|
|
|
|
public List<Transform> colliderList;
|
2026-06-14 19:07:11 +08:00
|
|
|
|
/// <summary>锁定点(colliderList 为空时使用,null 则用自身坐标)</summary>
|
|
|
|
|
|
public Transform lockPoint;
|
2026-07-31 15:58:50 +08:00
|
|
|
|
/// <summary>手动锁定时记录被点击的碰撞器,用于锁定锚点优先级(运行时赋值,不序列化)</summary>
|
|
|
|
|
|
[System.NonSerialized]
|
|
|
|
|
|
public Transform hitCollider;
|
2026-06-16 19:12:37 +08:00
|
|
|
|
/// <summary>冰冻特效(预制体内子物体,null 则跳过)</summary>
|
|
|
|
|
|
public GameObject bingDongEffect;
|
2026-05-27 19:47:06 +08:00
|
|
|
|
|
2026-07-31 17:31:51 +08:00
|
|
|
|
[Header("锁定控制(可选)")]
|
|
|
|
|
|
[Tooltip("是否需要锁定事件控制。默认false=一直可锁定;true=出场/切换状态期间不可锁定,需调用 NotifyLockable() 后才可锁定")]
|
|
|
|
|
|
public bool needLockEvent = false;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前是否可锁定。
|
|
|
|
|
|
/// - needLockEvent=false → 一直 true
|
|
|
|
|
|
/// - needLockEvent=true → 初始 false,等 NotifyLockable() 调用后变 true
|
|
|
|
|
|
/// ⭐ 用于嫦娥(出场→移动两步)和小白龙(多动画片段切换)等特殊鱼
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[System.NonSerialized]
|
|
|
|
|
|
public bool isLockable = true;
|
|
|
|
|
|
/// <summary>可锁定事件:needLockEvent=true 的鱼首次可锁定时触发,GameMainPanel 订阅用于自动锁定</summary>
|
|
|
|
|
|
public event Action<MonsterContral> OnLockable;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通知此鱼现在可以锁定了。needLockEvent=true 的鱼在出场结束/状态切换完成时调用。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void NotifyLockable()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isLockable)
|
|
|
|
|
|
{
|
|
|
|
|
|
isLockable = true;
|
|
|
|
|
|
OnLockable?.Invoke(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 切换锁定点(不切换锁定对象)。
|
|
|
|
|
|
/// ⭐ 用于小白龙等切换模型/动画片段后锁定点也随之变化的情况:
|
|
|
|
|
|
/// 重新从 colliderList 找第一个激活的碰撞器,更新为新的锁定锚点。
|
|
|
|
|
|
/// 如果当前没有被锁定则不做任何事。
|
|
|
|
|
|
/// 调用方(GameMainPanel_FishBase)负责更新 monCotl_Lock_Position。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public Transform SwitchLockPoint()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 优先用 lockPoint(若激活)
|
|
|
|
|
|
if (lockPoint != null && lockPoint.gameObject.activeInHierarchy)
|
|
|
|
|
|
return lockPoint;
|
|
|
|
|
|
// 其次 colliderList 第一个激活的
|
|
|
|
|
|
if (colliderList != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < colliderList.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var col = colliderList[i];
|
|
|
|
|
|
if (col != null && col.gameObject.activeInHierarchy)
|
|
|
|
|
|
return col;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-27 23:30:25 +08:00
|
|
|
|
[Header("创建视频(可选)")]
|
|
|
|
|
|
[Tooltip("鱼创建时播放的入场视频片段,播放结束后才开始移动。为空则无入场视频。播放控件由 GameMainPanel_FishBase 提供")]
|
|
|
|
|
|
public UnityEngine.Video.VideoClip createVideoClip;
|
|
|
|
|
|
/// <summary>createVideoClip 实际播放时长(秒),用于计算 moveTime 偏移。<=0 表示未播放/无视频</summary>
|
|
|
|
|
|
protected float _createVideoDuration = 0f;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>当前是否正在播放创建视频(由外部播放器 isPlaying 判断)</summary>
|
|
|
|
|
|
protected bool _isPlayingCreateVideo = false;
|
|
|
|
|
|
|
2026-06-03 18:27:45 +08:00
|
|
|
|
/// <summary>是否存活(活跃且未被回收)</summary>
|
|
|
|
|
|
public bool IsAlive => gameObject.activeInHierarchy;
|
|
|
|
|
|
|
2026-04-29 23:53:55 +08:00
|
|
|
|
|
2026-06-13 22:34:43 +08:00
|
|
|
|
public int rotationPY = 60;
|
|
|
|
|
|
|
2026-05-06 01:34:23 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// ================================================================
|
|
|
|
|
|
// Unity 生命周期
|
|
|
|
|
|
// ================================================================
|
2026-07-25 16:13:21 +08:00
|
|
|
|
public bool baseMmove;
|
2026-04-03 09:28:13 +08:00
|
|
|
|
public virtual void Awake()
|
2026-05-01 21:59:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
LoadHitEffect();
|
2026-07-25 16:13:21 +08:00
|
|
|
|
baseMmove = isMove;
|
2026-05-01 21:59:16 +08:00
|
|
|
|
}
|
2026-08-16 00:38:45 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查找受击特效子物体并强制隐藏。
|
|
|
|
|
|
/// 原为 async void(无 await 实际同步执行),现改为普通方法;
|
|
|
|
|
|
/// Awake 和每次 OnEnable 都会调用,保证池回收后不残留激活状态。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void LoadHitEffect()
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// 查找受击特效子物体并默认隐藏
|
|
|
|
|
|
var hitTra = FindDeepChild(transform, "HitEffect");
|
|
|
|
|
|
if (hitTra != null)
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-04-29 01:18:39 +08:00
|
|
|
|
hurt = hitTra.gameObject;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
hurt.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-29 23:53:55 +08:00
|
|
|
|
public virtual void OnEnable()
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-08-16 00:38:45 +08:00
|
|
|
|
// ⭐ 每次 OnEnable 重新查找并强制隐藏受击特效。
|
|
|
|
|
|
// 根因:HurtCoroutine 激活 hurt 后等 0.2s 再关闭,若鱼在这期间被
|
|
|
|
|
|
// 击杀回收(GO 停用 → 协程中断),hurt 会残留激活状态;
|
|
|
|
|
|
// 而 LoadHitEffect 原来只在 Awake 调用一次(池化对象不重跑 Awake),
|
|
|
|
|
|
// 重新生成时不会恢复 → 鱼带着受击特效出场。
|
|
|
|
|
|
// 另外池回收的鱼 transform 可能更换子层级,重新查找更稳妥。
|
|
|
|
|
|
LoadHitEffect();
|
|
|
|
|
|
|
2026-07-25 16:13:21 +08:00
|
|
|
|
// ⭐ 用 baseMmove 恢复 isMove:Awake 时记录预制体原始 isMove 状态。
|
|
|
|
|
|
// - 普通鱼预制体 isMove=true → 池回收后恢复 true,正常游动
|
|
|
|
|
|
// (之前被注释导致:冰冻时 FreezeAllFish 设 isMove=false → 鱼回收 →
|
|
|
|
|
|
// 重新生成后残留 false → 鱼永远不动)
|
|
|
|
|
|
// - 爆奖 Boss 预制体 isMove=false → 恢复 false,固定在 (0,0,0) 播放动画
|
|
|
|
|
|
// (金龙/雷龙/玉龙等不走路径,直到爆奖结束销毁)
|
|
|
|
|
|
// - 冰冻中重新激活的鱼,末尾会覆盖为 false,安全
|
|
|
|
|
|
isMove = baseMmove;
|
2026-07-31 17:31:51 +08:00
|
|
|
|
// ⭐ 重置可锁定状态:needLockEvent=true 的鱼每次重新生成都需等 NotifyLockable()
|
|
|
|
|
|
// needLockEvent=false 的鱼一直可锁定
|
|
|
|
|
|
isLockable = !needLockEvent;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
fishenum = (FishTypeEnum)monsterData.fishTypeConfig.fishType;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
uirootRect = UIManager.Instance.uiRoot.GetComponent<RectTransform>();
|
|
|
|
|
|
mainCamera = Camera.main;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
moveTime = 0f;
|
2026-07-27 23:30:25 +08:00
|
|
|
|
// ⭐ 重置创建视频状态:池回收的鱼 _isPlayingCreateVideo 可能残留 true,
|
|
|
|
|
|
// 重新生成时重置,由 Init 决定是否重新播放
|
|
|
|
|
|
_createVideoDuration = 0f;
|
|
|
|
|
|
_isPlayingCreateVideo = false;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
InitAnimList();
|
2026-08-01 00:49:49 +08:00
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 预解析 forwordXYZ 为枚举,避免 ApplyFacingDirection 每帧每鱼 string switch
|
|
|
|
|
|
RefreshFacingAxis();
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 同步缓存到 moveLine,避免 GetBezierPointJob 内每帧每鱼重复查询
|
|
|
|
|
|
// UIManager.Instance.uiRoot.GetComponent<RectTransform>() 和 Camera.main
|
|
|
|
|
|
monsterData.moveLine.cachedCamera = mainCamera;
|
|
|
|
|
|
monsterData.moveLine.cachedUirootRect = uirootRect;
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 缓存子物体引用(朝向控制用),避免 ApplyFacingDirection 每帧 transform.GetChild(0)
|
|
|
|
|
|
// 池回收的鱼 transform 可能更换子层级,每次 OnEnable 重新缓存
|
|
|
|
|
|
if (transform.childCount > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_cachedChild = transform.GetChild(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_cachedChild = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 14:45:43 +08:00
|
|
|
|
// ⭐ 2D鱼:重置渲染器缓存(池回收后子层级可能变化)并重新应用sortingOrder层级
|
|
|
|
|
|
_cached2DRenderers = null;
|
|
|
|
|
|
_appliedSortOrder = int.MinValue;
|
|
|
|
|
|
Apply2DOrder();
|
|
|
|
|
|
|
2026-08-01 00:49:49 +08:00
|
|
|
|
// ⭐ 池回收的鱼(isInitialized=false)不立即 ChangeState(move),
|
|
|
|
|
|
// 避免 OnMoveUpdata 用旧 moveTime/moveLine 误触发 Id_DieFish_NotFX
|
|
|
|
|
|
// Init 调用后会设置 isInitialized=true 并 ChangeState(move)
|
|
|
|
|
|
if (isInitialized)
|
|
|
|
|
|
{
|
|
|
|
|
|
ChangeState(CharacterStateType.move);
|
|
|
|
|
|
}
|
2026-07-24 23:47:50 +08:00
|
|
|
|
|
2026-07-25 16:13:21 +08:00
|
|
|
|
// ⭐ 同步冰冻特效状态:池回收的鱼 bingDongEffect 可能残留激活状态,
|
2026-07-24 23:47:50 +08:00
|
|
|
|
// 重新生成时会显示白色冰冻蒙皮。根据当前是否冰冻中决定显隐
|
|
|
|
|
|
bool frozen = CreateFishManager.Instance != null && CreateFishManager.Instance.isFrozen;
|
|
|
|
|
|
SetFreezeEffect(frozen);
|
|
|
|
|
|
if (frozen)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 冰冻中重新生成的鱼也要被冻结
|
|
|
|
|
|
isMove = false;
|
|
|
|
|
|
ChangeState(CharacterStateType.idle);
|
|
|
|
|
|
}
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
private void Update()
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-04-29 01:18:39 +08:00
|
|
|
|
OnUpdate(currState);
|
|
|
|
|
|
}
|
2026-06-13 22:34:43 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 销毁时自动从 CreateFishManager 的三个字典中移除自身,避免野指针。
|
|
|
|
|
|
/// </summary>
|
2026-04-29 23:53:55 +08:00
|
|
|
|
public virtual void OnDisable()
|
2026-04-29 01:18:39 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (CreateFishManager.Instance == null) return;
|
|
|
|
|
|
|
2026-08-01 00:49:49 +08:00
|
|
|
|
// ⭐ 重置初始化标志:鱼被回收后需要重新 Init 才能正常 Update
|
|
|
|
|
|
isInitialized = false;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>注册动画名称(子类可 override 扩展)。</summary>
|
|
|
|
|
|
public void InitAnimList()
|
|
|
|
|
|
{
|
|
|
|
|
|
dic_AnimKeyAnimName[CharacterAnimationType.move] = "stand2";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化路径并启动自动销毁计时。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="pathId">路线编号(与路线 key 前缀拼接)</param>
|
|
|
|
|
|
/// <param name="time">路径持续时间(秒)</param>
|
|
|
|
|
|
/// <param name="isyuzhen">是否为鱼阵成员</param>
|
|
|
|
|
|
/// <param name="yzID">鱼阵 ID(isyuzhen==true 时有效)</param>
|
|
|
|
|
|
public virtual void Init(string pathId, float time, bool isyuzhen = false, string yzID = "")
|
|
|
|
|
|
{
|
2026-07-20 16:30:28 +08:00
|
|
|
|
// ⭐ 保存 pathId,用于房主把活鱼广播给新加入玩家
|
|
|
|
|
|
lastMoveLineId = pathId;
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// 根据是否为鱼阵选择路线 key
|
|
|
|
|
|
string lineKey = isyuzhen
|
2026-06-13 22:34:43 +08:00
|
|
|
|
? YzMoveLineName + pathId
|
|
|
|
|
|
: NormalMoveLineName + pathId;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
if (!MonsterMoveLine.dic_BezierCurveDatas.TryGetValue(lineKey, out var bezierData))
|
2026-02-09 10:44:38 +08:00
|
|
|
|
{
|
2026-04-29 01:18:39 +08:00
|
|
|
|
Debug.LogError($"[MonsterContral] 找不到路线: {lineKey}");
|
2026-02-09 10:44:38 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
monsterData.moveLine.Init(bezierData, time);
|
2026-06-13 22:34:43 +08:00
|
|
|
|
isyz = isyuzhen;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
this.yzID = yzID;
|
|
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 缓存 yzID 前3字符前缀,避免 UpdateYZMovement 每帧每鱼 Substring 产生 GC
|
|
|
|
|
|
SetYzPrefix(yzID);
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// 鱼阵特殊处理:700 系扇形阵根据起点 y 值翻转朝向
|
2026-08-04 14:52:22 +08:00
|
|
|
|
if (isyuzhen && _yzPrefix == "700")
|
2026-02-03 11:35:19 +08:00
|
|
|
|
{
|
2026-04-29 01:18:39 +08:00
|
|
|
|
if (bezierData.start.y < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var scale = transform.localScale;
|
|
|
|
|
|
scale.y *= -1f;
|
|
|
|
|
|
transform.localScale = scale;
|
|
|
|
|
|
}
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 预计算 700/701 直线阵的世界起点 + 方向系数
|
|
|
|
|
|
// 原逻辑每帧每鱼 CanvasToWorld(start) + 判断 goUp,现 Init 一次性算好
|
|
|
|
|
|
CacheYzLineMoveData(bezierData);
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
fishenum = (FishTypeEnum)monsterData.fishTypeConfig.fishType;
|
|
|
|
|
|
|
2026-06-18 19:33:39 +08:00
|
|
|
|
// isMove=false 的鱼不需要移动路线,直接归位到 (0,0,0)
|
|
|
|
|
|
if (!isMove)
|
|
|
|
|
|
transform.position = Vector3.zero;
|
|
|
|
|
|
|
2026-07-27 23:30:25 +08:00
|
|
|
|
// ⭐ 创建视频启动逻辑由子类负责:
|
|
|
|
|
|
// - MonsterControl_BossBase 在自己 Init 里启动 createVideo
|
|
|
|
|
|
// - 普通鱼走父类 Init,但普通鱼一般不配 createVideo
|
|
|
|
|
|
// OnMoveUpdata 会检测 createVideo.isPlaying,视频播放期间不移动
|
|
|
|
|
|
|
2026-08-01 00:49:49 +08:00
|
|
|
|
// ⭐ 标记已初始化,并启动 move 状态(OnEnable 里 isInitialized=false 时不会 ChangeState)
|
|
|
|
|
|
isInitialized = true;
|
|
|
|
|
|
ChangeState(CharacterStateType.move);
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// 路径结束后自动销毁
|
2026-06-15 01:32:36 +08:00
|
|
|
|
//StartCoroutine(yanchi(gameObject, time, isyuzhen));
|
2026-05-01 06:26:50 +08:00
|
|
|
|
}
|
2026-06-13 22:34:43 +08:00
|
|
|
|
public IEnumerator yanchi(GameObject obj, float time, bool isyuzhen)
|
2026-05-01 06:26:50 +08:00
|
|
|
|
{
|
2026-06-13 22:34:43 +08:00
|
|
|
|
|
2026-05-01 06:26:50 +08:00
|
|
|
|
yield return new WaitForSeconds(time);
|
2026-05-01 21:59:16 +08:00
|
|
|
|
CreateFishManager.Instance.Id_DieFish_NotFX(guid);
|
2026-05-22 19:30:02 +08:00
|
|
|
|
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 状态机
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>切换到新状态(先触发旧状态 Exit,再触发新状态 Enter)。</summary>
|
2026-02-03 11:35:19 +08:00
|
|
|
|
public void ChangeState(CharacterStateType stateType)
|
|
|
|
|
|
{
|
|
|
|
|
|
OnExit(currState);
|
|
|
|
|
|
currState = stateType;
|
|
|
|
|
|
OnEnter(currState);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
public void OnEnter(CharacterStateType stateType)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (stateType)
|
|
|
|
|
|
{
|
2026-06-13 22:34:43 +08:00
|
|
|
|
case CharacterStateType.move: OnMoveEnter(); break;
|
|
|
|
|
|
case CharacterStateType.idle: break;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-03 11:35:19 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
public void OnUpdate(CharacterStateType stateType)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isMove) return;
|
2026-08-01 00:49:49 +08:00
|
|
|
|
// ⭐ 池回收的鱼(isInitialized=false)不执行移动逻辑,
|
|
|
|
|
|
// 避免 currState 残留为 move 时用旧 guid/moveLine 误触发 Id_DieFish_NotFX
|
|
|
|
|
|
// Init 调用后会设置 isInitialized=true
|
|
|
|
|
|
if (!isInitialized) return;
|
2026-02-03 11:35:19 +08:00
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
switch (stateType)
|
|
|
|
|
|
{
|
2026-06-13 22:34:43 +08:00
|
|
|
|
case CharacterStateType.move: OnMoveUpdata(); break;
|
|
|
|
|
|
case CharacterStateType.idle: break;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnExit(CharacterStateType stateType)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 当前无需处理,保留供子类 override
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 移动逻辑
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
|
|
|
|
|
|
public virtual void OnMoveEnter()
|
|
|
|
|
|
{
|
|
|
|
|
|
PlayAnim(CharacterAnimationType.move);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public virtual void OnMoveExit() { }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 每帧更新鱼的位置(贝塞尔曲线插值)和朝向。
|
|
|
|
|
|
/// 鱼阵(isyz==true)按阵型 ID 前缀分支,普通鱼走统一逻辑。
|
2026-08-04 14:52:22 +08:00
|
|
|
|
/// ⭐ 性能优化(零 GC + 缓存):
|
|
|
|
|
|
/// - 贝塞尔求值:静态 Vector2[] 缓存 + ThreadStatic 临时缓冲,零 GC 分配
|
|
|
|
|
|
/// - 朝向:forwordXYZ 预解析为枚举,避免每帧 string switch
|
|
|
|
|
|
/// - 子物体:OnEnable 缓存 _cachedChild,避免每帧 transform.GetChild(0)
|
|
|
|
|
|
/// - 鱼阵 700/701:Init 预计算起点世界坐标 + 方向系数
|
|
|
|
|
|
/// - yzID 前缀:Init 缓存 _yzPrefix,避免每帧 Substring
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual void OnMoveUpdata()
|
|
|
|
|
|
{
|
2026-06-15 21:51:42 +08:00
|
|
|
|
if (!isMove) return; // 冰冻中不计时
|
2026-07-27 23:30:25 +08:00
|
|
|
|
|
|
|
|
|
|
// ⭐ 创建视频播放期间不移动、不累计 moveTime
|
|
|
|
|
|
// 视频结束后才开始走路线;视频时长不计入 moveTime(单独累计到 _createVideoDuration)
|
|
|
|
|
|
// 隐藏控件由 loopPointReached 回调负责(OnCreateVideoFinished)
|
|
|
|
|
|
if (_isPlayingCreateVideo)
|
|
|
|
|
|
{
|
|
|
|
|
|
_createVideoDuration += Time.deltaTime;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
moveTime += Time.deltaTime;
|
|
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 终点检测:每帧执行(轻量)
|
|
|
|
|
|
if (moveTime >= monsterData.moveLine.moveTime)
|
|
|
|
|
|
{
|
|
|
|
|
|
// ⭐ 路径走完后立即停止移动,防止 Id_DieFish_NotFX 每帧重复触发
|
|
|
|
|
|
// (野鱼 guid 已被覆盖时,Id_DieFish_NotFX 会 NOT_FOUND,但 moveTime 一直 >= moveTime,
|
|
|
|
|
|
// 不停止的话每帧都会调用,产生海量 NOT_FOUND 日志)
|
|
|
|
|
|
isMove = false;
|
|
|
|
|
|
CreateFishManager.Instance.Id_DieFish_NotFX(guid);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 每帧执行重计算(已无 GC 压力,分帧会导致鱼体一卡一卡)
|
2026-04-29 01:18:39 +08:00
|
|
|
|
if (isyz)
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateYZMovement();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateNormalMovement();
|
|
|
|
|
|
}
|
2026-08-04 14:52:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 预计算 700/701 直线阵的世界起点 + 方向系数。
|
|
|
|
|
|
/// ⭐ 优化:原 UpdateYZMovement 每帧每鱼 CanvasToWorld(start) + 判断 goUp,
|
|
|
|
|
|
/// 现在 Init 一次性算好,运行时直接用缓存值。
|
|
|
|
|
|
/// 非直线阵(702/704/710 等)标记 _isYzLineMove=false 跳过。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void CacheYzLineMoveData(MonsterMoveLine.BezierCurveData bezierData)
|
|
|
|
|
|
{
|
|
|
|
|
|
_isYzLineMove = false;
|
|
|
|
|
|
_yzLineInited = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (!isyz || string.IsNullOrEmpty(_yzPrefix)) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (_yzPrefix != "700" && _yzPrefix != "701") return;
|
|
|
|
|
|
|
|
|
|
|
|
// 世界起点(CanvasToWorld 已在 OnEnable 缓存 uirootRect/mainCamera)
|
|
|
|
|
|
_yzStartWorld = CoordinateTransformation.CanvasToWorld(
|
|
|
|
|
|
bezierData.start, uirootRect, mainCamera);
|
|
|
|
|
|
|
|
|
|
|
|
// 700:根据起点 y 值决定正方向;701:固定向上(dir=-1)
|
|
|
|
|
|
bool goUp = (_yzPrefix == "701") || (bezierData.start.y > 0);
|
|
|
|
|
|
_yzLineDir = goUp ? -1f : 1f;
|
|
|
|
|
|
|
|
|
|
|
|
_isYzLineMove = true;
|
|
|
|
|
|
_yzLineInited = true;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>鱼阵移动:按 yzID 前缀选择运动方程。</summary>
|
|
|
|
|
|
private void UpdateYZMovement()
|
|
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 700/701 直线阵:使用预计算缓存,零 CanvasToWorld 调用
|
|
|
|
|
|
if (_isYzLineMove && _yzLineInited)
|
2026-04-29 01:18:39 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
Vector2 pos = _yzStartWorld + (Vector3)(_yzLineDir * transform.up * moveTime
|
|
|
|
|
|
* monsterData.moveLine.moveTime);
|
|
|
|
|
|
transform.position = pos;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 用 Init 时缓存的 _yzPrefix,避免每帧每鱼 yzID.Substring(0,3) GC 分配
|
|
|
|
|
|
if (string.IsNullOrEmpty(_yzPrefix)) return;
|
|
|
|
|
|
|
|
|
|
|
|
switch (_yzPrefix)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 700 / 701:直线向前移动(沿 up 方向)—— 兜底分支,正常走缓存路径
|
2026-04-29 01:18:39 +08:00
|
|
|
|
case "700":
|
|
|
|
|
|
case "701":
|
2026-06-13 22:34:43 +08:00
|
|
|
|
{
|
|
|
|
|
|
var startWorld = CoordinateTransformation.CanvasToWorld(
|
|
|
|
|
|
monsterData.moveLine.bezierCurveData.start, uirootRect, mainCamera);
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
2026-06-13 22:34:43 +08:00
|
|
|
|
// 700:根据起点 y 值决定正方向;701:固定向上
|
2026-08-04 14:52:22 +08:00
|
|
|
|
bool goUp = (_yzPrefix == "701") || (monsterData.moveLine.bezierCurveData.start.y > 0);
|
2026-06-13 22:34:43 +08:00
|
|
|
|
float dir = goUp ? -1f : 1f;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
2026-06-13 22:34:43 +08:00
|
|
|
|
Vector2 pos = startWorld + (Vector3)(dir * transform.up * moveTime
|
|
|
|
|
|
* monsterData.moveLine.moveTime);
|
|
|
|
|
|
transform.position = pos;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 702 / 710:贝塞尔曲线路径
|
|
|
|
|
|
case "702":
|
2026-05-22 19:30:02 +08:00
|
|
|
|
case "7007":
|
|
|
|
|
|
case "710": // 顺序阵:每条路径逐一延迟生成
|
|
|
|
|
|
case "704":
|
2026-06-13 22:34:43 +08:00
|
|
|
|
{
|
|
|
|
|
|
Vector2 next = monsterData.moveLine.GetBezierPointJob(moveTime);
|
|
|
|
|
|
if (fishenum != FishTypeEnum.Boss鱼)
|
|
|
|
|
|
ApplyFacingDirection(next);
|
|
|
|
|
|
transform.position = next;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>普通鱼移动:贝塞尔曲线路径 + 自动朝向。</summary>
|
|
|
|
|
|
private void UpdateNormalMovement()
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector2 next = monsterData.moveLine.GetBezierPointJob(moveTime);
|
|
|
|
|
|
|
2026-06-05 01:04:14 +08:00
|
|
|
|
//if (fishenum != FishTypeEnum.Boss鱼)
|
2026-06-13 22:34:43 +08:00
|
|
|
|
ApplyFacingDirection(next);
|
2026-04-29 01:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
transform.position = next;
|
|
|
|
|
|
}
|
2026-06-13 22:34:43 +08:00
|
|
|
|
public bool isRotate = true;
|
2026-06-20 15:00:15 +08:00
|
|
|
|
public bool isNot1 = false;
|
2026-04-29 01:18:39 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据 forwordXYZ 字段将物体朝向目标点方向。
|
|
|
|
|
|
/// 为空时 2D 鱼用 up,3D 鱼用 forward。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="nextPos">下一帧目标位置</param>
|
|
|
|
|
|
private void ApplyFacingDirection(Vector2 nextPos)
|
|
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 使用 OnEnable 缓存的 _cachedChild,避免每帧 transform.GetChild(0) 调用开销
|
|
|
|
|
|
// 兜底:缓存丢失时重新获取一次(理论上不会发生)
|
|
|
|
|
|
Transform child = _cachedChild;
|
|
|
|
|
|
if (child == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (transform.childCount == 0) return;
|
|
|
|
|
|
child = transform.GetChild(0);
|
|
|
|
|
|
_cachedChild = child;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-21 22:38:18 +08:00
|
|
|
|
Vector3 localPos = Vector3.zero;
|
|
|
|
|
|
localPos.z = depthOffset;
|
|
|
|
|
|
child.localPosition = localPos;
|
2026-05-27 19:47:06 +08:00
|
|
|
|
if (!isRotate) return;
|
|
|
|
|
|
|
|
|
|
|
|
Vector2 toNext = nextPos - (Vector2)transform.position;
|
2026-06-09 20:20:30 +08:00
|
|
|
|
if (toNext.sqrMagnitude < 0.0001f) return;
|
2026-05-27 19:47:06 +08:00
|
|
|
|
|
2026-08-04 14:52:22 +08:00
|
|
|
|
Vector2 dirNorm = toNext.normalized; // ⭐ 缓存归一化向量,避免分支内重复计算
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 用预解析的枚举 switch,避免每帧每鱼 string 比较
|
2026-06-09 20:20:30 +08:00
|
|
|
|
// 处理未指定朝向轴的情况(默认行为)
|
2026-08-04 14:52:22 +08:00
|
|
|
|
if (_facingAxis == FacingAxis.None)
|
2026-04-29 01:18:39 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (!is3D)
|
2026-05-27 19:47:06 +08:00
|
|
|
|
{
|
|
|
|
|
|
float angle = Mathf.Atan2(toNext.y, toNext.x) * Mathf.Rad2Deg;
|
|
|
|
|
|
transform.rotation = Quaternion.AngleAxis(angle - 90f, Vector3.forward);
|
|
|
|
|
|
}
|
2026-04-29 01:18:39 +08:00
|
|
|
|
else
|
2026-05-27 19:47:06 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
transform.rotation = Quaternion.LookRotation(dirNorm, Vector3.up);
|
2026-05-27 19:47:06 +08:00
|
|
|
|
}
|
2026-04-29 01:18:39 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-09 20:20:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 是否为子物体单独控制朝向和深度
|
|
|
|
|
|
if (ischild0)
|
2026-06-05 01:04:14 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
// ⭐ 全部使用缓存 child,不再 transform.GetChild(0)
|
|
|
|
|
|
switch (_facingAxis)
|
2026-06-09 20:20:30 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
case FacingAxis.X:
|
2026-06-13 22:34:43 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.right = dirNorm;
|
|
|
|
|
|
var vv = child.eulerAngles;
|
2026-08-11 16:16:48 +08:00
|
|
|
|
// ⭐ 根据 z 轴角度判断鱼朝向(左/右半边),决定 x 侧倾符号
|
|
|
|
|
|
// child.right=dirNorm 在 2D 平面主要改变 z 轴旋转
|
|
|
|
|
|
// z<180(朝上半边)→负, z>=180(朝下半边)→正
|
|
|
|
|
|
vv.x = rotationPY * (vv.z < 180 ? -1 : 1);
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.eulerAngles = vv;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2026-08-04 14:52:22 +08:00
|
|
|
|
case FacingAxis.NegX:
|
2026-06-13 22:34:43 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.right = -dirNorm;
|
|
|
|
|
|
var vv = child.eulerAngles;
|
2026-08-11 16:16:48 +08:00
|
|
|
|
vv.x = -rotationPY * (vv.z < 180 ? -1 : 1);
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.eulerAngles = vv;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2026-08-04 14:52:22 +08:00
|
|
|
|
case FacingAxis.Y:
|
2026-06-13 22:34:43 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.up = dirNorm;
|
|
|
|
|
|
var vv = child.eulerAngles;
|
2026-08-11 16:16:48 +08:00
|
|
|
|
// ⭐ 根据 z 轴角度判断鱼朝向(左/右半边),决定 y 侧倾符号
|
|
|
|
|
|
// child.up=dirNorm 在 2D 平面主要改变 z 轴旋转
|
|
|
|
|
|
// z<180(朝左半边)→负, z>=180(朝右半边)→正
|
|
|
|
|
|
vv.y = rotationPY * (vv.z < 180 ? -1 : 1);
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.eulerAngles = vv;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2026-08-04 14:52:22 +08:00
|
|
|
|
case FacingAxis.NegY:
|
2026-06-13 22:34:43 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.up = -dirNorm;
|
|
|
|
|
|
var vv = child.eulerAngles;
|
2026-08-11 16:16:48 +08:00
|
|
|
|
vv.y = -rotationPY * (vv.z < 180 ? -1 : 1);
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.eulerAngles = vv;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2026-08-04 14:52:22 +08:00
|
|
|
|
case FacingAxis.Z:
|
2026-06-13 22:34:43 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.forward = dirNorm;
|
|
|
|
|
|
var vv = child.eulerAngles;
|
2026-06-20 15:00:15 +08:00
|
|
|
|
if (isNot1)
|
|
|
|
|
|
{
|
|
|
|
|
|
vv.z = rotationPY;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
vv.z = rotationPY * (vv.y < 180 ? -1 : 1);
|
|
|
|
|
|
}
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.eulerAngles = vv;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2026-08-04 14:52:22 +08:00
|
|
|
|
case FacingAxis.NegZ:
|
2026-06-13 22:34:43 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.forward = -dirNorm;
|
|
|
|
|
|
var vv = child.eulerAngles;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
vv.z = -rotationPY * (vv.y < 180 ? -1 : 1);
|
2026-08-04 14:52:22 +08:00
|
|
|
|
child.eulerAngles = vv;
|
2026-06-13 22:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2026-06-09 20:20:30 +08:00
|
|
|
|
}
|
2026-06-05 01:04:14 +08:00
|
|
|
|
}
|
2026-06-09 20:20:30 +08:00
|
|
|
|
else
|
2026-04-29 01:18:39 +08:00
|
|
|
|
{
|
2026-06-09 20:20:30 +08:00
|
|
|
|
// 控制父物体自身朝向
|
2026-08-04 14:52:22 +08:00
|
|
|
|
switch (_facingAxis)
|
2026-06-09 20:20:30 +08:00
|
|
|
|
{
|
2026-08-04 14:52:22 +08:00
|
|
|
|
case FacingAxis.X: transform.right = dirNorm; break;
|
|
|
|
|
|
case FacingAxis.NegX: transform.right = -dirNorm; break;
|
|
|
|
|
|
case FacingAxis.Y: transform.up = dirNorm; break;
|
|
|
|
|
|
case FacingAxis.NegY: transform.up = -dirNorm; break;
|
|
|
|
|
|
case FacingAxis.Z: transform.forward = dirNorm; break;
|
|
|
|
|
|
case FacingAxis.NegZ: transform.forward = -dirNorm; break;
|
2026-06-09 20:20:30 +08:00
|
|
|
|
}
|
2026-04-29 01:18:39 +08:00
|
|
|
|
}
|
2026-06-13 22:34:43 +08:00
|
|
|
|
}
|
2026-06-09 20:20:30 +08:00
|
|
|
|
public bool ischild0 = false;
|
|
|
|
|
|
public int depthOffset;
|
2026-08-19 14:45:43 +08:00
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
// ⭐ 2D层级(2D鱼用,与3D鱼的z轴depthOffset深度兼容并存)
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
[Header("2D层级(2D鱼)")]
|
|
|
|
|
|
[Tooltip("是否为2D渲染鱼:用sortingOrder控制层级(3D鱼继续用z轴depthOffset深度,互不影响)")]
|
|
|
|
|
|
public bool is2DFish = false;
|
|
|
|
|
|
[Tooltip("2D鱼的sortingOrder层级值,运行时可调 Set2DOrder(int) 修改")]
|
|
|
|
|
|
public int sortOrder2D = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>2D鱼渲染器缓存(设置sortingOrder用,OnEnable重建,避免重复GetComponentsInChildren)</summary>
|
|
|
|
|
|
private Renderer[] _cached2DRenderers;
|
|
|
|
|
|
/// <summary>已应用的sortingOrder值(值未变化时跳过重复设置)</summary>
|
|
|
|
|
|
private int _appliedSortOrder = int.MinValue;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置2D层级(sortingOrder)。对鱼身上所有渲染器统一生效,
|
|
|
|
|
|
/// 多渲染器按层级顺序依次 +1 递增,保持鱼内部前后遮挡关系不变。
|
|
|
|
|
|
/// 仅 is2DFish=true 时生效;3D鱼仍走 z 轴 depthOffset,兼容现有逻辑。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void Set2DOrder(int order)
|
|
|
|
|
|
{
|
|
|
|
|
|
sortOrder2D = order;
|
|
|
|
|
|
Apply2DOrder();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>应用sortingOrder到全部子渲染器(值相同则跳过)</summary>
|
|
|
|
|
|
private void Apply2DOrder()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!is2DFish) return;
|
|
|
|
|
|
if (_appliedSortOrder == sortOrder2D) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (_cached2DRenderers == null)
|
|
|
|
|
|
_cached2DRenderers = GetComponentsInChildren<Renderer>(true);
|
|
|
|
|
|
|
|
|
|
|
|
_appliedSortOrder = sortOrder2D;
|
|
|
|
|
|
for (int i = 0; i < _cached2DRenderers.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var r = _cached2DRenderers[i];
|
|
|
|
|
|
if (r != null)
|
|
|
|
|
|
r.sortingOrder = sortOrder2D + i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 01:18:39 +08:00
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 动画
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>播放指定类型动画(需要先在 dic_AnimKeyAnimName 中注册)。</summary>
|
|
|
|
|
|
public void PlayAnim(CharacterAnimationType animType, bool loop = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (skeleton == null) return;
|
|
|
|
|
|
if (!dic_AnimKeyAnimName.TryGetValue(animType, out var animName)) return;
|
|
|
|
|
|
|
|
|
|
|
|
skeleton.AnimationState.SetAnimation(0, animName, loop);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 受击 & 死亡
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
|
2026-08-09 17:57:46 +08:00
|
|
|
|
/// <summary>触发受击闪烁(0.2s 后自动关闭)。子类可 override 实现受击阶段逻辑(如爆裂金鲨首炮切战损)。</summary>
|
|
|
|
|
|
public virtual void OnHurt()
|
2026-04-29 01:18:39 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (hurt != null)
|
|
|
|
|
|
StartCoroutine(HurtCoroutine());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private IEnumerator HurtCoroutine()
|
|
|
|
|
|
{
|
|
|
|
|
|
hurt.SetActive(true);
|
|
|
|
|
|
yield return new WaitForSeconds(0.2f);
|
|
|
|
|
|
hurt.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 执行死亡:播放音效并销毁 GameObject。
|
|
|
|
|
|
/// 子类可 override 添加额外逻辑(如 Boss 爆炸动画)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual void DieFish(RoomKillNotice obj = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (FishResourcesData.fishResourcesDatasDic.TryGetValue(fishTypeID, out var res)
|
|
|
|
|
|
&& res.dieAudio != null && res.dieAudio.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
AudioManager.Instance.PlayerFX(res.dieAudio[0]);
|
|
|
|
|
|
}
|
2026-05-01 21:59:16 +08:00
|
|
|
|
CreateFishManager.Instance.Id_DieFish_NotFX(guid);
|
|
|
|
|
|
|
2026-04-29 23:53:55 +08:00
|
|
|
|
//Destroy(gameObject);
|
2026-04-29 01:18:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
// 工具方法
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>递归深度查找子物体(广度优先)。</summary>
|
|
|
|
|
|
private Transform FindDeepChild(Transform parent, string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (Transform child in parent)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (child.name == name) return child;
|
|
|
|
|
|
var found = FindDeepChild(child, name);
|
|
|
|
|
|
if (found != null) return found;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2026-02-03 11:35:19 +08:00
|
|
|
|
}
|