반응형

RPG게임소스.zip
0.04MB

몬스터를 플레이어가 처치하고 난 다음 다시 몬스터를 재 생성시키는 작업을 하겠습니다

EnemyFSM 스크립트 수정

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

public class EnemyFSM : MonoBehaviour
{
    public enum State
    {
        Idle,  //정지
        Chase,  //추적
        Attack,  //공격
        Dead,   //사망
        NoState  //아무 일도 없는 상태
    }

    public State currentState = State.Idle;

    EnemyParams myParams;

    EnemyAni myAni;

    Transform player;

    PlayerParams playerParams;

    float chaseDistance = 5f; // 플레이어를 향해 몬스터가 추적을 시작할 거리
    float attackDistance = 2.5f; // 플레이어가 안쪽으로 들오오게 되면 공격을 시작
    float reChaseDistance = 3f; // 플레이어가 도망 갈 경우 얼마나 떨어져야 다시 추적

    float rotAnglePerSecond = 360f; // 초당 회전 각도
    float moveSpeed = 1.3f; // 몬스터의 이동 속도

    float attackDelay = 2f;
    float attackTimer = 0f;

    public ParticleSystem hitEffect;

    public GameObject selectMark;

    //리스폰 시킬 몬스터를 담을 변수 
    GameObject myRespawnObj;

    //리스폰 오브젝트에서 생성된 몇번째 몬스터에 대한 정보
    public int spawnID { get; set; }


    //몬스터가 처음 생성될때의 위치를 저장
    Vector3 originPos;

    void Start()
    {
        myAni = GetComponent<EnemyAni>();
        myParams = GetComponent<EnemyParams>();
        myParams.deadEvent.AddListener(CallDeadEvent);

        ChangeState(State.Idle, EnemyAni.IDLE);

        player = GameObject.FindGameObjectWithTag("Player").transform;
        playerParams = player.gameObject.GetComponent<PlayerParams>();

        hitEffect.Stop();
        HideSelection();
    }

    public void HideSelection()
    {
        selectMark.SetActive(false);
    }

    public void ShowSelection()
    {
        selectMark.SetActive(true);
    }

    // 몬스터가 어느 리스폰 오브젝트로부터 만들었졋는지에 대한 정보를 전달 받을 함수
    public void SetRespawnObj(GameObject respawnObj, int spawnID,Vector3 originPos)
    {
        myRespawnObj = respawnObj;
        this.spawnID = spawnID;
        this.originPos = originPos;
    }

    //몬스터가 죽는 순간 처리 명령어
    void CallDeadEvent()
    {
        ChangeState(State.Dead, EnemyAni.DIE);
        player.gameObject.SendMessage("CurrentEnemyDead");

        //몬스터가 사망했을때 나는 소리
        SoundManager.instance.PlayEnemyDie();

        StartCoroutine(RemoveMeFromWorld());
    }

    IEnumerator RemoveMeFromWorld()
    {
        yield return new WaitForSeconds(1f);

        ChangeState(State.Idle, EnemyAni.IDLE);

        //리스폰 오브젝트에 자기 자신을 제거해 달라는 요청
        myRespawnObj.GetComponent<RespawnObj>().RemoveMonster(spawnID);
    }

    public void ShowHitEffect()
    {
        hitEffect.Play();
    }

