[Project] 두더지 잡기(2)


모바일 플랫폼, 해상도 설정

(1) Android로 플랫폼을 변경한다.

(2) 해상도를 16 : 9 LandScape로 변경한다.

현재 점수 출력

(1) 현재 점수 출력을 위해 TextmeshPro(TextScore)를 생성하고 알맞게 설정한다.

(2) 캔버스 오브젝트의 Canvas Scale 컴포넌트의 Render Mode를 Scale With Screen Size로 변경하고 Reference Resolution을 1920 x 1080으로 바꾸고 Match를 0.5로 설정한다.

(3) 점수, 게임 시간 등 게임 관리를 위한 GameController스크립트를 만든다.

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

public class GameController : MonoBehaviour
{
    private int score;
    public int Score
    {
        get => score;
        set=>score=Mathf.Max(0,value);
    }
}

(4) GameController를 컴포넌트로 가질 빈 오브젝트(GameController)를 생성한다.

(5) 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 GameController gameController; // 점수 증가를 위한 GameController
    //=============================================
    [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;

            //=============================================
            // 점수 증가
            gameController.Score += 50;
            //=============================================

            // 망치를 위로 이동시키는 코루틴 재생
            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;
        }
    }
}

(6) Hammer오브젝트에 Hammer 컴포넌트의 변수에 GameController를 할당

(7) 점수, 콤보 등 게임 내의 데이터를 출력하는 InGameTextViewer스크립트를 만든다.

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

public class InGameTextViewer : MonoBehaviour
{
    [SerializeField]
    private GameController gameController;
    [SerializeField]
    private TextMeshProUGUI textScore;

    private void Update()
    {
        textScore.text = "Score " + gameController.Score;
    }
}

(8) GameController오브젝트에 InGameTextViewer를 컴포넌트로 추가하고 변수들을 할당해준다.

image

그럼 게임 실행하고나서 두더지 공격에 성공할 때 50점씩 점수가 올라가고 화면에 출력되는 모습을 볼 수 있다.

게임 시작 전 카운트 다운

(1) 카운트 다운 텍스트 출력을 위해 TextMeshPro(TextCountDown)를 생성하고 알맞게 설정한다.

(2) 카운트다운 제어 및 텍스트출력을 위한 CountDown스크립트를 만든다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using TMPro;
public class CountDown : MonoBehaviour
{
    [System.Serializable]
    private class CountDownEvent : UnityEvent { }
    private CountDownEvent endOfCountDown;      // 카운트 다운 종료 후 외부 메소드 실행을 위해 이벤트 클래스 사용

    private TextMeshProUGUI textCountDown;      // 카운트 다운 텍스트를 출력하는 Text UI
    private AudioSource audioSource;        // 카운트 다운 사운드 재생

    [SerializeField]
    private int maxFontSize;        // 폰트의 최대 크기
    [SerializeField]
    private int minFontSize;        // 폰트의 최소 크기

    private void Awake()
    {
        endOfCountDown = new CountDownEvent();
        textCountDown=GetComponent<TextMeshProUGUI>();
        audioSource=GetComponent<AudioSource>();
    }

    public void StartCountDown(UnityAction action,int start=3,int end = 1)
    {
        StartCoroutine(OnCountDown(action, start, end));
    }

    private IEnumerator OnCountDown(UnityAction action,int start,int end)
    {
        // action 메소드를 이벤트에 등록
        endOfCountDown.AddListener(action);

        while (start>end-1)
        {
            // 카운트 다운 사운드 재생
            audioSource.Play();

            // 카운트 다운 텍스트 설정
            textCountDown.text=start.ToString();

            // 폰트 크기를 변경하는 애니메이션 (재생 완료 시 아래 코드 실행)
            yield return StartCoroutine("OnFontAnimation");

            // 카운트 다운 숫자 1 감소
            start--;
        }
        // action 메소드를 실행
        endOfCountDown.Invoke();

        // action 메소드를 이벤트에서 제거
        endOfCountDown.RemoveListener(action); 

        // 카운트 다운 오브젝트 비활성화
        gameObject.SetActive(false);
    }
    private IEnumerator OnFontAnimation()
    {
        float percent = 0;
        while (percent < 1)
        {
            percent += Time.deltaTime;

            // 폰트 크기를 200에서 100까지 percent 시간 동안 감소
            textCountDown.fontSize=Mathf.Lerp(maxFontSize, minFontSize, percent);

            yield return null;
        }
    }
}

