如何实现Unity3D Cinemachine第三人称移动端按住拖拽式旋转相机
Fixing Mobile Swipe-to-Rotate Third-Person Camera (Genshin-Style)
Hey there! Let's get that mobile camera behaving like Genshin Impact's swipe-to-rotate setup—no more constant spinning when holding the screen. Here's a step-by-step solution that works with your existing Input System setup:
Step 1: Update Your Input Action Map
First, add a new input action to handle mobile touch drags:
- Open your
PlayerInput Action asset (the one referenced inplayerInput). - In the
PlayerMainaction map, add a new action namedCameraLookwith Type = Pointer. - Bind it to
Touchscreen -> Primary Touch -> Position(this lets us track the touch's position over time). - Optional: For desktop testing, add a fallback binding to
Mouse -> Positionso you can test drags with your mouse.
Step 2: Modify Your Player Controller Code
Update your existing code to handle touch drag logic, and separate mobile/desktop behavior if needed. Here's the revised code with comments explaining each change:
#region Variables [SerializeField] private float playerSpeed = 2.0f; [SerializeField] private float jumpHeight = 1.0f; [SerializeField] private float gravityValue = -9.81f; [SerializeField] private float rotationSpeed = 0.1f; // New variables for camera drag [SerializeField] private float cameraDragSensitivity = 0.5f; [SerializeField] private float maxVerticalAngle = 80f; [SerializeField] private float minVerticalAngle = 10f; private float rotationVelocity; private Transform cameraMain; private Transform child; private Player playerInput; private CharacterController controller; private Vector3 playerVelocity; private bool groundedPlayer; // Touch state tracking private Vector2 lastTouchPosition; private bool isDraggingCamera = false; #endregion #region Main Methods private void Awake() { playerInput = new Player(); controller = GetComponent<CharacterController>(); } private void OnEnable() { playerInput.Enable(); // Subscribe to touch start/end events playerInput.PlayerMain.CameraLook.performed += ctx => StartCameraDrag(ctx.ReadValue<Vector2>()); playerInput.PlayerMain.CameraLook.canceled += _ => StopCameraDrag(); } private void OnDisable() { playerInput.Disable(); // Unsubscribe to avoid memory leaks playerInput.PlayerMain.CameraLook.performed -= ctx => StartCameraDrag(ctx.ReadValue<Vector2>()); playerInput.PlayerMain.CameraLook.canceled -= _ => StopCameraDrag(); } private void Start() { cameraMain = Camera.main.transform; child = transform.GetChild(0).transform; } void Update() { groundedPlayer = controller.isGrounded; if (groundedPlayer && playerVelocity.y < 0) { playerVelocity.y = 0f; } // Handle player movement (your existing logic, no changes needed here) Vector2 movementInput = playerInput.PlayerMain.Move.ReadValue<Vector2>(); Vector3 move = (cameraMain.forward * movementInput.y + cameraMain.right * movementInput.x); move.y = 0f; if (move != Vector3.zero) { float targetAngle = Mathf.Atan2(move.x, move.z) * Mathf.Rad2Deg; float angle = Mathf.SmoothDampAngle(child.eulerAngles.y, targetAngle, ref rotationVelocity, rotationSpeed); child.rotation = Quaternion.Euler(0f, angle, 0f); controller.Move(move * Time.deltaTime * playerSpeed); } // Handle jump (existing logic) if (playerInput.PlayerMain.Jump.triggered && groundedPlayer) { playerVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue); } playerVelocity.y += gravityValue * Time.deltaTime; controller.Move(playerVelocity * Time.deltaTime); // Handle camera drag rotation (new logic) if (isDraggingCamera) { Vector2 currentTouchPosition = playerInput.PlayerMain.CameraLook.ReadValue<Vector2>(); Vector2 touchDelta = currentTouchPosition - lastTouchPosition; // Rotate camera around player horizontally (left/right drag) cameraMain.RotateAround(transform.position, Vector3.up, touchDelta.x * cameraDragSensitivity); // Rotate camera vertically (up/down drag) with angle limits (like Genshin) float verticalRotation = -touchDelta.y * cameraDragSensitivity; Vector3 currentCamRotation = cameraMain.eulerAngles; // Fix wrap-around issue with Euler angles (0-360) float currentX = currentCamRotation.x > 180 ? currentCamRotation.x - 360 : currentCamRotation.x; float newX = Mathf.Clamp(currentX + verticalRotation, minVerticalAngle, maxVerticalAngle); cameraMain.eulerAngles = new Vector3(newX, currentCamRotation.y, 0f); // Update last touch position for next frame lastTouchPosition = currentTouchPosition; } } // Helper methods for touch state private void StartCameraDrag(Vector2 startPosition) { isDraggingCamera = true; lastTouchPosition = startPosition; } private void StopCameraDrag() { isDraggingCamera = false; } #endregion
Key Changes Explained
- Touch State Tracking: We use
isDraggingCamerato only rotate the camera when the user is actively dragging (not just holding the screen). - Camera Rotation:
RotateAroundkeeps the camera orbiting around your player's position, which is perfect for third-person perspective.- Vertical rotation is clamped to prevent the camera from flipping too far up/down (adjust
minVerticalAngleandmaxVerticalAngleto your preference).
- Input System Event Subscriptions: We use
performedandcanceledevents to detect when the user starts/stops touching the screen, which is more reliable than checking in Update alone.
Testing Tips
- Switch Unity's Game view to a mobile resolution (e.g., 1080x1920) and use the mouse to simulate touch drags.
- For actual mobile testing, build and deploy to your device—make sure Input System's mobile support is enabled in Project Settings.
内容的提问来源于stack exchange,提问作者CodingNeeded
相关产品推荐
相关产品推荐

