[project] 두더지 잡기(1)


프로젝트 기본 설정

(1) 해상도는 1920 x 1080으로 바꿔준다.

(2) MainCamera 위치(0 / 1.8 / -2.1) 회전(45 / 0 / 0)로 설정한다.

(3) MainCamera의 Camera컴포넌트 Clear Flags(Solid Color) / 배경색 (검정)으로 설정한다.

바닥 오브젝트

(1) 바닥 오브젝트에 적용 할 Material을 하나 만들고 이름을 Ground로 바꾸고 색을 초록색으로 바꾼다.

(2) 바닥으로 사용할 큐브 오브젝트를 만들고 이름을 Ground로 설정하고 Ground Material을 입히고 크기(20 / 0.1 / 20)으로 바꾼다. Ground오브젝트는 화면에 보여지는 용도이기 때문에 콜라이더를 삭제한다.

두더지가 배치되는 구멍 오브젝트

(1) 구멍 오브젝트에 사용할 Materal을 하나 만들고 이름을 Hole로 바꾼다. 색을 검정 Metallic(1) / Smoothness(1)으로 설정해준다.

(2) 구멍을 구성하는 Cube들을 그룹으로 묶어 줄 빈 오브젝트를 만들고 이름을 Hole로 설정한다.

(4) 구멍의 4면으로 사용되는 큐브 오브젝트를 4개 생성한다.

(5) HoleLeft 위치(-0.25 / 0 / 0) 크기(0.1 / 0.2 / 0.6)

​ HoleRight 위치(0.25 / 0 / 0) 크기(0.1 / 0.2 / 0.6)

​ HoleForward 위치(0 / 0 / 0.25) 크기(0.5 / 0.2 / 0.1) HoleBack 위치(0 / 0 / -0.25) 크기(0.5 / 0.2 / 0.1)

(6) Hole오브젝트를 프리펩으로 만든다.

(7) 구멍 오브젝트들을 그룹으로 관리하는 빈 오브젝트(Holes)를 만든다. Holes오브제트의 자식으로 Hole프리펩을 9개 만든다.

(8) Hole 위치(-1 / 0 / 1)

​ Hole(1) 위치(0 / 0 / 1)

​ Hole(2) 위치(1 / 0 / 1)

​ Hole(3) 위치(-1 / 0 / 0)

​ Hole(4) 위치(0 / 0 / 0)

​ Hole(5) 위치(1 / 0 / 0)

​ Hole6() 위치(-1 / 0 / -1)

​ Hole(7) 위치(0 / 0 / -1)

​ Hole(8) 위치(1 / 0 / -1)

image

여태 잘 따라왔다면 위와 같은 모습일 것이다.

두더지 오브젝트

(1) 두더지 오브젝트에 적용 할 Material(Mole)을 만든다.

(2) 두더지 오브젝트 제작을 위해 Capsule(Mole)을 만들고 크기(0.3 / 0.3 / 0.3)으로 설정하고 Mole 머테리얼을 입힌다.

(3) Mole오브젝트를 프리펩으로 만든다.

(4) 두더지 오브젝트들을 그룹으로 관리하는 빈 오브젝트(Moles)를 만든다.

(5) Molse오브젝트의 자식으로 Mole프리펩을 9개 넣는다.

(6) Mole위치(-1 / 0 / 1)

​ Mole(1) 위치(0 / 0 / 1)

​ Mole(2) 위치(1 / 0 / 1)

​ Mole(3) 위치(-1 / 0 / 0)

​ Mole(4) 위치(0 / 0 / 0)

​ Mole(5) 위치(1 / 0 / 0)

​ Mole() 위치(-1 / 0 / -1)

​ Mole(7) 위치(0 / 0 / -1)

​ Mole(8) 위치(1 / 0 / -1)

image

잘 따라했으면 위와같은 상황일 것이다.

두더지 행동 및 제어

(1) 오브젝트 이동을 제어하는 Movement3D 스크립트를 만든다.

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

public class Movement3D : MonoBehaviour
{
    [SerializeField]
    private float moveSpeed = 0;
    private Vector3 moveDirection= Vector3.zero;

    private void Update()
    {
        transform.position+=moveDirection*moveSpeed*Time.deltaTime;
    }

    public void MoveTo(Vector3 direction)
    {
        moveDirection=direction;
    }
}

