본문 바로가기
Unity

Unity(2) - Basic Move, Rotation

by Srff5123 2026. 2. 13.
728x90

 

 

저번 글에서는 맛보기로 2d로 가볍게 유니티에 대해서 알아보았다

오늘은 조금 빠르게 배워보고자 3d로 만들어서 해볼려고 한다

 

프로젝트 생성에서 3d Universal로 생성하여 준다.

최종 목표는 모바일로 마인크래프트와 비슷하게 구현하는걸로 생각중이다

 

그러므로 일단 2 / 13 시작 - 오늘의 작업은 

캐릭터의 상하좌우 움직임과 점프, 앉기, 공격만들어 주기

카메라 : 터치 드래그로 시점 회전 ( 요, 피치 사용), 피치는 제한을 걸어줌(목이 꺾기진 않으니)

모바일 입력 : 왼쪽 화면에 상하좌우 버튼 , 오른쪽 화면에는 점프, 앉기, 화면 터치 시 공격

 

이렇게 일단 기초적인 부분이 되겠다

프로젝트가 생성이 완료 되었으면 우선 모바일 환경 플랫폼으로 만들어주자

File -> Build Settings or Build Profiles에서

글쓴이는 안붕이라 Android로 하겠다

이걸 해야 유니티가 모바일용 임포트 / 설정으로 자동으로 맞추어 준다.

 

간단하게 설명하면 

Texture Compresiion = Use Player Settings로 나중에 최적화 할 떄 필요한 부분이라 나중에 해도되고

Run Device / Patch는 기기 연결해서 테스트 하는 부분

 

다음은 상황별로 필요한 부분인데

Google play에 올린다면 

Build App Bundle 

플레이스토어 배포용은 보통 AAB가 표준인데 나중에 체크해도됌 왜냐 지금은 테스트 단계이기 때문

 

용량 / 속도 관련

Compression Method = LZ4로 개발중에는 이걸로 해야 빌드가 빨라짐

출시 직전이라고 하면 LZ4HC로 바꿔서 용량을 줄임

 

상단에 뜨는 경고 / 알림 2개는

Diagnostics Data(개발자 데이터 프레임 워크)

Unity는 기본으로 진단 데이터 수집을 켜둔다는 안내 현재는 Dismiss해도 괜찮음

 

shared scene list + Add Build Profile

지금은 Android가 공유 씬 목록을 쓰고 있다는 말인데

나중에 ios / pc 로 할 경우 Add Build Profile로 분리하면 됌

 

즉 그냥 Switch하면됌

 

이제 다음 Edit -> Project Settings -> Player -> Android

 

Orientation(가로/세로 고정)

Minimum API Level ( 적당히 잡아주기)

IL2CPP = 빌드 백엔드 (출시용)

 

핸드폰을 통해 자주 테스트를 한다면 나중 Development Build만 켜서 빌드/실행하면 디버깅이 편해진다.

 

자 가볍게 에디터 창에 대해서 설명을 해보겠다 

 

Scene - 편집 화면( 오브젝트 배치 / 이동 / 회전)

Game - 실행했을 경우 플레이어가 보게 될 화면

Hierarchy - 현재 씬에 존재하는 오브젝트 목록

Inspector - 선택한 오브젝트의 설정(컴포넌트 창)

Project - Assets 폴더(파일 / 스크립트 / 프리팹 / 머테리얼 등)

Console - 에러 / 경고 / 로그

 

자 이제 Assets 폴더에서 

 

Scripts, Prefabs, Materials 폴더를 만들어준다 ( 폴더로 미리 크게 크게 분류해둬야 나중에 찾기 쉬움)

 

다음 Scene을 저장부터 할건데 상단 메뉴에서 File -> Save As 로  Assets / Scenes에 Main으로 저장한다.

 

이제 설정은 다했으니 오브젝트를 만들어주자

상단 메뉴에서 GameObject -> 3D Object -> Plane

Hierarchy에서 plane을 선택 Inspector에서 Transform 위치는 0,0,0 크기는 10, 1, 10정도로 해서 

플레이어가 서 있을 바닥을 만들어 준다.

 

다음 3D Object로 Capsule 소환해서 y좌표 1로 나머진 0 ( 땅에 박혀있지 않도록) 생성  -> Player

다음 Add Component를 통해 CharacterContoroller를 검색 추가

 

Assets 폴더에서 Movohavior Script만들고 해당 이름은 PlayerMove로 설정 후 오브젝트에 상속시켜줌

다음 더블클릭으로 visual을 열어주고 코드를 짜보자

