반응형
[26회차 수업내용]
1. 게임수학(외적/내적 등)
2. 타일 생성 / 포탑 설치 버튼 코드
3. 조이스틱 구현
1.1 내적
: 두 벡터가 얼마나 같은 방향으로 향하는지
- A·B = |A||B|cos(θ)
using UnityEngine;
public class MathDot : MonoBehaviour
{
// 두 개의 3차원 벡터 정의
public Vector3 vecA = new Vector3(1, 0, 0); // (1, 0, 0) → x축 방향 벡터
public Vector3 vecB = new Vector3(0, 1, 0); // (0, 1, 0) → y축 방향 벡터
void Start()
{
// 내적 값 : Vector3.Dot(vecA, vecB)
// 두 벡터가 수직일 때 → 내적 = 0
// float result = Vector3.Dot(vecA, vecB); // 두 벡터 사이 각도의 cos 값 반환 (여기서는 0)
// 두 벡터 사이 각도 구하기 (단위: °)
float result = Vector3.Angle(vecA, vecB); // (여기서는 90° 반환)
Debug.Log($"벡터의 끼인각 : {result}"); // 출력 결과: 벡터의 끼인각 : 90
}
}
| Vector3.Dot(A, B) | float (cosθ 값) | 두 벡터의 방향 비교 (양/음/0) |
| Vector3.Angle(A, B) | float (각도 °) | 두 벡터 사이의 각도 구하기 |
1.1.1 프로그래밍에서 내적 활용 예시
- 각도 구하기 → Vector3.Angle(A, B)
- 방향 비교 (예: 카메라 앞에 있는지 체크)
- 조명 계산 (조명 세기 = 내적 값) → 광원과 표면 방향 내적 → 광원 밝기 결정
- 충돌 판정, 물리 계산 등
1.2 외적
- 두 벡터 A, B의 외적 결과는 새로운 벡터를 반환.
- 이 벡터는 A와 B 모두에 수직인 방향을 가짐.
- 크기:→ 두 벡터가 직각이면 최대, 같은 방향이면 0

