Week 03 / Period 02

Unity는 코드를 언제 실행하는가

MonoBehaviour의 호출 순서, Inspector의 값, 프레임 사이의 시간을 연결해 하나의 안정적인 움직임이 만들어지는 원리를 설명합니다.

산호색 오브젝트가 초기화된 뒤 네 개의 시간 프레임을 따라 일정하게 이동하고 회전하는 장면
수업 방식
교수자 설명과 Live Demo
핵심 질문
같은 속도가 기기마다 같으려면?
연결 개념
field · method · if
새 개념
lifecycle · serialization · deltaTime
학생 활동
실행 결과 예측과 코드 추적
다음 교시
독립 구현·검증 미션
교수자 설명 자료 교수자가 작성하고 실행하며, 학생은 원인을 예측합니다.

학생이 메뉴를 따라 누르는 시간이 아닙니다. 교수자는 코드를 한 덩어리씩 공개하고 Inspector 값을 바꾸어 결과를 시연합니다. 학생은 각 변화 전에 결과를 먼저 말합니다.

오늘의 도착점 “Inspector에서 정한 초당 속도를 Update가 읽고, deltaTime만큼 나누어 매 프레임 Transform에 적용한다”라고 설명할 수 있습니다.

코드의 줄과 화면의 변화가 연결되어야 합니다. 단순히 ‘deltaTime을 곱한다’가 아니라 왜 필요한지까지 이해하는 것이 목표입니다.

  1. 1교시 회수

    field, method, if의 역할을 완성 코드에서 다시 찾습니다.

  2. 생명주기

    Awake, Start, Update의 호출 시점과 책임을 구분합니다.

  3. Inspector 직렬화

    코드 기본값과 instance별 저장값의 차이를 설명합니다.

  4. 시간 기반 변화

    프레임 이동량과 초당 속도의 차이를 계산합니다.

  5. 완성 코드 시연

    이동·회전·경계 처리를 화면 결과와 한 줄씩 연결합니다.

  6. 예측 점검

    Inspector 변화 세 가지의 결과를 코드로 설명합니다.

MonoBehaviour는 Unity와 맺은 호출 약속입니다

Awake(), Start(), Update()는 우리가 직접 호출하지 않아도 Unity가 정해진 시점에 호출하는 event function입니다.

  1. Awake()로드될 때 한 번

    자기 Component 참조나 내부 초기 상태처럼 가장 이른 준비를 둡니다.

  2. OnEnable()활성화될 때마다

    Component가 사용 가능한 상태가 될 때 호출됩니다. 이번 주에는 존재만 알아둡니다.

  3. Start()첫 Update 전에 한 번

    활성화된 Component가 본격적으로 동작하기 전 시작 준비를 마칩니다.

  4. Update()Play 중 매 프레임

    입력 확인, Transform 변화처럼 시간에 따라 계속 달라지는 일을 둡니다.

Start · once한 번 준비하는 일

시작 위치 기억, 첫 상태 설정, 시작 로그처럼 반복할 이유가 없는 작업입니다.

Update · repeated계속 갱신하는 일

위치 이동, 회전, 조건 검사처럼 프레임마다 새 결과가 필요한 작업입니다.

이름과 대소문자도 약속의 일부입니다

update()START()는 문법 오류가 없어도 Unity의 event function으로 호출되지 않습니다. “오류는 없는데 움직이지 않는다”면 함수 이름, 대소문자, 괄호 형태를 먼저 확인합니다.

Prediction 01

시작 위치 저장과 계속되는 이동은 어디에 둘까요?

실행 횟수를 기준으로 선택합니다.

Inspector는 instance의 설정을 저장합니다

Unity의 직렬화는 field 값을 Scene이나 Prefab 데이터로 저장하고 Inspector에 표시할 수 있게 합니다. 같은 Script라도 instance마다 다른 값이 가능한 이유입니다.

Course default · 캡슐화와 조절
[SerializeField] private float moveSpeed = 2f;
[SerializeField] private bool shouldRotate = true;
01Default

코드가 새 Component의 출발값을 제안합니다.

02Serialize

Unity가 지원하는 field 값을 Scene 또는 Prefab에 저장합니다.

03Inspect

개발자가 코드를 고치지 않고 값을 조절합니다.

04Instance

각 Component가 서로 다른 저장값으로 같은 행동을 실행합니다.

public fieldpublic float moveSpeed;

Inspector에 보이지만 다른 Script도 직접 접근할 수 있어 값의 변경 범위가 넓습니다.

Course default[SerializeField] private float moveSpeed;