(2) 두더지의 행동을 제어하는 MoleFSM스크립트를 만든다.

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

// 지하에 대기, 지상에 대기, 지하->지상 이동, 지상->지하 이동
public enum MoleState {  UnderGround=0, OnGround,MoveUp, MoveDown}

public class MoleFSM : MonoBehaviour
{
    [SerializeField]
    private float waitTimeOnGround;  // 지면에 올라와서 내려가기까지 기다리는 시간
    [SerializeField]
    private float limitMinY;        // 내려갈 수 있는 최소 y 위치
    [SerializeField]
    private float limitMaxY;       //올라올 수 있는 최대 y 위치


    private Movement3D movement3D;  // 위 아래 이동을 위한 Movement3D

    // 두더지의 현재 상태 (set은 MoleFSM 클래스 내부에서만)
    public MoleState MoleState { get; private set; }

    private void Awake()
    {
        movement3D = GetComponent<Movement3D>();

        ChangeState(MoleState.UnderGround);
    }
    public void ChangeState(MoleState newState)
    {
        // 열거형 변수를 ToString() 메소드를 이요해 문자열로 변환하면
        // "UnderGround"와 같이 열거형 요소 이름 변환

        // 이전에 재생중이던 상태 종료
        StopCoroutine(MoleState.ToString());
        // 상태 변경
        MoleState = newState;
        // 새로운 상태 재생
        StartCoroutine(MoleState.ToString());
    }
    // 두더지가 바닥에서 대기하는 상태로 바닥 위치로 두더지 위치 설정
    private IEnumerator UnderGround()
    {
        // 이동 방향을 : (0, 0, 0) [정지]
        movement3D.MoveTo(Vector3.zero);
        // 두더지의 y 위치를 홀에 숨어있는 limitMinY 위치로 설정
        transform.position=new Vector3(transform.position.x,limitMinY,transform.position.z);

        yield return null;
    }
    private IEnumerator OnGround()
    {
        // 이동 방향을 : (0, 0, 0) [정지]
        movement3D.MoveTo(Vector3.zero);
        // 두더지의 y 위치를 홀에 숨어있는 limitMinY 위치로 설정
        transform.position = new Vector3(transform.position.x, limitMaxY, transform.position.z);

        yield return new WaitForSeconds (waitTimeOnGround);

        ChangeState (MoleState.MoveDown);
    }
    private IEnumerator MoveUp()
    {
        // 이동 방향 : (0, 1, 0 )[위]
        movement3D.MoveTo(Vector3.up);
        while (true)
        {
            // 두더지 y 위치가 limitMaxY에 도달하면 상태 변경
            if (transform.position.y >= limitMaxY)
            {
                // OnGround 상태로 변경
                ChangeState (MoleState.OnGround);
            }
            yield return null;
        }
    }
    private IEnumerator MoveDown()
    {
        // 이동 방향 : (0, -1, 0 )[아래]
        movement3D.MoveTo(Vector3.down);
        while (true)
        {
            // 두더지 y 위치가 limitMinY에 도달하면 상태 변경
            if (transform.position.y <= limitMinY)
            {
                // OnGround 상태로 변경
                ChangeState(MoleState.UnderGround);
            }
            yield return null;
        }
    }
}

(3) Mole프리펩에 Movement3D, Mole FSM을 컴포넌트로 추가한다.

(4) 임의의 두더지를 홀 밖으로 나오게 하는 MoleSpawner스크립트를 만든다.

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

public class MoleSpawner : MonoBehaviour
{
    [SerializeField]
    private MoleFSM[] moles;        // 맵에 존재하는 두더지들
    [SerializeField]
    private float spawnTime;        // 두더지 등장 주기

    private void Start()
    {
        StartCoroutine("SpawnMole");
    }

    private IEnumerator spawnMole()
    {
        while (true)
        {
            // 0 ~ Moles.Lenth-1 중 임의의 숫자 선택
            int index = Random.Range(0, moles.Length );
            // index번째 두더지의 상태를 "MoveUp"으로 변경
            moles[index].ChangeState(MoleState.MoveUp);

            // 스폰시간 동안 대기
            yield return new WaitForSeconds(spawnTime);
        }
    }
}

(5) 빈 오브젝트(MoleSpawner)를 생성하고 MoleSpawner스크립트를 추가하고 Moles배열에 맵에 존재하는 Mole을 넣어준다.

