EasyCastleUNITY

주말과제: 이동하고 몬스터 공격 본문

유니티 기초

주말과제: 이동하고 몬스터 공격

EasyCastleT 2023. 8. 5. 17:22

1. Dog 이동 구현

DogController

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

public class DogController : MonoBehaviour
{
    Animator anim;
    public float moveSpeed = 10.0f;
    public int state;
    //public Transform target;
    private bool isMoveStart = false;
    private bool isAttackStart = false;
    private Vector3 targetPosition;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = this.GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //왼쪽 버튼을 누른다면 
        if (Input.GetMouseButtonDown(0))
        {
            //카메라를 통해, 화면좌표를 월드상의 Ray 객체로 만든다.
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            //씬 뷰의 화면에 Ray를 출력
            //길이 100의 ray를 빨간색으로 3초동안 보여줌
            Debug.DrawRay(ray.origin, ray.direction * 100f, Color.red, 3f);

            RaycastHit hit; //raycasthit 변수 정의
            //충돌체크 , 충돌에 대한 정보를 hit에 저장한다. 
            if(Physics.Raycast(ray.origin,ray.direction*100f,out hit))
            {
                Debug.Log(hit.point); //충돌된 지점의 월드 좌표
                //해당 좌표로 Dog 이동 
                this.targetPosition = hit.point;
                //해당 좌표를 쳐다보도록 함
                this.transform.LookAt(hit.point);
                this.isMoveStart = true;
            }
        }
        //이동
        if (this.isMoveStart)
        {
            //local좌표계로 정면으로 이동함, 정면으로 4의 속도로 이동
            this.transform.Translate(Vector3.forward * 4f * Time.deltaTime);
            //목표 좌표와 Dog사이의 거리를 계산
            //거리를 통해, 도착여부를 판단
            float distance = Vector3.Distance(this.transform.position, this.targetPosition);
            if(distance <= 0.1f) // 도착
            {
                this.isMoveStart = false;
            }
        }
    }
}

애니메이션 제외 이동 구현

2. 충돌객체의 정보 받아오기 (Plane을 제외한 모든 Collider는 Box Collider 이용)

충돌객체 정보 받아오고 콘솔로 출력, 몬스터들에게는 Monster라는 tag를 부여함

 

현재의 문제점: 몬스터의 Collider 크기로 인해 Dog가 Monster에 도착하면, 공중에 뜨게 된다. 

Collider크기도 문제였지만 근본적으로 LookAt 함수 자체가 가지는 문제점이었다. 

해결방법1: 몬스터 Collider의 y로의 크기를 없앤다. 

해결방법2:  DogController에 있는 targetPosition의 y좌표를 일괄적으로 0으로 계산하도록 한다. 

해결방법3: 고민중, 다른 방법도 있을 것 같다. 

2번의 해결방법을 이용하였다. 

문제의 원인: LookAt함수가 원인이었다. 같은 위상에서는 유용한 함수지만,

몬스터처럼 입체적인 Collider와 Ray를 통해 충돌계산을 하면 y는 0이 아니게 되는데

이것을 hit.point로 그대로 받아서, 공중에 뜬 상태로 이동하는 것이 지속이 되게 되었다. 

그래서 아래와 같은 방식으로 이를 해결하였다.

타겟 포지션을 hit.point를 받아오는 것이 아니라, y의 좌표를 0으로 가지는 새로운 벡터를 만들어 targetPosition에 저장하고 이 좌표를 보도록 LookAt에 적용하여, 계속 같은 위상(y=0)에서 LookAt을 하도록 했다.

3. 애니메이션 설정

3-1. Dog Animator

3가지 애니메이션을 이용했다. Idle,Run,Attack01 

DogController

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

public class DogController : MonoBehaviour
{
    Animator anim;
    public float moveSpeed = 10.0f;
    public int state;
    //public Transform target;
    private bool isMoveStart = false;
    private bool isAttackStart = false;
    private Vector3 targetPosition;
    private string tag;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = this.GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //왼쪽 버튼을 누른다면 
        if (Input.GetMouseButtonDown(0))
        {
            //카메라를 통해, 화면좌표를 월드상의 Ray 객체로 만든다.
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            //씬 뷰의 화면에 Ray를 출력
            //길이 100의 ray를 빨간색으로 3초동안 보여줌
            Debug.DrawRay(ray.origin, ray.direction * 100f, Color.red, 3f);

            RaycastHit hit; //raycasthit 변수 정의
            //충돌체크 , 충돌에 대한 정보를 hit에 저장한다. 
            if(Physics.Raycast(ray.origin,ray.direction*100f,out hit))
            {
                 Debug.LogFormat("hit.point: {0}", hit.point); //충돌된 지점의 월드 좌표
                //해당 좌표로 Dog 이동 
                this.targetPosition = new Vector3(hit.point.x,0,hit.point.z);
                Debug.LogFormat("targetPosition:{0}", targetPosition);
                //충돌 객체 정보 받기
                GameObject hitObject = hit.transform.gameObject;
                tag = hit.collider.tag;
             //   Debug.Log(hitObject);
                Debug.Log(tag);
                //해당 좌표를 쳐다보도록 함
                this.transform.LookAt(this.targetPosition);
                this.anim.SetInteger("State", 1);
                this.isMoveStart = true;
            }
        }
        //이동
        if (this.isMoveStart)
        {
            //local좌표계로 정면으로 이동함, 정면으로 4의 속도로 이동
            this.transform.Translate(Vector3.forward * 4f * Time.deltaTime);
            //목표 좌표와 Dog사이의 거리를 계산
            //거리를 통해, 도착여부를 판단
            float distance = Vector3.Distance(this.transform.position, this.targetPosition);
            if(distance <= 0.1f) // 도착
            {
                this.isMoveStart = false;
                if(tag == "Monster") //태그가 Monster면
                {
                    this.anim.SetInteger("State", 2); //공격
                }
                else this.anim.SetInteger("State", 0);//아니면 대기 
            }
        }
    }
}

