캐릭터 움직임 및 애니메이션
This commit is contained in:
19
Assets/Scripts/Colosseum.Game.asmdef
Normal file
19
Assets/Scripts/Colosseum.Game.asmdef
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Colosseum.Game",
|
||||
"rootNamespace": "Colosseum",
|
||||
"references": [
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Networking.Transport",
|
||||
"Unity.Transport",
|
||||
"Unity.InputSystem"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Assets/Scripts/Colosseum.Game.asmdef.meta
Normal file
7
Assets/Scripts/Colosseum.Game.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 597e1695ce4bb584f96f8673b0cf7a6a
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Editor.meta
Normal file
8
Assets/Scripts/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3cd76473ef5dcf44afccfab5fbdbfc6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
Assets/Scripts/Editor/ConnectionUIEditor.cs
Normal file
53
Assets/Scripts/Editor/ConnectionUIEditor.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.UI.Editor
|
||||
{
|
||||
[CustomEditor(typeof(ConnectionUI))]
|
||||
public class ConnectionUIEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
EditorGUILayout.LabelField("Connection Controls", EditorStyles.boldLabel);
|
||||
|
||||
ConnectionUI connectionUI = (ConnectionUI)target;
|
||||
|
||||
EditorGUI.BeginDisabledGroup(!Application.isPlaying);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Start Host", GUILayout.Height(30)))
|
||||
{
|
||||
connectionUI.StartHost();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Start Client", GUILayout.Height(30)))
|
||||
{
|
||||
connectionUI.StartClient();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Start Server", GUILayout.Height(30)))
|
||||
{
|
||||
connectionUI.StartServer();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Disconnect", GUILayout.Height(25)))
|
||||
{
|
||||
connectionUI.Disconnect();
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Play Mode에서만 연결할 수 있습니다.", MessageType.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
2
Assets/Scripts/Editor/ConnectionUIEditor.cs.meta
Normal file
2
Assets/Scripts/Editor/ConnectionUIEditor.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 088a15576b464764b85a18f4dacb1a43
|
||||
1838
Assets/Scripts/InputSystem_Actions.cs
Normal file
1838
Assets/Scripts/InputSystem_Actions.cs
Normal file
File diff suppressed because it is too large
Load Diff
2
Assets/Scripts/InputSystem_Actions.cs.meta
Normal file
2
Assets/Scripts/InputSystem_Actions.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b884d077dc9f2543a6b5cca0957e6d6
|
||||
1058
Assets/Scripts/InputSystem_Actions.inputactions
Normal file
1058
Assets/Scripts/InputSystem_Actions.inputactions
Normal file
File diff suppressed because it is too large
Load Diff
14
Assets/Scripts/InputSystem_Actions.inputactions.meta
Normal file
14
Assets/Scripts/InputSystem_Actions.inputactions.meta
Normal file
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 052faaac586de48259a63d0c4782560b
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
|
||||
generateWrapperCode: 1
|
||||
wrapperCodePath:
|
||||
wrapperClassName:
|
||||
wrapperCodeNamespace:
|
||||
8
Assets/Scripts/Network.meta
Normal file
8
Assets/Scripts/Network.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee7b4209244032546a087316c8f2f2ed
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Assets/Scripts/Network/NetworkLauncher.cs
Normal file
45
Assets/Scripts/Network/NetworkLauncher.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
namespace Colosseum.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// 네트워크 연결 관리 (Host/Client 시작)
|
||||
/// </summary>
|
||||
public class NetworkLauncher : MonoBehaviour
|
||||
{
|
||||
[Header("UI References")]
|
||||
[SerializeField] private GameObject connectionUI;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 게임 시작 시 UI 표시
|
||||
if (connectionUI != null)
|
||||
connectionUI.SetActive(true);
|
||||
}
|
||||
|
||||
public void StartHost()
|
||||
{
|
||||
NetworkManager.Singleton.StartHost();
|
||||
HideConnectionUI();
|
||||
}
|
||||
|
||||
public void StartClient()
|
||||
{
|
||||
NetworkManager.Singleton.StartClient();
|
||||
HideConnectionUI();
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
NetworkManager.Singleton.StartServer();
|
||||
HideConnectionUI();
|
||||
}
|
||||
|
||||
private void HideConnectionUI()
|
||||
{
|
||||
if (connectionUI != null)
|
||||
connectionUI.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Network/NetworkLauncher.cs.meta
Normal file
2
Assets/Scripts/Network/NetworkLauncher.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a94a6fa23cedaf545aea2b1057800149
|
||||
56
Assets/Scripts/Network/PlayerSpawnPoint.cs
Normal file
56
Assets/Scripts/Network/PlayerSpawnPoint.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// 플레이어 스폰 위치 마커
|
||||
/// 씬에 배치하여 플레이어가 스폰될 위치를 지정
|
||||
/// </summary>
|
||||
public class PlayerSpawnPoint : MonoBehaviour
|
||||
{
|
||||
[Header("Spawn Settings")]
|
||||
[SerializeField] private bool useRotation = true;
|
||||
|
||||
private static System.Collections.Generic.List<PlayerSpawnPoint> spawnPoints = new System.Collections.Generic.List<PlayerSpawnPoint>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
spawnPoints.Add(this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
spawnPoints.Remove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 사용 가능한 스폰 포인트 중 하나를 반환
|
||||
/// </summary>
|
||||
public static Transform GetRandomSpawnPoint()
|
||||
{
|
||||
if (spawnPoints.Count == 0)
|
||||
return null;
|
||||
|
||||
int index = Random.Range(0, spawnPoints.Count);
|
||||
return spawnPoints[index].transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스폰 포인트 개수 반환
|
||||
/// </summary>
|
||||
public static int SpawnPointCount => spawnPoints.Count;
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawWireSphere(transform.position, 0.5f);
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawLine(transform.position, transform.position + transform.forward * 1.5f);
|
||||
|
||||
// 화살표 머리
|
||||
Vector3 arrowPos = transform.position + transform.forward * 1.5f;
|
||||
Gizmos.DrawLine(arrowPos, arrowPos - transform.forward * 0.3f + transform.right * 0.2f);
|
||||
Gizmos.DrawLine(arrowPos, arrowPos - transform.forward * 0.3f - transform.right * 0.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Network/PlayerSpawnPoint.cs.meta
Normal file
2
Assets/Scripts/Network/PlayerSpawnPoint.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aba9a2eb2571da14d901ed51c8866f47
|
||||
8
Assets/Scripts/Player.meta
Normal file
8
Assets/Scripts/Player.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b58faa7a971b2c4aa9cadf4132ac7a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
98
Assets/Scripts/Player/PlayerAnimationController.cs
Normal file
98
Assets/Scripts/Player/PlayerAnimationController.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
namespace Colosseum.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 플레이어 애니메이션 컨트롤러
|
||||
/// 이동 속도에 따라 Idle/Walk/Run 애니메이션 제어
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Animator))]
|
||||
[RequireComponent(typeof(PlayerMovement))]
|
||||
public class PlayerAnimationController : NetworkBehaviour
|
||||
{
|
||||
[Header("Animation Parameters")]
|
||||
[SerializeField] private string speedParam = "Speed";
|
||||
[SerializeField] private string isGroundedParam = "IsGrounded";
|
||||
[SerializeField] private string jumpTriggerParam = "Jump";
|
||||
[SerializeField] private string landTriggerParam = "Land";
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private float speedSmoothTime = 0.1f;
|
||||
|
||||
private Animator animator;
|
||||
private PlayerMovement playerMovement;
|
||||
private CharacterController characterController;
|
||||
private float currentSpeed;
|
||||
private float speedVelocity;
|
||||
private bool wasGrounded = true;
|
||||
private bool isJumpingAnimation;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
playerMovement = GetComponent<PlayerMovement>();
|
||||
characterController = GetComponent<CharacterController>();
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
if (!IsOwner)
|
||||
{
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
|
||||
UpdateAnimationParameters();
|
||||
}
|
||||
|
||||
private void UpdateAnimationParameters()
|
||||
{
|
||||
// PlayerMovement에서 직접 속도 가져오기
|
||||
float targetSpeed = playerMovement.CurrentMoveSpeed;
|
||||
|
||||
// 부드러운 속도 변화
|
||||
currentSpeed = Mathf.SmoothDamp(currentSpeed, targetSpeed, ref speedVelocity, speedSmoothTime);
|
||||
|
||||
// 지면 접촉 상태
|
||||
bool isGrounded = characterController.isGrounded;
|
||||
|
||||
// 착지 감지 (공중에서 지면으로)
|
||||
if (!wasGrounded && isGrounded && isJumpingAnimation)
|
||||
{
|
||||
PlayLand();
|
||||
}
|
||||
|
||||
// 애니메이터 파라미터 설정
|
||||
animator.SetFloat(speedParam, currentSpeed);
|
||||
animator.SetBool(isGroundedParam, isGrounded);
|
||||
|
||||
wasGrounded = isGrounded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 점프 애니메이션 트리거 (외부에서 호출)
|
||||
/// </summary>
|
||||
public void PlayJump()
|
||||
{
|
||||
if (IsOwner)
|
||||
{
|
||||
isJumpingAnimation = true;
|
||||
animator.SetTrigger(jumpTriggerParam);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 착지 애니메이션 트리거
|
||||
/// </summary>
|
||||
private void PlayLand()
|
||||
{
|
||||
isJumpingAnimation = false;
|
||||
animator.SetTrigger(landTriggerParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player/PlayerAnimationController.cs.meta
Normal file
2
Assets/Scripts/Player/PlayerAnimationController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dafca28b13e62ee43893a43187dc3535
|
||||
76
Assets/Scripts/Player/PlayerCamera.cs
Normal file
76
Assets/Scripts/Player/PlayerCamera.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Colosseum.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 3인칭 카메라 컨트롤러
|
||||
/// </summary>
|
||||
public class PlayerCamera : MonoBehaviour
|
||||
{
|
||||
[Header("Camera Settings")]
|
||||
[SerializeField] private float distance = 5f;
|
||||
[SerializeField] private float height = 2f;
|
||||
[SerializeField] private float rotationSpeed = 2f;
|
||||
[SerializeField] private float minPitch = -30f;
|
||||
[SerializeField] private float maxPitch = 60f;
|
||||
|
||||
private Transform target;
|
||||
private float yaw;
|
||||
private float pitch;
|
||||
private Camera cameraInstance;
|
||||
private InputSystem_Actions inputActions;
|
||||
|
||||
public void Initialize(Transform playerTransform, InputSystem_Actions actions)
|
||||
{
|
||||
target = playerTransform;
|
||||
inputActions = actions;
|
||||
|
||||
// 기존 메인 카메라 사용 또는 새로 생성
|
||||
cameraInstance = Camera.main;
|
||||
if (cameraInstance == null)
|
||||
{
|
||||
var cameraObject = new GameObject("PlayerCamera");
|
||||
cameraInstance = cameraObject.AddComponent<Camera>();
|
||||
cameraObject.tag = "MainCamera";
|
||||
}
|
||||
|
||||
// 초기 각도
|
||||
yaw = target.eulerAngles.y;
|
||||
pitch = 20f;
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (target == null || cameraInstance == null) return;
|
||||
|
||||
HandleRotation();
|
||||
UpdateCameraPosition();
|
||||
}
|
||||
|
||||
private void HandleRotation()
|
||||
{
|
||||
if (inputActions == null) return;
|
||||
|
||||
// Input Actions에서 Look 입력 받기
|
||||
Vector2 lookInput = inputActions.Player.Look.ReadValue<Vector2>();
|
||||
float mouseX = lookInput.x * rotationSpeed * 0.1f;
|
||||
float mouseY = lookInput.y * rotationSpeed * 0.1f;
|
||||
|
||||
yaw += mouseX;
|
||||
pitch -= mouseY;
|
||||
pitch = Mathf.Clamp(pitch, minPitch, maxPitch);
|
||||
}
|
||||
|
||||
private void UpdateCameraPosition()
|
||||
{
|
||||
// 구면 좌표로 카메라 위치 계산
|
||||
Quaternion rotation = Quaternion.Euler(pitch, yaw, 0f);
|
||||
Vector3 offset = rotation * new Vector3(0f, 0f, -distance);
|
||||
offset.y += height;
|
||||
|
||||
cameraInstance.transform.position = target.position + offset;
|
||||
cameraInstance.transform.LookAt(target.position + Vector3.up * height * 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player/PlayerCamera.cs.meta
Normal file
2
Assets/Scripts/Player/PlayerCamera.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ffd5f6c47d39e94f92515c69e69a9a1
|
||||
234
Assets/Scripts/Player/PlayerMovement.cs
Normal file
234
Assets/Scripts/Player/PlayerMovement.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Unity.Netcode;
|
||||
using Colosseum.Network;
|
||||
|
||||
namespace Colosseum.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 3인칭 플레이어 이동 (네트워크 동기화)
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class PlayerMovement : NetworkBehaviour
|
||||
{
|
||||
[Header("Movement Settings")]
|
||||
[SerializeField] private float moveSpeed = 5f;
|
||||
[SerializeField] private float rotationSpeed = 10f;
|
||||
[SerializeField] private float gravity = -9.81f;
|
||||
|
||||
[Header("Jump Settings")]
|
||||
[SerializeField] private float jumpForce = 5f;
|
||||
|
||||
private CharacterController characterController;
|
||||
private Vector3 velocity;
|
||||
private Vector2 moveInput;
|
||||
private InputSystem_Actions inputActions;
|
||||
private bool isJumping;
|
||||
private bool wasGrounded;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 이동 속도 (애니메이션용)
|
||||
/// </summary>
|
||||
public float CurrentMoveSpeed => moveInput.magnitude * moveSpeed;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 현재 지면 접촉 상태
|
||||
/// </summary>
|
||||
public bool IsGrounded => characterController != null ? characterController.isGrounded : false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 점프 중 상태
|
||||
/// </summary>
|
||||
public bool IsJumping => isJumping;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
// 로컬 플레이어가 아니면 입력 비활성화
|
||||
if (!IsOwner)
|
||||
{
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
characterController = GetComponent<CharacterController>();
|
||||
|
||||
// 스폰 포인트에서 위치 설정
|
||||
SetSpawnPosition();
|
||||
|
||||
// Input Actions 초기화
|
||||
inputActions = new InputSystem_Actions();
|
||||
inputActions.Player.Enable();
|
||||
|
||||
// Move 액션 콜백 등록
|
||||
inputActions.Player.Move.performed += OnMovePerformed;
|
||||
inputActions.Player.Move.canceled += OnMoveCanceled;
|
||||
|
||||
// Jump 액션 콜백 등록
|
||||
inputActions.Player.Jump.performed += OnJumpPerformed;
|
||||
|
||||
// 카메라 설정
|
||||
SetupCamera();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 스폰 위치 설정
|
||||
/// </summary>
|
||||
private void SetSpawnPosition()
|
||||
{
|
||||
Transform spawnPoint = PlayerSpawnPoint.GetRandomSpawnPoint();
|
||||
|
||||
if (spawnPoint != null)
|
||||
{
|
||||
// CharacterController 비활성화 후 위치 설정 (충돌 문제 방지)
|
||||
characterController.enabled = false;
|
||||
transform.position = spawnPoint.position;
|
||||
transform.rotation = spawnPoint.rotation;
|
||||
characterController.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 네트워크 정리리
|
||||
/// </summary>
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
if (inputActions != null)
|
||||
{
|
||||
inputActions.Player.Move.performed -= OnMovePerformed;
|
||||
inputActions.Player.Move.canceled -= OnMoveCanceled;
|
||||
inputActions.Player.Jump.performed -= OnJumpPerformed;
|
||||
inputActions.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnMovePerformed(InputAction.CallbackContext context)
|
||||
{
|
||||
moveInput = context.ReadValue<Vector2>();
|
||||
}
|
||||
|
||||
|
||||
private void OnMoveCanceled(InputAction.CallbackContext context)
|
||||
{
|
||||
moveInput = Vector2.zero;
|
||||
}
|
||||
|
||||
|
||||
private void OnJumpPerformed(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!isJumping && characterController.isGrounded)
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SetupCamera()
|
||||
{
|
||||
var cameraController = GetComponent<PlayerCamera>();
|
||||
if (cameraController == null)
|
||||
{
|
||||
cameraController = gameObject.AddComponent<PlayerCamera>();
|
||||
}
|
||||
cameraController.Initialize(transform, inputActions);
|
||||
}
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
|
||||
ApplyGravity();
|
||||
Move();
|
||||
}
|
||||
|
||||
|
||||
private void ApplyGravity()
|
||||
{
|
||||
if (wasGrounded && velocity.y < 0)
|
||||
{
|
||||
velocity.y = -2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity.y += gravity * Time.deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Move()
|
||||
{
|
||||
if (characterController == null) return;
|
||||
|
||||
// 이동 방향 계산 (카메라 기준)
|
||||
Vector3 moveDirection = new Vector3(moveInput.x, 0f, moveInput.y);
|
||||
moveDirection = TransformDirectionByCamera(moveDirection);
|
||||
moveDirection.Normalize();
|
||||
|
||||
// 이동 적용
|
||||
Vector3 moveVector = moveDirection * moveSpeed * Time.deltaTime;
|
||||
characterController.Move(moveVector + velocity * Time.deltaTime);
|
||||
|
||||
// 회전 (이동 중일 때만)
|
||||
if (moveDirection != Vector3.zero)
|
||||
{
|
||||
Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
|
||||
}
|
||||
|
||||
// 착지 체크 (Move 후에 isGrounded가 업데이트됨)
|
||||
if (!wasGrounded && characterController.isGrounded && isJumping)
|
||||
{
|
||||
OnJumpEnd();
|
||||
}
|
||||
|
||||
// 다음 프레임을 위해 현재 상태 저장
|
||||
wasGrounded = characterController.isGrounded;
|
||||
}
|
||||
|
||||
|
||||
private void Jump()
|
||||
{
|
||||
isJumping = true;
|
||||
velocity.y = jumpForce;
|
||||
|
||||
// 애니메이션 컨트롤러에 점프 알림
|
||||
var animController = GetComponent<PlayerAnimationController>();
|
||||
if (animController != null)
|
||||
{
|
||||
animController.PlayJump();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 점프 중 상태가 끝나면 IsJumping = false;
|
||||
/// </summary>
|
||||
public void OnJumpEnd()
|
||||
{
|
||||
isJumping = false;
|
||||
}
|
||||
|
||||
|
||||
private Vector3 TransformDirectionByCamera(Vector3 direction)
|
||||
{
|
||||
if (Camera.main == null) return direction;
|
||||
|
||||
Transform cameraTransform = Camera.main.transform;
|
||||
Vector3 cameraForward = cameraTransform.forward;
|
||||
Vector3 cameraRight = cameraTransform.right;
|
||||
|
||||
// Y축 제거
|
||||
cameraForward.y = 0f;
|
||||
cameraRight.y = 0f;
|
||||
cameraForward.Normalize();
|
||||
cameraRight.Normalize();
|
||||
|
||||
return cameraRight * direction.x + cameraForward * direction.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player/PlayerMovement.cs.meta
Normal file
2
Assets/Scripts/Player/PlayerMovement.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db1d9c2d6f86e254f9889e2fa9d41e31
|
||||
72
Assets/Scripts/Player/PlayerNetworkController.cs
Normal file
72
Assets/Scripts/Player/PlayerNetworkController.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
namespace Colosseum.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 플레이어 네트워크 상태 관리 (HP, MP 등)
|
||||
/// </summary>
|
||||
public class PlayerNetworkController : NetworkBehaviour
|
||||
{
|
||||
[Header("Stats")]
|
||||
[SerializeField] private float maxHealth = 100f;
|
||||
[SerializeField] private float maxMana = 50f;
|
||||
|
||||
// 네트워크 동기화 변수
|
||||
private NetworkVariable<float> currentHealth = new NetworkVariable<float>(100f);
|
||||
private NetworkVariable<float> currentMana = new NetworkVariable<float>(50f);
|
||||
|
||||
public float Health => currentHealth.Value;
|
||||
public float MaxHealth => maxHealth;
|
||||
public float Mana => currentMana.Value;
|
||||
public float MaxMana => maxMana;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
// 초기화
|
||||
if (IsServer)
|
||||
{
|
||||
currentHealth.Value = maxHealth;
|
||||
currentMana.Value = maxMana;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 대미지 적용 (서버에서만 실행)
|
||||
/// </summary>
|
||||
[Rpc(SendTo.Server)]
|
||||
public void TakeDamageRpc(float damage)
|
||||
{
|
||||
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage);
|
||||
|
||||
if (currentHealth.Value <= 0f)
|
||||
{
|
||||
HandleDeath();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마나 소모 (서버에서만 실행)
|
||||
/// </summary>
|
||||
[Rpc(SendTo.Server)]
|
||||
public void UseManaRpc(float amount)
|
||||
{
|
||||
currentMana.Value = Mathf.Max(0f, currentMana.Value - amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마나 회복 (서버에서만 실행)
|
||||
/// </summary>
|
||||
[Rpc(SendTo.Server)]
|
||||
public void RestoreManaRpc(float amount)
|
||||
{
|
||||
currentMana.Value = Mathf.Min(maxMana, currentMana.Value + amount);
|
||||
}
|
||||
|
||||
private void HandleDeath()
|
||||
{
|
||||
// TODO: 사망 처리 로직
|
||||
Debug.Log($"Player {OwnerClientId} died!");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player/PlayerNetworkController.cs.meta
Normal file
2
Assets/Scripts/Player/PlayerNetworkController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5b322d198fc60a41ae175ea9c58a337
|
||||
8
Assets/Scripts/UI.meta
Normal file
8
Assets/Scripts/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99571f232b8b003448d6d5eba9562fb4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
100
Assets/Scripts/UI/ConnectionUI.cs
Normal file
100
Assets/Scripts/UI/ConnectionUI.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
using Unity.Netcode.Transports.UTP;
|
||||
|
||||
namespace Colosseum.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 네트워크 연결 설정 (Inspector에서 제어)
|
||||
/// </summary>
|
||||
public class ConnectionUI : MonoBehaviour
|
||||
{
|
||||
[Header("Connection Settings")]
|
||||
[SerializeField] private string ipAddress = "127.0.0.1";
|
||||
[SerializeField] private ushort port = 7777;
|
||||
|
||||
[Header("Status (Read Only)")]
|
||||
[SerializeField, Tooltip("현재 연결 상태")] private string connectionStatus = "Disconnected";
|
||||
|
||||
private UnityTransport transport;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
transport = NetworkManager.Singleton?.GetComponent<UnityTransport>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
UpdateTransportSettings();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateConnectionStatus();
|
||||
}
|
||||
|
||||
private void UpdateConnectionStatus()
|
||||
{
|
||||
if (NetworkManager.Singleton == null)
|
||||
{
|
||||
connectionStatus = "No NetworkManager";
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkManager.Singleton.IsServer && NetworkManager.Singleton.IsHost)
|
||||
connectionStatus = "Host";
|
||||
else if (NetworkManager.Singleton.IsServer)
|
||||
connectionStatus = "Server";
|
||||
else if (NetworkManager.Singleton.IsClient)
|
||||
connectionStatus = NetworkManager.Singleton.IsConnectedClient ? "Connected" : "Connecting...";
|
||||
else
|
||||
connectionStatus = "Disconnected";
|
||||
}
|
||||
|
||||
public void StartHost()
|
||||
{
|
||||
UpdateTransportSettings();
|
||||
NetworkManager.Singleton.StartHost();
|
||||
Debug.Log("[Network] Started as Host");
|
||||
}
|
||||
|
||||
public void StartClient()
|
||||
{
|
||||
UpdateTransportSettings();
|
||||
NetworkManager.Singleton.StartClient();
|
||||
Debug.Log($"[Network] Connecting to {ipAddress}:{port}...");
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
UpdateTransportSettings();
|
||||
NetworkManager.Singleton.StartServer();
|
||||
Debug.Log("[Network] Started as Server");
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
if (NetworkManager.Singleton != null)
|
||||
{
|
||||
NetworkManager.Singleton.Shutdown();
|
||||
Debug.Log("[Network] Disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTransportSettings()
|
||||
{
|
||||
if (transport != null)
|
||||
{
|
||||
transport.SetConnectionData(ipAddress, port);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (Application.isPlaying && transport != null)
|
||||
{
|
||||
UpdateTransportSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/ConnectionUI.cs.meta
Normal file
2
Assets/Scripts/UI/ConnectionUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11a53a74e3d983f478f37ef0c99d5847
|
||||
Reference in New Issue
Block a user