(3) CountDown 오브젝트에 CountDown스크립트, Audio Source 컴포넌트를 추가한다.

(4) MoleSpawner스크립트를 수정한다.

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

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

    //===================================================
    // 두더지를 바로 생성하지 말고 카운트 다운이 완료된 후에 생성되도록 GameController
    // 에서 SetUp() 메소드를 호출
    public void SetUp()
    {
        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) GameController스크립트를 수정한다.

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

public class GameController : MonoBehaviour
{
    //==============================
    [SerializeField]
    private CountDown countDown;
    [SerializeField]
    private MoleSpawner moleSpawner;
    //==============================
    private int score;
    public int Score
    {
        get => score;
        set=>score=Mathf.Max(0,value);
    }
    //==============================
    private void Start()
    {
        countDown.StartCountDown(GameStart);
    }
    private void GameStart()
    {
        moleSpawner.SetUp();
    }
    //==============================
}

(6) GameController컴포넌트의 변수들을 할당해준다.

이제 게임 실행을 하면 카운트 다운이 되는 걸 볼 수 있다.

제한 시간 설정

(1) 제한 시간 출력을 위해 Slider UI 오브젝트(SliderPlayTime)를 하나 생성한다.

(2) 알맞게 위치와 크기를 설정하고 Handle Slide Area를 삭제하고 Slider 컴포넌트의 Interactable를 비활성화해서 터치 상호작용이 작동하지 않도록 해준다.

(3) 제한 시간 출력을 위한 TextMeshPro(TextPlayTime)를 생성하고 SliderPlayTime의 자식으로 넣어준다.

(4) GameController스크립트를 수정해준다.

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

public class GameController : MonoBehaviour
{
    [SerializeField]
    private CountDown countDown;
    [SerializeField]
    private MoleSpawner moleSpawner;
    private int score;
    public int Score
    {
        get => score;
        set=>score=Mathf.Max(0,value);
    }
    //=================================================
    // [field:SerializeField] : 자동 구현 프로퍼티를 인스펙터창에서 보이게 할 때 사용 
    [field:SerializeField]
    public float MaxTime { private set; get; }
    public float CurrentTime { private set; get; }
    //=================================================

    private void Start()
    {
        countDown.StartCountDown(GameStart);
    }
    private void GameStart()
    {
        moleSpawner.SetUp();
        //=================================================
        StartCoroutine("OnTimeCount");
        //=================================================
    }
    //=================================================
    private IEnumerator OnTimeCount()
    {
        CurrentTime = MaxTime;
        while (CurrentTime > 0)
        {
            CurrentTime-=Time.deltaTime;

            yield return null;
        }
    }
    //=================================================
}

(5) GameController컴포넌트의 MaxTime을 60으로 설정

(7) InGameTextViewer 스크립트를 수정한다.

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

public class InGameTextViewer : MonoBehaviour
{
    [SerializeField]
    private GameController gameController;
    [SerializeField]
    private TextMeshProUGUI textScore;
    //=======================================
    [SerializeField]
    private TextMeshProUGUI textPlayTime;
    [SerializeField]
    private Slider sliderPlayTime;
    //=======================================

    private void Update()
    {
        textScore.text = "Score " + gameController.Score;
        //=================================================
        textPlayTime.text = gameController.CurrentTime.ToString("F1");
        sliderPlayTime.value = gameController.CurrentTime / gameController.MaxTime;
        //=================================================
    }
}

image

이제 화면 하단에 남은 시간이 나오는 걸 알 수 있다.

두더지 색상 설정과 등장 확률

(1) 두더지의 종류에 따라 기능을 부여하기 위해 MoleFSM스크립트를 수정한다.

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

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

// 두더지 종류 (기본, 점수-, 시간 +)
public enum MoleType { Normal=0,Red,Blue}

