using DG.Tweening; using DG.Tweening.Core; using DG.Tweening.Plugins.Options; using IVDataFormat; using Spine; using Spine.Unity; using System; using System.Collections; using System.Collections.Generic; using System.Numerics; using UnityEngine; using AnimationState = Spine.AnimationState; using Quaternion = UnityEngine.Quaternion; using Vector2 = UnityEngine.Vector2; using Vector3 = UnityEngine.Vector3; // 전투용 캐릭터/몬스터에서 상속받는 부모 클래스. public class CreatureBase : MonoBehaviour { protected static readonly Vector3 V3_MonSizeNormal = new Vector3(0.75f, 0.75f, 0.75f); protected static readonly Vector3 V3_MonSizeElite = new Vector3(0.8f, 0.8f, 0.8f); protected static readonly Vector3 V3_MonSizeBoss = new Vector3(1f, 1f, 1f); protected TweenerCore twcSummon = null; protected TweenerCore twcUnsummon = null; protected BattleMgr battleMgr; protected int iIndex = -1; protected bool bInitNeed = true; protected bool bLoading = false; protected bool bSummonNeed = false; protected bool bFriendly = false; protected bool bcoward = false; protected int isDamaged = 0; #region Battle State public enum eState { idle, move, attack, damage, die, skill, ceremony, warp_in, warp_out, attack2, attack3, rush, } public enum eCreatureClass { charEnemy = -2, character = -1, normal = 0, elite = 1, boss = 2, bigboss=3, summon = 10 } /// /// 몬스터 위치별 번호 /// public enum eDgCreatureClass { DgN1, DgN2, DgN3, DgN4, DgN5, DgE1, DgBs, DgAwBs } public bool IsInit { get; private set; } protected ParticleSystem ptcHit; [SerializeField] protected SkeletonAnimation skAnim; Dictionary animEventHandlers = new Dictionary(); AnimationState _anState; protected AnimationState anState { get => _anState; set { if(_anState != null) _anState.Event -= HandleAnimEvent; KeyValuePair[] handlerPairs = { new KeyValuePair(nameof(AttackDamage), AttackDamage), new KeyValuePair(nameof(CheckAttackState), CheckAttackState), new KeyValuePair(nameof(CheckDamageState), CheckDamageState), new KeyValuePair(nameof(CheckDieState), CheckDieState), new KeyValuePair(nameof(SkillEffect), SkillEffect), new KeyValuePair(nameof(CheckSkillState), CheckSkillState), new KeyValuePair(nameof(CheckCeremonyState), CheckCeremonyState), new KeyValuePair(nameof(CheckWarp_outState), CheckWarp_outState), }; _anState = value; animEventHandlers.Clear(); for(int i = 0; i < handlerPairs.Length; ++i) { var eventData = skAnim.Skeleton.Data.FindEvent(handlerPairs[i].Key); if (eventData != null) animEventHandlers.Add(eventData, handlerPairs[i].Value); } _anState.Event += HandleAnimEvent; } } protected HpBar hpBar; [SerializeField] protected eState animState = eState.idle; [SerializeField] protected eCreatureClass creatureClass = eCreatureClass.normal; protected int iTargetIndex = -1; protected Vector3 v3TargetPos = Vector3.zero; protected float fTargetDistance = 9999f; protected float fUseSkillRange = 0f; protected bool bCanMove = true; protected bool bCanAttack = true; protected bool bDead = false; protected bool bSummon = false; protected bool bAttackable = false; public bool IsLookRight { get; protected set; } [SerializeField] protected float fAttackTick = 0f; protected float fAttackTime = 1.2f; protected float fSearchTick = 0f; protected const float F_SearchTime = 0.1f; protected BoxCollider areaSize; protected Vector3 minArea; protected Vector3 maxArea; [Range(0.1f, 1.0f)] protected float atkSpeed = 1.0f; [Range(1.0f, 4.0f)] protected float motionSpeed = 1.0f; protected Stack stackSkillAvail; #endregion Battle State #region Status protected float fRange = 7f; protected float frecogRange = 10f; // 플레이어 인식범위 protected float enemySlowSpeed = 0.2f; // 적군 농땡이 속도 protected eMonsterType eType; protected bool alertPlayer = false; protected BigInteger biAtk = 0L; protected BigInteger biHp = 10; protected float fCrtDam = dConst.RateMaxFloat; protected int iCrtRate = 0; protected float fMov = dConst.RateMaxFloat; protected float fSpd = dConst.RateMaxFloat; protected BigInteger biAtkCalc = 1; protected BigInteger biHpCalc = 10; protected BigInteger biHpCur = 10; protected float fCrtDamCalc = dConst.RateMaxFloat; protected int iCrtRateCalc = 0; protected float fMovCalc = 3f; protected float fSpdCalc = dConst.RateMaxFloat; #endregion Status #region Debuff public class cDamage { public int sec; public float time; public BigInteger atk; public float crtdam; public int crtrate; public int idnum; } // 둔화. protected float fDecMov = 0f; protected float fDecMovTime = 0f; // 출혈. protected int iDamageSecCnt = 0; protected cDamage[] damageSecs = new cDamage[2] { new cDamage(), new cDamage() }; // 기절. protected bool bStun = false; protected float fStunTime = 0f; // 기절/넉백 쿨타임. protected float fStunPushCool = 0f; #endregion Debuff protected int backAllowed = 1; [SerializeField] float backtime = 0f; protected bool isdash = false; protected virtual void Start() { Init(); if (bInitNeed && !bLoading) { bInitNeed = false; anState = skAnim.AnimationState; } } protected void Update() { initAnimTimeScale(); if (BattleMgr.Instance.BattlePause || bSummon || bDead) return; float ftimedec = Time.deltaTime; #region Debuff // 둔화. if (fDecMovTime > 0f) { fDecMovTime -= ftimedec; if (fDecMovTime <= 0f) { fDecMov = 0f; fMovCalc = fMov * 12f / dConst.RateMaxFloat; } } // 출혈. if (iDamageSecCnt > 0) { for (int i = 0; i < damageSecs.Length; i++) { if (damageSecs[i].time <= 0f) continue; damageSecs[i].time -= ftimedec; if (damageSecs[i].time <= 0f) { GetDamage(damageSecs[i].atk, damageSecs[i].crtdam, damageSecs[i].crtrate, damageSecs[i].idnum); iDamageSecCnt--; continue; } int isec = (int)damageSecs[i].time; if (isec < damageSecs[i].sec) { damageSecs[i].sec = isec; GetDamage(damageSecs[i].atk, damageSecs[i].crtdam, damageSecs[i].crtrate, damageSecs[i].idnum); } } } // 기절. if (bStun) { fStunTime -= ftimedec; if (fStunTime <= 0f) { bStun = false; } } else if (fStunPushCool > 0f) { fStunPushCool -= ftimedec; } #endregion Debuff if (bStun) return; // 유저가 터치하면 이동 if (bFriendly && VirtualPad.isTouchScreen()) { ChangeLookDirection(VirtualPad.GetPointDirection()); if (animState != eState.move) { ChangeStateForce(eState.move); } } // 아니라면 AI로 동작 else { switch (animState) { case eState.idle: // 공격 대기 시간 체크. if (fAttackTick < fAttackTime) { fAttackTick += Time.deltaTime; if (fAttackTick >= fAttackTime) bAttackable = true; } // 목표 재설정 대기 시간 체크. if (fSearchTick < F_SearchTime) { fSearchTick += Time.deltaTime; if (fSearchTick >= F_SearchTime) SearchTarget(); } // 현재 상태가 idle이고 공격 범위 안에 있으며 공격력이 0이 아니면 공격 체크. if (animState == eState.idle && fTargetDistance <= fRange && biAtk != 0) CheckAttack(); break; case eState.move: // 공격 대기 시간 체크. if (fAttackTick < fAttackTime) { fAttackTick += Time.deltaTime; if (fAttackTick >= fAttackTime) bAttackable = true; } // 목표 재설정 대기 시간 체크. if (fSearchTick < F_SearchTime) { fSearchTick += Time.deltaTime; if (fSearchTick >= F_SearchTime) SearchTarget(); } // 현재 상태가 move이고 이동 이후 공격 범위 안에 있으며 공격력이 0이 아니면 공격 체크. if (animState == eState.move && MoveToTarget() <= fRange && biAtk != 0) CheckAttack(); break; // 스킬 중에 구르는 모션이 필요한게 있음. 아직 파악 불가. case eState.rush: backAllowed--; // 공격 대기 시간 체크. if (fAttackTick < fAttackTime) { fAttackTick += Time.deltaTime; if (fAttackTick >= fAttackTime) bAttackable = true; } //1f 가 시간 backtime += Time.deltaTime; if (backtime > 0.4f) { CheckAttack(); backtime = 0; isdash = false; } break; } } } #region Base & Init // 컴포넌트 세팅. public void SetComponent(BattleMgr mgr, HpBar hpbar) { battleMgr = mgr; hpBar = hpbar; } // 피아 지정. public void SetFriendly(bool bfriendly) { bFriendly = bfriendly; } // 크리쳐 종류, 인덱스 지정. public void SetClassIndex(eCreatureClass cls, int index) { creatureClass = cls; iIndex = index; switch (creatureClass) { case eCreatureClass.normal: skAnim.transform.localScale = V3_MonSizeNormal; break; case eCreatureClass.elite: skAnim.transform.localScale = V3_MonSizeElite; break; case eCreatureClass.boss: skAnim.transform.localScale = V3_MonSizeBoss; break; } } // 초기화. public void Init() { if(IsInit) return; IsInit = true; ptcHit = transform.Find("hit").GetComponent(); InitTween(); stackSkillAvail = new Stack(); } // 트윈 초기화. protected virtual void InitTween() { twcSummon = transform.DOScale(Global.V3_1, 0.8f).ChangeStartValue(Global.V3_001).SetEase(Ease.OutQuad).OnComplete(SummonEnd).SetAutoKill(false).Pause(); twcUnsummon = transform.DOScale(Global.V3_1, 2.0f).SetEase(Ease.InQuad).OnComplete(UnsummonEnd).SetAutoKill(false).Pause(); } #endregion Base & Init #region Stat // 스탯 세팅. public void SetStatus(BigInteger fatk, BigInteger fhp, float fcrtdam, int icrtrate, float fmov, float fspd, float frange, bool bhpcurkeep = false, bool isrunaway = false, int igroup = 1) { biAtk = fatk; biHp = fhp; fCrtDam = fcrtdam; iCrtRate = icrtrate; fMov = fmov; fSpd = fspd; eType = (eMonsterType)igroup; biAtkCalc = biAtk; biHpCalc = biHp; if (!bhpcurkeep) biHpCur = biHpCalc; fCrtDamCalc = fCrtDam; iCrtRateCalc = iCrtRate; fSpdCalc = fSpd; fMovCalc = fMov * 12f / dConst.RateMaxFloat; if (fDecMov > 0f) fMovCalc *= (1f - fDecMov); fRange = frange / dConst.RateMaxFloat; float foffset = dConst.RateMaxFloat / fSpdCalc; atkSpeed = Mathf.Lerp(0.1f, 0.75f, foffset); // 공속이 높을 수록 0.1로 motionSpeed = Mathf.Lerp(4.0f, 1.5f, foffset); // 공속이 높을 수록 4로 fAttackTime = 0.5f * atkSpeed; bcoward = false; if (isrunaway) { bcoward = true; } areaSize = BattleMgr.SGetBgSize(); minArea = areaSize.bounds.min; maxArea = areaSize.bounds.max; } public eCreatureClass GetCreatureClass() { return creatureClass; } public BigInteger GetAtk() { return biAtkCalc; } public float GetCrtDam() { return fCrtDamCalc; } public int GetCrtRate() { return iCrtRateCalc; } public eMonsterType GetMonType() { return eType; } public bool GetDie() { return bDead; } #endregion Stat #region Transform Control /// /// 타겟 위치 참조하여 바라보는 방향 변경. /// protected void ChangeFront() { ChangeLookDirection(v3TargetPos - transform.position); } protected void ChangeLookDirection(Vector2 lookDirection) { if (lookDirection.x >= 0) { IsLookRight = true; transform.rotation = Quaternion.Euler(Vector3.zero); } else { IsLookRight = false; transform.rotation = Quaternion.Euler(Global.V3_RotRev); } } public virtual void MoveToDir(Vector2 moveVec) { switch (animState) { case eState.warp_in: case eState.warp_out: case eState.die: case eState.rush: return; } Vector3 v3framepos; if(bFriendly) v3framepos = Vector3.MoveTowards(transform.position, transform.position + (Vector3)moveVec, fMovCalc * Time.deltaTime); else v3framepos = Vector3.MoveTowards(transform.position, v3TargetPos, fMovCalc * Time.deltaTime); v3framepos.z = v3framepos.y * 2f; transform.position = v3framepos; ChangeLookDirection(moveVec); } // 타겟쪽으로 이동(1프레임). protected virtual float MoveToTarget() { if (!bcoward) { if (fMovCalc <= 0f) return fTargetDistance; } ChangeFront(); Vector3 v3framepos = Vector3.MoveTowards(transform.position, v3TargetPos, fMovCalc * Time.deltaTime); v3framepos.z = v3framepos.y * 0.1f; transform.position = v3framepos; fTargetDistance = Vector3.Distance(v3framepos, v3TargetPos); return fTargetDistance; } // 타겟쪽에서 멀어짐 이동(1프레임). protected float BackToTarget() { if (!bcoward) { if (fMovCalc <= 0f) return fTargetDistance; } float speed = 0.02f; ChangeFront(); Vector3 moveVec = -(v3TargetPos - transform.position).normalized; transform.Translate(moveVec * fMovCalc * speed); Vector3 v3framepos = transform.position; if (bcoward) { if (v3framepos.x < -25) { v3framepos.x = -25; } if (v3framepos.x > 25) { v3framepos.x = 25; } if (v3framepos.y < -25) { v3framepos.y = -25; } if (v3framepos.y > 25) { v3framepos.y = 25; } } v3framepos.z = v3framepos.y * 2f; return fTargetDistance; } // 타겟쪽에게 대시 이동(1프레임). public void FrontToTarget(float fdt) { isdash = true; ChangeState(eState.rush); ChangeFront(); Vector3 moveVec = (v3TargetPos - transform.position).normalized * fdt;//5 가 거리 transform.DOMove(transform.position + moveVec, 0.8f).SetEase(Ease.OutQuart).OnComplete(InitPos);//1f 가 시간 } public void InitPos() { MoveToTarget(); } // 타겟 반대편으로 이동 public void FrontToTargetR(float fdt) { isdash = true; ChangeState(eState.rush); Vector3 moveVec = -((v3TargetPos - transform.position).normalized * fdt);//5 가 거리 transform.DOMove(transform.position + moveVec, 0.8f).SetEase(Ease.OutQuart);//1f 가 시간 } public virtual void MoveToRush(int dir, float fdt, bool bright) { Vector3 dirVec; switch (dir) { case 0: dirVec = Vector3.up; break; case 1: dirVec = Vector3.Lerp(Vector3.up, Vector3.right, 0.33f).normalized; break; case 2: dirVec = Vector3.Lerp(Vector3.up, Vector3.right, 0.66f).normalized; break; case 3: dirVec = Vector3.right; break; case 4: dirVec = Vector3.Lerp(Vector3.right, Vector3.down, 0.33f).normalized; break; case 5: dirVec = Vector3.Lerp(Vector3.right, Vector3.down, 0.66f).normalized; break; case 6: dirVec = Vector3.down; break; case 7: dirVec = Vector3.Lerp(Vector3.down, Vector3.left, 0.33f).normalized; break; case 8: dirVec = Vector3.Lerp(Vector3.down, Vector3.left, 0.66f).normalized; break; case 9: dirVec = Vector3.left; break; case 10: dirVec = Vector3.Lerp(Vector3.left, Vector3.up, 0.33f).normalized; break; case 11: dirVec = Vector3.Lerp(Vector3.left, Vector3.up, 0.66f).normalized; break; default: dirVec = (v3TargetPos - transform.position).normalized; break; } dirVec = bright ? dirVec : new Vector3(dirVec.x * -1, dirVec.y, dirVec.z); Vector3 moveVec = dirVec * fdt;//5 가 거리 transform.DOMove(transform.position + moveVec, 0.2f).SetEase(Ease.OutQuart);//1f 가 시간 } #endregion Transform Control #region Object Control public bool IsBattleAvail() { return gameObject.activeSelf && !bDead && !bSummon; } public bool IsSkillAvail() { return fTargetDistance <= fRange && (animState == eState.idle || animState == eState.attack); } // 애니메이션 로드 대기중. public void SetLoading() { bLoading = true; } public virtual void Summon(Vector3 v3pos, bool bfrontright) { transform.position = v3pos; ChangeLookDirection(new Vector2(bfrontright ? 1f : -1f, 0f)); skAnim.skeleton.A = 1.0f; Summon(); } public virtual void Summon() { // 애니메이션 로드 대기중이면 소환 안함. 로드 완료되는 타이밍에 소환. if (bLoading) { bSummonNeed = true; return; } bDead = false; bSummon = true; bAttackable = false; fAttackTick = 0f; iTargetIndex = -1; v3TargetPos = Vector3.zero; fTargetDistance = 9999f; gameObject.SetActive(true); twcSummon.Restart(); // 애니메이션이 다른 애니메이션으로 변경되면 새로 로드해야 함. if (bInitNeed) { bInitNeed = false; anState = skAnim.AnimationState; } ChangeStateForce(eState.idle); } public void SummonEnd() { bSummon = false; SearchTarget(); } public virtual void Unsummon() { bSummon = true; bAttackable = false; twcUnsummon.Restart(); } public void UnsummonEnd() { ChangeState(eState.idle); gameObject.SetActive(false); } #endregion Object Control #region Skill & Attack // 사용 가능한 스킬 추가. public void AddSkillAvail(int idx) { stackSkillAvail.Push(idx); } // 사용 가능한 스킬 모두 제거. public void ClearSkillAvail() { stackSkillAvail.Clear(); } public virtual void CheckSkill() { if (bStun) return; // 스킬이 사용 가능할 경우. if (stackSkillAvail.Count > 0 && iTargetIndex >= 0) { //스킬 사용부분 int iskillid = stackSkillAvail.Peek(); int iskillused = battleMgr.UseSkill(bFriendly, iskillid, transform, iTargetIndex, battleMgr.GetTrfTarget(bFriendly, iTargetIndex), fCrtDamCalc, iCrtRateCalc, biAtkCalc); // 스킬 사용함. if (iskillused > 0) { stackSkillAvail.Pop(); //ChangeState(eState.skill); return; } // 사용할 수 없는 스킬. if (iskillused < 0) { stackSkillAvail.Pop(); } } } public virtual void CheckAttack() { if (bStun) return; // 공격 가능(공격 속도 참조한 공격 시간 경과됨). if (bAttackable) { ChangeFront(); ChangeState(eState.attack); } else { ChangeState(eState.idle); } } #endregion Skill & Attack #region Damage & Heal & Die // 피격. public virtual void GetDamage(BigInteger biatk, float fcrtdam, int icrtrate, int idnum = -1) { if (BattleMgr.Instance.BattlePause || bSummon || bDead) return; bool bcrt = BattleMgr.SIsCritical(icrtrate); biatk = BattleMgr.SCalcCrtDam(biatk, fcrtdam, icrtrate, bcrt); if (biHpCur >= biHpCalc) biHpCur = biHpCalc; if (idnum != -1) BattleMgr.SSetTotalDamage(biatk, idnum); biHpCur -= biatk; if (biHpCur <= 0) { hpBar.ShowHpBar(0f); battleMgr.AddPvpDamage(bFriendly, biatk, 0f); ChangeState(eState.die); } else { float fractionalPart = (float)(biHpCur * 10000 / biHpCalc) / 10000f; hpBar.ShowHpBar(fractionalPart); battleMgr.AddPvpDamage(bFriendly, biatk, fractionalPart); if (eType == eMonsterType.SmallObject) { ChangeState(eState.damage); } else { StartCoroutine(DamagedRed()); } } battleMgr.ShowDamage(creatureClass, transform.position, biatk, bcrt); if (!ptcHit.isPlaying) { ptcHit.Play(); } } IEnumerator DamagedRed() { Spine.Skeleton sk = skAnim.skeleton; sk.SetColor(Color.red); yield return new WaitForSeconds(0.2f); sk.SetColor(Color.white); } // 회복. public void GetHeal(float frate) { if (BattleMgr.Instance.BattlePause || bSummon || bDead) return; BigInteger birate = new BigInteger(frate); BigInteger biadd = biHpCalc * birate / dConst.RateMaxBi; biHpCur += biadd; if (biHpCur > biHpCalc) biHpCur = biHpCalc; //hpBar.ShowHpBar(fHpCur / fHpCalc); } // 사망시 처리. protected virtual void OnDie() { bDead = true; bAttackable = false; SetIsDamaged(0); twcUnsummon.Restart(); } public int GetIsDamaged() { return isDamaged; } public void SetIsDamaged(int idamcnt) { isDamaged = idamcnt; } #endregion Damage & Heal & Die #region Debuff // 초기화. public void ResetDebuff() { fDecMov = 0f; fDecMovTime = 0f; damageSecs[0].time = 0; damageSecs[1].time = 0; bStun = false; fStunTime = 0f; fStunPushCool = 0f; } // 둔화(이동속도 감소). public void SetDecMove(float ftime, float fvalue) { if (BattleMgr.Instance.BattlePause || bSummon || bDead) return; if (fvalue < fDecMov) return; fDecMov = fvalue; fDecMovTime = ftime; fMovCalc = fMov * 12f / dConst.RateMaxFloat; if (fDecMov > 0f) fMovCalc *= (1f - fDecMov); } // 출혈(초당 데미지). public virtual void SetDamageSec(float ftime, BigInteger fatk, float fcrtdam, int icrtrate, int idnum = -1) { if (BattleMgr.Instance.BattlePause || bSummon || bDead) return; if (ftime < 1f) { return; } int index = -1; for (int i = 0; i < damageSecs.Length; i++) { if (damageSecs[i].time <= 0f) { index = i; break; } } if (index < 0) return; damageSecs[index].time = ftime - 0.05f; damageSecs[index].sec = (int)ftime; damageSecs[index].atk = fatk; damageSecs[index].crtdam = fcrtdam; damageSecs[index].crtrate = icrtrate; damageSecs[index].idnum = idnum; iDamageSecCnt++; } // 기절. public void SetStun(float ftime) { if (BattleMgr.Instance.BattlePause || bSummon || bDead) return; if (fStunPushCool > 0f) return; if (!bStun) { bStun = true; ChangeStateForce(eState.idle); if (creatureClass == eCreatureClass.boss) fStunPushCool = 10f; } fStunTime = ftime; } // 넉백. public void PushFrame(float fpushforce) { if (BattleMgr.Instance.BattlePause || bSummon || bDead || fStunPushCool > 0f) return; if (BattleMgr.CurrentBattleType != BattleMgr.BattleType.GuardianDungeon) PushFrameForce(fpushforce); if (creatureClass == eCreatureClass.boss) fStunPushCool = 10f; } // 넉백(강제). protected void PushFrameForce(float fpushforce) { if (BattleMgr.CurrentBattleType == BattleMgr.BattleType.GuardianDungeon) return; Vector3 v3moveto = transform.position; fpushforce *= 0.5f; if (IsLookRight) { v3moveto.x -= 10; } else { v3moveto.x += 10; } Vector3 v3framepos = Vector3.MoveTowards(transform.position, v3moveto, fpushforce); Vector3 v3movedir = v3framepos - transform.position; v3movedir.y = 0f; v3movedir.z = 0f; if (gameObject.activeSelf) { transform.DOLocalMoveX(transform.position.x + v3movedir.x, 0.5f).SetEase(Ease.OutExpo); } } // 끌어당기기 public void SetPull(BigInteger bivalue, Transform trf) { if (BattleMgr.CurrentBattleType != BattleMgr.BattleType.GuardianDungeon) return; float pullValue = (int)bivalue / dConst.RateMaxFloat; Vector3 v3framepos = Vector3.Lerp(transform.position, trf.position, pullValue); if (gameObject.activeSelf) { transform.DOLocalMove(v3framepos, 0.5f).SetEase(Ease.Linear); } } #endregion Debuff #region Target & Animation // 타겟 위치 찾기. protected virtual void SearchTarget() { } // 애니메이션 이벤트. 스파인 애니메이션에서 호출됨. void HandleAnimEvent(TrackEntry trackEntry, Spine.Event e) { if(animEventHandlers.TryGetValue(e.Data, out System.Action action)) action(); } // 캐릭터 상태(애니메이션) 변경. public void ChangeState(eState changestate) { if (changestate == animState && changestate != eState.attack && changestate != eState.skill) return; animState = changestate; switch (animState) { case eState.idle: anState.SetAnimation(0, "idle", true); break; case eState.move: anState.SetAnimation(0, "move", true); break; case eState.attack: case eState.attack2: case eState.attack3: bAttackable = false; fAttackTick = 0f; anState.SetAnimation(0, animState.ToString(), false); break; case eState.die: anState.SetAnimation(0, "die", false); OnDie(); break; case eState.skill: bAttackable = false; fAttackTick = 0f; anState.SetAnimation(0, "attack", false); break; case eState.ceremony: anState.SetAnimation(0, "ceremony", false); break; case eState.warp_in: anState.SetAnimation(0, "warp_in", false); break; case eState.warp_out: anState.SetAnimation(0, "warp_out", false); break; case eState.rush: anState.SetAnimation(0, "rush", false); break; case eState.damage: anState.SetAnimation(0, "damage", false); break; } } // 캐릭터 상태(애니메이션) 변경. public void ChangeStateForce(eState changestate) { if (anState == null) return; animState = changestate; switch (animState) { case eState.idle: anState.SetAnimation(0, "idle", true); if (BattleMgr.CurrentBattleType == BattleMgr.BattleType.GuardianDungeon) { skAnim.gameObject.SetActive(true); ChangeLookDirection(new Vector2(-1.0f, 0.0f)); } else { skAnim.gameObject.SetActive(true); ChangeLookDirection(new Vector2(1.0f, 0.0f)); } break; case eState.move: anState.SetAnimation(0, "move", true); break; case eState.attack: bAttackable = false; fAttackTick = 0f; anState.SetAnimation(0, animState.ToString(), false); break; case eState.die: anState.SetAnimation(0, "die", false); OnDie(); break; case eState.skill: bAttackable = false; fAttackTick = 0f; anState.SetAnimation(0, "attack", false); break; case eState.ceremony: anState.SetAnimation(0, "ceremony", false); break; case eState.warp_in: anState.SetAnimation(0, "warp_in", false); break; case eState.warp_out: anState.SetAnimation(0, "warp_out", false); break; case eState.rush: anState.SetAnimation(0, "rush", false); break; case eState.damage: anState.SetAnimation(0, "damage", false); break; } } //애니메이션 속도 초가화 public void initAnimTimeScale() { if (animState == eState.attack || animState == eState.attack2) skAnim.timeScale = 1.0f * motionSpeed; else skAnim.timeScale = 1.0f; } #endregion Target & Animation #region Ani Event // 공격 애니메이션 마지막 프레임. public void CheckAttackState() { SearchTarget(); } // 스킬 애니메이션 마지막 프레임. public void CheckSkillState() { SearchTarget(); } // 피해받음 애니메이션 마지막 프레임. public void CheckDamageState() { ChangeState(eState.idle); } // 사망 애니메이션 마지막 프레임. public void CheckDieState() { } // 세레머니 애니메이션 마지막 프레임. public void CheckCeremonyState() { ChangeState(eState.idle); } public void CheckWarp_outState() { ChangeState(eState.idle); } // 공격 애니메이션 중간 키(데미지 타이밍). public virtual void AttackDamage() { } // 스킬 애니메이션 중간 키. public virtual void SkillEffect() { } #endregion Ani Event }