    public void AttackCalculate()
    {
        playerParams.SetEnemyAttack(myParams.GetRandomAttack());
    }

    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:
                IdleState();
                break;
            case State.Chase:
                ChaseState();
                break;
            case State.Attack:
                AttackState();
                break;
            case State.Dead:
                DeadState();
                break;
            case State.NoState:
                NoState();
                break;          
        }
    }

    public void ChangeState(State newState, string aniName)
    {
        if (currentState == newState)
        {
            return;
        }

        currentState = newState;
        myAni.ChangeAni(aniName);
    }
    void IdleState()
    {
        if (GetDistanceFromPlayer() < chaseDistance)
        {
            ChangeState(State.Chase, EnemyAni.WALK);
        }
    }

    void ChaseState()
    {
        //몬스터가 공격 가능 거리 안으로 들어가면 공격 상태
        if (GetDistanceFromPlayer() < attackDistance)
        {
            ChangeState(State.Attack, EnemyAni.ATTACK);
        }
        else
        {
            TurnToDestination();
            MoveToDestination();
        }   
    }

    void AttackState()
    {
        if (GetDistanceFromPlayer() > reChaseDistance)
        {
            attackTimer = 0f;
            ChangeState(State.Chase, EnemyAni.WALK);
        }
        else
        {
            if (attackTimer > attackDelay)
            {
                transform.LookAt(player.position);
                myAni.ChangeAni(EnemyAni.ATTACK);

                attackTimer = 0f;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            attackTimer += Time.deltaTime;
        }
    }
    void DeadState()
    {
        GetComponent<BoxCollider>().enabled = false;
    }
    void NoState()
    {

    }

    void TurnToDestination()
    {
        Quaternion lookRotation = Quaternion.LookRotation(player.position - transform.position);

        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        transform.position = Vector3.MoveTowards(transform.position, player.position, moveSpeed * Time.deltaTime);
    }

    //플레이어와 거리을 재는 함수
    float GetDistanceFromPlayer()
    {
        float distance = Vector3.Distance(transform.position, player.position);
        return distance;
    }

    // Update is called once per frame
    void Update()
    {
        UpdateState();
    }
}

 

 

 

RespawnObj 스크립트수정

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

public class RespawnObj : MonoBehaviour
{
    List<Transform> spawnPos = new List<Transform>();
    GameObject[] monsters;

    public GameObject monPrefab;
    public int spawnNumber = 1;
    public float respawnDelay = 3f;

    int deadMonsters = 0;
    void Start()
    {
        MakeSpawnPos();
    }
    void MakeSpawnPos()
    {
        foreach ( Transform pos in transform)
        {
            if (pos.tag == "Respawn")
            {
                spawnPos.Add(pos);
            }
        }
        if (spawnNumber > spawnPos.Count)
        {
            spawnNumber = spawnPos.Count;
        }

        monsters = new GameObject[spawnNumber];

        MakeMonsters();
    }

    //프리팹으로 부터 몬스터를 만들어 관리하는 함수
    void MakeMonsters()
    {
        for (int i = 0; i < spawnNumber; i++)
        {
            GameObject mon = Instantiate(monPrefab, spawnPos[i].position, Quaternion.identity) as GameObject;

            mon.GetComponent<EnemyFSM>().SetRespawnObj(gameObject, i, spawnPos[i].position);

            mon.SetActive(false);

            monsters[i] = mon;

            GameManager.instance.AddNewMonsters(mon);
        }
    }

    public void RemoveMonster(int spawnID)
    {
        //Destroy(monsters[spawnID]);

        deadMonsters++;

        monsters[spawnID].SetActive(false);
        print(spawnID + "monster was killed");

        //죽은 몬스터의 숫자가 몬스터 배열의 크기와 같다면 일정시간후에 몬스터를 다시 생성
        if (deadMonsters == monsters.Length)
        {
            StartCoroutine(InitMonsters());
            deadMonsters = 0;
        }
    }

    IEnumerator InitMonsters()
    {
        yield return new WaitForSeconds(respawnDelay);

        GetComponent<SphereCollider>().enabled = true;
    }

    void SpawnMonster()
    {
        for (int i = 0; i <monsters.Length; i++)
        {
            monsters[i].SetActive(true);
        }
    }

    private void OnTriggerEnter(Collider col)
    {
        if (col.gameObject.tag == "Player")
        {
            SpawnMonster();
            GetComponent<SphereCollider>().enabled = false;
        }
    }
    void Update()
    {
        
    }
}

 

 

 

빈 폴더를 생성하고 이름을 Texture 라 합니다

 

Texture 폴더를 선택하고 Import new Asset 를 눌러서 텍스트를 선택하고 임포트합니다

 

 

 