using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class PlayerMove : MonoBehaviour
{
    public float speed = 5f;
    public float jumpSpeed = 6f;
    public float gravity = 20f; 

    private CharacterController cc;
    private Vector3 velocity;

    void Awake()
    {
        cc = GetComponent<CharacterController>();
    }

    void Update()
    {
        // 이동 입력
        float x = Input.GetAxisRaw("Horizontal");
        float z = Input.GetAxisRaw("Vertical");

        Vector3 move = (transform.right * x + transform.forward * z).normalized;

        // 바닥 처리 + 점프
        if (cc.isGrounded)
        {
            if (velocity.y < 0f) velocity.y = -2f;

            if (Input.GetKeyDown(KeyCode.Space))
                velocity.y = jumpSpeed;
        }

        // 중력(항상 아래로)
        float g = Mathf.Abs(gravity);
        velocity.y -= g * Time.deltaTime;

        // 한 번에 이동 적용(수평 + 수직)
        Vector3 motion = move * speed + velocity;
        cc.Move(motion * Time.deltaTime);
    }
}

이렇게 되겠다

이제 스타트를해서 실행해보니 이게 뭔가 에러와 경고가 엄청 떴다

이거를 해결하기 위해서는 Edit -> Project Setting -> Player -> Other Settings에서 

Active Input Handling을 Both로 바꾸어 주어야 한다.

바꿔주고 실행해보면 이동과 점프가 잘 되어지는 것을 확인할 수 있다.

 

모바일이니까 화면에 조이스틱을 통한 움직임을 만들어 줄것이다.

패키지와 애셋을 쓰는 방법이 있는데 필자는 패키지 없이 유니티에서 기본 제공을 해주는 UGUI의 Canvas, Button, Image, EventSystem으로 구현을 할것이다.

Canvas 추가 해주고 Canvas 안에 상속으로 Image JoystickBG -> JoystickHandle 만들어준다. 

다음 점프 UI도 만들어주자

이번엔 Canvas에서 Button 추가해주고 처음에 이러한 창이 뜨는데 import해주면 된다. 아래는 안눌러도된다

추가 완료했으면 ui들의 위치를 조정해주자 Anchor를 조이스틱은 왼쪽하단, 버튼은 오른쪽하단으로 잡아주고 조정해준다

다음 CreateEmpty를 추가해주고 이제 코드를 조금 만들어보자

Scripts폴더에  MobileControls와 SompleJoystick을 만들어주고

using UnityEngine;
using UnityEngine.EventSystems;

public class SimpleJoystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
// IPointerDownHandler, IDragHandler, IPointerUpHandler - 터치 / 드래그를 받기 위한 인터페이스
{
    [SerializeField] private RectTransform handle;
    // 조이스틱 손잡이 이미지(안쪽 동그라미)
    [SerializeField] private float radius = 100f; // BG 크기에 맞춰 조절
    // 조이스틱 손잡이가 움직일 수 있는 최대거리, 너무 작으면 민감해지고, 크면 둔해짐

    public Vector2 Value { get; private set; } // (-1~1)

    private RectTransform bg;
    private Canvas canvas;
    private Camera uiCam;

    void Awake()
    {
        bg = (RectTransform)transform;
        canvas = GetComponentInParent<Canvas>();
        uiCam = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
    }

    public void OnPointerDown(PointerEventData eventData) => OnDrag(eventData);

    public void OnDrag(PointerEventData eventData)
    {
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(bg, eventData.position, uiCam, out var local))
        {
            return;
            // 터치한 화면 좌표를 조이스틱 배경 내부 로컬 좌표로 변환을 해준다.
            // UI는 월드 좌표가 아닌 RectTransform좌표계를 사용하기 떄문
        }
            

        // local 좌표를 반지름 기준으로 클램프
        Vector2 clamped = Vector2.ClampMagnitude(local, radius);
        // 손가락이 멀리 가도 손잡이는 원 밖으로 못나가게 제한
        handle.anchoredPosition = clamped;

        Value = clamped / radius; 
        // -1 ~ 1 범위로 제한하여 플레이어가 쓰기 좋은 값으로 입력값으로 만들어준다.
        // x = -1, y = 1 왼쪽끝과 위쪽 끝
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        handle.anchoredPosition = Vector2.zero;
        // 손을 떼면 가운데로 북귀하며
        Value = Vector2.zero;
        // 멈춰줌
    }
}

 

using UnityEngine;

public class MobileControls : MonoBehaviour
{
    // 조이스틱 값을 매 프레임 PlayerMove에 넣어주고
    // 점프 버튼 클릭 시 PlayerMove.QueueJump()를 호출해준다

