유니티 기초/복습

[주말과제] SimpleRPG 합치기

다모아 2023. 8. 14. 00:38

계속 hit.collider.tag나 OnTriggerEnter로 하고있었는데 Portal이 안되서 왜 안되지.. 하고 계속 고민하다가 생각해보니

Portal에 Collider가 없다는걸 깨달았다.. 이걸로 30분 동안,, 코드는 문제 없는거 같은데 왜 인식이 안되지.. 하고 고민했다

 

그리고

여기에서 계속 if(item != null) 안에서 Portal을 찾고있어서 안나왔던거였다..

 

그리고 버그가 있다. 버그는

버그 1. 캐릭터가 hit point로 가는데 공중으로 떠오른다.

버그 2. 마우스 클릭으로 몬스터 공격을 빨리하면 공격 모션도 없이 임팩트 모션이 나가서 몬스터가 죽는다.

 

모르는 부분

1. 보스스테이지에 영웅 장비 옮겨서 소환하기[InfoManager를 쓰면 될 것 같은데 어떻게 써야할지 잘 모르겠음]

2. Raycast로 Move 시키는건 알겠는데 ,

보스(Raycast를 이용하지 않고)를 범위내에 누군가 있으면 타겟한테 이동시키는걸 어떻게 해야할지 모르겠음.

3. Gizmo 공격사거리 부분을 어떻게 나타내셨는지도 모르겠음

 

못한 부분

1. 보스 스테이지 영웅 소환

2. 보스 공격, 이동, 피해를 받다, 특성


BossController [못했음]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Real
{
    public class BossController : MonoBehaviour
    {
        // Start is called before the first frame update
        void Start()
        {

        }

        // Update is called once per frame
        void Update()
        {

        }
        private void OnDrawGizmos()
        {
            GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 15, 40);
        }

        private void Move()
        {

        }
    }
}

 

GameEnums

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Real
{
    public class GameEnums
    {
        public enum eHeroType
        {
            Idle, Run, Attack
        }

        public enum eMonsterType
        {
            Idle, Hit, Die
        }

        public enum eMonsterSpawn
        {
            Slime, Turtle
        }

        public enum eItemType
        {
            Sword, Shield, HealthPotion
        }
    }
}

 

GraveDestroyer[묘지삭제]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GraveDestroyer : MonoBehaviour
{
    private GameObject graveGO;

    // Start is called before the first frame update
    void Start()
    {
        this.graveGO = GetComponent<GameObject>();
        this.StartCoroutine(this.CoWaitForSecondAfterDestroy());
    }

    private IEnumerator CoWaitForSecondAfterDestroy()
    {
        yield return new WaitForSeconds(1.5f);
        Destroy(this.gameObject);
    }
}

 

