user efc0adced8 ADD: 드론 카메라 게임패드 제어 시스템
게임패드(Xbox/PS)를 이용한 6DOF 드론 카메라 자유비행 모드 추가
- GamepadInputHandler: 게임패드 입력 처리 (스틱, 트리거, 버튼, D-pad)
- DroneCameraMode: 관성 기반 드론 물리 시뮬레이션 및 타겟 자동추적
- CameraController: 드론 모드 토글, 프리셋별 드론 상태 저장/복원
- SystemController: 아바타 Head 콜라이더 자동 생성 및 관리
- StreamDeckServerManager: 드론 모드 WebSocket 연동

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 22:57:59 +09:00

179 lines
6.4 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 게임패드 입력을 처리하여 드론 카메라 제어에 필요한 값을 제공합니다.
/// Unity Input System의 Gamepad.current를 사용합니다.
/// </summary>
public class GamepadInputHandler : MonoBehaviour
{
[Header("Deadzone Settings")]
[SerializeField] private float stickDeadzone = 0.15f;
[Header("Response Curve")]
[SerializeField] private float stickExponent = 2.0f; // 비선형 응답 곡선 (2 = 제곱)
[Header("Speed Multipliers")]
[SerializeField] private float slowMultiplier = 0.3f; // LT 누를 때
[SerializeField] private float boostMultiplier = 3.0f; // RT 누를 때
// 스틱 값 (데드존 + 커브 적용 후)
public Vector2 LeftStick { get; private set; }
public Vector2 RightStick { get; private set; }
// 트리거 값 (0~1)
public float LeftTrigger { get; private set; }
public float RightTrigger { get; private set; }
// 속도 배율 (LT/RT 기반)
public float SpeedMultiplier { get; private set; } = 1f;
// 버튼 상태 (이번 프레임에 눌렸는지)
public bool ButtonSouthPressed { get; private set; } // A (Xbox) / Cross (PS)
public bool ButtonEastPressed { get; private set; } // B (Xbox) / Circle (PS)
public bool ButtonNorthPressed { get; private set; } // Y (Xbox) / Triangle (PS)
public bool ButtonWestPressed { get; private set; } // X (Xbox) / Square (PS)
public bool LeftShoulderPressed { get; private set; } // LB
public bool RightShoulderPressed { get; private set; }// RB
public bool LeftShoulderHeld { get; private set; }
public bool RightShoulderHeld { get; private set; }
public bool StartPressed { get; private set; }
// D-pad는 소비형 플래그 (LateUpdate에서 읽은 뒤 자동 리셋)
private bool _dpadUp, _dpadDown, _dpadLeft, _dpadRight;
// D-pad 이전 프레임 상태 (엣지 감지용)
private bool _dpadUpHeld, _dpadDownHeld, _dpadLeftHeld, _dpadRightHeld;
public bool DpadUpPressed => _dpadUp;
public bool DpadDownPressed => _dpadDown;
public bool DpadLeftPressed => _dpadLeft;
public bool DpadRightPressed => _dpadRight;
// 게임패드 연결 상태
public bool IsGamepadConnected => Gamepad.current != null;
private bool wasConnected = false;
private void Update()
{
var gamepad = Gamepad.current;
if (gamepad == null)
{
if (wasConnected)
{
Debug.Log("[GamepadInputHandler] 게임패드 연결 해제됨");
wasConnected = false;
}
ResetAll();
return;
}
if (!wasConnected)
{
Debug.Log($"[GamepadInputHandler] 게임패드 연결됨: {gamepad.displayName}");
wasConnected = true;
}
// 스틱
LeftStick = ApplyDeadzoneAndCurve(gamepad.leftStick.ReadValue());
RightStick = ApplyDeadzoneAndCurve(gamepad.rightStick.ReadValue());
// 트리거
LeftTrigger = gamepad.leftTrigger.ReadValue();
RightTrigger = gamepad.rightTrigger.ReadValue();
// 속도 배율 계산
SpeedMultiplier = CalculateSpeedMultiplier();
// 버튼 (이번 프레임 pressed)
ButtonSouthPressed = gamepad.buttonSouth.wasPressedThisFrame;
ButtonEastPressed = gamepad.buttonEast.wasPressedThisFrame;
ButtonNorthPressed = gamepad.buttonNorth.wasPressedThisFrame;
ButtonWestPressed = gamepad.buttonWest.wasPressedThisFrame;
LeftShoulderPressed = gamepad.leftShoulder.wasPressedThisFrame;
RightShoulderPressed = gamepad.rightShoulder.wasPressedThisFrame;
LeftShoulderHeld = gamepad.leftShoulder.isPressed;
RightShoulderHeld = gamepad.rightShoulder.isPressed;
StartPressed = gamepad.startButton.wasPressedThisFrame;
// D-pad: ReadValue()로 방향 판별 (일부 게임패드에서 wasPressedThisFrame이 안 먹는 경우 대응)
Vector2 dpadValue = gamepad.dpad.ReadValue();
bool dpadUpNow = dpadValue.y > 0.5f;
bool dpadDownNow = dpadValue.y < -0.5f;
bool dpadLeftNow = dpadValue.x < -0.5f;
bool dpadRightNow = dpadValue.x > 0.5f;
// 엣지 감지: 이전 프레임에 안 눌려있다가 이번에 눌린 경우만 플래그 세움
if (dpadUpNow && !_dpadUpHeld) _dpadUp = true;
if (dpadDownNow && !_dpadDownHeld) _dpadDown = true;
if (dpadLeftNow && !_dpadLeftHeld) _dpadLeft = true;
if (dpadRightNow && !_dpadRightHeld) _dpadRight = true;
_dpadUpHeld = dpadUpNow;
_dpadDownHeld = dpadDownNow;
_dpadLeftHeld = dpadLeftNow;
_dpadRightHeld = dpadRightNow;
}
private Vector2 ApplyDeadzoneAndCurve(Vector2 raw)
{
float magnitude = raw.magnitude;
if (magnitude < stickDeadzone)
return Vector2.zero;
// 데드존 이후 0~1로 리매핑
float normalized = (magnitude - stickDeadzone) / (1f - stickDeadzone);
normalized = Mathf.Clamp01(normalized);
// 응답 곡선 적용
float curved = Mathf.Pow(normalized, stickExponent);
return raw.normalized * curved;
}
private float CalculateSpeedMultiplier()
{
// LT와 RT가 동시에 눌리면 기본 속도
if (LeftTrigger > 0.1f && RightTrigger > 0.1f)
return 1f;
if (LeftTrigger > 0.1f)
return Mathf.Lerp(1f, slowMultiplier, LeftTrigger);
if (RightTrigger > 0.1f)
return Mathf.Lerp(1f, boostMultiplier, RightTrigger);
return 1f;
}
/// <summary>
/// D-pad 플래그를 소비합니다. LateUpdate에서 읽은 후 호출해주세요.
/// </summary>
public void ConsumeDpad()
{
_dpadUp = false;
_dpadDown = false;
_dpadLeft = false;
_dpadRight = false;
}
private void ResetAll()
{
LeftStick = Vector2.zero;
RightStick = Vector2.zero;
LeftTrigger = 0f;
RightTrigger = 0f;
SpeedMultiplier = 1f;
ButtonSouthPressed = false;
ButtonEastPressed = false;
ButtonNorthPressed = false;
ButtonWestPressed = false;
LeftShoulderPressed = false;
RightShoulderPressed = false;
LeftShoulderHeld = false;
RightShoulderHeld = false;
StartPressed = false;
_dpadUp = false;
_dpadDown = false;
_dpadLeft = false;
_dpadRight = false;
}
}