Plane을 찍으면 거기로 달려가서 멈추고, Monster를 찍으면 그곳으로 이동후 공격 실행

Slime Animator

세가지 애니메이션을 사용, IdleNormal, GetHit, Die 

이 애니메이션들을 조금 수정하여 Loop하지 않도록 바꿈 

4. 완성 (간단 UI 포함)

몬스터 슬라임의 hp를 3으로 잡고 가까이 몬스터에 접근하면 1회에 한하여 몬스터를 공격하도록 했다. 

실행결과

1.몬스터에게 접근하면 공격을 1회 실행

2.이 공격으로 monsterHp가 1씩 감소

3. 사망하는 순간 몬스터의 Die 애니메이션 실행

4. 사망한 후 가까이 가도, 공격 안하는 것을 확인 

전체적인 모습

DogController

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

public class DogController : MonoBehaviour
{
    Animator anim;
    public float moveSpeed = 10.0f;
    public int state;
    //public Transform target;
    private bool isMoveStart = false;
    private bool isAttackStart = false;
    private Vector3 targetPosition;
    private string tag;
    private GameObject monsterGo;
    private MonsterController monster;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = this.GetComponent<Animator>();
        this.monsterGo = GameObject.Find("Slime");
        this.monster = this.monsterGo.GetComponent<MonsterController>();
    }

    // Update is called once per frame
    void Update()
    {
        //왼쪽 버튼을 누른다면 
        if (Input.GetMouseButtonDown(0))
        {
            //카메라를 통해, 화면좌표를 월드상의 Ray 객체로 만든다.
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            //씬 뷰의 화면에 Ray를 출력
            //길이 100의 ray를 빨간색으로 3초동안 보여줌
            Debug.DrawRay(ray.origin, ray.direction * 100f, Color.red, 3f);

            RaycastHit hit; //raycasthit 변수 정의
            //충돌체크 , 충돌에 대한 정보를 hit에 저장한다. 
            if(Physics.Raycast(ray.origin,ray.direction*100f,out hit))
            {
                 Debug.LogFormat("hit.point: {0}", hit.point); //충돌된 지점의 월드 좌표
                //해당 좌표로 Dog 이동 
                this.targetPosition = new Vector3(hit.point.x,0,hit.point.z);
                Debug.LogFormat("targetPosition:{0}", targetPosition);
                //충돌 객체 정보 받기
                GameObject hitObject = hit.transform.gameObject;
                tag = hit.collider.tag;
             //   Debug.Log(hitObject);
                Debug.Log(tag);
                //해당 좌표를 쳐다보도록 함
                this.transform.LookAt(this.targetPosition);
                this.anim.SetInteger("State", 1);
                this.isMoveStart = true;
            }
        }
       

        //이동
        if (this.isMoveStart)
        {
            //local좌표계로 정면으로 이동함, 정면으로 4의 속도로 이동
            this.transform.Translate(Vector3.forward * 4f * Time.deltaTime);
            //목표 좌표와 Dog사이의 거리를 계산
            //거리를 통해, 도착여부를 판단
            float distance = Vector3.Distance(this.transform.position, this.targetPosition);
            if(distance <= 0.1f) // 도착
            {
                this.isMoveStart = false;
                if (tag == "Monster") //태그가 Monster면
                {
                    Attack();
                }
                else { 
                    this.anim.SetInteger("State", 0);//아니면 대기 
                }

            }
        }
    }

    private void Attack()
    {
        //monster의 hp가 0이되면 return해서 아래가 동작하지 않도록 함
        if (this.monster.hp <= 0) return; 
        this.anim.SetInteger("State", 2); //공격
        this.monster.anim.SetInteger("MonsterState", 1);
        this.monster.hp--;
    }
}

MonsterController

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

public class MonsterController : MonoBehaviour
{
    public Animator anim;
    public int hp;
    private GameObject monsterHpGO;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = this.GetComponent<Animator>();
        this.monsterHpGO = GameObject.Find("MonsterHp");
        this.hp = 3; //해당  hp는 DogController에서 감소
    }

    // Update is called once per frame
    void Update()
    {
        Text text = monsterHpGO.GetComponent<Text>();
        text.text = string.Format("{0}", this.hp); //UI Text
        if (this.hp <= 0)
        {
            this.hp = 0;
            this.anim.SetInteger("MonsterState", 2); //사망
        }
        else
        {
            this.anim.SetInteger("MonsterState", 0); //대기 
        }
    }
}

유의 했던 점

1.애니메이션이 여러번 반복 -> 구글링을 통해 Loop가 문제인 것을 찾아 loop를 중지시켰다.

2. 몬스터가 사망한 이후, Dog가 접근하면 다시 공격하는 문제 -> return을 응용하여 해결했다.