//==============================================

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

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

    private MeshRenderer meshRenderer;       // 두더지 색설정을 위한 MeshRenderer

    private MoleType moleType;          // 두더지의 종류
    private Color defaultColor;         // 기본 두더지의 색상(173, 135, 24)

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

    // 두더지의 종류(MoleType에 따라 두더지 색상 변경)
    public MoleType MoleType
    {
        set
        {
            moleType = value;
            
            switch(moleType)
            {
                case MoleType.Normal:
                    meshRenderer.material.color = defaultColor;
                    break;
                case MoleType.Red:
                    meshRenderer.material.color = Color.red;
                    break;
                case MoleType.Blue:
                    meshRenderer.material.color= Color.blue;
                    break;
            }
        }
        get => moleType;
    }

    //=============================================

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

        meshRenderer = GetComponent<MeshRenderer>();

        defaultColor = meshRenderer.material.color; // 두더지의 최초 색상 저장

        //=========================================

        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;
        }
    }
}

(2) 두더지의 등장 확률을 정하기 위해 MoleSpawner스크립트를 수정한다.

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

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

    //===========================================

    // 두더지 등장 확률 (Normal : 85%, Red : 10%, Blue : 5%)
    private int[] spawnPercents = new int[3] { 85, 10, 5 };

    //===========================================

    // 두더지를 바로 생성하지 말고 카운트 다운이 완료된 후에 생성되도록 GameController
    // 에서 SetUp() 메소드를 호출
    public void SetUp()
    {
        StartCoroutine("SpawnMole");
    }
    private IEnumerator SpawnMole()
    {
        while (true)
        {
            // 0 ~ Moles.Lenth-1 중 임의의 숫자 선택
            int index = Random.Range(0, moles.Length );

            //=================================================

            // 선택된 두더지의 속성 설정
            moles[index].MoleType = SpawnMoleType();

            //=================================================

            // index번째 두더지의 상태를 "MoveUp"으로 변경
            moles[index].ChangeState(MoleState.MoveUp);

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

    //=====================================

    private MoleType SpawnMoleType()
    {
        int percent = Random.Range(0, 100);
        float cumulative = 0;

        for(int i = 0; i < spawnPercents.Length; i++)
        {
            cumulative+=spawnPercents[i];

            if (percent < cumulative)
            {
                return (MoleType)i;
            }
        }

        return MoleType.Normal;
    }

    //=====================================
}

두더지 색상별 타격 효과 설정

(1) 파란색 두더지를 공격했을 때 시간이 증가하는데 현재시간이 최대 시간보다 증가하지 않게 설정한다. GameController스크립트를 수정하자.

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

public class GameController : MonoBehaviour
{
    [SerializeField]
    private CountDown countDown;
    [SerializeField]
    private MoleSpawner moleSpawner;
    private int score;
    //=====================

    private float currentTime;

    //=====================
    public int Score
    {
        get => score;
        set=>score=Mathf.Max(0,value);
    }
    // [field:SerializeField] : 자동 구현 프로퍼티를 인스펙터창에서 보이게 할 때 사용 
    [field:SerializeField]
    public float MaxTime { private set; get; }
    //=======================================

    public float CurrentTime 
    { 
        set=> currentTime = Mathf.Clamp(value,0,MaxTime);
        get => currentTime;
    }

    //=======================================

    private void Start()
    {
        countDown.StartCountDown(GameStart);
    }
    private void GameStart()
    {
        moleSpawner.SetUp();
        StartCoroutine("OnTimeCount");
    }
    private IEnumerator OnTimeCount()
    {
        CurrentTime = MaxTime;
        while (CurrentTime > 0)
        {
            CurrentTime-=Time.deltaTime;

            yield return null;
        }
    }
}

(2) 두더지를 공격 할 때마다 두더지의 속성에 따라 타격 사운드가 달라지게 설정한다. 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 AudioClip[] audioClips;     // 두더지를 타격했을 때 재생되는 사운드

    //=========================================
    [SerializeField]
    private GameController gameController; // 점수 증가를 위한 GameController
    [SerializeField]
    private ObjectDetector objectDetector;      // 마우스 클릭으로 오브젝트 선택을 위한 ObjectDetector
    private Movement3D movement3D;      // 망치 오브젝트 이동을 위한 Movement
    //=========================================

    private AudioSource audioSource;

    //=========================================

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

        audioSource= GetComponent<AudioSource>(); 

        //=========================================

        // 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;

            //======================================================

            // 두더지의 색상에 따라 처리 (점수, 시간, 사운드 재생)
            MoleHitProcess(mole);

            //======================================================

            // 망치를 위로 이동시키는 코루틴 재생
            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;
        }
    }
    //=======================================================

    private void MoleHitProcess(MoleFSM mole)
    {
        if(mole.MoleType == MoleType.Normal)
        {
            gameController.Score += 50;
        }
        else if (mole.MoleType == MoleType.Red)
        {
            gameController.Score -= 300;
        }
        else if (mole.MoleType == MoleType.Blue)
        {
            gameController.CurrentTime += 3;
        }

        // 사운드 재생 (Normal=0, Red=1, Blue=2)
        PlaySound((int)mole.MoleType);
    }

    //=======================================================

    //=======================================================

    private void PlaySound(int index)
    {
        audioSource.Stop();
        audioSource.clip=audioClips[index];
        audioSource.Play();
    }

    //=======================================================
}

