📔(3D) Player & Camera 세트 코드

BamgasiJM·2026년 4월 4일

Unity GenArt

목록 보기
8/41
post-thumbnail

📒PlayerController.cs

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(Rigidbody))]
public class PlayerController : MonoBehaviour
{
    [Header("이동")]
    [Range(1f, 20f)] public float moveSpeed = 6f;
    [Range(1f, 3f)]  public float runMultiplier = 1.5f;
    [Range(0f, 1f)]  public float airControl = 0.4f;
    [Range(1f, 30f)] public float rotationSpeed = 10f;

    [Header("카메라 좌우 회전 (A/D)")]
    [Range(30f, 180f)][Tooltip("A/D 키로 카메라 yaw가 회전하는 속도 (도/초)")]
    public float cameraYawSpeed = 90f;

    [Header("점프")]
    [Range(1f, 20f)] public float jumpForce = 7f;
    [Range(1f, 5f)]  public float fallMultiplier = 2.5f;
    [Range(1f, 5f)]  public float lowJumpMultiplier = 2f;

    [Header("지면 감지")]
    public LayerMask groundLayer = ~0;
    [Range(0.05f, 0.5f)] public float groundCheckRadius = 0.1f;
    [Range(0.01f, 0.5f)] public float groundCheckDistance = 0.2f;
    public Vector3 groundCheckOriginOffset = new Vector3(0f, -0.9f, 0f);

    Rigidbody _rb;

    InputAction _moveAction;
    InputAction _jumpAction;
    InputAction _runAction;

    bool _isGrounded;
    bool _jumpQueued;
    bool _isRunning;

    float _forwardInput;
    float _cameraYaw;

    public float CameraYaw => _cameraYaw;
    public Vector3 MoveDir { get; private set; }
    public Vector3 FaceDir { get; private set; }


    void Awake()
    {
        _rb = GetComponent<Rigidbody>();
        _rb.freezeRotation = true;

        _cameraYaw = transform.eulerAngles.y;

        SetupInputActions();
    }

    void SetupInputActions()
    {
        _moveAction = new InputAction("Move", InputActionType.Value, expectedControlType: "Vector2");
        _moveAction.AddCompositeBinding("2DVector")
            .With("Up",    "<Keyboard>/w")
            .With("Down",  "<Keyboard>/s")
            .With("Left",  "<Keyboard>/a")
            .With("Right", "<Keyboard>/d");
        _moveAction.AddCompositeBinding("2DVector")
            .With("Up",    "<Keyboard>/upArrow")
            .With("Down",  "<Keyboard>/downArrow")
            .With("Left",  "<Keyboard>/leftArrow")
            .With("Right", "<Keyboard>/rightArrow");

        _jumpAction = new InputAction("Jump", InputActionType.Button);
        _jumpAction.AddBinding("<Keyboard>/space");
        _jumpAction.started += ctx => { _jumpQueued = true; };

        _runAction = new InputAction("Run", InputActionType.Button);
        _runAction.AddBinding("<Keyboard>/leftShift");
        _runAction.AddBinding("<Keyboard>/rightShift");
    }

    void OnEnable()  { _moveAction.Enable(); _jumpAction.Enable(); _runAction.Enable(); }
    void OnDisable() { _moveAction.Disable(); _jumpAction.Disable(); _runAction.Disable(); }

    void Update()
    {
        ReadInput();
    }

    void FixedUpdate()
    {
        CheckGround();

        if (_jumpQueued && _isGrounded)
        {
            DoJump();
            _jumpQueued = false;
        }
        else if (!_isGrounded)
        {
            _jumpQueued = false;
        }

        Move();
        ApplyBetterGravity();
    }

    void ReadInput()
    {
        Vector2 raw = _moveAction.ReadValue<Vector2>();
        _isRunning = _runAction.IsPressed();

        _cameraYaw += raw.x * cameraYawSpeed * Time.deltaTime;

        _forwardInput = raw.y;

        Vector3 camForward = Quaternion.Euler(0f, _cameraYaw, 0f) * Vector3.forward;

        MoveDir = _forwardInput * camForward;
        if (MoveDir.sqrMagnitude > 0.01f)
            MoveDir = MoveDir.normalized;

        FaceDir = raw.sqrMagnitude > 0.01f ? camForward : Vector3.zero;
    }