using UnityEngine;
public class MathCross : MonoBehaviour
{
// 두 개의 3차원 벡터 정의
public Vector3 vecA = new Vector3(1, 0, 0); // x축 방향 벡터 (→)
public Vector3 vecB = new Vector3(0, 1, 0); // y축 방향 벡터 (↑)
void Start()
{
// 외적 결과: 두 벡터에 모두 수직인 새로운 벡터 반환
Vector3 result = Vector3.Cross(vecA, vecB);
Debug.Log("벡터 A와 벡터 B의 외적 : " + result);
// 예상 결과: (0, 0, 1) → z축 방향 벡터 (점 기준으로 화면 밖으로 나가는 방향)
}
}
| Vector3.Cross(a, b) | Vector3 | 두 벡터에 수직인 벡터 반환 → 법선 벡터, 회전축 등 |
1.2.1 외적 활용 예시
- 법선 벡터 구하기 → 3D 모델 표면 방향 계산
- 회전 방향 결정
- 물체 회전 축 구하기
- 두 벡터로 이루어진 평면 넓이 구하기
1.3 Vector3.Reflect()
# inDirection : 입사 벡터 / inNormal : 반사할 표면의 법선 벡터
Vector3 Vector3.Reflect(Vector3 inDirection, Vector3 inNormal);
- Vector3.Reflect()는 입사 벡터가 표면에 부딪혀 반사되는 방향 벡터를 구하는 메서드.
1.3.1 활용 예시
- 물리 반사 효과 구현
- 빛 반사 시뮬레이션 (거울 효과)
- 벽에 튕기는 탄환 궤적
1.4 Math.Lerp()
: 시작 값(a)에서 도착 값(b)까지, 진행 정도(t)에 비례해서 반환하는 보간 함수
float Mathf.Lerp(float a, float b, float t);
//( 시작값, 종료값, 보간계수(0~1 사이 부동 소수점 = 진행정도)
- 내부 공식 : Lerp(a, b, t) = a + (b - a) \times t
- t는 반드시 0~1 사이 값 사용
- 0 미만 → extrapolate (시작값보다 더 작아짐)
- 1 초과 → extrapolate (종료값보다 더 커짐)
- 부드러운 애니메이션에는 보통 Time.deltaTime 활용
1.4.1 활용 예시
- 물체 위치 이동 → transform.position = Vector3.Lerp(start, end, t)
- 값 변화 → 체력, 투명도, 스케일 등 자연스러운 전환
- 애니메이션 보간 처리
- 슬라이더 값 변화 등 UI 애니메이션 효과
1.4.2. 예시 코드
using UnityEngine;
public class LerpEx : MonoBehaviour
{
// 이동할 목표 위치 (Transform)
public Transform target;
// 시간 측정용 변수
public float timer; // 경과 시간
public float percent; // 진행 비율 (0 ~ 1)
public float lerpTime; // 목표 위치까지 도달하는 데 걸리는 총 시간
// 시작 위치 저장용
public Vector3 startPos;
void Start()
{
// 시작 위치 저장 (초기 위치)
startPos = transform.position;
}
void Update()
{
// 매 프레임 경과 시간 증가
timer += Time.deltaTime;
// 현재 진행 비율 계산 (0 → 시작 위치, 1 → 목표 위치)
percent = timer / lerpTime;
// 시작 위치 → 목표 위치로 보간하여 위치 이동
transform.position = Vector3.Lerp(startPos, target.position, percent);
}
}
= 선형 보간(linear interpolation) 이라서 속도가 일정하게 이동
2.1 순차적으로 타일 생성 코드 실습
using System.Collections;
using UnityEngine;
public class SetTile : MonoBehaviour
{
// 생성할 타일 프리팹 (Inspector에서 연결)
public GameObject tilePrefab;
// 타일 행(rows)과 열(cols) 개수
public int rows = 5, cols = 5;
// Start 코루틴 → 타일을 하나씩 순차적으로 생성
IEnumerator Start()
{
// 행 반복 (Z축 방향)
for (int i = 0; i < rows; i++)
{
// 열 반복 (X축 방향)
for (int j = 0; j < cols; j++)
{
// 현재 타일 위치 설정 (가로 j, 세로 0, 세로줄 i)
var pos = new Vector3(j, 0, i);
// 타일 프리팹 생성
GameObject tile = Instantiate(tilePrefab, pos, Quaternion.identity);
// 타일의 Renderer 가져오기 → 색상 변경용
Renderer renderer = tile.GetComponent<Renderer>();
// 체스판 패턴처럼 흰색/검은색 반복
if ((i + j) % 2 == 0) // 인덱스 합이 짝수 → 흰색
renderer.material.color = Color.white;
else // 인덱스 합이 홀수 → 검은색
renderer.material.color = Color.black;
// 0.1초마다 하나씩 생성 → 천천히 생성 효과
yield return new WaitForSeconds(0.1f);
}
}
}
}
[ 생성 순서 ]
(i=0, j=0) → (0,0,0)
(i=0, j=1) → (1,0,0)
(i=0, j=2) → (2,0,0)
....
(i=1, j=0) → (0,0,1)
(i=1, j=1) → (1,0,1)
(i=1, j=2) → (2,0,1)
...
2.2 포탑 설치 타일 코드
using System;
using UnityEngine;
public class Tile : MonoBehaviour
{
// 설치할 포탑 프리팹 배열 (Inspector에서 설정)
public GameObject[] turretPrefabs;
// 마우스로 타일 클릭 시 호출되는 이벤트
void OnMouseDown()
{
// 이 타일 위치에 turretPrefabs[0] 프리팹 생성
Instantiate(turretPrefabs[0], transform.position, Quaternion.identity);
}
}
2.3 클로저 이슈
C#에서 for문 안의 람다(익명 함수=이름이 없는 함수)가 변수를 참조할 때, 반복문이 끝날 때 변수 값이 마지막 값으로 덮여버리는 문제
for (int i = 0; i < 5; i++)
{
int j = i; // 클로저 이슈 방지 → 버튼마다 고유 값 유지
buttons[i].onClick.AddListener(() => ChangeIndex(j));
}
- i 값을 새로운 지역 변수 j에 복사
- 만약 i를 바로 람다 함수 안에서 사용하면, 모든 람다가 같은 i 변수를 참조해서 마지막 값인 5가 적용됨
[ 터렛 버튼 연결 _ 클로저 이슈 해결]
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class SetTile : MonoBehaviour
{
public GameObject tilePrefab;
public int rows = 5, cols = 5;
// 터렛 선택용 버튼 배열 (Inspector에서 연결)
public Button[] buttons;
// 현재 선택된 터렛 종류 (다른 스크립트에서 접근 가능)
public static int turretIndex;
void Awake()
{
// 버튼에 클릭 이벤트 추가
for (int i = 0; i < 5; i++)
{
int j = i; // 클로저 이슈 방지 → 버튼마다 고유 값 유지
buttons[i].onClick.AddListener(() => ChangeIndex(j));
}
}
IEnumerator Start()
{
// 행(Z축 방향) 반복
for (int i = 0; i < rows; i++)
{
// 열(X축 방향) 반복
for (int j = 0; j < cols; j++)
{
// 현재 타일 위치 설정 (가로 j, 세로 0, 세로줄 i)
var pos = new Vector3(j, 0, i);
// 타일 프리팹 생성
GameObject tile = Instantiate(tilePrefab, pos, Quaternion.identity);
Renderer renderer = tile.GetComponent<Renderer>();
// 체스판 패턴 색상 설정 (인덱스 합이 짝수면 흰색, 홀수면 검정색)
if ((i + j) % 2 == 0) // 짝수
renderer.material.color = Color.white;
else // 홀수
renderer.material.color = Color.black;
// 0.1초 간격으로 타일 생성 → 애니메이션 효과
yield return new WaitForSeconds(0.1f);
}
}
}
// 선택한 버튼의 인덱스 값을 turretIndex에 저장
void ChangeIndex(int index)
{
turretIndex = index;
}
}
2.3.1.=> 람다 표현식 (Lambda Expression)
- 익명 함수(이름 없는 함수)를 만드는 문법
- 왼쪽에는 입력값, 오른쪽에는 실행할 코드(결과)
(x, y) => x + y
3.1 캐릭터 이동, 점프 (복습)
public class KnightController_Keyboard : MonoBehaviour
{
private Animator animator;
private Rigidbody2D knightRb;
private Vector3 inputDir; // 입력 방향 벡터 (x: 좌우, y: 상하)
[SerializeField] private float moveSpeed = 3f; // 이동 속도
[SerializeField] private float jumpPower = 13f; // 점프 힘
private bool isGround; // 캐릭터가 지면에 닿아있는지 여부 판단 변수
void Start()
{
animator = GetComponent<Animator>();
knightRb = GetComponent<Rigidbody2D>();
}
void Update() // 일반적인 작업 (입력 처리 등)
{
InputKeyboard(); // 키보드 입력 처리 함수 호출
}
void FixedUpdate() // 물리 연산 처리 (고정 프레임마다 호출)
{
Move();
}
// 지면과 충돌 시 호출
void OnCollisionEnter2D(Collision2D other)
{
// 충돌한 오브젝트가 "Ground" 태그를 가지고 있다면
if (other.gameObject.CompareTag("Ground"))
{
animator.SetBool("isGround", true); // 애니메이터에 지면 접촉 상태 전달
isGround = true; // 지면에 닿았다고 상태 저장
}
}
// 지면과 충돌이 끝날 때 호출
void OnCollisionExit2D(Collision2D other)
{
// 충돌이 끝난 오브젝트가 "Ground" 태그라면
if (other.gameObject.CompareTag("Ground"))
{
animator.SetBool("isGround", false); // 애니메이터에 지면에서 떨어짐 상태 전달
isGround = false; // 지면에서 떨어졌다고 상태 저장
}
}
// 키보드 입력 처리 함수
void InputKeyboard()
{
// 수평 및 수직 방향 입력값 가져오기 (범위 -1 ~ 1)
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
inputDir = new Vector3(h, v, 0); // 입력값으로 방향 벡터 생성
Jump(); // 점프 처리 함수 호출
SetAnimation(); // 애니메이션 상태 설정 함수 호출
}
void Move()
{
// 좌우 입력이 있을 때만 x축 속도 변경
if (inputDir.x != 0)
knightRb.linearVelocityX = inputDir.x * moveSpeed; // 이동 속도 적용
}
void Jump()
{
// 스페이스바 키를 눌렀고, 캐릭터가 지면에 있을 때만 점프 실행
if (Input.GetKeyDown(KeyCode.Space) && isGround)
{
animator.SetTrigger("Jump"); // 점프 애니메이터 트리거 실행
knightRb.AddForceY(jumpPower, ForceMode2D.Impulse); // 위쪽으로 점프 힘 적용 (임펄스 모드)
}
}
// 애니메이션 상태 설정 함수
void SetAnimation()
{
if (inputDir.x != 0) // 좌우 입력이 있으면
{
// x 방향에 따라 캐릭터 스케일 좌우 반전 (오른쪽 1, 왼쪽 -1)
var scaleX = inputDir.x > 0 ? 1 : -1;
transform.localScale = new Vector3(scaleX, 1, 1);
animator.SetBool("isRun", true); // 달리기 애니메이션 실행
}
else if (inputDir.x == 0) // 입력이 없으면
animator.SetBool("isRun", false); // 달리기 애니메이션 종료
}
}
3.2 Fall 애니메이션 구현

