
1단계: 화면에 놓일 오브젝트를 모두 나열

2단계: 오브젝트를 움직일 수 있는 컨트롤러 스크립트 정함
사과, 폭탄(위에서 아래로 떨어짐), 바구니(사용자가 조작) 컨트롤러
3단계: 오브젝트를 자동으로 생성할 수 있는 제너레이터 스크립트 정함
사과, 폭탄 제너레이터 스크립트
4단계: UI 갱신할 수 있도록 감독 스크립트 준비
UI 갱신이나 게임의 진행 상황을 판단
➡️ 득점과 제한 시간 UI가 필요함
5단계: 스크립트를 만드는 흐름 생각

*작성 필요 스크립트
사과 컨트롤러 & 폭탄 컨트롤러
▪ 사과와 폭탄을 화면 위에서 아래로 떨어트림
▪ 아이템이 무대 아래로 이동해 보이지 않으면 소멸시킴
▪ 사과와 폭탄 모두 같은 동작을 하므로 아이템 컨트롤러를 한 개로 정리할 수 있음
바구니 제너레이터
▪ 탭한 곳으로 바구니를 이동시킴
▪ 이동하는 좌표는 바둑판 중심이 되게 함
아이템 제너레이터
▪ 사과와 폭탄을 화면 윗부분에 생성
▪ 게임 진행 상황에 맞춰 생성 속도나 폭탄 비율을 변화시킴
게임 씬 감독
▪ 제한 시간과 득점을 관리
▪ 사과를 잡으면 100점을 더하고, 폭탄을 잡으면 얻은 점수를 절반으로 줄임
▪ 제한 시간은 60초부터 카운트다운하고, 이 값을 UI에 표시

▪ 사과와 폭탄이 떨어지는 위치에 그림자를 넣어 아이템이 어디에 떨어질지 나타내야 함
▪ 그림자를 넣으려면 라이트(광원)를 설정
▪ 유니티에서 게임 세계를 비추는 라이트
: Directional Light, Point Light, Spotlight, Area Light

▪ 라이트가 씬 위에 배치되어 있으면 그림자 위치나 그림자가 보이는 방향을 유니티가 자동으로 계산해서 표시
▪ 3D 프로젝트에는 기본적으로 Directional Light가 배치되어 있음
▪ 그림자를 사용해 아이템의 낙하 지점을 표시하기 때문에 라이트 방향을 조절
✓ 라이트가 사선으로 아이템을 비추면 그림자를 보면서 아이템 낙하 지점을 파악하기 어려움
✓ 라이트가 아이템을 바로 위에서 ✅수직으로 비추어야 아이템 낙하 지점에 그림자가 정확히 생김

▪Transform 항목의 Rotation을 90, 0, 0으로 설정
✓ 라이트가 바닥을 수직으로 비춤
▪ 계속해서 바구니를 배치하고 사용자 입력에 따라 바구니를 이동시켜 보겠음
▪ ‘움직이는 오브젝트를 만드는 방법’에 따라
바구니 배치하기 → 스크립트 작성하기 → 스크립트 적용하기 순서로 작업을 진행
움직이는 오브젝트를 만드는 방법
① Scene 뷰에 오브젝트를 배치
② 오브젝트를 움직이는 방법을 쓴 스크립트를 작성
③ 작성한 스크립트를 오브젝트에 적용
BasketController 스크립트
▪ 탭한 곳으로 바구니를 이동시키는 스크립트 작성
▪ 무대는 3×3 구역으로 나뉘어 있으며 탭한 구역 중심으로 바구니를 배치
▪ 무대는 한 변의 길이가 3인 정방형
✓ X축 방향만 생각하면 탭한 좌표가
-1.5 ≤ X < -0.5일 때는 X = -1.0,
-0.5 ≤ X < 0.5일 때는 X = 0.0,
0.5 ≤ X < 1.5일 때는 X = 1.0으로 바구니를 이동하면 좋음
✓ 즉, 탭 좌표를 반올림한 값이 바구니를 배치할 좌표
▪ 유니티에는 Mathf.RoundToInt라는 반올림 메서드가 있음

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class BasketController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Application.targetFrameRate = 60;
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Mathf.Infinity))
{
float x = Mathf.RoundToInt(hit.point.x);
float z = Mathf.RoundToInt(hit.point.z);
transform.position = new Vector3(x, 0, z);
}
}
}
}
▪ 탭된 좌표(Input.mousePosition)를 바탕으로 바구니를 이동시키는 좌표를 계산
▪ Input.mousePosition은 스크린 좌표이므로 그대로 사용할 수 없음
▪ 스크린 좌표를 월드 좌표로 변환해 사용할 수 있도록 ✅ScreenPointToRay 메서드를 사용해 카메라 좌표에서 게임 화면 안쪽 방향으로 향해 나가는 광선(Ray)을 구함
▪ Physics.Raycast 메서드를 사용해 광선이 stage 오브젝트에 닿았는지 알아봄
✓ Physics.Raycast의 hit 매개변수 앞에 out 키워드가 붙어 있음
✓ 이 키워드는 ‘out에 계속 메서드 내 값을 채워 변수로 반환해 주세요!’라는 의미
✓ Raycast 메서드 안에서 광선이 stage와 충돌한 좌표를 hit.point 변수에 채워 반환
✓ 그 좌표를 RoundToInt 메서드를 사용해 반올림하고 바구니 좌표에 대입