이제 게임을 실행하면 랜덤으로 두더지가 올라왔다 내겨가는 모습을 볼 수 있다.

망치 오브젝트

(1) 망치오브젝트에 적용할 머테리얼(Hammer)을 만든다.

(2) 망치 오브젝트를 만들기 위해 큐브 오브젝트(Hammer)를 생성한다. 위치(0 / 2 / 0) 크기(0.4 / 0.5 / 0.4)로 설정하고 머테리얼을 입힌다.

(3) 망치 오브젝트는 충돌처리를 하지 않기 때문에 콜라이더를 삭제해준다.

(4) 망치 손잡이 제작을 위해 큐브 오브젝트(HammerGrip)를 생성하고 Hammer오브젝트의 자식으로 배치한다.위치(1 / 0 / 0) 크기(2 / 0.2 / 0.2)로 설정하고 머테리얼을 입힌다. 콜라이더도 삭제해준다.

image

(그림자가 설정되어 있으면 게임 뷰에 망치 그림자가 보이기 때문에 MeshRenderer 컴포넌트의 Cast Shadow옵션을 off로 설정)

마우스 클릭으로 두더지 공격 및 망치 내려치기

(1) 마우스 클릭으로 오브젝트를 탐색하는 ObjectDetector스크립트를 만든다.

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

public class ObjectDetector : MonoBehaviour
{
    [System.Serializable]
    public class RaycastEvent : UnityEvent<Transform> { }       // 이벤트 클래스 정의
                                                                                           // 등록되는 이벤트 메소드는 Transform 매개변수 1개를 가지는 메소드

    [HideInInspector]
    public RaycastEvent raycastEvent=new RaycastEvent();

    private Camera mainCamera;
    private Ray ray;
    private RaycastHit hit;

    private void Awake()
    {
        mainCamera = Camera.main;
    }
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            ray=mainCamera.ScreenPointToRay(Input.mousePosition);

            if(Physics.Raycast(ray,out hit, Mathf.Infinity))
            {
                // 부딪힌 오브젝트의 Transform 정보를 매개변수로 이벤트 호출
                raycastEvent.Invoke(hit.transform);
            }
        }
    }
}

(2) 빈 오브젝트(ObjectDetector)를 생성하고 ObjectDetector스크립트를 추가한다.

(3) 망치 제어와 두더지 공격을 설정하는 Hammer스크립트를 만든다.

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

public class Hammer : MonoBehaviour
{
    [SerializeField]
    private float maxY;     // 망치의 최대 y 위치
    [SerializeField]
    private float minY;     // 망치의 최소 y 위치
    [SerializeField]
    private ObjectDetector objectDetector;      // 마우스 클릭으로 오브젝트 선택을 위한 ObjectDetector
    private Movement3D movement3D;      // 망치 오브젝트 이동을 위한 Movement

    private void Awake()
    {
        movement3D = GetComponent<Movement3D>();

        // OnHit 메소드를 ObjectDetector Class의 raycastEvent에 이벤트로 등록
        // ObjectDetector의 raycastEvent.Invoke(hit,transform); 메소드가
        // 호출될 때마다 OnHit(Transform target) 메소드가 호출된다
        objectDetector.raycastEvent.AddListener(OnHit);
    }

    private void OnHit(Transform target)
    {
        if (target.gameObject.tag == "Mole")
        {
            MoleFSM mole =target.GetComponent<MoleFSM>();

            // 두더지가 홀 안에 있을때는 공격 불가
            if (mole.MoleState == MoleState.UnderGround) return;

            // 망치의 위치 설정
            transform.position = new Vector3(target.position.x, minY, target.position.z);

            // 망치에 맞았기 때문에 두더지의 상태를 바로 "UnderGround"로 설정
            mole.ChangeState(MoleState.UnderGround);

            // 망치를 위로 이동시키는 코루틴 재생
            StartCoroutine("MoveUp");
        }
    }
    private IEnumerator MoveUp()
    {
        // 이동 방향 (0 / 1 / 0) [위]
        movement3D.MoveTo(Vector3.up);

        while (true)
        {
            if (transform.position.y >= maxY)
            {
                movement3D.MoveTo(Vector3.zero);
                break;
            }
            yield return null;
        }
    }
}