    [SerializeField] private SimpleJoystick joystick;
    [SerializeField] private PlayerMove player;
    // Inspector에서 드래그로 연결해주는 참조
    // 코드에서 Find로 찾으면 느림, 작은 프로젝트에서는 드래그 연결이 제일 단순하며 안전하다


    void Update()
    {
        if (player != null && joystick != null)
            player.SetMoveInput(joystick.Value);
        // 조이 스틱은 "드래그 중일 떄만 값이 바뀌지만
        // PlayerMove는 매 프레임 현재 입력을 알고 있기에 매 프레임을 전달
    }

    // 버튼 OnClick에 연결할 함수
    public void OnJumpPressed()
    {
        if (player != null)
            player.QueueJump();

        // Button의 OnClick이 호출할 수 있는 형태 - 매개변수 없는 pulbic void
        // PlayerMove에 점프 예약걸어줌
    }
}

PlayerMove도 조금 수정해야하니 

using UnityEngine;

[RequireComponent(typeof(CharacterController))]
// RequireComponent = CharacterController가 반드시 있어야 한다는 것으로
// 인식 못하거나 없는 경우에 경고를 줘서 미리 알려주는 역할을 해줌
public class PlayerMove : MonoBehaviour
{
    [Header("Move")]
    public float speed = 5f;

    [Header("Jump/Gravity")] // Inspector창에서 값을 변경할 수 있도록 해줌, 수치 조절하기 편하도록
    public float jumpSpeed = 6f;
    public float gravity = 20f;

    private CharacterController cc;
    private Vector3 velocity; // 점프 / 낙하를 저장할 변수 y축의 속도
    // 자동으로 적용되지 않기 때문에 직접 속도로 관리해준다.

    // 모바일/키보드 입력 버퍼
    private Vector2 moveInput;       // 조이스틱이 -1 ~ 1 범위로 주는 방향 값
    private bool jumpQueued;         // 버튼 눌렀을 때 1회 점프 예약
    // UI입력은 Update보다 먼저 또는 나중에 들어올 수 있기 때문에 이벤트를 저장해준다.
    // c++ 보이드 포인터를 이용한 함수바인드라고 보면 된다

    void Awake()
    {
        cc = GetComponent<CharacterController>();
    }

    // UI 조이스틱
    public void SetMoveInput(Vector2 input) => moveInput = Vector2.ClampMagnitude(input, 1f);
    // 호출을 받아 이동 입력을 갱신한다.
    // clampMagnitude = 조이스틱이 대각선으로 갈 경우 값이 1을 넘지 않게 해줌
    // 옛날 게임 보면 대각선으로 갈때 속도가 빠름 그런거 방지 
    // clamp는 맥스값을 제한

    // UI 점프
    public void QueueJump() => jumpQueued = true;
    // 점프 버튼이 눌리면 점프 이벤트 예약
    // 실제 점프는 Update에서 grounded일떄 처리해준다.

    void Update()
    {
        // (PC 테스트용) 키보드도 같이 허용 아래 2줄 
        moveInput = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        if (Input.GetKeyDown(KeyCode.Space)) jumpQueued = true;

        Vector3 move = (transform.right * moveInput.x + transform.forward * moveInput.y).normalized;
        // moveInput x,y를 플레이어의 로컬 방향 기준으로 변환
        // transform.forward = 플레이어가 보는 앞방향, right = 오른쪽
        // 나중 시점 회전을 붙여, 조이스틱 위가 바라보는 방향으로 움직이게 해줌

        if (cc.isGrounded) // 캐릭터가 바닥에 닿아 있을 때만 점프를 허용해줌
        {
            if (velocity.y < 0f) velocity.y = -2f;
            // 0f 일 경우 -2f를 넣어주어 지면에 붙어 있다는것을 안정적으로 유지되게 해준다.

            if (jumpQueued)
                velocity.y = jumpSpeed;
        }

        jumpQueued = false; // 점프는 1회성, 여러번 되지 않도록 방지

        velocity.y -= Mathf.Abs(gravity) * Time.deltaTime;
        // 중력을 매 프레임 속도에 누적으로 더해줌
        // abs 절대값을 통해 +로만 유지되도록

        Vector3 motion = move * speed + velocity;
        cc.Move(motion * Time.deltaTime);
        // CharacterController는 이동량을 Move로 넘겨야 움직인다
        // 떄문 속소 * deltaTime = 이번 프레임 이동 거리로 계산해서 넘겨주어 이동시킨다. 
    }
}

 

