반응형
[25회차 수업내용] 250619
1. 캐릭터의 몬스터 공격 ( 24회차 몬스터 이어서 )
2. 아이템 획득
3. 게임수학
4. 게임 수학을 통한 터렛 회전 실습
1.1 캡슐화 실습
///Monster 스크립트
//기존 - Move() 내에서 8f 보다 오른쪽으로 가면 좌우 반전 -> 직접 dir = -1;, dir = 1;
//dir 는 직접접근 불가 private
//수정 = 캡슐화 _dir 필드는 private라서 외부에서 직접 접근 불가
//Dir 프로퍼티를 제공해서 필요한 경우 외부 접근 허용
public int Dir
{
get { return dir; } // 외부에서 dir 값을 읽을 수 있음
set { dir = value; } // 외부에서 dir 값을 수정할 수 있음
}
//flip 처리 코드 분리 → SetFlip(dir) 메서드로 따로 분리
void Move()
{
if (!isMove) // 이동 불가능 상태면 이동 처리 중단
return;
// 현재 방향(dir)에 따라 오른쪽 기준으로 이동
transform.position += Vector3.right * dir * moveSpeed * Time.deltaTime;
// 오른쪽 끝(8f) 도달 시 → 왼쪽으로 방향 전환
if (transform.position.x > 8f)
dir = -1;
// 왼쪽 끝(-8f) 도달 시 → 오른쪽으로 방향 전환
else if (transform.position.x < -8f)
dir = 1;
// 현재 방향에 따라 스프라이트 반전 처리
SetFlip(dir);
}
// 이동 방향에 따라 스프라이트 좌우 반전 설정
public void SetFlip(int dir)
{
if (dir > 0)
sRenderer.flipX = false; // 오른쪽 이동 시 → 스프라이트 원본 방향 유지
else
sRenderer.flipX = true; // 왼쪽 이동 시 → 좌우 반전 처리
1.2 아이템 인터페이스
using UnityEngine;
public interface IItem
{
GameObject Obj { get; set; }
void Get();
}
IItem → 규칙(기능의 약속)만 정의 → 게임 오브젝트에 붙이지 않음
- 아이템이 가져야 할 공통 규칙 정의.
- 어떤 아이템이든 Obj 라는 GameObject를 반드시 가지고 있어야 함.
- 어떤 아이템이든 Get() 이라는 메서드를 반드시 만들어야 함.
1.3아이템 클릭 시 인벤토리 획득
[코인_아이템]
using System;
using UnityEngine;
public class Coin : MonoBehaviour, IItem
{
private Inventory inventory; // 인벤토리 참조 (획득 시 아이템 저장)
public enum CoinType { Gold, Green, Blue }
public CoinType coinType;
public float price; // 코인의 가치 (가격)
void Start()
{
// 씬 내의 인벤토리 오브젝트 자동 참조
inventory = FindFirstObjectByType<Inventory>();
/// Coin 자신을 Obj에 저장 (필수 - IItem 약속 지킴)
// = IItem 인터페이스의 Obj 프로퍼티에 현재 오브젝트 할당
Obj = gameObject;
}
// 마우스 클릭 시 호출 (유니티 내장 메서드)
void OnMouseDown()
{
Get();
}
// 게임 오브젝트 참조 (IItem 인터페이스 구현용)
public GameObject Obj { get; set; }
// IItem 인터페이스 구현 → 아이템 획득 시 실행되는 동작 정의
public void Get()
{
Debug.Log($"{this.name}을 획득했습니다.");
inventory.AddItem(this); // 인벤토리에 현재 코인 추가
gameObject.SetActive(false);// 화면에서 사라짐
}
}
- IItem 인터페이스가 약속한 Obj와 Get() 구현
- Obj에 자기 자신인 gameObject 저장 → 나중에 인벤토리가 이걸 사용
- Get() 실행 시 인벤토리에 등록
[인벤토리]
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
// 인벤토리에 저장된 아이템(게임 오브젝트) 리스트
public List<GameObject> items = new List<GameObject>();
// 아이템을 인벤토리에 추가하는 메서드
// IItem 인터페이스를 구현한 객체를 받아서, 그 객체가 가진 게임 오브젝트를 리스트에 추가
public void AddItem(IItem item)
{
items.Add(item.Obj);
}
}
- IItem 인터페이스를 구현한 어떤 아이템이든 받아서 그 아이템의 Obj(GameObject)를 리스트에 저장함
- ➔ 타입은 IItem으로 받지만, 실제로 저장하는 건 GameObject
1.4 캐릭터로 몬스터 때리기 구현
1.4.1 Monster 스크립트
public IEnumerator Hit(float damage)
- 코루틴 퍼블릭으로 수정
1.4.2. 플레이어 컨트롤러
using System;
using System.Collections;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private Animator animator;
[SerializeField] private GameObject hitBox;// 공격 판정용 히트박스 오브젝트
[SerializeField] private float moveSpeed = 3f; // 플레이어 이동 속도
private float h, v;// 이동 입력 값 (수평, 수직)
private bool isAttack = false; // 공격 중 여부 (연속 공격 방지)
void Start()
{
animator = GetComponent<Animator>();
}
void Update()
{
Move();
Attack();
}
void Move()
{
h = Input.GetAxis("Horizontal");
v = Input.GetAxis("Vertical");
if (h == 0 && v == 0) // 아무 입력이 없으면 -> Idle 애니메이션
{
animator.SetBool("Run", false);
}
else // 입력이 있으면 -> Run 애니메이션, 이동 처리
{
// 만약 h 가 0보다 크면(오른쪽 방향) scaleX 에 1을 할당, 그렇지 않으면(왼쪽 방향 또는 0) -1을 할당
int scaleX = h > 0 ? 1 : -1;
transform.localScale = new Vector3(scaleX, 1, 1);// 방향에 따라 좌우 반전
animator.SetBool("Run", true);
var dir = new Vector3(h, v, 0).normalized; // 방향 벡터 (정규화)
transform.position += dir * moveSpeed * Time.deltaTime; //위치이동
}
}
void Attack()
{
if (Input.GetKeyDown(KeyCode.Space) && !isAttack) // 스페이스바 입력 + 공격 중이 아닐 때
{
StartCoroutine(AttackRoutine()); // 공격 코루틴 실행
}
}
// 공격 동작을 처리하는 코루틴
IEnumerator AttackRoutine()
{
isAttack = true; // 공격 중 상태
hitBox.SetActive(true); // 히트박스 활성화 (적과 충돌 시 타격 판정)
yield return new WaitForSeconds(0.25f); // 공격 지속 시간
hitBox.SetActive(false); // 히트박스 비활성화
isAttack = false; // 공격 끝
}
// 적 몬스터와 충돌 시 호출됨 (2D 트리거 충돌 감지)
void OnTriggerEnter2D(Collider2D other)
{
if (other.GetComponent<Monster>() != null) // 충돌한 대상이 몬스터라면
{
Monster monster = other.GetComponent<Monster>();
StartCoroutine(monster.Hit(1)); // 몬스터 피격 처리 (데미지 1)
}
}
// 아이템과 충돌 시 호출됨 (2D 일반 충돌 감지)
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.GetComponent<IItem>() != null) // 충돌한 대상이 아이템이라면
{
IItem item = other.gameObject.GetComponent<IItem>();
item.Get(); // 아이템 획득 처리
}
}
}
transform.localScale 이란?
- 게임 오브젝트의 로컬(자기 기준) 크기(scale) 상태를 나타내는 속성
- Vector3 타입으로 되어있으며, 각 축(X, Y, Z)에 대한 크기 배율을 의미
- -1 같은 음수 값을 주면 좌우/상하 반전(뒤집기) 효과를 낼 수 있음
hitbox 설정
- 빈 게임오브젝트 만들고
- 적절한 위치로 옮기고
- Collider 컴포넌트 달고 (Is Trigger 체크)
- 스크립트에 연결
[SerializeField] private GameObject hitBox;
*리마인드
var dir = new Vector3(h, v, 0).normalized; // 방향 벡터 (정규화)
- 정규화를 통해 대각선 이동 속도도 일정하게 유지함
if (other.gameObject.GetComponent<IItem>() != null)
- other: 충돌하거나 트리거에 닿은 상대방의 콜라이더 정보
- other.gameObject: 그 콜라이더가 붙어있는 게임 오브젝트
- GetComponent<IItem>(): 해당 오브젝트에 IItem 인터페이스를 구현한 컴포넌트가 있는지 찾아봄
- != null: null 이 아니라면, 즉 만약 찾았다면(즉, IItem을 구현한 컴포넌트가 존재하면) 조건문 통과
Monster monster = other.GetComponent<Monster>();
StartCoroutine(monster.Hit(1)); // 몬스터 피격 처리 (데미지 1)
- 충돌한 대상에 Monster 컴포넌트가 있으면, 그 몬스터 객체를 가져와서 Hit 코루틴을 시작
3. 게임 수학
3.1. Unity의 Mathf 클래스 <-> C#의 System.Math 클래스
- Mathf는 float를 사용 (유니티는 주로 float 사용)
- Unity에서 최적화 되어있고, 게임 개발에 맞춰져 있음
- 각도 변환 상수 제공 → 도↔라디안 변환 편함
ex
| 절댓값 | Mathf.Abs(x) | Abs(-5) → 5 |
| 최대/최소 | Mathf.Max(a, b) / Min(a, b) | Max(3,7) → 7 |
| 범위 제한 | Mathf.Clamp(val, min, max) | Clamp(15, 0, 10) → 10 |
| 보간 | Mathf.Lerp(a, b, t) | Lerp(0, 10, 0.5f) → 5 |
| 삼각함수 | Mathf.Sin(radian) / Cos(radian) | Sin(π/2) → 1 |
| 각도 구하기 | Mathf.Atan2(y, x) * Mathf.Rad2Deg | Atan2(1,0) → 90° |
| 제곱근 | Mathf.Sqrt(x) | Sqrt(9) → 3 |
| 반올림 | Mathf.Round(x) / Floor(x) / Ceil(x) | Round(2.7f) → 3 |
3.2 사인법칙 (Law of Sines)
: 삼각형에서 각과 마주 보는 변의 길이 비율이 일정하다.

