[project] 두더지 잡기(1)
in Unity
프로젝트 기본 설정
(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)
여태 잘 따라왔다면 위와 같은 모습일 것이다.
두더지 오브젝트
(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)
잘 따라했으면 위와같은 상황일 것이다.
두더지 행동 및 제어
(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)로 설정하고 머테리얼을 입힌다. 콜라이더도 삭제해준다.
(그림자가 설정되어 있으면 게임 뷰에 망치 그림자가 보이기 때문에 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를 할당한다.
이제 게임을 실행하고 두더지 공격에 성공하면 카메라도 흔들리고 파티클이 생성되는 걸 볼 수 있다.