▪ 카메라에서 나온 Ray가 무대에 닿지 않고 빠져나감
▪ 광선과 충돌 판정을 올바르게 할 수 있도록 무대에 Collider 컴포넌트를 적용
▪ 콜라이더가 stage 전면을 덮도록 Box Collider 조정
아이템 배치하기
▪ 바구니가 움직이면 다음에는 아이템을 떨어트림
▪ 떨어지는 아이템은 사과와 폭탄
▪ 이러한 아이템을 씬에 배치하고 나서 컨트롤러 스크립트를 작성
ItemController

▪ Update 메서드의 Translate 메서드를 사용해 아이템을 매 프레임마다 조금씩 아래 방향으로 이동시킴
▪ 아이템이 무대 아래로 내려가 보이지 않으면(Y 좌표가 -1.0 미만) 아이템을 소멸시킴
▪ 이전 프로젝트에서 화살을 움직이는 방법과 같음
▪ 사과와 폭탄이 모두 같은 동작을 하므로 ItemController를 재사용
▪ 떨어진 아이템을 바구니로 받고, 아이템을 받으면 일단 Console 창에 ‘잡았다!’고 표시하는 스크립트를 작성
▪ 바구니로 아이템을 받으려면 양측이 충돌한 것을 알아야 함
▪ 충돌했는지 판정하는 데 Physics를 이용
▪ Physics를 이용하면 오브젝트들이 충돌했을 때 오브젝트에 적용된 스크립트의 OnTriggerEnter 메서드를 호출할 수 있음

바구니와 아이템 충돌 판정하기
❇️Physics로 충돌 판정을 하려면 다음 두 가지 조건이 필요함
✓ 양쪽 오브젝트에 Collider 컴포넌트가 적용되어 있어야 함
✓ 적어도 한쪽 오브젝트에 Rigidbody 컴포넌트가 적용되어 있어야 함
▪ 바구니와 아이템(사과와 폭탄) 양쪽 모두에 Collider 컴포넌트를 적용해야 하고,
바구니에는 Rigidbody 컴포넌트도 적용
▪ 사과에 Collider 컴포넌트(Sphere Collider)를 적용
▪ 폭탄에 Collider 컴포넌트(Sphere Collider)를 적용
▪ 바구니에는 Rigidbody와 Collider 컴포넌트(Box Collider)를 적용
▪ 바구니에 아이템이 들어 있는지 판정하는 콜라이더 적용
▪ 바구니와 아이템끼리는 충돌 반응이 필요하지 않음
✓ Box Collider 항목의 Is Trigger를 체크
▪ 바구니가 아이템과 충돌했을 때 호출되는 OnTriggerEnter 메서드 추가 (BasketController)
public class BasketController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Application.targetFrameRate = 60;
}
// 충돌 상황 스크립트에서 감지
void OnTriggerEnter(Collider other)
// other은 사과 or basket -> 이를 구분하는게 tag
{
Debug.Log("잡았다!");
Destroy(other.gameObject);
}
▪ 유니티의 2D 게임에서는 충돌할 때 OnTriggerEnter2D 메서드가 호출되지만
3D 게임에서는 OnTriggerEnter 메서드가 호출
▪ 바구니에 아이템이 닿으면 Console 창에 잡았다!를 표시하고 아이템을 소멸시킴
✓ 아이템을 소멸시키려면 충돌한 아이템이 무엇인지 알아야 하는데, 다행히 유니티에는 충돌 상대를 알 수 있는 방법이 있음
✅ 충돌 상대는 OnTriggerEnter 메서드의 "매개변수"로 전달
✅ 다만 매개변수로 전달되는 것은 "충돌 상대의 게임 오브젝트가 아닌 충돌 상대의 게임 오브젝트로 적용된 ❇️콜라이더"

