Menu
CoddyTech

영웅

Coddy Unity 여정의 Unity 게임 아키텍처 섹션에 포함된 레슨. 64개 중 50번째.

이 장에서는 과정의 마지막 게임인 Arena Survivor를 만듭니다. 적들이 성벽으로 둘러싸인 투기장으로 물밀듯이 들어오고, 여러분은 검으로 적들을 물리칩니다. 적에는 여러분을 향해 걸어오는 잡졸과 잠시 기다렸다가 돌진하는 구슬이 있습니다. 한 웨이브를 물리치면 파워업이 남고, HUD에는 체력과 웨이브가 표시됩니다. 체력이 다 떨어지면 플레이가 끝나고 최고 기록이 저장됩니다. 이 게임에서는 이 섹션의 도구들을 적절히 활용합니다. 두 종류의 적을 위한 추상 클래스 Enemy, 피해를 입을 수 있는 모든 것을 위한 IDamageable, ScriptableObject 에셋으로 만드는 웨이브, 인터페이스를 사용하는 픽업, C# 이벤트만 수신하는 HUD, 그리고 JSON 저장 기능을 사용합니다. 열두 개의 스크립트는 모두 처음부터 프로젝트에 포함되어 있습니다. 아직 Hero만 작동하며, 나머지 스크립트에는 이후 수업에 필요한 필드가 있고 각 수업에서 더 많은 기능을 채워 넣습니다.

영웅의 검은 앞에 있는 대상을 공격하므로, 휘두를 때 대상이 ‘앞에 있는지’ 확인해야 합니다. Vector3.Dot(a, b)는 두 벡터에서 서로 대응하는 부분끼리 곱한 다음 모두 더합니다. 즉, a.x * a.x + a.y * b.y + a.z * b.z입니다. 길이가 1인 두 방향 벡터의 경우, 이 값은 두 벡터 사이 각도의 코사인입니다. 두 벡터가 같은 방향을 가리키면 1, 각도가 30도이면 0.866, 45도이면 0.707, 직각이면 0, 반대 방향이면 -1입니다. 따라서 ‘내가 향한 방향에서 30도 이내’라는 조건을 각도를 계산하지 않고도 ‘내적이 0.866 이상’이라는 조건으로 표현할 수 있습니다. 이 보초 로봇은 플레이어가 10유닛 이내에 있고 30도 시야각 안에 들어오면 경보를 울립니다.

public Transform player;
public float range = 10f;

void Update()
{
    Vector3 toPlayer = player.position - transform.position;
    toPlayer.y = 0f;
    bool near = toPlayer.magnitude <= range;
    bool inView = Vector3.Dot(transform.forward, toPlayer.normalized) >= 0.866f;
    if (near && inView)
    {
        Debug.Log("Alarm!");
    }
}

두 방향 벡터의 길이는 모두 1이어야 합니다. transform.forward의 길이는 항상 1이지만, toPlayer의 길이는 거리에 따라 달라지므로 먼저 정규화합니다. 그렇지 않으면 플레이어가 두 배 멀리 있을 때 내적도 두 배가 됩니다. 정규화하기 전에 y를 0으로 설정하므로, 더 높은 보행로에 있는 플레이어는 높이가 아니라 지도상 위치에 따라 계산됩니다. 내적의 부호로 앞쪽과 뒤쪽을 구분할 수 있습니다. 0보다 크면 앞쪽 절반 어디에든 있고, 0보다 작으면 뒤쪽 어디에든 있습니다.

challenge icon

챌린지

중급

영웅에게 검을 쥐여 주세요.

  1. Update()에서 Space 키 또는 마우스 왼쪽 버튼을 눌렀고 Time.time >= nextSwing이면 nextSwing = Time.time + swingCooldown;을 설정하고 Swing()을 호출하세요.
  2. Swing()에서 Physics.OverlapSphere(transform.position, reach)을 반복문으로 순회하세요.
  3. 맞은 대상 중 태그가 Enemy이고, 평평하게 만든 방향 toHit(y를 0으로 설정)가 Vector3.Dot(transform.forward, toHit.normalized) >= 0.5f를 만족하는 대상을 파괴하세요.

‘플레이’를 누르고 휘둘러 보세요. 앞에 있는 허수아비가 사라집니다. 그런 다음 ‘확인’을 누르세요.

직접 해보기

using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;

public class Hero : MonoBehaviour
{
    [SerializeField] private float speed = 5f;
    [SerializeField] private float reach = 2f;
    [SerializeField] private float swingCooldown = 0.5f;
    [SerializeField] private GameObject slash;

    private CharacterController controller;
    private InputAction moveAction;
    private float nextSwing;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        moveAction = InputSystem.actions.FindAction("Move");
    }

    void Update()
    {
        Vector2 input = moveAction.ReadValue<Vector2>();
        Vector3 move = new Vector3(input.x, 0f, input.y);
        controller.SimpleMove(move * speed);
        if (move != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(move);
        }

        // Space 키나 왼쪽 클릭으로 공격하며, swingCooldown초마다 최대 한 번만 공격할 수 있습니다
    }

    void Swing()
    {
        StartCoroutine(ShowSlash());
        // 영웅의 앞쪽에서 reach 이내에 있고 Enemy 태그가 붙은 모든 것을 파괴합니다
    }

    IEnumerator ShowSlash()
    {
        slash.SetActive(true);
        yield return new WaitForSeconds(0.1f);
        slash.SetActive(false);
    }
}

Unity 게임 아키텍처의 모든 레슨

직접 연습해 보세요: Unity 플레이그라운드