using Spine.Unity; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; public enum IFF { Our, Enemy } public enum DamageType { None, Fixed, Rate, RateHP, Bomb } public class Character : MonoBehaviour { public enum State { HP_Change, AH_Change, Mana_Change, Inventory, EquipItem, Status, ActiveItem } public EventConnect ec = new EventConnect(); [HideInInspector] public MapManager map; public string NAME = ""; CharacterData data; [Space(10)] public IFF iff = IFF.Our; public bool ALL_HIT = false; public Collider hitBox; [HideInInspector] public int layerEnemy = 0; private void Awake() { map = (MapManager)FindObjectOfType(typeof(MapManager)); skill = GetComponent(); motion = GetComponent(); statusEffect = GetComponent(); } private void Start() { tag = $"{iff}"; //gameObject.layer = LayerMask.NameToLayer($"{(iff)}"); layerEnemy = ALL_HIT ? LayerMask.NameToLayer($"HitAll") : LayerMask.NameToLayer($"Hit{(iff == IFF.Our ? IFF.Enemy : IFF.Our)}"); hitBox.gameObject.layer = LayerMask.NameToLayer($"{(iff)}"); gameObject.tag = $"{(iff)}"; if (matHP30Down != null) { matHP30Down_Copy = new Material(matHP30Down); matHP30Down_Copy.SetTexture("_MainTex", mainTexture); scvHP30Down.Init(matHP30Down_Copy); } matHit_Copy = new Material(matHit); matHit_Copy.SetTexture("_MainTex", mainTexture); if (matHitResist != null) { matHitResist_Copy = new Material(matHitResist); matHitResist_Copy.SetTexture("_MainTex", mainTexture); } matHide_Copy = new Material(matHide); matHide_Copy.SetTexture("_MainTex", mainTexture); scvHide.Init(matHide_Copy); } Collider col; void OnCollider(bool ON) { if (col == null) col = GetComponent(); col.enabled = ON; } /// /// 캐릭터의 초기화를 진행합니다 /// /// /// public void OnInitialized(int HP, int HP_Max) { if(HP == 0) { if (data == null) data = DataManager.Instance.dataMonster.DataGet(NAME); HP = HP_Max = data.HP; ATK = data.ATK; DLAATK = data.DLA_ATK; SPDMOVE = data.SPD_MOVE; motion.RunMotionTimeScaleChanged(data.MOVE_TIME_SCALE); } statusEffect.OnInitialized();//생성했을때 상태이상 초기화 this.HP_Max = HP_Max; ec.Invoke((int)State.HP_Change, this.HP = HP); OnCollider(true); Material_Restore(); OnDefense = null; hitBox.gameObject.SetActive(true); Show_Start(); } //public void ProjectileCreate(Projectile projectile, Vector3 dir) //{ // if (projectile != null) // { // projectile.spdMovePer = SPDATK_PER; // projectile.OnInitialized(this, layerEnemy, dir, RANGE); // } //} [Space(10)] public int ATK = 1; /// /// 캐릭터의 공격력이 변경 되어 이를 적용합니다. /// /// /// public void ATKChanged(int value, bool ADD = true) { if (ADD) ATK += value; else ATK = value; ec.Invoke((int)Character.State.Status, null); } public float ATK_PER = 1f; /// /// 캐릭터의 공경력 % 가 변경 되어 이를 적용합니다. /// /// /// public void ATKRateChanged(float value, bool ADD = true) { if (ADD) ATK_PER += value; else ATK_PER = value; ec.Invoke((int)Character.State.Status, null); } /// /// 현재 데미지를 계산합니다. /// /// public int CalculateATK() { return (int)(ATK * ATK_PER); } [Space(10)] public float SPDATK_PER = 1f; [System.Serializable] public class SpeedAttackEvent : UnityEvent { } [FormerlySerializedAs("onSpeedAttacked")] [SerializeField] [Space(10)] private SpeedAttackEvent onSpeedAttacked = new SpeedAttackEvent(); /// /// 공격 모션의 속도가 변경 되어 이를 적용합니다. /// /// /// public void SpeedAttackChanged(float value, bool ADD = true) { if (ADD) SPDATK_PER += value; else SPDATK_PER = value; onSpeedAttacked.Invoke(SPDATK_PER); ec.Invoke((int)Character.State.Status, null); } [Space(10)] public float DLAATK = 0; public float DLAATK_PER = 1f; public float DLAATK_MIN = 0.2f; //[System.Serializable] //public class DelayAttackEvent : UnityEvent { } //[FormerlySerializedAs("onDelayAttacked")] //[SerializeField] //[Space(10)] //private DelayAttackEvent onDelayAttacked = new DelayAttackEvent(); /// /// 공격 후 딜레이가 변경되어 이를 적용합니다. /// /// /// public void DelayAttackChanged(float add, bool RATE = true) { if (RATE) { DLAATK_PER += add; //onDelayAttacked.Invoke(DLAATK_PER, RATE); } else { DLAATK = add; //onDelayAttacked.Invoke(DLAATK, RATE); } //ec.Invoke((int)Character.State.Status, null); } /// /// 현재 후 딜레이를 계산합니다. /// /// public float CalculateAttackDelay() { float apply = DLAATK * DLAATK_PER; apply = apply < DLAATK_MIN ? DLAATK_MIN : apply; return apply; } [Space(10)] public float RANGE = 0; //[System.Serializable] //public class RangeEvent : UnityEvent { } //[FormerlySerializedAs("onRangeed")] //[SerializeField] //[Space(10)] //private RangeEvent onRangeed = new RangeEvent(); /// /// 사거리가 변경 되어 이를 적용합니다. /// /// /// public void RangeChanged(float add, bool RATE) { if (RATE) { RANGE += add; //onRangeed.Invoke(DLAATK_PER, RATE); } else { RANGE = add; //onRangeed.Invoke(DLAATK, RATE); } //ec.Invoke((int)Character.State.Status, null); } [Space(10)] public float SPDMOVE = 40;//기본 이동 속도, 캐릭터 마다 다름 public float SPDMOVE_PER = 1f; [System.Serializable] public class SpeedMoveEvent : UnityEvent { } [FormerlySerializedAs("onSpeedMoveed")] [SerializeField] [Space(10)] private SpeedMoveEvent onSpeedMoveed = new SpeedMoveEvent(); /// /// 이동 속도가 변경되어 이를 적용합니다. /// /// public void SpeedMoveChanged(float add) { SPDMOVE_PER += add; onSpeedMoveed.Invoke(SPDMOVE_PER); ec.Invoke((int)Character.State.Status, null); } /// /// 현재 이동속도를 계산합니다. /// /// public float CalculateMoveSpeed() { float spdMoveApply = SPDMOVE * SPDMOVE_PER; return spdMoveApply < 60 ? spdMoveApply : 60; } int LIFE_MAX = 20; public bool LifeMaxCheck() { return HP_Max + AH <= LIFE_MAX; } [Space(10)] public int HP = 3; public bool DieCheck() { return HP < 1; } public int HP_Max = 4; /// /// HP 에 데미지를 적용합니다. /// /// public void CalculateHP(int Damage) { if (0 < AH) { AH -= Damage; Damage = AH < 0 ? -AH : 0; } HP -= Damage; } /// /// HP 가 회복 가능한 상태인지 확인합니다. /// /// /// public bool RestoreCheck(int value) { return HP < HP_Max; } /// /// HP 를 회복합니다. /// /// /// public bool Restore(int value) { if (HP < HP_Max) { HP += value; HP = HP_Max < HP ? HP_Max : HP; ec.Invoke((int)State.HP_Change, HP); return true; } return false; } /// /// HP 를 최대 HP 의 % 로 회복합니다. /// /// /// public bool Restore_MaxRate(float value) { HP += (int)(HP_Max * value); HP = HP_Max < HP ? HP_Max : HP; Material_Restore(); ec.Invoke((int)State.HP_Change, HP); return true; } private void OnDisable() { //scvHide.isPlay = false; scvHide.Stop(); } public int AH = 0; /// /// 아머하트가 변경되어 이를 적용합니다. /// /// public void OnChanged_ArmorHeart(int value) { AH = value; ec.Invoke((int)State.HP_Change, 0); } /// /// 아머하트가 회복 가능한지 확인합니다. /// /// /// public bool ArmorHeartCheck(int add) { int MAX = LIFE_MAX - HP_Max; return AH + add <= MAX; } /// /// 아머하트를 회복합니다. /// /// /// public bool OnAdded_ArmorHeart(int add) { int MAX = LIFE_MAX - HP_Max; if (AH + add <= MAX) { AH = AH + add; ec.Invoke((int)State.HP_Change, HP); return true; } return false; } public int MANA = 0; public int MANA_MAX = 0; /// /// 마나 회복 가능한지 확인합니다. /// /// public bool ManaCheck() { return MANA < MANA_MAX; } /// /// 마나를 회복 혹은 사용 합니다. /// /// /// public bool OnAdded_Mana(int add) { if (MANA + add <= MANA_MAX) { MANA += add; MANA = MANA_MAX < MANA ? MANA_MAX : MANA; ec.Invoke((int)State.Mana_Change, MANA); return true; } return false; } public System.Action OnDie;//Character -> 때린 캐릭터 public System.Action OnHit;//Character -> 죽은 캐릭터 public System.Action OnDefense; public System.Action OnResurrection; /// /// 공격을 받았으며 이를 처리합니다. /// HitBox.cs -> OnDamaged 에서 호출 /// /// public void Damaged(object data) { if (NO_DAMAGE) return; HitCheck hitCheck = (HitCheck)data; //if (hitCheck.owner == this && hitCheck.damageType != DamageType.Bomb) // return; if (OnDefense != null) { Debug.LogWarning($"{name} 가 {((HitCheck)data).owner.name} 의 공격을 방어"); System.Action OnDefenseTmp = OnDefense; OnDefense = null; OnDefenseTmp(((HitCheck)data).owner); return; } //회피 스킬이 있는 경우, 회피율 확인 if (skill.Active("Hit", "Flee") != null) { Debug.LogWarning($"{name} 가 {hitCheck.owner.name} 의 공격을 회피"); } else { Damaged_Process(hitCheck); } } [Space(10)] public SoundPlayer spHit; public SoundPlayer spDie; /// /// 캐릭터가 받은 데미지를 적용합니다. /// /// 공격한 캐릭터 void Damaged_Process(HitCheck attacker) { if (spHit != null) SoundManager.Instance.EffectPlay(spHit.gameObject); Character owner = attacker.owner; int damage = 0; switch(attacker.damageType) { case DamageType.None: damage = owner.CalculateATK(); break; case DamageType.Fixed://고정 데미지 damage = (int)attacker.damageValue; break; case DamageType.Rate://배율 데미지 damage = (int)(owner.CalculateATK() * attacker.damageValue); break; case DamageType.RateHP://HP 비율 { if (iff == IFF.Enemy) damage = (int)(HP * attacker.damageValue); else if (iff == IFF.Our) damage = 1;//플레이어는 HP 방식이 다름 } break; case DamageType.Bomb://폭탄 데미지 { if (iff == IFF.Enemy) damage = (int)attacker.damageValue; else if (iff == IFF.Our) damage = 1;//플레이어는 HP 방식이 다름 } break; } Damage_Added(ref damage); //if (iff == owner.iff) //{ // if (iff == IFF.Enemy) // damage = 150; // else if (iff == IFF.Our) // damage = 1;//플레이어는 HP 방식이 다름 //} Vector3 dir = -(attacker.transform.position - transform.position).normalized; Damage_Apply(damage, owner, dir, 0, false, () => { List list = owner.skill.SkillList_Trigger("HitEnd"); for (int i = 0; i < list.Count; i++) { if (list[i].Activate("HitEnd")) { switch (list[i].work) { case "StatusEffect": { statusEffect.Add(list[i]); } break; } } } }); } void Damage_Added(ref int damage) { float mag = 1; StatusEffect.Data data = statusEffect.Check("Freeze");//상태이상:빙결 효과 걸린 상태에서 맞게되면 추가 데미지 if (data != null) mag += data.skill.value; //Debug.LogWarning($"추가데미지 : {damage} * {mag}"); damage = (int)(damage * mag); //Debug.LogWarning($"최종데미지 : {damage}"); } public void Damage_Apply(int damage, Character attacker, Vector3 knockBackDir, float knockBackAddRate, bool MOTION_FORCE, System.Action OnEnd) { CalculateHP(damage);//데미지 적용 ec.Invoke((int)State.HP_Change, 0); if (DieCheck()) { NO_DAMAGE = true; hitBox.gameObject.SetActive(false); OnLocked(true);//죽었음 행동 정지 statusEffect.OnInitialized();//죽었을 경우, 상태이상 초기화 bool SKILL = skill.Active("Die", null, active => { switch (active.work) { case "Resurrection"://몬스터 부활 { motion.isSkill = true; Restore_MaxRate(active.value); motion.typeBefore = Motion.Type.AttackEnd; motion.Play_Force(active.motion2, () => { OnCollider(true); motion.Play(Motion.Type.Idle, true); Invoke("Resurrection", 0.5f); }); } break; } }); //캐릭터 죽음 if (OnDie != null && !SKILL) { if (spDie != null) SoundManager.Instance.EffectPlay(spDie.gameObject); OnCollider(false); OnDie(this, Vector3.zero); } } else { Hit_Start(durationHit);//히트 이팩트 실행 if (OnHit != null) OnHit(attacker, knockBackDir, knockBackAddRate, false, () => { if (OnEnd != null) OnEnd(); }); } } /// /// 스킬 등에 의해 부활했을 경우 초기화를 진행합니다 /// public void Resurrection() { NO_DAMAGE = false; motion.isSkill = false; OnDefense = null; hitBox.gameObject.SetActive(true); gameObject.SetActive(true); OnLocked(false); OnResurrection(this); } [Space(10)] public Motion motion; [Space(10)] public Skill skill; [Space(10)] public StatusEffect statusEffect; [Space(5)] public bool STUN = false; [Space(10)] public List drops; void DropInitialized() { for (int i = 0; i < drops.Count; i++) drops[i].Get(); } [System.Serializable] public class LockEvent : UnityEvent { } [FormerlySerializedAs("onLocked")] [SerializeField] [Space(10)] private LockEvent onLocked = new LockEvent(); /// /// 캐릭터의 행동을 제한 합니다. /// /// public void OnLocked(bool LOCK) { onLocked.Invoke(LOCK); } [System.Serializable] public class WarpEvent : UnityEvent { } [FormerlySerializedAs("onWarp")] [SerializeField] [Space(10)] private WarpEvent onWarp = new WarpEvent(); /// /// 캐릭터를 해당 위치로 이동시킵니다. /// /// public void OnWarp(Vector3 pos) { onWarp.Invoke(pos); } /// /// 아이템을 습득했으므로 습득모션을 재생합니다. /// /// /// public void OnGain(GameObject dropItem, System.Action OnEvent) { OnLocked(NO_DAMAGE = true); motion.Play(Motion.Type.AttackEnd); motion.Play(Motion.Type.Get, true, () => { OnLocked(NO_DAMAGE = false); }, ev => { OnEvent(); }, true); } [Space(10)] public SkeletonAnimation skeletonAnimation; public Texture mainTexture; public Material matOrigin; [Space(5)] public bool USE_HP30DOWN = true; public float hpDownRate = 0.2f; public ShaderControlValue scvHP30Down; public Material matHP30Down; Material matHP30Down_Copy; /// /// HP 의 % 에 따라 다른 효과를 적용합니다. /// public void Material_Restore() { if (USE_HP30DOWN && matHP30Down_Copy != null && HP < HP_Max * hpDownRate) { skeletonAnimation.CustomMaterialOverride[matOrigin] = matHP30Down_Copy; scvHP30Down.Play(true, true, null); } else { skeletonAnimation.CustomMaterialOverride.Remove(matOrigin); scvHP30Down.Play(false, false, null); } } [Space(5)] public Material matHit; Material matHit_Copy; public float durationHit = 0f; [System.Serializable] public class HitSyncEvent : UnityEvent { } [FormerlySerializedAs("onHitSynced")] [SerializeField] private HitSyncEvent onHitSynced = new HitSyncEvent(); [Space(5)] public Material matHitResist; Material matHitResist_Copy; public bool HIT_RESIST = false; public float durationHitResist = 0f; public bool NO_DAMAGE = false; [System.Serializable] public class ResistSyncEvent : UnityEvent { } [FormerlySerializedAs("onResistSynced")] [SerializeField] private ResistSyncEvent onResistSynced = new ResistSyncEvent(); /// /// 히트 이팩트를 시작합니다. /// /// public void Hit_Start(float duration) { if (duration == 0) return; CancelInvoke("Hit_End"); skeletonAnimation.CustomMaterialOverride[matOrigin] = matHit_Copy; if (HIT_RESIST) NO_DAMAGE = true; Invoke("Hit_End", duration);//히트 이팩트 종료 onHitSynced.Invoke(duration);//스파인, 어태치먼트의 경우 따로 적용해야함 } /// /// 히트 이팩트를 종료하고 원상태로 되돌립니다 /// void Hit_End() { //skeletonAnimation.CustomMaterialOverride.Remove(matOrigin); Material_Restore(); //무적상태 적용 if (HIT_RESIST) { skeletonAnimation.CustomMaterialOverride[matOrigin] = matHitResist_Copy; Invoke("HitResist_End", durationHitResist); onResistSynced.Invoke(durationHitResist); } } /// /// 무적상태를 종료합니다. /// void HitResist_End() { //skeletonAnimation.CustomMaterialOverride.Remove(matOrigin); Material_Restore(); NO_DAMAGE = false; } [Space(10)] public ShaderControlValue scvHide; public Material matHide; Material matHide_Copy; /// /// 캐릭터가 죽었으므로 숨김이팩트를 시작합니다. /// public void Hide_Start() { skeletonAnimation.CustomMaterialOverride[matOrigin] = matHide_Copy; scvHide.Play(); } public void Hide_End() { Debug.LogWarning($"Hide_End"); //skeletonAnimation.CustomMaterialOverride.Remove(matOrigin); Material_Restore(); gameObject.SetActive(false); } [Space(10)] public ShaderControlValue scvShow; public Material matShow; Material matShow_Copy; /// /// 캐릭터가 초기화 됐으면 맵에 나타나게 합니다. /// public void Show_Start() { return; if (scvShow != null) { if (matShow != null) { if (matShow_Copy == null) { matShow_Copy = new Material(matShow); matShow_Copy.SetTexture("_MainTex", mainTexture); scvShow.Init(matShow_Copy); } skeletonAnimation.CustomMaterialOverride[matOrigin] = matShow_Copy; scvShow.Play(); } } } }