HeroController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace Real
{
    public class HeroController : MonoBehaviour
    {
        [SerializeField]
        private GameObject swordGo;
        [SerializeField]
        private GameObject shieldGo;
        [SerializeField]
        private GameObject swordPrefab;
        [SerializeField]
        private GameObject shieldPrefab;
        [SerializeField]
        private Transform weaponTrans;
        [SerializeField]
        private Transform shieldTrans;
        [SerializeField]
        private float radius = 1f; //반지름
        private float moveSpeed = 4f; //움직임속도
        private float impact = 0.39984f; //타격 임팩트 시간
        private int damage = 1;

        private MonsterController target;

        private GameEnums.eHeroType state;

        private Vector3 targetPosition;

        private Coroutine moveRoutine;
        private Coroutine attackRoutine;

        private Animator anim;

        public Action<MonsterController> onMoveComplete;

        public Action enterBossStage;
        public float Radius
        {
            get
            {
                return this.radius;
            }
        }

        public int Damage
        {
            get
            {
                return this.damage;
            }
        }
        // Start is called before the first frame update
        void Start()
        {
            this.anim = GetComponent<Animator>();
        }

        void Update()
        {
            //방패 착용중이 아니라면
            bool hasShield = this.HasShield();
            if (!hasShield)
            {
                //프리팹 인스턴스 생성
                GameObject go = Instantiate(this.shieldPrefab);
                //부모지정
                go.transform.SetParent(this.shieldTrans);
                //위치 초기화
                go.transform.localPosition = Vector3.zero;
                //회전 초기화
                go.transform.localRotation = Quaternion.Euler(-0.106f, -104.917f, 0.077f);
            }

            bool hasWeapon = this.HasWeapon();
            //무기 착용중이 아니라면
            if (!hasWeapon)
            {
                //프리팹 인스턴스 생성
                GameObject go = Instantiate(this.swordPrefab);
                //부모지정
                go.transform.SetParent(this.weaponTrans);
                //위치 초기화
                go.transform.localPosition = Vector3.zero;
                //회전 초기화
                go.transform.localRotation = Quaternion.identity;
            }
        }
        //몬스터타겟이 들어오면 거기로 움직이기
        public void Move(MonsterController target)
        {
            this.target = target;
            this.targetPosition = this.target.gameObject.transform.position;
            if (this.moveRoutine != null)
            {
                this.StopCoroutine(this.moveRoutine);
            }
            this.moveRoutine = this.StartCoroutine(this.CoMove());
        }

        //땅을 타겟하면 거기로 움직이기
        public void Move(Vector3 targetPosition)
        {
            this.target = null;
            this.targetPosition = targetPosition;

            if(this.moveRoutine != null)
            {
                this.StopCoroutine(this.moveRoutine);
            }
            this.moveRoutine = this.StartCoroutine(this.CoMove());
        }

        //코루틴으로 움직이기
        private IEnumerator CoMove()
        {
            while (true)
            {
                float distance = Vector3.Distance(this.transform.position, this.targetPosition);
                this.transform.LookAt(this.targetPosition);
                this.PlayAnimation(GameEnums.eHeroType.Run);
                this.transform.Translate(Vector3.forward * this.moveSpeed * Time.deltaTime);

                //몬스터 타겟이 있을경우
                if (this.target != null)
                {
                    if (distance <= 2f)
                    {
                        break;
                    }
                }
                else //몬스터 타겟이 없을경우
                {
                    //거리가 0.1f라면
                    if (distance <= 0.1f)
                    {
                        break;
                    }
                }
                yield return null;
            }
            this.PlayAnimation(GameEnums.eHeroType.Idle);
            this.onMoveComplete(this.target);
        }

        //공격
        public void Attack(MonsterController target)
        {
            if (this.attackRoutine != null)
            {
                this.StopCoroutine(this.attackRoutine);
            }
            this.attackRoutine = this.StartCoroutine(this.CoAttack());

        }

        //코루틴 공격
        private IEnumerator CoAttack()
        {
            this.PlayAnimation(GameEnums.eHeroType.Attack);

            yield return new WaitForSeconds(this.impact);
            Debug.Log("impact");

            AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
            yield return new WaitForSeconds(animStateInfo.length);

            this.PlayAnimation(GameEnums.eHeroType.Idle);

        }

        //애니메이션 멤버메서드
        private void PlayAnimation(GameEnums.eHeroType state)
        {
            if(this.state != state)
            {
                this.state = state;
                this.anim.SetInteger("State", (int)state);
            }
        }

        private void UnEquipWeapon()
        {
            if(this.swordGo != null)
            {
                Destroy(this.swordGo);
            }
            else
            {
                Debug.Log("착용중인 무기가 없습니다.");
            }
        }

        private void UnEquipShield()
        {
            if (this.shieldGo != null)
            {
                Destroy(this.shieldGo);
            }
            else
            {
                Debug.Log("착용중인 방패가 없습니다.");
            }
        }

        private bool HasWeapon()
        {
            return this.weaponTrans.childCount > 0;
        }

        private bool HasShield()
        {
            return this.shieldTrans.childCount > 0;
        }

        private void OnDrawGizmos()
        {
            GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1, 40);
        }

        private void OnTriggerEnter(Collider other)
        {
            ItemController item = other.gameObject.GetComponent<ItemController>();
            if(item != null)
            {
                Debug.LogFormat("itemType: {0}", item.itemType);

                if (item.gameObject.tag == "Sword")
                {
                    this.UnEquipWeapon();
                    Destroy(item.gameObject);
                }
                else if (item.gameObject.tag == "Shield")
                {
                    this.UnEquipShield();
                    Destroy(item.gameObject);
                }
            }

            if (other.gameObject.tag == "Portal")
            {
                Debug.LogFormat("other.tag : {0}", other.gameObject.tag);
                this.enterBossStage();
            }
        }
    }
}

 