▪ 현재는 사과와 폭탄 중 무엇을 받았는지는 알 수 없음
▪ 받은 아이템을 판별해야 하므로 유니티가 제공하는 Tag(태그) 구조를 사용
☑️ Tag를 사용하면 오브젝트에 특정 이름(태그)을 달 수 있어 스크립트에서도 이 태그를 사용해 오브젝트를 판별할 수 있음
➡️ 여기서는 Apple과 Bomb이라는 태그를 만들어 각 오브젝트에 붙임
BasketController스크립트 수정
받은 것이 사과인지 폭탄인지 판별 -> OnTriggerEnter 메소드 수정
void OnTriggerEnter(Collider other)
// other은 사과 or basket -> 이를 구분하는게 tag
{
// Debug.Log("잡았다!");
if (other.gameObject.tag == "Apple")
{
Debug.Log("Tag=Apple");
}
else
{
Debug.Log("Tag=Bomb");
}
Destroy(other.gameObject);
// 이건 basketController라서 other -> basket (시험)
}
▪ 아이템을 받을 수 있게 되었는데 ‘받았다는 느낌’이 들지 않음
▪ 받았을 때의 반응은 바구니 크기 변경이나 느낌표 등 여러 가지를 생각할 수 있음
✓ 이번 프로젝트에서는 효과음을 적용함
▪ 효과음을 내는 데 AudioSource 컴포넌트를 사용
*효과음을 여러 개 내는 방법
① 효과음을 낼 오브젝트에 AudioSource 컴포넌트를 적용(Add Component)
② 언제 어떤 효과음을 낼 지 스크립트에서 지정,,
③ 스크립트의 변수에 효과음 파일을 대입,
효과음을 내는 시점을 스크립트에서 지정
▪ 소리를 내고 싶은 음원을 AudioSource 컴포넌트에 직접 등록
▪ AudioSource 컴포넌트에 등록할 수 있는 음원은 한 종류밖에 없음
✓ 사과와 폭탄을 받을 때 울리는 음을 분리해야 하므로 스크립트를 사용해 음원을 지정


스크립트의 변수에 음성 파일 대입
▪ 스크립트에서는 효과음 변수를 선언했을 뿐이므로(AudioClip을 넣는 상자를 만들었을 뿐이므로) 변수에 음성 파일 실체를 대입
▪ 여기서는 이미 익숙한 아웃렛 접속을 사용
✅아웃렛 접속
① 스크립트 쪽에 콘센트 구멍을 만들어야 하므로 스크립트 변수 앞에 public 접근 수식자를 붙임
② public 접근 수식자를 붙인 변수가 Inspector 창에 보임
③ 콘센트 구멍에 대입할 오브젝트(음성 파일)를 Inspector 창으로 드래그&드롭
프리팹 만들기
▪ 아이템 움직임을 만들었으므로 아이템을 자동으로 생성할 수 있는 공장을 건설
▪ 공장의 역할: ‘사과와 폭탄을 일정 시간 간격으로 임의의 위치에 무작위로 생성한다’는 것
공장을 만드는 방법
① 이미 있는 오브젝트를 사용해 프리팹을 만듦
② 제너레이터 스크립트를 만듦
③ 빈 오브젝트에 제너레이터 스크립트를 적용
④ 제너레이터 스크립트에 프리팹을 전달
사과 프리팹과 폭탄 프리팹 만들기
▪ applePrefab & bombPrefab
▪ 제너레이터 스크립트에서는 ‘일정 시간 간격으로 사과나 폭탄을 무작위 위치에 생성’
▪ 한꺼번에 모든 기능을 구현 x
✓ 우선은 ‘사과가 1초 간격으로 떨어진다’처럼 일부 기능을 만들고 조금씩 기능을 추가해 나가야 함
ItemGenerator 스크립트 생성

▪ 제너레이터 스크립트를 빈 오브젝트에 적용
▪ Project 창에서 ItemGenerator 스크립트를 클릭하고 Hierarchy 창의 ItemGenerator 오브젝트로 드래그&드롭
▪ 작성한 제너레이터 스크립트에 프리팹 실체를 전달