Inspector에는 보이되, class 밖에는 내부 구현으로 숨깁니다.

코드 기본값보다 저장된 Inspector 값이 우선할 수 있습니다

Component를 붙인 뒤 Inspector에서 moveSpeed를 5로 저장했다면 그 instance는 5를 사용합니다. 이후 코드의 기본값을 3으로 바꾸어도 이미 직렬화된 값이 자동으로 3으로 바뀌지 않을 수 있습니다. “코드를 고쳤는데 값이 그대로다”라는 상황의 흔한 원인입니다.

Prediction 02

같은 Script를 쓰는 두 오브젝트가 서로 다른 속도로 움직일 수 있는 이유는?

코드와 데이터 중 무엇이 달라지는지 고릅니다.

속도는 ‘프레임당’이 아니라 ‘초당’으로 정의합니다

컴퓨터마다 한 초에 그리는 프레임 수가 다릅니다. 프레임마다 같은 거리를 더하면 빠른 기기에서 더 멀리 움직이므로, 직전 프레임부터 흐른 시간을 곱합니다.

Per frame · unstabledistance = speed

속도 2를 매 프레임 더하면 30 FPS에서는 1초에 60, 60 FPS에서는 120만큼 움직입니다.

Per second · stabledistance = speed × deltaTime

각 프레임의 이동량은 달라도 1초 동안 더한 총거리는 속도와 거의 같아집니다.

환경한 프레임 시간프레임 이동량1초 총거리
30 FPS약 0.033초2 × 0.033 ≈ 0.066약 2 unit
60 FPS약 0.0167초2 × 0.0167 ≈ 0.033약 2 unit

이번 프레임의 변화량 = 초당 변화량 × Time.deltaTime

Time.deltaTime은 ‘시간 그 자체’가 아닙니다

게임 시작 후 누적된 전체 시간이 아니라, 바로 앞 프레임에서 현재 프레임까지 걸린 초 단위 간격입니다. 프레임마다 조금씩 달라질 수 있습니다.

Prediction 03

속도 4, deltaTime = 0.25라면 이번 프레임 이동량은?

speed × deltaTime을 계산합니다.

Translate와 Rotate는 Transform의 상태를 바꿉니다

이번 주에는 물리 힘을 사용하지 않고 Transform을 직접 바꿉니다. 다음 주 Rigidbody2D를 배우면 물리 기반 이동과 책임을 구분합니다.

Vector3.right

방향

월드의 오른쪽을 나타내는 (1, 0, 0) 방향 벡터입니다. 속도와 시간을 곱하면 이번 프레임의 이동 벡터가 됩니다.

Space.World

기준 좌표계

오브젝트가 회전하더라도 화면의 오른쪽, 즉 월드 x축 방향으로 이동하게 합니다.

Vector3.forward

2D 회전축

화면을 뚫고 나오는 z축 방향입니다. 2D Sprite는 이 축을 중심으로 돌아갑니다.

rotationSpeed

부호와 방향

양수와 음수는 반대 회전 방향을 만듭니다. 크기의 절댓값은 초당 각속도를 나타냅니다.

Transform API · 한 프레임의 변화
transform.Translate(
    Vector3.right * moveSpeed * Time.deltaTime,
    Space.World
);

transform.Rotate(
    Vector3.forward,
    rotationSpeed * Time.deltaTime,
    Space.Self
);
  • 방향 × 속도 × 시간어느 방향으로, 초당 얼마나, 이번 프레임 동안 얼마만큼 변할지를 하나의 벡터로 만듭니다.
  • Space.World이동 기준을 월드로 고정합니다. 회전 중인 Sprite도 계속 화면 오른쪽으로 갑니다.
  • Space.Self회전을 현재 오브젝트의 축을 기준으로 적용합니다. 2D의 z축 회전에서는 결과가 직관적입니다.

완성 코드를 실행 흐름으로 읽습니다

아래 코드는 교수자 Live Demo의 완성본입니다. 학생은 복사해 따라 치지 않고, field가 어느 method에서 사용되는지 색인하듯 추적합니다.

MotionController.cs · 교수자 시연 완성본
using UnityEngine;

public class MotionController : MonoBehaviour
{
    [SerializeField, Min(0f)]
    private float moveSpeed = 2f;

    [SerializeField]
    private bool shouldRotate = true;

    [SerializeField]
    private float rotationSpeed = 90f;

    [SerializeField]
    private float resetAtX = 8f;

    [SerializeField]
    private float restartAtX = -8f;

    private void Start()
    {
        Debug.Log(name + "의 움직임을 시작합니다.");
    }