HeroGenerator[모르겠음]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Real
{
    public class HeroGenerator : MonoBehaviour
    {
        [SerializeField]
        private GameObject heroPrefab;

        // Start is called before the first frame update
        void Start()
        {

        }

        // Update is called once per frame
        void Update()
        {

        }
        public void Generate()
        {
            GameEnums.eItemType weaponType = InfoManager.instance.GetWeapon();
            GameEnums.eItemType shieldType = InfoManager.instance.GetShield();
        }
    }
}

 

InfoManager [잘모르겠음]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Real
{
    public class InfoManager
    {
        public static readonly InfoManager instance = new InfoManager();

        private GameEnums.eItemType weaponType;
        private GameEnums.eItemType shieldType;

        public GameEnums.eItemType GetWeapon()
        {
            return this.weaponType;
        }
        public GameEnums.eItemType GetShield()
        {
            return this.shieldType;
        }
    }
}

 

ItemController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Real
{
    public class ItemController : MonoBehaviour
    {
        public GameEnums.eItemType itemType;
        // Start is called before the first frame update
        void Start()
        {

        }

        // Update is called once per frame
        void Update()
        {

        }
    }
}

 

ItemGenerator

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Real
{
    public class ItemGenerator : MonoBehaviour
    {
        [SerializeField]
        private List<GameObject> prefabList;

        // Start is called before the first frame update
        void Start()
        {

        }

        // Update is called once per frame
        void Update()
        {

        }

        public void Generate(GameEnums.eItemType itemType, Vector3 position)
        {
            //인덱스화
            int index = (int)itemType;
            //프리팹 선택하기
            GameObject prefab = this.prefabList[index];
            //프리팹 인스턴스화
            GameObject go = Instantiate(prefab);
            //위치설정
            go.transform.position = position;
        }
    }
}

 

MonsterController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace Real
{
    public class MonsterController : MonoBehaviour
    {
        [SerializeField]
        private float radius = 1f;
        private float impact = 0.39984f;
        private int hp = 3;
        private int maxHp;
        private Coroutine hitRoutine;

        private Animator anim;

        private GameEnums.eMonsterType state;

        public Action onHit;
        public Action onDie;

        public float Radius
        {
            get
            {
                return this.radius;
            }
        }

        public int Hp
        {
            get
            {
                return this.hp;
            }
            set
            {
                if (value == 0)
                {
                    Debug.LogFormat("몬스터의 체력: {0}", value);
                    this.Die();
                }
                else
                {
                    hp = value;
                    Debug.LogFormat("몬스터의 체력: {0}", hp);
                }
            }
        }

        // Start is called before the first frame update
        void Start()
        {
            this.anim = GetComponent<Animator>();
            this.maxHp = this.hp;
        }

        public void HitDamage()
        {
            if (this.hitRoutine != null)
            {
                this.StopCoroutine(this.hitRoutine);
            }
            this.hitRoutine = this.StartCoroutine(this.CoWaitCompleteHitDamage());
        }

        private IEnumerator CoWaitCompleteHitDamage()
        {
            yield return null;

            yield return new WaitForSeconds(this.impact);
            this.PlayAnimation(GameEnums.eMonsterType.Hit);
            this.onHit();

            AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
            yield return new WaitForSeconds(animStateInfo.length - this.impact * 2);
            this.PlayAnimation(GameEnums.eMonsterType.Idle);

        }

        public void Die()
        {
            Destroy(this.gameObject);
            this.onDie();

        }

        public void PlayAnimation(GameEnums.eMonsterType state)
        {
            if (this.state != null)
            {
                this.state = state;
                this.anim.SetInteger("State", (int)state);
            }
        }
        private void OnDrawGizmos()
        {
            GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1, 40);
        }
    }
}

 

