게임패드(Xbox/PS)를 이용한 6DOF 드론 카메라 자유비행 모드 추가 - GamepadInputHandler: 게임패드 입력 처리 (스틱, 트리거, 버튼, D-pad) - DroneCameraMode: 관성 기반 드론 물리 시뮬레이션 및 타겟 자동추적 - CameraController: 드론 모드 토글, 프리셋별 드론 상태 저장/복원 - SystemController: 아바타 Head 콜라이더 자동 생성 및 관리 - StreamDeckServerManager: 드론 모드 WebSocket 연동 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
179 lines
6.4 KiB
C#
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;
|
|
}
|
|
}
|