(4) Hammer 오브젝트에 Movement3D, Hammer스크립트를 추가한다.

(5) Mole태그를 만들어 Mole오브젝트의 태그를 Mole로 변경한다.

이제 게임 실행을 해보면 두더지가 올라왔을 때 마우스로 클릭하면 망치가 두더지 위치로 내려갔다가 올라가는 걸 볼 수 있다.

두더지 공격 효과 설정

망치로 두더지를 내려칠 때 공격 효과를 설정한다.

(1) 카메라가 흔들리는 효과를 주기 위해 ShakeCamera스크립트를 작성한다.

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

public class ShakeCamera : MonoBehaviour
{
    // 싱글톤 처리를 위한 instance 변수 설정
    private static ShakeCamera instance;
    // 외부에서 Get 접근만 가능하도록 Instance 프로퍼티 선언
    public static ShakeCamera Instance => instance;

    private float shakeTime;
    private float shakeIntensity;

    // Main Camera의 오브젝트에 컴포넌트로 적용하면
    // 게임을 실행할 떄 메모리 할당 / 생성자 메소드 실행
    // 이 때 자기 자신의 정보를 instance 변수에 저장
    public ShakeCamera()
    {
        // 자기 자신에 대한 정보를 static 형태의 instance변수에 저장해서
        // 외부에서 쉽게 접근할 수 있도록 함
        instance = this;
    }

    public void OnShakeCamera(float shakeTime = 1.0f, float shakeIntensity = 0.1f)
    {
        this.shakeTime = shakeTime;
        this.shakeIntensity = shakeIntensity;
        StopCoroutine("ShakeByPosition");
        StartCoroutine("ShakeByPosition");
    }
    private IEnumerator ShakeByPosition()
    {
        Vector3 startPosition=transform.position;

        while (shakeTime > 0.0f)
        {
            // 초기 위치로부터 구 범위(Size 1) * shakeIntensity의 범위 안에서 카메라 위치 변동
            transform.position = startPosition + Random.insideUnitSphere * shakeIntensity;

            shakeTime -= Time.deltaTime;
            yield return null;
        }
        transform.position = startPosition;
    }
}

(2) Main Camera 오브젝트에 ShakeCamera스크립트를 추가한다.

(3) Hammer 스크립트를 수정한다.

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

public class Hammer : MonoBehaviour
{
    [SerializeField]
    private float maxY;     // 망치의 최대 y 위치
    [SerializeField]
    private float minY;     // 망치의 최소 y 위치
    [SerializeField]
    private ObjectDetector objectDetector;      // 마우스 클릭으로 오브젝트 선택을 위한 ObjectDetector
    private Movement3D movement3D;      // 망치 오브젝트 이동을 위한 Movement

    private void Awake()
    {
        movement3D = GetComponent<Movement3D>();

        // OnHit 메소드를 ObjectDetector Class의 raycastEvent에 이벤트로 등록
        // ObjectDetector의 raycastEvent.Invoke(hit,transform); 메소드가
        // 호출될 때마다 OnHit(Transform target) 메소드가 호출된다
        objectDetector.raycastEvent.AddListener(OnHit);
    }

    private void OnHit(Transform target)
    {
        if (target.gameObject.tag == "Mole")
        {
            MoleFSM mole =target.GetComponent<MoleFSM>();

            // 두더지가 홀 안에 있을때는 공격 불가
            if (mole.MoleState == MoleState.UnderGround) return;

            // 망치의 위치 설정
            transform.position = new Vector3(target.position.x, minY, target.position.z);

            // 망치에 맞았기 때문에 두더지의 상태를 바로 "UnderGround"로 설정
            mole.ChangeState(MoleState.UnderGround);

            //====================================
            // 카메라 흔들기
            ShakeCamera.Instance.OnShakeCamera(0.1f, 0.1f);
            //====================================

            // 망치를 위로 이동시키는 코루틴 재생
            StartCoroutine("MoveUp");
        }
    }
    private IEnumerator MoveUp()
    {
        // 이동 방향 (0 / 1 / 0) [위]
        movement3D.MoveTo(Vector3.up);

        while (true)
        {
            if (transform.position.y >= maxY)
            {
                movement3D.MoveTo(Vector3.zero);
                break;
            }
            yield return null;
        }
    }
}