MonsterGenerator

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Real {
    public class MonsterGenerator : MonoBehaviour
    {
        [SerializeField]
        private List<GameObject> prefabList;

        void Start()
        {
                
        }
        public MonsterController Generate(GameEnums.eMonsterSpawn spawn, Vector3 position)
        {
            //인덱스화
            int index = (int)spawn;
            //프리팹 배열에서 인덱스로 프리팹 가져옴
            GameObject prefab = this.prefabList[index];
            //프리팹 인스턴스 생성
            GameObject go = Instantiate(prefab);
            //위치 설정
            go.transform.position = position;

            return go.GetComponent<MonsterController>();
        }
    }
}

 

ParticleDestroyer

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Real
{
    public class ParticleDestroyer : MonoBehaviour
    {
        private ParticleSystem ps;
        // Start is called before the first frame update
        void Start()
        {
            this.ps = GetComponent<ParticleSystem>();
            this.StartCoroutine(this.CoWaitForPlayAfterDestroy());
        }

        private IEnumerator CoWaitForPlayAfterDestroy()
        {
            //프레임경과 기다렸다가
            yield return new WaitForSeconds(this.ps.duration);
            //파괴
            Destroy(this.gameObject);
        }
    }
}

 

Real_BossStageSceneMain [FadeIn]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace Real
{
    public class Real_BossStageSceneMain : MonoBehaviour
    {
        [SerializeField]
        private Image dim;

        // Start is called before the first frame update
        void Start()
        {
            this.StartCoroutine(this.FadeIn());
        }
        void Update()
        {

        }
        private IEnumerator FadeIn()
        {
            Color color = this.dim.color;

            while (true)
            {
                color.a -= 0.01f;
                this.dim.color = color;

                if (this.dim.color.a <= 0)
                {
                    break;
                }

                yield return null;
            }
        }
    }
}

 

Real_SimpleRpgSceneMain

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using static UnityEngine.GraphicsBuffer;

namespace Real
{
    public class Real_SimpleRpgSceneMain : MonoBehaviour
    {
        [SerializeField]
        private HeroController heroController;
        [SerializeField]
        private MonsterGenerator monsterGenerator;
        [SerializeField]
        private ItemGenerator itemGenerator;
        [SerializeField]
        private GameObject hitFxPrefab;
        [SerializeField]
        private GameObject gravePrefab;
        [SerializeField]
        private GameObject portalPrefab;
        [SerializeField]
        private Image dim;

        private MonsterController monsterController;
        private ItemController itemController;

        private List<MonsterController> monsterList;

        private Action onFadeOutComplete;
        // Start is called before the first frame update
        void Start()
        {
            this.heroController.enterBossStage = () => {
                this.StartCoroutine(this.FadeOut());
            };

            this.onFadeOutComplete = () => {
                SceneManager.LoadScene("Real_BossStageScene");
            };

            //컬렉션 사용 전 초기화
            this.monsterList = new List<MonsterController>();
            //몬스터 스폰
            MonsterController slime = this.monsterGenerator.Generate(GameEnums.eMonsterSpawn.Slime, new Vector3(0.5f, 0, 5));
            MonsterController turtle = this.monsterGenerator.Generate(GameEnums.eMonsterSpawn.Turtle, new Vector3(-5, 0, 5));
            //죽음 이벤트생성
            slime.onDie = () => {
                Debug.Log("죽음이벤트생성");

                Vector3 offset = new Vector3(0, 0, 0);
                Vector3 tpos = this.monsterController.transform.position;
                //프리팹 인스턴스화
                GameObject graveGo = Instantiate(this.gravePrefab);
                //위치설정
                graveGo.transform.position = tpos;
                //몬스터가 죽었을 때 추가했던 몬스터리스트 삭제
                this.monsterList.Remove(monsterController);
                Debug.LogFormat("남은 수: {0}", this.monsterList.Count);
                //아이템 드랍
                this.CreateItem(GameEnums.eItemType.Shield, slime.transform.position);

                //몬스터리스트가 0이라면
                if (this.monsterList.Count == 0)
                {
                    this.CreatePortal();
                }


            };
            //죽음 이벤트생성
            turtle.onDie = () => {
                Debug.Log("죽음이벤트생성");

                Vector3 offset = new Vector3(0, 0, 0);
                Vector3 tpos = this.monsterController.transform.position;
                //프리팹 인스턴스화
                GameObject graveGo = Instantiate(this.gravePrefab);
                //위치설정
                graveGo.transform.position = tpos;
                //몬스터가 죽었을 때 추가했던 몬스터리스트 삭제
                this.monsterList.Remove(monsterController);
                Debug.LogFormat("남은 수: {0}", this.monsterList.Count);
                //아이템드랍
                this.CreateItem(GameEnums.eItemType.Sword, turtle.transform.position);

                //몬스터리스트가 0이라면
                if (this.monsterList.Count == 0)
                {
                    this.CreatePortal();
                }

            };

            //몬스터 추가
            this.monsterList.Add(slime);
            this.monsterList.Add(turtle);

            //움직임이 끝난다면 이벤트 생성
            this.heroController.onMoveComplete = (target) => {
                //타겟이 있다면
                if (target != null)
                {
                    this.heroController.transform.LookAt(target.transform);
                    this.heroController.Attack(target);
                    this.monsterController.HitDamage();
                }
            };
        }

