안녕하세요, 여러분! 👋 게임의 생명은 플레이어와의 상호작용에 있죠. 그리고 그 상호작용의 가장 기본적인 형태가 바로 사용자 입력 처리입니다. 키보드를 누르고, 마우스를 클릭하고, 화면을 터치하는 이 모든 행동이 게임 세계에 생명을 불어넣습니다.
이번 시간에는 Unity에서 다양한 사용자 입력을 감지하고 게임 로직에 반영하는 방법을 깊이 있게 알아보겠습니다. 기본적인 키보드, 마우스 입력부터 모바일 환경의 터치 입력, 그리고 최신 트렌드인 새로운 Input System까지! 🚀 여러분의 게임을 한 단계 더 발전시킬 핵심 기술을 마스터해 보세요.
⌨️ 1. 키보드 입력 처리: 기본 중의 기본!
키보드는 PC 게임에서 가장 보편적인 입력 장치입니다. Unity는 직관적인 함수들을 통해 키보드 입력을 손쉽게 처리할 수 있도록 지원합니다.
주요 함수:
- Input.GetKey(KeyCode key): 특정 키가 눌려 있는 동안 계속 true를 반환합니다. 캐릭터 이동처럼 지속적인 입력 처리에 유용합니다.
- Input.GetKeyDown(KeyCode key): 특정 키가 눌리는 그 순간 한 번만 true를 반환합니다. 점프, 스킬 사용 등 단발성 액션에 적합합니다.
- Input.GetKeyUp(KeyCode key): 특정 키에서 손을 떼는 그 순간 한 번만 true를 반환합니다. 키를 뗄 때 발생하는 액션 (예: 차지 샷 발사)에 사용됩니다.
KeyCode 열거형: 어떤 키를 감지할지 지정할 때는 KeyCode 열거형을 사용합니다. 예를 들어 스페이스 바는 KeyCode.Space, 왼쪽 컨트롤 키는 KeyCode.LeftControl 과 같이 명시적으로 지정할 수 있습니다. IntelliSense (자동 완성) 기능을 활용하면 모든 키 코드를 쉽게 찾을 수 있습니다.
방향키 및 WASD 입력 처리: GetAxisRaw 캐릭터 이동과 같이 방향성을 가지는 입력은 Input.GetAxis 또는 Input.GetAxisRaw 함수를 사용하는 것이 더 효율적입니다.
- Input.GetAxis("Horizontal"): A/D 또는 좌/우 화살표 키 입력을 받아 -1.0 ~ 1.0 사이의 값으로 반환합니다. 약간의 가속/감속(smoothing)이 적용되어 부드러운 움직임을 구현할 때 좋습니다.
- Input.GetAxis("Vertical"): W/S 또는 위/아래 화살표 키 입력을 받아 -1.0 ~ 1.0 사이의 값으로 반환합니다. 마찬가지로 스무딩이 적용됩니다.
- Input.GetAxisRaw("Horizontal")/Input.GetAxisRaw("Vertical"): 스무딩 없이 즉각적으로 -1, 0, 1 중 하나의 값만 반환합니다. 반응성이 중요한 게임이나 격자 기반 이동 등에 유리합니다.
💡 실무 팁 & 심층 분석:
- Update() vs FixedUpdate(): 물리 기반 이동 시 FixedUpdate()에서 입력을 처리하는 경우가 많지만, GetKeyDown/Up은 프레임 기반이므로 Update()에서 감지하여 플래그 변수에 저장하고 FixedUpdate()에서 사용하는 패턴이 안정적입니다. GetKey나 GetAxis는 FixedUpdate()에서 직접 사용해도 비교적 괜찮지만, 프레임 드랍 시 입력 누락 가능성을 고려해야 합니다.
- 입력 관리자 (Input Manager): Unity 에디터의 Edit > Project Settings > Input Manager 에서 "Horizontal", "Vertical", "Jump", "Fire1" 등 기본 축(Axis) 설정을 확인하고 커스터마이징할 수 있습니다. 키 매핑 변경, 조이스틱 지원 추가 등이 가능합니다.
- 문제 해결: "키를 눌렀는데 반응이 없어요!" -> Update() 함수 안에 코드를 작성했는지, KeyCode가 올바른지, 스크립트가 활성화된 게임 오브젝트에 연결되어 있는지 확인해 보세요. Debug.Log()를 찍어보는 것이 가장 빠른 해결책일 때가 많습니다. 😉
[ C# 예시 코드: GetAxisRaw를 이용한 캐릭터 이동 ]
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public float moveSpeed = 5f;
void Update()
{
// Raw 축 입력을 받아 -1, 0, 1 중 하나를 반환
float moveHorizontal = Input.GetAxisRaw("Horizontal"); // A/D 또는 좌/우 화살표
float moveVertical = Input.GetAxisRaw("Vertical"); // W/S 또는 위/아래 화살표
// 이동 방향 벡터 계산 (정규화하여 대각선 이동 속도 보정)
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical).normalized;
// Rigidbody가 있다면 transform.Translate 대신 사용
// Rigidbody rb = GetComponent<Rigidbody>();
// rb.MovePosition(transform.position + movement * moveSpeed * Time.deltaTime);
// 간단한 이동 (물리 효과 없을 때)
transform.Translate(movement * moveSpeed * Time.deltaTime, Space.World);
// 디버깅: 현재 입력 값 확인
if (moveHorizontal != 0 || moveVertical != 0)
{
// Debug.Log($"Horizontal: {moveHorizontal}, Vertical: {moveVertical}");
}
}
}
🖱️ 2. 마우스 입력 처리: 클릭과 포인팅의 세계
마우스는 화면의 특정 지점을 가리키거나 클릭하는 데 사용되는 핵심 입력 장치입니다.
주요 함수:
- Input.GetMouseButton(int button): 마우스 버튼이 눌려 있는 동안 계속 true를 반환합니다. 드래그 앤 드롭 같은 동작에 사용됩니다. (0: 왼쪽, 1: 오른쪽, 2: 가운데 버튼)
- Input.GetMouseButtonDown(int button): 마우스 버튼을 누르는 순간 한 번만 true를 반환합니다. 일반적인 클릭 감지에 가장 많이 사용됩니다.
- Input.GetMouseButtonUp(int button): 마우스 버튼에서 손을 떼는 순간 한 번만 true를 반환합니다. 클릭 종료 시점 감지에 사용됩니다.
마우스 좌표 얻기: Input.mousePosition Input.mousePosition은 현재 마우스 커서의 화면 좌표(Screen Coordinates) 를 Vector3 형태로 반환합니다. (Z 값은 보통 0). 화면 좌표는 왼쪽 하단이 (0, 0)이고 오른쪽 상단이 (Screen.width, Screen.height)입니다.
Raycasting: 화면 클릭을 게임 월드로! ⚡ 마우스 클릭으로 게임 월드의 특정 오브젝트를 선택하거나 상호작용하려면 Raycasting이 필수적입니다. 화면 좌표의 마우스 위치에서 게임 월드로 가상의 광선(Ray)을 쏘아 충돌하는 오브젝트를 감지하는 기술입니다.
Raycasting 프로세스:
- Input.mousePosition으로 마우스 화면 좌표를 얻습니다.
- Camera.ScreenPointToRay(Input.mousePosition)를 사용하여 화면 좌표를 월드 공간의 Ray로 변환합니다. (주 카메라 Camera.main 사용 시 주의 - 태그 설정 필요)
- Physics.Raycast(ray, out RaycastHit hitInfo, float maxDistance, int layerMask) 함수를 호출하여 Ray를 발사합니다.
- ray: 2단계에서 생성한 Ray
- out RaycastHit hitInfo: Ray가 충돌한 오브젝트의 정보(충돌 지점, 충돌한 Collider 등)를 담을 변수
- maxDistance (선택 사항): Ray의 최대 탐지 거리
- layerMask (선택 사항): 특정 레이어의 오브젝트만 감지하도록 필터링
- Physics.Raycast()가 true를 반환하면 Ray가 어떤 Collider와 충돌했다는 의미이며, hitInfo 변수를 통해 충돌 정보를 얻을 수 있습니다.
💡 실무 팁 & 심층 분석:
- UI 클릭 vs 월드 클릭: Canvas 기반의 UI 요소(버튼 등) 클릭은 EventSystem이 자동으로 처리합니다. 3D 월드 오브젝트 클릭 감지는 Physics.Raycast를 사용해야 합니다. UI가 월드 오브젝트를 가리는 경우, Raycast가 UI 뒤의 오브젝트를 감지하지 못하도록 설정하거나 (Graphic Raycaster 설정 확인), 혹은 EventSystem.current.IsPointerOverGameObject()를 사용하여 UI 위에 마우스가 있는지 먼저 확인하는 것이 좋습니다.
- 카메라: Camera.main은 "MainCamera" 태그가 붙은 카메라를 찾는 편리한 방법이지만, 성능상 매번 찾는 것보다 Camera 변수를 선언하고 인스펙터에서 할당하거나 GetComponent<Camera>()로 캐싱해두는 것이 좋습니다.
- 성능 최적화: Physics.Raycast는 비용이 아주 저렴한 연산은 아닙니다. 매 프레임 수백 개의 Raycast를 실행해야 한다면, maxDistance를 적절히 설정하고 layerMask를 사용하여 불필요한 충돌 검사를 피하는 것이 중요합니다.
- Physics2D.Raycast: 2D 게임에서는 Physics2D.Raycast를 사용하고, Collider2D 컴포넌트가 부착된 오브젝트를 감지합니다. 작동 방식은 3D와 유사합니다.
[ C# 예시 코드: 마우스 클릭으로 오브젝트 색상 변경 ]
using UnityEngine;
public class ClickableObject : MonoBehaviour
{
private Renderer objectRenderer;
private Color originalColor;
void Start()
{
objectRenderer = GetComponent<Renderer>();
if (objectRenderer != null)
{
originalColor = objectRenderer.material.color;
}
}
void Update()
{
// 왼쪽 마우스 버튼 클릭 감지
if (Input.GetMouseButtonDown(0))
{
// 메인 카메라에서 마우스 위치로 Ray 발사
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
// Ray가 Collider에 부딪혔는지 확인
if (Physics.Raycast(ray, out hitInfo))
{
// 부딪힌 오브젝트가 이 스크립트가 붙어있는 오브젝트인지 확인
if (hitInfo.collider.gameObject == gameObject)
{
ChangeColor();
Debug.Log($"Clicked on: {gameObject.name}");
}
}
}
}
void ChangeColor()
{
if (objectRenderer != null)
{
// 랜덤 색상으로 변경 (예시)
objectRenderer.material.color = new Color(Random.value, Random.value, Random.value);
// 일정 시간 후 원래 색상으로 복구 (Invoke 사용 예시)
Invoke("RestoreColor", 1.0f);
}
}
void RestoreColor()
{
if (objectRenderer != null)
{
objectRenderer.material.color = originalColor;
}
}
}
(이 스크립트를 3D 오브젝트에 붙이고, 해당 오브젝트에 Collider 컴포넌트가 있어야 작동합니다.)
📱 3. 터치 입력 처리 (모바일): 손끝으로 만나는 세계
모바일 게임 개발의 핵심은 바로 터치 입력입니다. Unity는 단일 터치부터 멀티 터치, 제스처까지 다양한 터치 입력을 처리할 수 있는 기능을 제공합니다.
주요 함수 및 속성:
- Input.touchCount: 현재 화면에 닿아 있는 손가락(터치)의 개수를 반환합니다. 멀티 터치 감지의 기본입니다.
- Input.GetTouch(int index): 특정 인덱스(0부터 touchCount - 1까지)의 터치 정보를 Touch 구조체 형태로 반환합니다.
Touch 구조체 주요 속성:
- fingerId: 각 터치를 고유하게 식별하는 ID입니다.
- position: 터치의 현재 화면 좌표 (Input.mousePosition과 동일한 좌표계).
- phase: 터치의 상태를 나타내는 TouchPhase 열거형 값입니다.
- Began: 손가락이 화면에 처음 닿았을 때.
- Moved: 손가락이 화면 위에서 움직였을 때.
- Stationary: 손가락이 움직이지 않고 화면에 머물러 있을 때.
- Ended: 손가락이 화면에서 떨어졌을 때.
- Canceled: 시스템 이벤트 등으로 터치가 취소되었을 때 (예: 전화 수신).
멀티 터치 및 제스처 인식: 두 손가락을 이용한 핀치 줌(Pinch Zoom), 스와이프(Swipe) 등의 제스처는 여러 터치(Input.touchCount > 1)의 위치와 상태 변화를 추적하여 구현해야 합니다.
- 핀치 줌: 두 터치(Input.GetTouch(0), Input.GetTouch(1))의 이전 프레임 거리와 현재 프레임 거리를 비교하여 거리가 멀어지면 확대, 가까워지면 축소 로직을 구현합니다.
- 스와이프: 터치가 Began 상태일 때 시작 위치를 기록하고, Ended 상태일 때 종료 위치와의 차이 및 시간을 계산하여 특정 방향과 속도 이상의 움직임이면 스와이프로 판정합니다.
💡 실무 팁 & 심층 분석:
- 플랫폼 의존성: 터치 입력 코드는 모바일 플랫폼에서만 의미가 있습니다. PC 환경 테스트 시에는 마우스 입력으로 대체하거나, Unity Remote 앱을 사용하여 실제 기기에서 테스트하는 것이 좋습니다. #if UNITY_IOS || UNITY_ANDROID 전처리기 지시문을 사용하여 플랫폼별 코드 분기를 할 수 있습니다.
- Input.simulateMouseWithTouches: Project Settings > Input Manager (또는 Input System Package 설정) 에서 이 옵션을 활성화하면, 싱글 터치 입력을 마우스 입력(Input.GetMouseButtonDown(0), Input.mousePosition 등)처럼 시뮬레이션 해줍니다. PC와 모바일 양쪽을 지원해야 할 때 개발 편의성을 높일 수 있지만, 멀티 터치가 필요한 경우에는 직접 터치 처리를 해야 합니다.
- 제스처 라이브러리: 복잡한 제스처 인식(예: 회전, 탭 후 홀드 등)은 직접 구현하기 까다로울 수 있습니다. 에셋 스토어 등에서 검증된 제스처 인식 라이브러리를 활용하는 것도 좋은 방법입니다.
- UI 상호작용: 모바일에서도 UI 요소 터치는 EventSystem이 처리합니다. 월드 오브젝트 터치는 Raycasting (주로 Camera.ScreenPointToRay 사용)을 통해 구현합니다. EventSystem.current.IsPointerOverGameObject(touch.fingerId) 를 사용하여 특정 터치가 UI 위에 있는지 확인할 수 있습니다.
[ C# 예시 코드: 화면 터치 위치 로깅 ]
using UnityEngine;
using UnityEngine.UI; // UI Text 사용 예시
public class TouchLogger : MonoBehaviour
{
public Text debugText; // 화면에 로그를 표시할 UI Text (선택 사항)
void Update()
{
// 터치가 하나 이상 있는지 확인
if (Input.touchCount > 0)
{
// 모든 터치 정보를 순회
for (int i = 0; i < Input.touchCount; i++)
{
Touch touch = Input.GetTouch(i);
string logMessage = $"Touch {touch.fingerId}: Phase={touch.phase}, Pos={touch.position}\n";
// 터치 상태에 따른 추가 처리 (예시)
switch (touch.phase)
{
case TouchPhase.Began:
// 터치 시작 시점에 무언가 실행
Debug.Log($"Touch Began: {touch.position}");
break;
case TouchPhase.Moved:
// 터치 이동 시점에 무언가 실행 (예: 드래그)
break;
case TouchPhase.Ended:
// 터치 종료 시점에 무언가 실행 (예: 탭)
Debug.Log($"Touch Ended: {touch.position}");
break;
}
// UI Text에 로그 출력 (선택 사항)
if (debugText != null && i == 0) // 첫 번째 터치 정보만 표시 (간단 예시)
{
debugText.text = logMessage;
} else if (debugText != null && i > 0) {
debugText.text += logMessage; // 여러 터치 정보 누적
} else {
Debug.Log(logMessage); // 콘솔에 로그 출력
}
}
}
else
{
// 터치가 없을 때 UI 초기화 (선택 사항)
if (debugText != null)
{
// debugText.text = "No touches";
}
}
}
}
✨ 4. 새로운 Input System (선택 사항): 더 강력하고 유연하게!
Unity는 기존의 Input Manager (Legacy Input) 시스템의 한계를 극복하고 더 유연하고 강력한 입력 처리를 위해 새로운 Input System 패키지를 도입했습니다. 선택 사항이지만, 새로운 프로젝트에는 사용을 적극 권장합니다. 👍
왜 새로운 Input System인가?
- 액션 기반: "점프", "이동", "발사"와 같은 추상적인 액션(Action)을 정의하고, 여기에 다양한 입력(키보드, 마우스, 게임패드, 터치 등)을 바인딩(Binding)합니다. 코드에서는 특정 키(KeyCode.Space)가 아닌 "점프" 액션의 발생 여부를 감지하므로, 입력 장치나 키 매핑 변경에 훨씬 유연하게 대처할 수 있습니다.
- 일관된 장치 지원: 다양한 종류의 게임패드, 조이스틱 등을 별도 처리 없이 일관된 방식으로 지원합니다.
- 사용자 설정 용이: 플레이어가 게임 내에서 직접 키 바인딩을 변경하는 기능을 구현하기 훨씬 수월합니다.
- 컨텍스트 기반 입력: 게임 상태(메뉴, 플레이 중, 컷신 등)에 따라 활성화되는 입력 세트(Action Map)를 다르게 설정할 수 있습니다.
시작하기:
- 패키지 설치: Window > Package Manager 에서 "Input System"을 찾아 설치합니다.
- 활성화: 설치 후 나타나는 경고창에서 "Yes"를 누르거나, Edit > Project Settings > Player > Other Settings > Active Input Handling 에서 "Input System Package (New)" 또는 "Both" 를 선택합니다. (Both는 기존 Input Manager와 함께 사용 가능)
- Input Actions 에셋 생성: Project 창에서 Create > Input Actions 를 선택하여 입력 액션 에셋 파일을 만듭니다.
- 액션 맵 및 바인딩 설정: 생성된 에셋 파일을 더블 클릭하여 에디터 창을 엽니다. 여기서 액션 맵(예: "Player", "UI")을 만들고, 각 맵 안에 액션(예: "Move", "Jump", "Fire")을 정의한 후, 원하는 키, 버튼, 축 등을 바인딩합니다.
C# 스크립트에서 사용 방법:
- Player Input 컴포넌트 사용: 게임 오브젝트에 Player Input 컴포넌트를 추가하고, 생성한 Input Actions 에셋을 할당합니다. Behavior 설정에 따라:
- Send Messages: OnMove(), OnJump() 등 액션 이름에 해당하는 함수들을 스크립트에 구현하면 자동으로 호출됩니다. (가장 간편)
- Broadcast Messages: Send Messages와 유사하지만, 자식 오브젝트까지 메시지를 보냅니다.
- Invoke Unity Events: 인스펙터에서 각 액션에 대한 UnityEvent를 노출시켜, 드래그 앤 드롭으로 함수를 연결할 수 있습니다.
- Invoke CSharp Events: C# 이벤트를 사용하여 코드로 콜백 함수를 등록합니다. (가장 유연)
- C# 스크립트에서 직접 참조: 스크립트에서 Input Actions 에셋을 참조하고, 액션을 활성화/비활성화하며, 액션의 이벤트(performed, canceled 등)에 직접 콜백 함수를 등록하여 사용할 수도 있습니다.
💡 실무 팁 & 심층 분석:
- 학습 곡선: 기존 Input Manager에 비해 초기 설정과 개념 이해에 약간의 학습이 필요합니다. 하지만 장기적으로는 개발 효율성과 유연성 면에서 큰 이점을 제공합니다.
- 이벤트 기반 처리: 새로운 Input System은 이벤트 기반으로 작동하는 경우가 많습니다. Update()에서 매 프레임 입력을 확인하는 대신, 입력이 발생했을 때만 콜백 함수가 호출되므로 성능상 이점이 있을 수 있습니다.
- 디버깅: Input Debugger (Window > Analysis > Input Debugger)를 사용하면 연결된 장치와 현재 입력 상태를 시각적으로 확인하고 디버깅하는 데 매우 유용합니다.
- 마이그레이션: 기존 프로젝트를 새로운 Input System으로 전환하는 것은 가능하지만, 입력 관련 코드를 상당 부분 수정해야 할 수 있습니다. 프로젝트 규모와 복잡성을 고려하여 결정해야 합니다.
[ C# 예시 코드: Player Input 컴포넌트 (Send Messages) 사용 ]
using UnityEngine;
using UnityEngine.InputSystem; // Input System 네임스페이스 추가
public class PlayerControllerNewInput : MonoBehaviour
{
public float moveSpeed = 5f;
private Vector2 moveInput; // 입력 값을 저장할 변수
private Rigidbody rb; // Rigidbody 사용 시
void Awake()
{
rb = GetComponent<Rigidbody>(); // Rigidbody 컴포넌트 가져오기
}
// "Move" 액션이 발생했을 때 Player Input 컴포넌트가 호출할 함수
// 함수의 파라미터는 InputAction.CallbackContext 타입이어야 함
public void OnMove(InputAction.CallbackContext context)
{
moveInput = context.ReadValue<Vector2>(); // Vector2 형태로 입력 값 읽기
// Debug.Log($"Move Input: {moveInput}");
}
// "Jump" 액션이 발생했을 때 Player Input 컴포넌트가 호출할 함수
public void OnJump(InputAction.CallbackContext context)
{
// context.performed는 액션이 성공적으로 완료되었을 때 true (버튼 누름 등)
// context.canceled는 액션이 취소되었을 때 true (버튼 뗌 등)
if (context.performed)
{
Debug.Log("Jump Action Performed!");
// 여기에 점프 로직 구현 (예: rb.AddForce)
if (rb != null)
{
rb.AddForce(Vector3.up * 5f, ForceMode.Impulse);
}
}
}
// 물리 업데이트는 FixedUpdate에서 처리하는 것이 좋음
void FixedUpdate()
{
// moveInput 값을 사용하여 실제 이동 처리
Vector3 movement = new Vector3(moveInput.x, 0.0f, moveInput.y);
if (rb != null)
{
rb.MovePosition(rb.position + movement * moveSpeed * Time.fixedDeltaTime);
}
else
{
transform.Translate(movement * moveSpeed * Time.fixedDeltaTime, Space.World);
}
}
}
(이 스크립트를 사용하려면: 1. Input System 패키지를 설치하고 활성화합니다. 2. Input Actions 에셋을 만들고 "Player" 액션 맵 안에 "Move"(Vector2 타입)와 "Jump"(Button 타입) 액션을 정의하고 키를 바인딩합니다. 3. 스크립트를 부착할 게임 오브젝트에 Player Input 컴포넌트를 추가하고, 생성한 Input Actions 에셋을 할당합니다. Behavior를 Send Messages로 설정합니다. 4. 필요하다면 Rigidbody 컴포넌트를 추가합니다.)
🎯 결론: 입력을 지배하는 자가 게임을 지배한다!
사용자 입력 처리는 게임 개발의 가장 근본적이면서도 중요한 부분입니다. 오늘 우리는 Unity에서 제공하는 다양한 입력 처리 방법들 - 전통적인 키보드/마우스/터치 입력부터 강력하고 유연한 새로운 Input System까지 - 을 깊이 있게 살펴보았습니다.
각 방식의 장단점을 이해하고 프로젝트의 요구사항에 맞는 최적의 방법을 선택하는 것이 중요합니다. 간단한 프로토타입에는 레거시 방식이 빠를 수 있지만, 장기적인 확장성, 다양한 플랫폼 및 입력 장치 지원, 사용자 커스터마이징 기능 등을 고려한다면 새로운 Input System 도입을 적극적으로 검토해 보시길 바랍니다.
이제 배운 내용을 바탕으로 여러분의 게임에 생동감을 불어넣을 시간입니다! 끊임없이 실험하고 코드를 다듬으며 플레이어와 게임 세계를 잇는 매력적인 상호작용을 만들어나가세요. 💪 Happy Coding!
'C#' 카테고리의 다른 글
[C#]🎮 유니티 게임 개발의 핵심: 충돌(Collision)과 트리거(Trigger) 완벽 정복 가이드 🚀 (2) | 2025.04.11 |
---|---|
[C#]🎮 유니티 게임 개발의 핵심: 움직임 구현과 사용자 입력 마스터하기 ✨ (7) | 2025.04.09 |
[C#]🔧 유니티 핵심 완벽 이해: 게임 오브젝트, 컴포넌트, 프리팹 파헤치기 (feat. 트랜스폼 & 스크립트 제어) (5) | 2025.04.08 |
[C#]유니티 스크립팅 완전 정복: 게임에 생명을 불어넣는 핵심 문법✨ (4) | 2025.04.08 |
[C#]유니티, 게임 개발의 첫걸음! 설치부터 인터페이스 정복까지 (+꿀팁) 🚀 (5) | 2025.04.08 |