본문 바로가기

유니티(게임수학_외적내적/ 타일 생성 포탑 설치 실습 / 조이스틱구현1) _ 멋쟁이사자처럼 유니티 부트캠프 후기 26회차

@salmu2025. 6. 23. 14:14
반응형

[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 마우스 버튼 누를 때 호출

 

 

이동과 점프 구현을 반복하면서 코드 구조가 점점 더 심플하고 효율적으로 다듬어지는 걸 체감하고 있다. 특히 애니메이션은 조건이 점점 다양해지면서 움직임이 자연스럽게 연결되는 느낌이 들었다. 확실히 반복하면서 익숙해지고 자연스러워지는 과정이 중요하다는 걸 다시 느꼈다. 꾸준함이 답이다.

반응형
salmu
@salmu :: SMU 각종 기록

목차