    private void Update()
    {
        Move();
        RotateWhenEnabled();
        WrapAtBoundary();
    }

    private void Move()
    {
        Vector3 movement =
            Vector3.right * moveSpeed * Time.deltaTime;

        transform.Translate(movement, Space.World);
    }

    private void RotateWhenEnabled()
    {
        if (!shouldRotate)
        {
            return;
        }

        float angle =
            rotationSpeed * Time.deltaTime;

        transform.Rotate(
            Vector3.forward,
            angle,
            Space.Self
        );
    }

    private void WrapAtBoundary()
    {
        if (transform.position.x <= resetAtX)
        {
            return;
        }

        Vector3 nextPosition = transform.position;
        nextPosition.x = restartAtX;
        transform.position = nextPosition;
    }
}
  1. Start()Play 시작 뒤 한 번 이름이 포함된 로그를 남깁니다. 반복 동작과 초기 동작의 차이를 눈으로 확인하기 위한 시연입니다.
  2. Update()매 프레임 세 method를 같은 순서로 호출합니다. 전체 흐름을 읽는 목차 역할을 합니다.
  3. Move()오른쪽 방향, 초당 속도, 프레임 시간을 곱해 월드 좌표로 이동합니다.
  4. RotateWhenEnabled()shouldRotate가 false이면 즉시 돌아가고, true일 때만 시간 기반 각도를 적용합니다.
  5. WrapAtBoundary()x가 오른쪽 경계를 넘었을 때 y와 z는 유지하고 x만 왼쪽 시작값으로 바꿉니다.
  1. 코드 실행 전

    Inspector의 다섯 값을 짚고, 각 값이 사용되는 method를 학생에게 묻습니다.

  2. Play 직후

    Console에는 한 번만 로그가 생기고, Transform 값은 계속 바뀌는 차이를 보여 줍니다.

  3. 값 변경 전

    moveSpeed, shouldRotate, rotationSpeed 중 하나를 바꾸기 전에 결과를 예측하게 합니다.

  4. 경계 시연

    resetAtX를 화면 안쪽으로 옮겨 조건 통과 시점을 빠르게 관찰합니다.

값을 바꾸기 전에 화면을 예측합니다

아래 시뮬레이터는 교수자 설명용입니다. 실제 Unity의 Inspector를 단순화했으며, 코드가 아니라 field 값만 바뀌어도 결과가 달라진다는 점을 보여 줍니다.

Teacher demo · simplified model

MotionController 값 시연

Question 01moveSpeed를 두 배로 바꾸면?

같은 시간 동안 이동하는 거리가 약 두 배가 됩니다. 회전 속도에는 영향을 주지 않습니다.

Question 02shouldRotate를 끄면?

회전만 멈춥니다. Move()WrapAtBoundary()는 계속 호출됩니다.

Question 03rotationSpeed를 음수로 바꾸면?

회전 방향이 반대로 바뀝니다. 이동 방향은 Space.World의 오른쪽으로 유지됩니다.

한 화면의 움직임을 네 연결고리로 설명합니다

아래 순서를 끊기지 않게 말할 수 있으면 3교시 독립 미션을 시작할 준비가 된 것입니다.

01Inspector

instance별 field 값을 저장합니다.

02Update

Unity가 매 프레임 호출합니다.

03deltaTime

초당 속도를 프레임 변화량으로 바꿉니다.

04Transform

위치와 회전 상태가 실제로 달라집니다.

교수자 확인 질문

“회전은 멈추되 이동은 유지하려면 어느 값을 바꾸고, 어느 if가 어떤 method 호출을 건너뛰는가?”에 코드의 field 이름과 method 이름을 사용해 답합니다.

설명과 시연의 공식 근거

현재 수업 기준인 Unity 6.6 문서에서 실행 순서, 직렬화, 시간과 Transform API의 정확한 의미를 확인합니다.

Time

Time.deltaTime ↗

이전 프레임에서 현재 프레임까지 흐른 초 단위 시간

Transform

Transform.Rotate ↗

축, 각도, 기준 좌표계를 지정해 Transform 회전을 바꾸는 API

Console

Debug.Log ↗

실행 시점과 상태를 Console에서 관찰하기 위한 기본 출력

질문이 남았다면

shouldRotate를 false로 바꿨을 때 이동은 남고 회전만 사라졌습니다. 이 결과를 만드는 return의 범위가 어디까지인가요?”처럼 바꾼 값, 관찰한 결과, 궁금한 코드 범위를 함께 제시합니다.