    void Move()
    {
        float speed   = moveSpeed * (_isRunning ? runMultiplier : 1f);
        float control = _isGrounded ? 1f : airControl;

        Vector3 vel = _rb.linearVelocity;
        vel.x = Mathf.Lerp(vel.x, MoveDir.x * speed * control, control);
        vel.z = Mathf.Lerp(vel.z, MoveDir.z * speed * control, control);
        _rb.linearVelocity = vel;

        if (FaceDir.sqrMagnitude > 0.01f)
        {
            Quaternion targetRot = Quaternion.LookRotation(FaceDir);
            transform.rotation = Quaternion.Slerp(
                transform.rotation, targetRot, rotationSpeed * Time.fixedDeltaTime
            );
        }
    }

    void DoJump()
    {
        Vector3 vel = _rb.linearVelocity;
        vel.y = jumpForce;
        _rb.linearVelocity = vel;
    }

    void ApplyBetterGravity()
    {
        if (_rb.linearVelocity.y < 0f)
            _rb.linearVelocity += Vector3.up * Physics.gravity.y * (fallMultiplier - 1f) * Time.fixedDeltaTime;
        else if (_rb.linearVelocity.y > 0f && !_jumpAction.IsPressed())
            _rb.linearVelocity += Vector3.up * Physics.gravity.y * (lowJumpMultiplier - 1f) * Time.fixedDeltaTime;
    }

    void CheckGround()
    {
        Vector3 origin = transform.position + groundCheckOriginOffset;
        _isGrounded = Physics.SphereCast(
            origin, groundCheckRadius, Vector3.down,
            out _, groundCheckDistance, groundLayer,
            QueryTriggerInteraction.Ignore
        );
    }

    void OnDrawGizmosSelected()
    {
        Vector3 origin = transform.position + groundCheckOriginOffset;
        Gizmos.color = _isGrounded ? Color.green : Color.red;
        Gizmos.DrawWireSphere(origin, groundCheckRadius);
        Gizmos.DrawWireSphere(origin + Vector3.down * groundCheckDistance, groundCheckRadius);
    }
}

📒FollowCamera.cs

Inspector에서 Target으로 Player 오브젝트 설정하기

using UnityEngine;

// === FollowCamera ===
// PlayerController.CameraYaw 를 단일 기준으로 삼아 위치 계산
// 피드백 루프 없음 — Player 회전/속도 일절 미참조
public class FollowCamera : MonoBehaviour
{
    [Header("타겟")]
    public Transform target;

    [Header("오프셋")]
    [Range(2f, 15f)] public float distance = 5f;
    [Range(0f, 10f)] public float height = 2.5f;
    public Vector3 lookAtOffset = new Vector3(0f, 1f, 0f);

    [Header("추적 속도")]
    [Range(1f, 30f)] public float followSpeed = 8f;
    [Range(1f, 30f)] public float rotationSpeed = 10f;

    // ===
    PlayerController _player;

    void Start()
    {
        if (target == null) return;
        _player = target.GetComponent<PlayerController>();

        // 시작 위치 스냅
        transform.position = CalcDesiredPosition(_player.CameraYaw);
    }

    void LateUpdate()
    {
        if (target == null || _player == null) return;

        float yaw = _player.CameraYaw;

        Vector3 desired = CalcDesiredPosition(yaw);
        transform.position = Vector3.Lerp(transform.position, desired, followSpeed * Time.deltaTime);

        Vector3 lookTarget = target.position + lookAtOffset;
        Quaternion desiredRot = Quaternion.LookRotation(lookTarget - transform.position);
        transform.rotation = Quaternion.Slerp(transform.rotation, desiredRot, rotationSpeed * Time.deltaTime);
    }

    Vector3 CalcDesiredPosition(float yaw)
    {
        // yaw 방향의 반대편 + 높이
        Vector3 back = Quaternion.Euler(0f, yaw, 0f) * Vector3.back;
        return target.position + back * distance + Vector3.up * height;
    }
}

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0개의 댓글