using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using DG.Tweening.Core; using DG.Tweening.Plugins.Options; using Spine.Unity; using UnityEngine.UI; using System.Linq; public class HeroBattleObject : BattleObject { private GameObject _heroGo; public GameObject heroGo => _heroGo; public MotionBase motionBase; public TeamHero teamHero { get; private set; } // Buff 管理器(只有 Hero 有 buff 系统) public BattleObjectBuffMgr buffMgr; private RectTransform m_heroRectTrans; public RectTransform heroRectTrans { get { if (m_heroRectTrans == null && _heroGo != null) { m_heroRectTrans = _heroGo.GetComponent(); } return m_heroRectTrans; } } protected Action onDeathAnimationComplete; protected Renderer[] renderers; private List hB405_tagMCAddExps = new List(); private BattleHeroInfoBar _heroInfoBar; public BattleHeroInfoBar heroInfoBar => _heroInfoBar; private bool _isReborning = false; public bool isReborning { get => _isReborning; set => _isReborning = value; } public HeroBattleObject(BattleField _battleField) : base(_battleField) { } public void Init(GameObject _heroGo, TeamHero _teamHero, BattleCamp _camp) { this._heroGo = _heroGo; teamHero = _teamHero; Camp = _camp; ObjID = _teamHero.ObjID; InitInternal(); } private void InitInternal() { motionBase = new MotionBase(); motionBase.Init(_heroGo.GetComponentInChildren(true)); buffMgr = new BattleObjectBuffMgr(); buffMgr.Init(this); buffMgr.onIsControlChanged += OnControledChange; layerMgr = new BattleObjectLayerMgr(); layerMgr.Init(this); renderers = _heroGo.GetComponentsInChildren(true); _heroInfoBar = _heroGo.GetComponentInChildren(true); _heroInfoBar.SetBattleObject(this); // 根据阵营翻转血条 var heroInfoBarScale = _heroInfoBar.transform.localScale; heroInfoBarScale.x *= Camp == BattleCamp.Red ? 1 : -1; _heroInfoBar.transform.localScale = heroInfoBarScale; if (battleField is StoryBattleField && (battleField as StoryBattleField).battleState == StoryBattleState.Break) { //主线关卡休息中的不显示血条 heroInfoBar.SetActive(false); } else { heroInfoBar.SetActive(true); } SetFront(); if (battleField.ToString() != BattleConst.StoryBattleField && battleField.ToString() != BattleConst.TianziBillboradBattleField) { var hitArea = heroGo.GetComponent(); if (hitArea == null) { hitArea = heroGo.AddComponent(); hitArea.color = new Color(0, 0, 0, 0); //让射线检测到 } var btn = heroGo.GetComponent(); if (btn == null) { btn = heroGo.AddComponent(); btn.interval = 0.5f;// 防止频繁连续点击 } btn.AddListener(() => { // 判断点击的是我方阵营还是敌方阵营,目前左边都是我方阵营 bool isMySide = Camp == BattleCamp.Red; EventBroadcast.Instance.Broadcast(EventName.BATTLE_CLICK_HERO, new BattleClickHeroData() { battleName = battleField.ToString(), isMySide = isMySide, mapID = battleField.MapID, funcLineID = battleField.FuncLineID, npcID = teamHero.NPCID, posNum = teamHero.positionNum, heroID = teamHero.heroId, teams = battleField?.battleObjMgr.GetBattleObjList(isMySide ? BattleCamp.Red : BattleCamp.Blue), }); }); } } public override void Run() { motionBase.Run(); heroInfoBar.Run(); buffMgr.Run(); } public override void Pause() { motionBase.Pause(); } public override void Resume() { motionBase.Resume(); } public override void Destroy() { motionBase.Release(); motionBase = null; buffMgr.onIsControlChanged -= OnControledChange; buffMgr.Release(); buffMgr = null; teamHero = null; ObjID = 0; if (_heroGo != null) { GameObject.DestroyImmediate(_heroGo); _heroGo = null; } } // ============ 动画相关方法实现(通过 motionBase) ============ public override void PlayAnimation(MotionName motionName, bool loop) { motionBase.PlayAnimation(motionName, loop); } public override void ShowIllusionShadow(bool show, Color? color = null) { if (color.HasValue) { motionBase.ShowIllusionShadow(show, color.Value); } else { motionBase.ShowIllusionShadow(show); } } public override Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, bool isCounter, Action onComplete) { return motionBase.PlaySkillAnimation(skillConfig, skillBase, isCounter, onComplete); } public override bool CanStartDeath() { return motionBase.CanStartDeath(); } public override bool CanCastSkillAnimation(SkillConfig skillConfig) { return motionBase.CanCastSkill(skillConfig); } public override SkeletonAnimation GetSkeletonAnimation() { return motionBase.skeletonAnim; } public override void SetSkeletonAlpha(float alpha) { if (motionBase.skeletonAnim != null) { motionBase.skeletonAnim.skeleton.A = alpha; } } public override RectTransform GetRectTransform() => heroRectTrans; public override GameObject GetGameObject() => _heroGo; public override Transform GetTransform() => _heroGo?.transform; public override Vector3 GetPosition() => heroRectTrans != null ? heroRectTrans.position : Vector3.zero; public override BattleHeroInfoBar GetHeroInfoBar() => _heroInfoBar; public override void RefreshBuff(List buffList) { _heroInfoBar?.RefreshBuff(buffList); } public override void UpdateHP(float percentage) { _heroInfoBar?.UpdateHP(percentage); } public override bool IsReborning() => _isReborning; public override void SetReborning(bool value) => _isReborning = value; public override void SetActive(bool active) { if (_heroGo != null) { _heroGo.SetActive(active); } } public override void ResetPosition() { if (heroRectTrans != null) { heroRectTrans.anchoredPosition = Vector2.zero; } } public override void SetFacing(float direction) { if (_heroGo != null) { Vector3 scale = _heroGo.transform.localScale; scale.x = Mathf.Abs(scale.x) * direction; _heroGo.transform.localScale = scale; } } public override void ResetFacing() => SetFacing(1f); public override void StopMoveAnimation() { if (heroRectTrans != null) { DG.Tweening.DOTween.Kill(heroRectTrans); } } public override void ShowTips(string message, bool useArtText = false, bool followCharacter = true, float scaleRatio = 1f) { _heroInfoBar?.ShowTips(message, useArtText, followCharacter, scaleRatio); } public override void ShowTips(BattleHeroInfoBar.TipsInfo tipsInfo) { _heroInfoBar?.ShowTips(tipsInfo); } // 有变化了才会调用这个函数 private void OnControledChange(int groupType, bool value) { // 这里是受到硬控时候 需要表现的动画 if (groupType == BattleConst.HardControlGroup) { // 从没被硬控到被硬控 if (value) { motionBase.SetControledAnimation(); } else { motionBase.CancelControledAnimation(); } } } public override void OnObjInfoRefresh(H0418_tagObjInfoRefresh _refreshInfo) { // 天子的挑战拦截血条,不拦截怒气 BattleObject boss = battleField.FindBoss(); if (boss != null && battleField.MapID == 30020 && boss.ObjID == _refreshInfo.ObjID && _refreshInfo.RefreshType != (ushort)PlayerDataType.XP) return; switch ((PlayerDataType)_refreshInfo.RefreshType) { case PlayerDataType.HP: long toHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); if (!IsTianziBoss()) { heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp, false); } teamHero.curHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); // Debug.LogError("OnObjInfoRefresh " + teamHero.curHp); break; case PlayerDataType.MaxHP: teamHero.maxHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); if (!IsTianziBoss()) { heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp, false); } break; case PlayerDataType.XP: long toXp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); heroInfoBar.UpdateXP(teamHero.rage, toXp, 100); teamHero.rage = (int)GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); break; default: Debug.LogError("BattleObject.ObjInfoRefresh 出现意外类型 " + _refreshInfo.RefreshType.ToString()); break; } } public override DeathRecordAction Hurt(BattleHurtParam battleHurtParam, SkillRecordAction _parentSkillAction = null) { DeathRecordAction recordAction = null; bool isLastHit = battleHurtParam.hitIndex >= battleHurtParam.skillConfig.DamageDivide.Length - 1; bool firstHit = battleHurtParam.hitIndex == 0; // 添加调试日志 bool isHealing = BattleUtility.IsHealing(battleHurtParam.hurt); BattleDmgInfo dmgInfo = PopDamage(battleHurtParam); // ============ 应用目标的血量和护盾变化 ============ ApplyHurtToTarget(battleHurtParam, isLastHit); // 这里 if (dmgInfo.IsType(DamageType.Dodge) || dmgInfo.IsType(DamageType.CompletelyDodge))//如果被控制了还闪避了 要看看服务器怎么处理了 { if (isLastHit) { DodgeFinishAction dodgeFinish = new DodgeFinishAction(battleField, this); // 【使用 BattleField.recordPlayer】 // 原因:闪避完成动作是目标角色的独立行为,不是技能内部产生的 // 虽然是在Hurt过程中触发,但是闪避动作本身是目标的反应,应该由主RecordPlayer管理 // 使用InsertRecord可以插到队列最前面,保证闪避表现的优先级 battleField.recordPlayer.InsertRecord(dodgeFinish); } if (firstHit) { OnDodgeBegin(dmgInfo.IsType(DamageType.Dodge) ? DamageType.Dodge : DamageType.CompletelyDodge); } } bool isFatalAttack = (null != battleHurtParam.deadPack) && isLastHit; if (isFatalAttack) { if (null != battleHurtParam.battleDrops) { PushDropItems(battleHurtParam.battleDrops); } recordAction = battleField.OnObjsDead(new List() { battleHurtParam.deadPack }, _parentSkillAction, _parentSkillAction); } else { if (dmgInfo.IsType(DamageType.Block)) { battleField.battleEffectMgr.PlayEffect(this, BattleConst.BlockEffectID, heroRectTrans, Camp, teamHero.modelScale); } // else // { if ((dmgInfo.IsType(DamageType.Damage) || dmgInfo.IsRealdamage())) { if (!buffMgr.isControled[BattleConst.HardControlGroup]) { battleField.soundManager.PlayEffectSound(teamHero.heroConfig.HitSFX, false); motionBase.PlayAnimation(MotionName.hit, false); } } // } } return recordAction; } /// /// 应用目标的血量和护盾变化 /// private void ApplyHurtToTarget(BattleHurtParam battleHurtParam, bool isLastHit) { BattleHurtObj hurter = battleHurtParam.hurter; // 应用血量变化 teamHero.curHp = hurter.toHp; #if UNITY_EDITOR // 最后一击时验证血量是否与服务器一致 if (isLastHit) { BattleUtility.ValidateHpConsistency(battleHurtParam, "目标受伤"); } #endif } const float pingpongTime = 0.4f; // 闪避开始 public override void OnDodgeBegin(DamageType damageType) { RectTransform rectTrans = heroRectTrans; var tween = rectTrans.DOAnchorPos(new Vector3(-30, 0, 0), pingpongTime) .SetEase(Ease.OutCubic); motionBase.ShowIllusionShadow(true); int damageTypeInt = damageType == DamageType.Dodge ? (int)damageType : BattleConst.CompletelyDodge; DamageNumConfig damageNumConfig = DamageNumConfig.Get(damageTypeInt); string dodgeStr = ((char)damageNumConfig.prefix).ToString(); heroInfoBar.ShowTips(dodgeStr, true, false); tween.onComplete += () => { motionBase.ShowIllusionShadow(false); }; battleField.soundManager.PlayEffectSound(BattleConst.DodgeSoundID); battleField.battleTweenMgr.OnPlayTween(tween); } // 闪避结束 public override void OnDodgeEnd(Action _complete = null) { RectTransform rectTrans = heroRectTrans; var tween = rectTrans.DOAnchorPos(Vector3.zero, pingpongTime) .SetEase(Ease.OutCubic); tween.onComplete += () => { _complete?.Invoke(); }; battleField.battleTweenMgr.OnPlayTween(tween); } public override void OnDeath(Action _onDeathAnimationComplete, bool withoutAnime = false) { buffMgr.RemoveAllBuff(); battleField.soundManager.PlayEffectSound(teamHero.heroConfig.DeathSFX, false); if (withoutAnime) { SetDeath(); _onDeathAnimationComplete?.Invoke(); } else { motionBase.PlayDeadAnimation(() => { SetDeath(); _onDeathAnimationComplete?.Invoke(); }); } } public override void SetDeath() { teamHero.isDead = true; OnDeadAnimationComplete(); } protected virtual void OnDeadAnimationComplete() { // 或许看看溶解特效? YYL TODO heroGo.SetActive(false); // 防止给死亡对象又上buff buffMgr.RemoveAllBuff(); } // 释放者就是复活者时调用 public override void PreReborn(bool reviveSelf = false) { heroGo.SetActive(true); motionBase.skeletonAnim.skeleton.A = 0f; motionBase.skeletonAnim.LateUpdate(); heroRectTrans.anchoredPosition = Vector2.zero; motionBase.ResetForReborn(reviveSelf); } // 复活action public override void OnReborn(HB427_tagSCUseSkill.tagSCUseSkillHurt vNetData, bool reviveSelf = false, RecordAction parentAction = null) { isReborning = true; heroGo.SetActive(true); motionBase.ResetForReborn(reviveSelf); heroRectTrans.anchoredPosition = Vector2.zero; motionBase.skeletonAnim.skeleton.A = 0f; motionBase.skeletonAnim.LateUpdate(); } public override void AfterReborn() { // 清空所有 motionBase.ResetForReborn(false); isReborning = false; } // ============ 实现抽象访问方法 ============ public override BattleObjectBuffMgr GetBuffMgr() => buffMgr; public override int GetPositionNum() => teamHero.positionNum; public override float GetModelScale() => teamHero.modelScale; public override string GetName() => teamHero.name; protected override bool GetIsStunned() => teamHero.isStunned; protected override bool GetIsFrozen() => teamHero.isFrozen; protected override bool GetIsStoned() => teamHero.isStoned; protected override bool GetIsSlient() => teamHero.isSlient; protected override bool GetIsDisarmed() => teamHero.isDisarmed; protected override bool GetIsInvincible() => teamHero.isInvinceble; protected override bool GetIsDead() => teamHero.isDead; public override int GetRage() => teamHero.rage; protected override void ApplyCasterHpChange(long newHp) { if (teamHero == null) return; teamHero.curHp = newHp; } public override long GetCurHp() => teamHero == null ? 0 : teamHero.curHp; public override long GetMaxHp() => teamHero == null ? 0 : teamHero.maxHp; public override void SetCurHp(long value) { if (teamHero == null) return; teamHero.curHp = value; } public override void SetIsDead(bool value) { if (teamHero == null) return; teamHero.isDead = value; } public override int GetNPCID() => teamHero == null ? 0 : teamHero.NPCID; public override long GetFightPower() => teamHero == null ? 0 : teamHero.fightPower; // 伤害还要看 是否闪避 暴击 and so on 需要有一个DamageType 服务器应该会给 protected override BattleDmgInfo PopDamage(BattleHurtParam battleHurtParam) { BattleDmgInfo battleDmgInfo = new BattleDmgInfo(battleField.guid, battleHurtParam); // 天子的挑战拦截血条逻辑 BattleObject boss = battleField.FindBoss(); // 修复:battleHurtParam.hurtObj.ObjID -> battleHurtParam.hurter.hurtObj.ObjID if (boss != null && battleField.MapID == 30020 && boss.ObjID == battleHurtParam.hurter.hurtObj.ObjID) { EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo); return battleDmgInfo; } else { heroInfoBar.UpdateDamage(battleDmgInfo); // YYL TODO 是否需要挂在在自身的follow点上 EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo); return battleDmgInfo; } } /// /// 为施法者创建伤害信息(吸血/反伤) /// protected override BattleDmgInfo PopDamageForCaster(BattleHurtParam battleHurtParam) { // 传入 isCasterView=true 表示这是施法者视角 BattleDmgInfo battleDmgInfo = new BattleDmgInfo(battleField.guid, battleHurtParam, _isCasterView: true); BattleObject boss = battleField.FindBoss(); if (boss != null && battleField.MapID == 30020 && boss.ObjID == this.ObjID) { EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo); return battleDmgInfo; } else { heroInfoBar.UpdateDamage(battleDmgInfo); EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo); return battleDmgInfo; } } public override void HaveRest() { // YYL TODO // 休息状态 // 多一个zzz的一个特效 heroGo.SetActive(true); motionBase.HaveRest(); heroRectTrans.anchoredPosition = Vector2.zero; heroInfoBar.HaveRest(); isReborning = false; SetFront(); } public override void SetSpeedRatio(float ratio) { motionBase.SetSpeedRatio(ratio); heroInfoBar.SetSpeedRatio(ratio); } public override void PushDropItems(BattleDrops _battleDrops) { m_battleDrops = _battleDrops; } public override void PerformDrop() { if (null == m_battleDrops) return; EventBroadcast.Instance.Broadcast( EventName.BATTLE_DROP_ITEMS, battleField.guid, m_battleDrops, OnPerformDropFinish); } protected override void OnPerformDropFinish() { m_battleDrops = null; } public override void OnObjPropertyRefreshView(HB418_tagSCObjPropertyRefreshView vNetData) { // 天子的挑战拦截血条,不拦截怒气 BattleObject boss = battleField.FindBoss(); if (boss != null && battleField.MapID == 30020 && boss.ObjID == vNetData.ObjID && vNetData.RefreshType != (ushort)PlayerDataType.XP) return; long diffValue = GeneralDefine.GetFactValue(vNetData.DiffValue, vNetData.DiffValueEx); diffValue *= vNetData.DiffType == 0 ? -1 : 1; long newValue = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx); switch ((PlayerDataType)vNetData.RefreshType) { case PlayerDataType.HP: long toHp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx); bool isMinus = teamHero.curHp > toHp; if (!IsTianziBoss()) { heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp, false); } teamHero.curHp = newValue; // Debug.LogError("OnObjPropertyRefreshView " + teamHero.curHp); break; case PlayerDataType.MaxHP: teamHero.maxHp = newValue; if (!IsTianziBoss()) { heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp, false); } break; case PlayerDataType.XP: long toXp = newValue; heroInfoBar.UpdateXP(teamHero.rage, toXp, 100); teamHero.rage = (int)newValue; DamageNumConfig damageNumConfig = DamageNumConfig.Get((int)DamageType.RageUp); string message = BattleUtility.ConvertToArtFont(damageNumConfig, diffValue); heroInfoBar.ShowTips(new BattleHeroInfoBar.TipsInfo() { message = message, useArtText = true, followCharacter = true, scaleRatio = 1f, isRage = true }); break; default: Debug.LogError("BattleObject.ObjPropertyRefreshView 出现意外类型 " + vNetData.RefreshType.ToString()); break; } } public override void OnHurtTarget(BattleHurtParam battleHurtParam) { // 检查是否有吸血或反伤 bool hasSuckHp = battleHurtParam.caster.suckHpList != null && battleHurtParam.caster.suckHpList.Count > 0; bool hasReflectHp = battleHurtParam.caster.reflectHpList != null && battleHurtParam.caster.reflectHpList.Count > 0; if (!hasSuckHp && !hasReflectHp) { return; } // ============ 应用施法者的血量和护盾变化 ============ bool isLastHit = battleHurtParam.hitIndex >= battleHurtParam.skillConfig.DamageDivide.Length - 1; ApplyHurtToCaster(battleHurtParam, isLastHit); // 和Hurt一样,调用PopDamage处理吸血/反伤的显示 BattleDmgInfo casterDmgInfo = PopDamageForCaster(battleHurtParam); // 如果有反伤,施法者播放受击动画 if (hasReflectHp && casterDmgInfo.casterDamageList != null && casterDmgInfo.casterDamageList.Count > 0) { long totalReflect = casterDmgInfo.casterDamageList.Sum(d => d.damage); if (totalReflect > 0 && !buffMgr.isControled[BattleConst.HardControlGroup]) { motionBase.PlayAnimation(MotionName.hit, false); } } } /// /// 应用施法者的血量和护盾变化(吸血和反伤) /// private void ApplyHurtToCaster(BattleHurtParam battleHurtParam, bool isLastHit) { BattleCastObj caster = battleHurtParam.caster; // 应用血量变化 teamHero.curHp = caster.toHp; #if UNITY_EDITOR // 最后一击时验证血量是否与服务器一致 if (isLastHit) { BattleUtility.ValidateHpConsistencyForCaster(battleHurtParam, "施法者吸血/反伤"); } #endif } #if UNITY_EDITOR_STOP_USING public override void EditorRevive() { teamHero.curHp = 100; heroGo.SetActive(true); motionBase.PlayAnimation(MotionName.idle, true); } #endif protected override void OnPlayHitAnimation() { motionBase.PlayAnimation(MotionName.hit, false); } }