K-digital traning/유니티 기초

[주말과제]SimpleRPG

내꺼블로그 2023. 8. 14. 02:19

어떻게든 해내려고 했는데,,, 합치는게 생각보다 많이 빡세네요,,,(사실 합치는 것만 문제는 아님)

테스트에서는 되던 녀석들이 합치려니까 null reference exception 자꾸 뜨고,,, 찾는 것도 일이고,,,

일단 그나마 합친 부분들 올려봅니당,,,

 

(1)게임씬

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

public class GameSceneMain : MonoBehaviour
{
    [SerializeField]
    private Image dim;

    private System.Action onFadeOutComplete;

    [SerializeField]
    private MonsterGenerator monsterGenerator;

    [SerializeField]
    private GameObject heroPrefab;

    [SerializeField]
    private GameObject weaponPrefab;
    [SerializeField]
    private GameObject shieldPrefab;

    [SerializeField]
    private GameObject portalPrefab;

    [SerializeField]
    private GameObject hitFxPrefab;


    [SerializeField]
    private ItemGenerator itemGenerator;

    private List<MonsterController> monsterList;
    private List<ItemController> itemList;
    private HeroController heroController;

    // Start is called before the first frame update
    void Start()
    {
        GameObject hero = Instantiate(heroPrefab);
        this.heroController = hero.GetComponent<HeroController>();
        hero.transform.position = Vector3.zero;

        heroController = hero.GetComponent<HeroController>();
        heroController.onGetItem = (item) =>
        {
            Debug.LogFormat("{0}을 획득하였습니다.", item.ItemType);
            itemList.Remove(item);
            Destroy(item.gameObject);
            heroController.SetItem(item);
        };

        this.onFadeOutComplete = () =>
        {
            Debug.Log("FadeOut 완료");
        };
        /*heroController.onArrivePortal = () =>
        {
            InfoManager.Instance.heroPosition = Vector3.zero;
            InfoManager.Instance.portalPosition = InfoManager.Instance.heroPosition;
            this.StartCoroutine(this.FadeOut());
        };*/

        this.heroController.onMoveComplete = (target) =>
        {
            Debug.Log("이동을 완료하였습니다.");
            if (target != null)
            {
                Debug.LogFormat("{0}을 공격합니다.", target);
                this.heroController.Attack(target);
            }
        };
        this.heroController.onAttackComplete = () =>
        {
            Debug.Log("공격 끝");
        };

        this.monsterList = new List<MonsterController>();
        this.itemList = new List<ItemController>();
        MonsterController turtle = this.monsterGenerator.Generate(GameEnums.eMonsterType.Turtle, new Vector3(-3, 0, 0));
        MonsterController slime = this.monsterGenerator.Generate(GameEnums.eMonsterType.Slime, new Vector3(0, 0, 3));

        monsterList.Add(turtle);
        monsterList.Add(slime);
        Debug.LogFormat("this.monsterList.Count: {0}", this.monsterList.Count);
        foreach (MonsterController monster in this.monsterList)
        {
            Debug.LogFormat("monster: {0}", monster);
        }

        //몬스터 대리자
        for (int i = 0; i < monsterList.Count; i++)
        {
            MonsterController temp = monsterList[i];
            temp.onDie = (item) =>
            {
                ItemController itemController = this.CreateItem(item, temp.transform.position);
                itemList.Add(itemController);
                Destroy(temp.gameObject);
                monsterList.Remove(temp);
                Debug.LogFormat("count: {0}", monsterList.Count);
                if (monsterList.Count == 0)
                {
                    this.CompleteAttackMonster();
                }
            };
        }

        for (int i = 0; i < monsterList.Count; i++)
        {
            this.monsterList[i].onHit = () =>
            {
                Debug.Log("이펙트 생성");

                Vector3 offset = new Vector3(0, 0.5f, 0);
                Vector3 tpos = this.monsterList[i].transform.position + offset;
                Debug.LogFormat("생성위치: {0}", tpos);

                GameObject fxGo = Instantiate(this.hitFxPrefab);
                fxGo.transform.position = tpos;
                fxGo.GetComponent<ParticleSystem>().Play();
            };
        }
    }