using UnityEngine;
public class SinLaw : MonoBehaviour
{
public float aAngle = 30f; // 각 A (도 단위)
public float bAngle = 90f; // 각 B (도 단위)
public float aSide = 1f; // 변 a (각 A의 대변 길이)
void Start()
{
// 각도를 라디안으로 변환 (Mathf.Sin은 라디안 입력 필요)
float aRad = aAngle * Mathf.Deg2Rad;
float bRad = bAngle * Mathf.Deg2Rad;
// 사인법칙 적용 → bSide / sin(B) = aSide / sin(A)
float bSide = (aSide * Mathf.Sin(bRad)) / Mathf.Sin(aRad);
Debug.Log(bSide); // bSide 값 출력
}
}
- Mathf.Deg2Rad: “Degree to Radian” → 도(degree)를 라디안(radian)으로 바꿔줌
3.3 코사인법칙
삼각형에서 두 변의 길이와 끼인각(사이 각)을 알 때 나머지 변의 길이를 구하는 공식

using UnityEngine;
public class Cowlaw : MonoBehaviour
{
public float cAngle = 60f; // C각 (60도)
public float aSide = 1f; // a 변 길이
public float bSide = 1f; // b 변 길이
void Start()
{
float cRad = cAngle * Mathf.Deg2Rad; // 각도를 라디안으로 변환
float cSide = Mathf.Sqrt(Mathf.Pow(aSide, 2) + Mathf.Pow(bSide, 2) - 2 * aSide * bSide * Mathf.Cos(cRad));
Debug.Log(cSide); // 결과 출력
}
}
| Mathf.Cos() | 라디안 넣어서 코사인 값 구하기 |
| Mathf.Sqrt() | 제곱근(√) 구하기 (길이 구할 때 사용) |
| Mathf.Pow(x,2) | x² (제곱) |
3.4 탄젠트 법칙
삼각형에서 두 각과 그 사이 변 길이를 알 때, 또는 두 각과 한 변을 이용해 나머지 변 길이나 각을 구할 수 있는 공식