▪ 현재 상태:
✓ ‘아이템이 떨어지는 위치’ & ‘떨어지는 아이템 종류’ 고정
▪ 업그레이드:
1. 아이템이 떨어지는 위치를 무작위로 바꿈
2. 출현할 아이템을 무직위로 변경

ItemGenerator 수정

▪ 임의의 위치에 사과를 떨어트릴 수 있게 되었음
▪ 사과 중에 받으면 터지는 폭탄을 섞어 재미를 더할 수 있음
▪ 사과 대신 일정 확률로 폭탄이 생성되도록 수정해 보자
✓ 일정 확률로 폭탄을 생성하려면 어떻게 해야 할까?
✓ 예를 들어 20% 확률로 폭탄을 생성하려면 1부터 10까지 나오는 주사위를 던지고, 나온 주사위의 눈이 2 이하이면 폭탄을 생성하고 3 이상이면 사과를 생성하면 좋을 듯
// 폭탄이 떨어지는 비율을 정해보자

ItemGenerator 수정



▪ 공장에서는 아이템 생성에 관한 ☑️다양한 매개변수(생성 위치, 생성 속도, 아이템 종류 등)를 사용
▪ 매개변수를 변경하면 게임 난이도를 조절할 수 있음
▪ 레벨 디자인은 이러한 매개변수를 조절해 긴장감이 떨어지지 않도록 게임을 연출하는 것
▪ 매개변수를 일괄적으로 변경할 수 있도록 매개변수 조절용 메서드(setParameter)를 준비


▪ (1) 제한 시간과 (2) 득점 표시 두 가지를 UI로 준비
▪ ‘제한 시간 UI’에는 남아 있는 게임 시간을 카운트다운해 표시
▪ ‘득점 UI’에는 플레이어가 얻은 점수를 표시
✓ 사과를 받으면 점수에 100점을 더하고 폭탄을 받으면 점수를 절반으로 줄임
▪ UI를 만드는 방법과 마찬가지로 처음에 UI 리소스를 배치하고
다음으로 UI 내용을 갱신하도록 감독 스크립트를 작성
▪ 우선은 제한 시간용 UI를 배치해야 하므로 Hierarchy 창의 +를 클릭하고 UI → Text -TextMeshPro를 선택
▪ TMP Importer 창이 나타나면 Import TMPEssentials를 클릭하고 임포트가 끝나면 창을 닫음
▪ Hierarchy 창에 Text (TMP)가 만들어지면 이름을 Time으로 바꿈
제한시간 UI -> Time
득점표시 UI -> Point
▪ UI 리소스를 Scene 뷰에 배치 완
▪ 이 UI를 게임 진행 상황에 따라 갱신하는 감독을 작성
▪ 감독은 제한 시간과 득점을 관리하고 두 값이 갱신될 때 UI에 반영
▪ 감독을 만드는 방법은 이전과 같이 다음 세 단계
감독을 만드는 방법
① 감독 스크립트를 작성
② 빈 오브젝트를 만듦
③ 빈 오브젝트에 감독 스크립트를 적용
(1) 제한 시간만 먼저 구현!
▪ 제한 시간의 표시는 60초부터 카운트다운을 시작해 0초에 정지
▪ 제한 시간의 카운트다운에는 ✅각 프레임의 시간 차를 사용(Time.deltaTime) (1/60s씩 줄어듦)
▪ 게임을 시작할 때 제한 시간을 60초로 설정하고 프레임마다 현재 제한 시간에서 deltaTime을 빼면 카운트다운을 구현할 수 있음

GameDirector

(2) 감독에게 득점 관리 시키기 – 감독으로 UI 갱신
▪ 득점 자체는 감독이 관리
▪ 득점이 변하는 시점은 아이템이 바구니와 충돌할 때
✓ 이때 바구니 컨트롤러가 ‘득점을 늘려!, 줄여!’라고 감독에게 전하도록 함
✓ 그것을 받아 감독은 UI를 갱신
▪ 이러한 흐름을 정리하면 다음 두 가지 작업이 필요함
① 바구니 컨트롤러가 감독에게 득점 증감을 알림
② 감독이 UI를 갱신

GameDirector 수정


(2-2) 감독에게 득점 관리 시키기 – 바구니 컨트롤러에서 감독으로 득점 전달
BasketController 내 수정

❇️❇️ OnTriggerEnter() 메서드 수정

난이도에 직결될 것 같은 매개변수로는 다음 세 가지 정도를 생각할 수 있음
✓ 아이템 생성 속도
✓ 아이템 낙하 속도
✓ 사과와 폭탄 비율
GameDirector 수정