    // 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 * 1000f, Color.red, 2f);
            RaycastHit hit;
            if(Physics.Raycast(ray, out hit, 1000f))
            {
                Debug.LogFormat("hit collider: {0}", hit.collider.tag);
                if (hit.collider.tag == "Monster")
                {
                    this.heroController.Move(hit.collider.GetComponent<MonsterController>());
                }
                else
                    this.heroController.Move(hit.point);
            }
        }
    }
    private ItemController CreateItem(GameEnums.eItemType item, Vector3 position)
    {
        return this.itemGenerator.GenerateItem(item, position);
    }

    private void CompleteAttackMonster()
    {
        StartCoroutine(CoCreatePortal());
    }

    IEnumerator CoCreatePortal()
    {
        yield return new WaitForSeconds(0.3f);
        GameObject portal = Instantiate(portalPrefab);

        float x = Random.Range(-3.7f, 3.7f);
        float z = Random.Range(-3.7f, 3.7f);
        portal.transform.position = new Vector3(x, 0, z);
    }
}

 

 

 

(2)hero controller

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

public class HeroController : MonoBehaviour
{

    [SerializeField]
    private float moveSpeed = 1f;
    [SerializeField]
    private float radius = 1f;

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

    private float offset = 0.3f;
    private float impactTime = 0.399f;
    private Animator anim;
    private Coroutine moveCoroutine;
    private Coroutine attackCoroutine;
    private Coroutine hitDamageCoroutine;
    private Vector3 targetPosition;
    private GameEnums.eHeroState state;
    public System.Action<MonsterController> onMoveComplete;
    public System.Action onAttackComplete;
    private MonsterController target;
    private int damage = 8;

    public System.Action<ItemController> onGetItem;

    [SerializeField]
    private GameObject swordPrefab;

    [SerializeField]
    private GameObject shieldPrefab;

    [SerializeField]
    private GameObject weapon;
    [SerializeField]
    private GameObject shield;
    // Start is called before the first frame update
    void Start()
    {
        anim = this.GetComponent<Animator>();
    }

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

    //}


    //타겟(바닥)으로 이동
    public void Move(Vector3 targetPosition)
    {
        Debug.Log("바닥");
        this.target = null;
        this.targetPosition = targetPosition;
        this.transform.LookAt(this.targetPosition);
        this.PlayAnimation(GameEnums.eHeroState.Run);
    }

    //타겟(몬스터)으로 이동
    public void Move(MonsterController target)
    {
        Debug.Log("몬스터");
        this.target = target;
        this.targetPosition = this.target.transform.position;
        this.transform.LookAt(this.target.transform.position);
        this.PlayAnimation(GameEnums.eHeroState.Run);
    }


    //몬스터 공격
    public void Attack(MonsterController target)
    {
        this.target = target;
        this.PlayAnimation(GameEnums.eHeroState.Attack);
    }

    //애니메이션 작동
    private void PlayAnimation(GameEnums.eHeroState state)
    {
        if (this.state != state)
        {
            this.state = state;
            anim.SetInteger("State", (int)this.state);
        }
        switch (this.state)
        {
            //Run state
            case GameEnums.eHeroState.Run:
                if (moveCoroutine != null)
                {
                    StopCoroutine(moveCoroutine);
                }
                moveCoroutine = StartCoroutine(CoMove());
                break;
            //Attack state
            case GameEnums.eHeroState.Attack:
                if (attackCoroutine == null)
                {
                    attackCoroutine = StartCoroutine(CoAttack());
                }
                break;
        }
    }