지금 게임을 실행하면 두더지 공격에 성공했을 때 카메라가 흔들리는걸 볼 수 있다.

(4) 두더지 공격 효과에 적용할 머테리얼(MoleHitEffect)를 만든다.

( (Shader : Particles/Standard Unit) / (Rendering Mode : Fade)

(5) 두더지 공격 효과 제작을 위해 파티클(MoleHitEffect)을 만든다.

particle System Comonent [Main]

Duration : 2

Looping : false

Start LifeTime : 0.1 -2

Start Speed : 1

Start Size : 0.01-0.1

Gravity Modifier : 5

Max Particles : 100

particle System Comonent [Emission]

Rate over Time : 0

Bursts.Count : 50 - 100

particle System Comonent [Shape]

Shape : sphere

Radius : 0.1

particle System Comonent [Velocity over LifeTime]

Linear (-10, -10, -10)

​ (10, 10, 10)

particle System Comonent [Renerer]

Material : MolehitEffect

(6) MoleHitEffect 오브젝트를 프리펩으로 만든다.

(7) Effect자동 삭제를 위한 ParticleAutoDestroyer 스크립트를 만든다.

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

public class particleAutoDestroyer : MonoBehaviour
{
    private ParticleSystem particle;
    private void Awake()
    {
        particle = GetComponent<ParticleSystem>();  
    }
    private void Update()
    {
        if(particle.isPlaying==false)
            Destroy(gameObject);
    }
}

(8) MoleHitEffect프리펩에 ParticleAutoDestroyer를 추가한다.

(9) Hammer스크립트를 수정한다.

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

public class Hammer : MonoBehaviour
{
    [SerializeField]
    private float maxY;     // 망치의 최대 y 위치
    [SerializeField]
    private float minY;     // 망치의 최소 y 위치
    //=====================================================
    [SerializeField]
    private GameObject moleHitEffectPrefab;
    //=====================================================
    [SerializeField]
    private ObjectDetector objectDetector;      // 마우스 클릭으로 오브젝트 선택을 위한 ObjectDetector
    private Movement3D movement3D;      // 망치 오브젝트 이동을 위한 Movement

    private void Awake()
    {
        movement3D = GetComponent<Movement3D>();

        // OnHit 메소드를 ObjectDetector Class의 raycastEvent에 이벤트로 등록
        // ObjectDetector의 raycastEvent.Invoke(hit,transform); 메소드가
        // 호출될 때마다 OnHit(Transform target) 메소드가 호출된다
        objectDetector.raycastEvent.AddListener(OnHit);
    }

    private void OnHit(Transform target)
    {
        if (target.gameObject.tag == "Mole")
        {
            MoleFSM mole =target.GetComponent<MoleFSM>();

            // 두더지가 홀 안에 있을때는 공격 불가
            if (mole.MoleState == MoleState.UnderGround) return;

            // 망치의 위치 설정
            transform.position = new Vector3(target.position.x, minY, target.position.z);

            // 망치에 맞았기 때문에 두더지의 상태를 바로 "UnderGround"로 설정
            mole.ChangeState(MoleState.UnderGround);

            // 카메라 흔들기
            ShakeCamera.Instance.OnShakeCamera(0.1f, 0.1f);

            //=========================================================
            // 두더지 타격 효과 생성 (Particle의 색상을 두더지 색상과 동일하게 설정)
            GameObject clone =Instantiate(moleHitEffectPrefab,transform.position,Quaternion.identity);
            ParticleSystem.MainModule main=clone.GetComponent<ParticleSystem>().main;
            main.startColor=mole.GetComponent<MeshRenderer>().material.color;
            //=========================================================


            // 망치를 위로 이동시키는 코루틴 재생
            StartCoroutine("MoveUp");
        }
    }
    private IEnumerator MoveUp()
    {
        // 이동 방향 (0 / 1 / 0) [위]
        movement3D.MoveTo(Vector3.up);

        while (true)
        {
            if (transform.position.y >= maxY)
            {
                movement3D.MoveTo(Vector3.zero);
                break;
            }
            yield return null;
        }
    }
}

(10) Hammer오브젝트의 Hammer 컴포넌트에 MolehitEffect를 할당한다.

이제 게임을 실행하고 두더지 공격에 성공하면 카메라도 흔들리고 파티클이 생성되는 걸 볼 수 있다.




© 2022. by KSC

Powered by sora