4.1 퍼린 노이즈(Perlin Noise)
- 자연스러운 랜덤 패턴을 만들기 위한 함수
- 주로 지형 생성, 구름, 물결, 텍스처 등에 사용됨
- 일반 랜덤보다 부드럽고 연속적인 변화를 만들어줌
float x = 0.5f;
float y = 1.2f;
float noiseValue = Mathf.PerlinNoise(x, y);
Debug.Log(noiseValue); // 0~1 사이 값 출력
Sin 함수와 PerlinNoise를 활용한 라이트의 밝기 조절
using System;
using UnityEngine;
public class MathLight : MonoBehaviour
{
private Light light; // 이 게임오브젝트에 붙은 Light 컴포넌트 참조
private float theta; // 시간에 따라 증가하는 각도(라디안)
void Start()
{
light = GetComponent<Light>(); // Light 컴포넌트 찾아서 저장
}
void Update()
{
theta += Time.deltaTime; // 매 프레임마다 시간만큼 theta 증가 (시간 경과 표현)
light.intensity = Mathf.Sin(theta); // 빛 세기를 사인 함수로 주기적 변화 (0~1 사이가 아니라 -1~1 사이임)
// perlinNoise
// light.intensity = Mathf.PerlinNoise(theta, 0) * power;
}
}
4.2 삼각함수를 이용한 원회전
using UnityEngine;
public class MathCircle : MonoBehaviour
{
private float theta; // 각도(라디안)를 저장하는 변수, 시간이 지날수록 증가함
void Update()
{
theta += Time.deltaTime; // 매 프레임마다 시간에 따라 각도 증가 (초당 1 라디안씩 증가)
float x = Mathf.Cos(theta); // 현재 각도의 코사인 값 -> 원의 x 좌표 계산
float y = Mathf.Sin(theta); // 현재 각도의 사인 값 -> 원의 y 좌표 계산
Vector2 pos = new Vector2(x, y); // (x, y) 좌표를 2D 벡터로 만듦
transform.position = pos; // 게임 오브젝트의 위치를 (x, y)로 변경 -> 원을 따라 움직임
}
}
- 게임 오브젝트가 (0, 0) 중심을 기준으로 반지름 1인 원을 시계 반대 방향으로 일정한 속도로 계속 원형으로 움직이는 코드
4.3 Sin 함수를 이용한 터렛의 회전
using System;
using UnityEngine;
public class Turret : MonoBehaviour
{
public Transform turretHead; // 터렛 머리 부분 (회전시킬 부분)
private float theta; // 회전 각도 계산용 변수
public float rotSpeed = 1f; // 터렛 기본 회전 속도
public float rotRange = 60f; // 터렛 좌우 회전 범위 (± 각도)
private bool isTarget; // 타겟 감지 여부
// 바라볼 타겟(플레이어)
public Transform target;
void Update()
{
// 타겟이 없으면 기본 동작, 타겟 있으면 타겟 추적
if (!isTarget)
TurretIdle(); // 기본 회전 상태
else
LookTarget(); // 타겟 추적 회전
}
// 타겟이 없을 때 좌우로 자동 회전하는 동작
void TurretIdle()
{
theta += Time.deltaTime * rotSpeed; // 시간에 따라 각도 증가
float rotY = Mathf.Sin(theta) * rotRange; // 좌우로 부드럽게 흔들리는 값 계산
turretHead.localRotation = Quaternion.Euler(0, rotY, 0); // 계산한 각도로 회전 적용
}
// 타겟이 있을 때 타겟 방향으로 바라보는 동작
void LookTarget()
{
turretHead.LookAt(target); // 타겟 방향으로 회전
}
// 플레이어가 터렛 범위에 들어오면 타겟으로 설정
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
target = other.transform; // 플레이어 트랜스폼 저장
isTarget = true; // 타겟 감지 상태 변경
}
}
}
*리마인드
Quaternion.Euler(x, y, z)
- : 오일러 각도(°)를 쿼터니언(Quaternion) 으로 변환하는 함수
- 오일러 각은 사람이 이해하기 쉽지만, 짐벌락(Gimbal Lock) 문제가 있음
4.4 Vector의 크기를 구하기
using UnityEngine;
public class MathVector : MonoBehaviour
{
// 두 개의 3D 벡터 선언
public Vector3 vecA = new Vector3(3, 0, 0);
public Vector3 vecB = new Vector3(0, 4, 0);
void Start()
{
// vecA + vecB 벡터의 길이(크기) 구하기
float size = Vector3.Magnitude(vecA + vecB);
Debug.Log($"Magnitude : {size}"); // 결과: 5 (3,4,5 직각삼각형)
// vecA와 vecB 두 점 사이 거리 구하기
float distance = Vector3.Distance(vecA, vecB);
Debug.Log($"Distance : {distance}"); // 결과: ≈5
// vecA + vecB 벡터의 길이의 제곱 구하기 (루트 안 씌운 값)
float size2 = Vector3.SqrMagnitude(vecA + vecB);
Debug.Log($"SqrMagnitude : {size2}"); // 결과: 25
}
}
| Vector3.Magnitude | 벡터 길이 (√(x² + y² + z²)) | 5 |
| Vector3.Distance | 두 벡터 간 거리 (끝 점끼리 거리) | 5 |
| Vector3.SqrMagnitude | 벡터 길이의 제곱 (x² + y² + z²) | 25 |
캐릭터가 몬스터를 죽이고 아이템을 획득하는 일련의 과정들을 배우니 진짜 게임 개발에 가까워지고 있다는 기분이 들었다.아직은 복잡해서 바로바로 활용하긴 어렵지만, 차근차근 익숙해지고 싶다. 계속 리마인드식으로 반복적으로 정리하려고한다. 수학을 접목해 오브젝트 회전을 만드는 부분도 재미있었다.
반응형
'프로그래밍 > 유니티 부트캠프' 카테고리의 다른 글
| 유니티(조이스틱구현 2, Animator Blend Tree) _ 멋쟁이사자처럼 유니티 부트캠프 후기 27회차 (4) | 2025.06.24 |
|---|---|
| 유니티(게임수학_외적내적/ 타일 생성 포탑 설치 실습 / 조이스틱구현1) _ 멋쟁이사자처럼 유니티 부트캠프 후기 26회차 (0) | 2025.06.23 |
| 유니티(상속/인터페이스 연습/layer) _ 멋쟁이사자처럼 유니티 부트캠프 후기 24회차 (0) | 2025.06.18 |
| 유니티(캡슐화,인터페이스/상속 실습 - 몬스터, 아이템) _ 멋쟁이사자처럼 유니티 부트캠프 후기 23회차 (2) | 2025.06.17 |
| 유니티(오버로딩,상속,가상화/추상화,인터페이스 실습) _ 멋쟁이사자처럼 유니티 부트캠프 후기 22회차 (0) | 2025.06.17 |