3.3 Event Trigger 컴포넌트
- UI 요소에 여러 가지 이벤트를 쉽게 연결할 수 있는 컴포넌트
- 주로 UI 버튼, 이미지 등에 사용되며, 마우스나 터치 이벤트를 처리할 때 사용
- Inspector에서 클릭으로 여러 이벤트를 추가할 수 있어 비코딩 방식으로도 자주 사용됨

이벤트 예시
| Pointer Enter | 마우스 커서가 해당 UI 위에 올라갈 때 호출 |
| Pointer Exit | 마우스 커서가 해당 UI에서 벗어날 때 호출 |
| Pointer Down | 마우스 버튼 누를 때 호출 |
이동과 점프 구현을 반복하면서 코드 구조가 점점 더 심플하고 효율적으로 다듬어지는 걸 체감하고 있다. 특히 애니메이션은 조건이 점점 다양해지면서 움직임이 자연스럽게 연결되는 느낌이 들었다. 확실히 반복하면서 익숙해지고 자연스러워지는 과정이 중요하다는 걸 다시 느꼈다. 꾸준함이 답이다.
반응형
'프로그래밍 > 유니티 부트캠프' 카테고리의 다른 글
| 유니티(콤보 공격 구현, 타일맵 Props 실습) _ 멋쟁이사자처럼 유니티 부트캠프 후기 28회차 (1) | 2025.06.26 |
|---|---|
| 유니티(조이스틱구현 2, Animator Blend Tree) _ 멋쟁이사자처럼 유니티 부트캠프 후기 27회차 (4) | 2025.06.24 |
| 유니티(몬스터 공격 실습/아이템획득/게임수학/터렛회전) _ 멋쟁이사자처럼 유니티 부트캠프 후기 25회차 (2) | 2025.06.23 |
| 유니티(상속/인터페이스 연습/layer) _ 멋쟁이사자처럼 유니티 부트캠프 후기 24회차 (0) | 2025.06.18 |
| 유니티(캡슐화,인터페이스/상속 실습 - 몬스터, 아이템) _ 멋쟁이사자처럼 유니티 부트캠프 후기 23회차 (2) | 2025.06.17 |