이렇게 되겠다

이제 실행해보면

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

이렇게 나오게 된다.

 

모바일 입력을 위한 조이스틱 이동과 버튼 점프, 점프 이후 낙하 구현까지 완료하였다

다음 작업은 터치 드래그를 이용한 시점 회전을 추가하고 다음 글로 넘어가겠다

Canvas에 UI Image를 추가해주고 LookArea로 설정

다음 Anchor를 왼쪽 화면으로 설정하고 해당 부분만 쓰도록 크기를 설정해주고(조이스틱이랑 겹치기 떄문)

color값을 투명하게 alpha를 0으로 설정해준다.

 

이제 Scripts에 LookDrag파일 만들어주고 코드 작성

using UnityEngine;
using UnityEngine.EventSystems;

public class LookDrag : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
    // 아까의 조이스틱과 같은 터치 & 드래그 인터페이스 
{
    [Header("Targets")]
    [SerializeField] private Transform playerYaw;   // 좌우 회전 (Player)
    [SerializeField] private Transform cameraPitch; // 상하 회전 (Camera)
    // 좌우는 몸 전체가 도는것이 부드럽고 상하는 고개까딱으로 하는게 자연스러움
    // 플레이어 몸까지 위아래로 회전하면 이동방향 or 충돌이 생겨 이상해진다.

    [Header("Sensitivity")]
    [SerializeField] private float yawSensitivity = 0.15f;
    [SerializeField] private float pitchSensitivity = 0.12f;
    // 드래그한 픽셀 변화량을 회전 각도로 바꿔주는 계수
    // 숫자가 커질수록 조금만 움직여도 많이 회전하게 되도록한다.
    // 사람에 따라 감도가 다르기 떄문에 inpector로 수정가능

    [Header("Pitch Clamp")]
    [SerializeField] private float minPitch = -80f;
    [SerializeField] private float maxPitch = 80f;
    // 카메라 상하 회전 제한 걸기
    // 목꺾김 방지 or 롤오버 문제가 생김
    // 그렇기에 80 80으로 설정

    private float pitch;
    private Vector2 lastPos;
    private bool dragging;
    // pitch 카메라 상하 각도(누적값)
    // lastPos 이전 터치 위치 
    // 드래그 중인지에 대한 여부

    void Awake()
    {
        if (cameraPitch != null)
        {
            pitch = cameraPitch.localEulerAngles.x;
            if (pitch > 180f) pitch -= 360f;
            // 카메라 초기 pitch 가져오기
            // localEulerAngles는 -10 ~ 350으로 우리가 편하게 쓰는 -180 ~ 180으로 변경
        }
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        dragging = true;
        lastPos = eventData.position;
        // 드래그 시작시 true
        // 기준점을 터치한 위치로 잡아줌 - 그래야 다음 값이 제대로 나옴
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (!dragging || playerYaw == null || cameraPitch == null) return;

        Vector2 delta = eventData.position - lastPos;
        lastPos = eventData.position;
        // 드래그 거리가 얼마나 움직였는지에 대한 값을 회전량을 변환

        // 좌우: Player 자체 회전
        float yaw = delta.x * yawSensitivity;
        playerYaw.Rotate(0f, yaw, 0f, Space.World);
        // 손가락이 오른쪽으로 움직이면 x가 올라감
        // space.world = Y축 기준으로 회전하여 축이 꼬일 확률을 줄여줌

        // 상하: Camera의 로컬 회전
        pitch -= delta.y * pitchSensitivity;
        pitch = Mathf.Clamp(pitch, minPitch, maxPitch);
        cameraPitch.localRotation = Quaternion.Euler(pitch, 0f, 0f);
        // 손가락을 올리면 시선을 위로 보도록 -y 를해줌
        // Quaternion.Eluer 카메라 로컬 회전, 로컬로 해야 플레이어가 돌 경우 카메라가 같이 따라가며
        // 상하만 별도로 적용이 된다.
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        dragging = false;
        // 손 떼면 드래그 종료
    }
}

 

 

이렇게 하면 오른쪽 화면을 드래그 했을 경우 좌우에는 플레이어의 몸이, 상하로는 카메라가 회전한다.

728x90

'Unity' 카테고리의 다른 글

Unity(6) - Atlas Block Map & Crafting Table (2)  (0) 2026.03.10
Unity(5) - Inventory & Crafting  (1) 2026.03.05
Unity(4) - Ray Cast & Block Destroy  (0) 2026.02.24
Unity(3) - Block Map  (1) 2026.02.19
Unity (1)  (0) 2026.02.11