    //이동 coroutine
    private IEnumerator CoMove()
    {
        GameEnums.eHeroState temp;
        while (true)
        {
            float distance = Vector3.Distance(this.transform.position, this.targetPosition);
            if (target != null) {
                if (distance <= this.radius + this.target.Radius - offset)
                {
                    temp = GameEnums.eHeroState.Attack;
                    break;
                }
            }
            if (distance < 0.1f)
            {
                temp = GameEnums.eHeroState.Idle;
                break;
            }
            this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
            yield return null;
        }
        this.PlayAnimation(temp);
        this.onMoveComplete(this.target);
    }

    //공격 coroutine
    private IEnumerator CoAttack()
    {
        yield return new WaitForSeconds(this.impactTime);
        Debug.Log("Impact");
        AnimatorStateInfo animatorStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
        target.HitDamage(animatorStateInfo.length - this.impactTime, this.damage);
        yield return new WaitForSeconds(animatorStateInfo.length - this.impactTime);
        this.PlayAnimation(GameEnums.eHeroState.Idle);
        this.attackCoroutine = null;
        this.onAttackComplete();
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, radius);
    }
    private void OnTriggerEnter(Collider other)
    {
        PlayAnimation(GameEnums.eHeroState.Idle);
        if (other.tag == "Item")
        {
            if (moveCoroutine != null)
                StopCoroutine(moveCoroutine);
            ItemController item = other.GetComponent<ItemController>();
            onGetItem(item);
        }
        else if (other.tag == "Portal")
        {

        }
    }

    public void SetItem(ItemController item)
    {
        GameEnums.eItemType itemType = item.ItemType;
        switch (itemType)
        {
            case GameEnums.eItemType.Sword:
                if (weapon.transform.childCount > 0)
                {
                    GameObject preWeapon = weapon.transform.GetChild(0).gameObject;
                    preWeapon.transform.SetParent(null);
                    float x = preWeapon.transform.position.x;
                    float z = preWeapon.transform.position.z;
                    preWeapon.transform.position = new Vector3(x, 0.3f, z);
                }
                GameObject swordGo = Instantiate(swordPrefab, weapon.transform);
                swordGo.transform.localPosition = Vector3.zero;
                swordGo.transform.localRotation = Quaternion.identity;
                break;
            case GameEnums.eItemType.Shield:
                if (shield.transform.childCount > 0)
                {
                    GameObject preShield = shield.transform.GetChild(0).gameObject;
                    preShield.transform.SetParent(null);
                    float x = preShield.transform.position.x;
                    float z = preShield.transform.position.z;
                    preShield.transform.position = new Vector3(x, 0.3f, z);
                }
                GameObject shieldGo = Instantiate(shieldPrefab, shield.transform);
                shieldGo.transform.localPosition = Vector3.zero;
                shieldGo.transform.localRotation = Quaternion.Euler(new Vector3(0, -80, 0));

                break;
            case GameEnums.eItemType.HealthPotion:
                break;
        }
    }
}

 

 

 

(3)monster controller

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

public class MonsterController : MonoBehaviour
{

    [SerializeField]
    private float radius = 1f;

    private float hitTime;
    public GameEnums.eMonsterType monsterType;
    private GameEnums.eMonsterState state;
    [SerializeField]
    private GameEnums.eItemType itemType;
    private Animator anim;
    private Coroutine getHitCoroutine;
    public System.Action onHit;
    public System.Action<GameEnums.eItemType> onDie;
    [SerializeField]
    private int hp;
    public float Radius
    {
        get { return this.radius; }
    }
    // Start is called before the first frame update
    void Start()
    {
        anim=this.GetComponent<Animator>();
    }

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

    //}

    public void HitDamage(float hitTime, int damage)
    {
        this.hitTime = hitTime;
        Debug.Log("hitDamage");
        this.hp -= damage;
        if (this.hp <= 0)
            PlayAnimation(GameEnums.eMonsterState.Die);
        else PlayAnimation(GameEnums.eMonsterState.Damage);
    }