두더지 타격 텍스트 정보 출력

(1) 두더지를 타격했을 때 점수, 시간등의 정보들을 출력하기 위해 TextMeshPro(TextGetScore)를 생성한다.

(2) 두더지가 있는 위치에 텍스트를 배치하고 활성 / 비활성화해서 계속 사용할 수 있게 TextGetScore를 복제해서 총 9개를 만든다.

(3) 각 위치를 다음과 같이 설정한다.

TextGetScore 위치 : (-280 / 280 / 0)

TextGetScore (1) 위치 : (0 / 280 / 0)

TextGetScore (2) 위치 : (280 / 280 / 0)

TextGetScore (3) 위치 : (-350 / 100 / 0)

TextGetScore (4) 위치 : (0 / 100 / 0)

TextGetScore (5) 위치 : (350 / 100 / 0)

TextGetScore (6) 위치 : (-470 / -200 / 0)

TextGetScore (7) 위치 : (0 / -200 / 0)

TextGetScore (8) 위치 : (470 / -200 / 0)

(4) 타격 텍스트 출력을 위한 MoleHitTextViewer스크립트를 만든다.

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

public class MolehitTextViewer : MonoBehaviour
{
    [SerializeField]
    private float moveSpeed = 30.0f;       // 이동 속도
    private Vector2 defaultPosition;        // 이동 애니메이션 있어서 초기 위치 저장
    private TextMeshProUGUI textHit;
    private RectTransform rectHit;

    private void Awake()
    {
        textHit = GetComponent<TextMeshProUGUI>();
        rectHit = GetComponent<RectTransform>();
        defaultPosition=rectHit.anchoredPosition;

        gameObject.SetActive(false);
    }

    public void OnHit(string hitData, Color color)
    {
        // 오브젝트를 화면에 보이도록 설정
        gameObject.SetActive(true);
        // Score +XX, Score -300, Time +3과 같이 출력할 정보 설정
        textHit.text = hitData;

        // 텍스트가 위로 이동하며 점점 사라지는 OnAnimation() 코루틴 실행
        StopCoroutine("OnAnimation");
        StartCoroutine("OnAnimation", color);
    }
    private IEnumerator OnAnimation(Color color)
    {
        // 오브젝트를 On / Off 해서 사용하고, 이동 애니메이션을 했기 때문에 위치 리셋
        rectHit.anchoredPosition = defaultPosition;

        while (color.a > 0)
        {
            // Vector2.up 방향으로 이동
            rectHit.anchoredPosition+=Vector2.up*moveSpeed*Time.deltaTime;
            // 투명도 1 -> 0으로 감소
            color.a -= Time.deltaTime;
            textHit.color = color;

            yield return null;
        }

        gameObject.SetActive(false);
    }
}

(5) TextGetScore오브젝트들을 선택하고 MolehitTextViewer스크립트를 컴포넌트로 추가해준다.