임포트한 텍스트를 선택하고  속성을 Texture type 을 Sprite and UI 로 바꾸고 아래 그림과 같이 바꾼다

 

 

비활성이였던 Enemy 오브젝트를 활성한 다음 Enemy 오브젝트를 선택하고 자식으로 빈 오브젝트를 만듬니다 그리고 이름을 SelectMarkPos 라 하고 자식으로 Quad를 생성합니다

 

 

 

빈 폴더를 생성하여 이름을 Materials 라 하고 폴더 안에 material을 생성하고 이름을 Selection이라 합니다

 

 

Selection 메터리얼을 선택하고 속성에서 Shader 를 Unlit/Transparent 를 선택하고 임포트한 텍스쳐를 드래그하여 그림과같이 올려 놓습니다

 

 

아래 그림과 같이 Enemy를 선택하고 Selection 메트리얼 을 드래그하여 자식으로 있는 Quad 에 드래그하여 올려 놓습니다.그리고 그림과 같이 Quad의 위치와 각도를 몬스터의 아래 방향을 향하도록 변경합니다

 

 

 

그리고 Enemy 오브젝트를 선택하고  수정한 EnemyFSM 스크립트의 Select Mark 부분에 Quad 오브젝트를 드래그 드롭 합니다 그리고 프리팹을 다시 저장 하기 위해서 Override 를 눌러서 Apply All 을 합니다

 

 

Enemy 오브젝트를 다시 비활성 상태로 돌아 감니다

 

 

하이라키에 빈 오브젝트를 만들과 이름을 GameManager 라 합니다

 

 

 

GameManager 스크립트를 생성하고 GameManager 오브젝트에 붙힘니다

 

 

 

GameManager 스크립트작성

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

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    List<GameObject> monsters = new List<GameObject>();

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        
    }

    //외부에서 전달된 몬스터가 기존에 리스트에 보관하고 있는 몬스터와 일치하는지 여부를 체크
    public void AddNewMonsters(GameObject mon)
    {

        //인자로 넘어온 몬스터가 기존의 리스트에 존재하면 sameExist = true 아니면 false
        bool sameExist = false;
        for (int i = 0; i < monsters.Count; i++)
        {
            if (monsters[i] == mon)
            {
                sameExist = true;
                
                break;
            }
        }

        if (sameExist == false)
        {
            monsters.Add(mon);
        }
        
    }
         

    public void RemoveMonster(GameObject mon)
    {
        foreach (GameObject monster in monsters)
        {
            if (monster == mon)
            {
                monsters.Remove(monster);
                break;
            }

        }
    }

    //현재 플레이어가 클릭한 몬스터만 선택마크가 표시
    public void ChangeCurrentTarget(GameObject mon)
    {
        DeselectAllMonsters();
        mon.GetComponent<EnemyFSM>().ShowSelection();
    }

    public void DeselectAllMonsters()
    {
        for (int i = 0; i < monsters.Count; i++)
        {
            monsters[i].GetComponent<EnemyFSM>().HideSelection();
        }
    }
    // Update is called once per frame
    void Update()
    {
        
    }
}

 

 

 

그리고  PlayerFSM 스크립트를 선택하고  AttackEnemy(GameObject enemy) 함수 부분을 수정합니다.

 public void AttackEnemy(GameObject enemy)
    {
        if (curEnemy != null && curEnemy == enemy)
        {
            return;
        }

        //적(몬스터)의 파라미터를 변수에 저장
        curEnemyParams = enemy.GetComponent<EnemyParams>();

        if (curEnemyParams.isDead == false)
        {
            curEnemy = enemy;
            curTargetPos = curEnemy.transform.position;

            GameManager.instance.ChangeCurrentTarget(curEnemy);

            ChangeState(State.Move, PlayerAni.ANI_WALK);
        }
        else
        {
            curEnemyParams = null;
        }
  
    }

 

게임을 실행하고 몬스터를 선택 하였을때 몬스터 표시 마크가 생성 되고 몬스터들이 다 죽은후 리스폰 되는지 확인 합니다

 

 

 

 

반응형

+ Recent posts