    private void PlayAnimation(GameEnums.eMonsterState state)
    {
        if (this.state != state)
        {
            this.state = state;
            anim.SetInteger("State", (int)this.state);
        }
        switch (this.state)
        {
            case GameEnums.eMonsterState.Damage:
                {
                    if (getHitCoroutine == null)
                        getHitCoroutine = StartCoroutine(CoHitDamage());
                }
                break;
            case GameEnums.eMonsterState.Die:
                StartCoroutine(CoDie());
                break;
        }
    }

    private IEnumerator CoHitDamage()
    {
        Debug.Log("CoHitDamage");
        anim.Play("GetHit", -1, 0f);
        this.onHit();
        yield return new WaitForSeconds(0.833f);
        PlayAnimation(GameEnums.eMonsterState.Idle);
        getHitCoroutine = null;
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.cyan;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, radius);
    }

    public void Die()
    {
        StartCoroutine(CoDie());
    }

    private IEnumerator CoDie()
    {
        Debug.LogFormat("monster state: {0}", this.state);
        AnimatorStateInfo animatorStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
        yield return new WaitForSeconds(animatorStateInfo.length);
        onDie(this.itemType);
        yield return null;
    }
}

 

 

 

 

(4)item generator

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

public class ItemGenerator : MonoBehaviour
{
    [SerializeField]
    private List<GameObject> itemPrefabs;

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

    }

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

    }

    public ItemController GenerateItem(GameEnums.eItemType itemType, Vector3 position)
    {
        GameObject prefab = itemPrefabs[(int)itemType];
        GameObject item = Instantiate(prefab);
        item.transform.position = position;
        if (itemType == GameEnums.eItemType.Shield)
            item.transform.Translate(Vector3.up * 0.5f);
        return item.GetComponent<ItemController>();
    }
}

 

 

 

 

(5) item controller

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

public class ItemController : MonoBehaviour
{
    [SerializeField]
    private GameEnums.eItemType itemType;

    public GameEnums.eItemType ItemType { get { return itemType; } }
}

 

 

 

 

(6) monster generator

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

public class MonsterGenerator : MonoBehaviour
{
    

    [SerializeField]
    private List<GameObject> prefabList;
    // Start is called before the first frame update
    //void Start()
    //{

    //}

    /// <summary>
    /// 몬스터 생성
    /// </summary>
    /// <param name="monsterType">생성 하려고 하는 몬스터의 타입</param>
    /// <param name="initPosition">생성된 몬스터의 초기 월드좌표param>
    public MonsterController Generate(GameEnums.eMonsterType monsterType, Vector3 initPosition)
    {
        Debug.LogFormat("monster: {0}", monsterType);
        //몬스터 타입에 따라 어떤 프리팹으로 복사본(인스턴스)를 생성할지 결정 해야 함

        int index = (int)monsterType;

        GameObject prefab = this.prefabList[index];

        Debug.LogFormat("prefab: {0}", index);
        GameObject go = Instantiate(prefab);        //위치를 결정 하지 않은 상태이기때문(프리팹에 설정된 위치에 생성됨)
        go.transform.position = initPosition;
        MonsterController monsterController = go.GetComponent<MonsterController>();
        monsterController.monsterType = monsterType;

        return monsterController;

    }
}

 

 

 

(7)infomanager

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

public class InfoManager
{
    public static readonly InfoManager Instance = new InfoManager();
    public Vector3 heroPosition;
    public Vector3 portalPosition;

    public GameObject heroWeaponPrefab;
    public GameObject heroShieldPrefab;
    
    private InfoManager()
    {
    
    }

    /*public void SaveHeroItems()
    {

    }*/
}

 

 

 

(8)game enum

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

public class GameEnums
{
    public enum eMonsterType
    {
        Turtle, Slime
    }

    public enum eItemType
    {
        Sword, Shield, HealthPotion
    }

    public enum eHeroState
    {
        Idle, Run, Attack, Damage, Die
    }

    public enum eMonsterState
    {
        Idle, Run, Attack, Damage, Die
    }
}