(6) MoleFSM 스크립트를 수정해준다.

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

// 지하에 대기, 지상에 대기, 지하->지상 이동, 지상->지하 이동
public enum MoleState {  UnderGround=0, OnGround,MoveUp, MoveDown}
// 두더지 종류 (기본, 점수-, 시간 +)
public enum MoleType { Normal=0,Red,Blue}

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

    private Movement3D movement3D;  // 위 아래 이동을 위한 Movement3D
    private MeshRenderer meshRenderer;       // 두더지 색설정을 위한 MeshRenderer

    private MoleType moleType;          // 두더지의 종류
    private Color defaultColor;         // 기본 두더지의 색상(173, 135, 24)

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

    // 두더지의 종류(MoleType에 따라 두더지 색상 변경)
    public MoleType MoleType
    {
        set
        {
            moleType = value;
            
            switch(moleType)
            {
                case MoleType.Normal:
                    meshRenderer.material.color = defaultColor;
                    break;
                case MoleType.Red:
                    meshRenderer.material.color = Color.red;
                    break;
                case MoleType.Blue:
                    meshRenderer.material.color= Color.blue;
                    break;
            }
        }
        get => moleType;
    }
    //==========================================================

    // 두더지가 배치되어 있는 순번 (왼쪽 상단부터 0)
    [field:SerializeField]
    public int MoleIndex { private set; get; }

    //==========================================================
    private void Awake()
    {
        movement3D = GetComponent<Movement3D>();
        meshRenderer = GetComponent<MeshRenderer>();
        defaultColor = meshRenderer.material.color; // 두더지의 최초 색상 저장

        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;
        }
    }
}

(7) 에디터창에서 Mole오브젝트를 왼쪽상단에서 부터 0으로 설정해준다.

(8) 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 AudioClip[] audioClips;     // 두더지를 타격했을 때 재생되는 사운드
    //===========================================================

    [SerializeField]
    private MolehitTextViewer[] molehitTextViewer;      // 타격한 두더지 위치에 타격 정보 텍스트 출력

    //===========================================================
    [SerializeField]
    private GameController gameController; // 점수 증가를 위한 GameController
    [SerializeField]
    private ObjectDetector objectDetector;      // 마우스 클릭으로 오브젝트 선택을 위한 ObjectDetector
    private Movement3D movement3D;      // 망치 오브젝트 이동을 위한 Movement

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

        // 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;

            // 두더지의 색상에 따라 처리 (점수, 시간, 사운드 재생)
            MoleHitProcess(mole);

            // 망치를 위로 이동시키는 코루틴 재생
            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;
        }
    }
    private void MoleHitProcess(MoleFSM mole)
    {
        if(mole.MoleType == MoleType.Normal)
        {
            gameController.Score += 50;
            // MoleIndex로 순번을 설정해 두었기 때문에 같은 자리에 있는 TextGetScore 텍스트 출력
            // 하얀색 텍스트로 점수 증가 표현
            molehitTextViewer[mole.MoleIndex].OnHit("Score +50", Color.white);
        }
        else if (mole.MoleType == MoleType.Red)
        {
            gameController.Score -= 300;
            // 빨간색 텍스트로 점수 감소 표현
            molehitTextViewer[mole.MoleIndex].OnHit("Score -300", Color.red);
        }
        else if (mole.MoleType == MoleType.Blue)
        {
            gameController.CurrentTime += 3;
            // 파란색 텍스트로 시간 증가 표현
            molehitTextViewer[mole.MoleIndex].OnHit("Time +3", Color.blue);
        }

        // 사운드 재생 (Normal=0, Red=1, Blue=2)
        PlaySound((int)mole.MoleType);
    }
    private void PlaySound(int index)
    {
        audioSource.Stop();
        audioSource.clip=audioClips[index];
        audioSource.Play();
    }
}

(9) Hammer오브젝트의 Hammer컴포넌트의 MoleHitTextViewer변수에 TextGetScore오브젝트들을 순서대로 넣어준다.

이제 게임을 실행하면 두더지가 공격받은 위치에 알맞은 텍스트가 나오는 것을 볼 수 있다.




© 2022. by KSC

Powered by sora