        // Update is called once per frame
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                Debug.DrawRay(ray.origin, ray.direction * 100f, Color.red, 2f);

                RaycastHit hit;
                if(Physics.Raycast(ray, out hit, 100f)) {
                    //몬스터라면
                    if (hit.collider.tag == "Monster")
                    {
                        MonsterController monsterController = hit.collider.gameObject.GetComponent<MonsterController>();
                        this.monsterController = monsterController;

                        //히트이펙트 이벤트생성
                        this.monsterController.onHit = () => {
                            Debug.Log("이펙트생성");

                            Vector3 offset = new Vector3(0, 0.5f, 0);
                            Vector3 tpos = this.monsterController.transform.position + offset;
                            //프리팹 인스턴스 생성
                            GameObject fxGo = Instantiate(this.hitFxPrefab);
                            //위치 설정
                            fxGo.transform.position = tpos;
                            //파티클 실행
                            fxGo.GetComponent<ParticleSystem>().Play();
                            monsterController.Hp -= heroController.Damage;

                        };

                        //두 원점사이의 거리
                        float distance = Vector3.Distance(this.heroController.transform.position, hit.collider.gameObject.transform.position);

                        //반지름의 합
                        float sumRadius = this.heroController.Radius + this.monsterController.Radius;
                        Debug.LogFormat("distance, sumRadius: {0}, {1}", distance, sumRadius);
                        Debug.LogFormat("hit.collider.tag : {0}", hit.collider.tag);

                        //distance <= sumRadius 가 true라면
                        if (distance <= sumRadius)
                        {
                            this.heroController.Attack(this.monsterController);
                            this.monsterController.HitDamage();
                        }
                        else 
                        {
                            this.heroController.Move(this.monsterController);
                        }
                    }
                    //땅이라면
                    else if(hit.collider.tag == "Ground")
                    {
                        Debug.LogFormat("hit.collider.tag : {0}", hit.collider.tag);
                        this.heroController.Move(hit.point);
                    }
                    else
                    {
                        this.heroController.Move(hit.point);
                    }
                }
            }
        }

        private void CreatePortal()
        {
            float rand = UnityEngine.Random.Range(-5f, 5f);

            //프리팹 인스턴스화
            GameObject go = Instantiate(this.portalPrefab);
            //위치설정
            go.transform.position = new Vector3(rand, 0, rand);
        }

        private void CreateItem(GameEnums.eItemType itemType, Vector3 position)
        {
            this.itemGenerator.Generate(itemType, position);
        }

        private IEnumerator FadeOut()
        {
            Color color = this.dim.color;

            while (true)
            {
                color.a += 0.01f;
                this.dim.color = color;

                if (this.dim.color.a >= 1)
                {
                    break;
                }

                yield return null;
            }
            this.onFadeOutComplete();
        }
    }
}

 

'유니티 기초 > 복습' 카테고리의 다른 글

SimpleRPG 복습  (0) 2023.08.08
로비씬, 사과, 폭탄 [복습]  (0) 2023.08.08
챕터6 ClimbCloud [미완] - 복습  (0) 2023.08.04
챕터 5 고양이 탈출 복습 - [완]  (0) 2023.08.04
챕터5 고양이탈출 - CatEscape - 복습  (0) 2023.08.02