输入系统
Unity 提供了多种处理玩家输入的方式。本章将介绍传统的 Input Manager 和新的 Input System 包。
传统 Input Manager
Unity 内置的 Input 类提供了简单的输入检测方法,适合快速原型开发。
键盘输入
public class KeyboardInput : MonoBehaviour
{
void Update()
{
// ========== 按键检测 ==========
// 按下按键(仅触发一次)
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("空格键按下");
}
// 按住按键(持续触发)
if (Input.GetKey(KeyCode.W))
{
Debug.Log("W键按住中");
}
// 松开按键(仅触发一次)
if (Input.GetKeyUp(KeyCode.Space))
{
Debug.Log("空格键松开");
}
// 使用字符串(不区分大小写)
if (Input.GetKeyDown("space"))
{
Debug.Log("空格键");
}
// ========== 常用按键 ==========
// 方向键:KeyCode.UpArrow, DownArrow, LeftArrow, RightArrow
// 字母:KeyCode.A - KeyCode.Z
// 数字:KeyCode.Alpha0 - KeyCode.Alpha9
// 功能键:KeyCode.F1 - KeyCode.F12
// 控制键:KeyCode.LeftControl, LeftShift, LeftAlt
// 回车:KeyCode.Return, KeyCode.KeypadEnter
// ESC:KeyCode.Escape
}
}
鼠标输入
public class MouseInput : MonoBehaviour
{
void Update()
{
// ========== 鼠标按钮 ==========
// 0 = 左键, 1 = 右键, 2 = 中键
if (Input.GetMouseButtonDown(0))
{
Debug.Log("鼠标左键按下");
}
if (Input.GetMouseButton(0))
{
Debug.Log("鼠标左键按住");
}
if (Input.GetMouseButtonUp(0))
{
Debug.Log("鼠标左键松开");
}
// ========== 鼠标位置 ==========
// 屏幕坐标(左下角为原点)
Vector3 mousePosition = Input.mousePosition;
Debug.Log($"鼠标位置: {mousePosition}");
// ========== 鼠标移动 ==========
// 鼠标水平移动(上一帧到现在的变化)
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
// 鼠标滚轮
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (scroll > 0)
{
Debug.Log("滚轮向上");
}
else if (scroll < 0)
{
Debug.Log("滚轮向下");
}
}
}
虚拟轴(Axis)
虚拟轴将键盘输入映射为 -1 到 1 的连续值,适合平滑移动控制。
public class AxisInput : MonoBehaviour
{
public float moveSpeed = 5f;
public float rotationSpeed = 100f;
void Update()
{
// ========== 获取轴值 ==========
// Horizontal: A/D 或 左右方向键 (-1 到 1)
float horizontal = Input.GetAxis("Horizontal");
// Vertical: W/S 或 上下方向键 (-1 到 1)
float vertical = Input.GetAxis("Vertical");
// GetAxisRaw 无平滑过渡,立即响应
float rawHorizontal = Input.GetAxisRaw("Horizontal");
// ========== 移动控制 ==========
Vector3 movement = new Vector3(horizontal, 0, vertical);
transform.Translate(movement * moveSpeed * Time.deltaTime);
// ========== 鼠标视角控制 ==========
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
// 水平旋转(Y轴)
transform.Rotate(Vector3.up * mouseX * rotationSpeed * Time.deltaTime);
}
}
配置输入轴
Edit > Project Settings > Input Manager 可配置虚拟轴:
Name: Horizontal
Negative Button: left
Positive Button: right
Alt Negative Button: a
Alt Positive Button: d
Gravity: 3
Dead: 0.001
Sensitivity: 3
Snap: ☑
Invert: ☐
Type: Joystick Axis
Axis: X axis
Joy Num: Get Motion from all Joysticks
新的 Input System
Input System 是 Unity 推荐的现代输入解决方案,提供更强大、更灵活的输入处理。
安装 Input System
- Window > Package Manager
- 搜索 "Input System"
- 点击 Install
- 重启 Unity(会提示切换后端)
创建输入动作资产
- Project 窗口右键 > Create > Input Actions
- 命名为
PlayerInputActions - 双击打开 Input Actions 编辑器
配置输入动作
PlayerInputActions (Input Action Asset)
├── Player (Action Map)
│ ├── Move (Action)
│ │ ├── WASD [Keyboard]
│ │ ├── Arrow Keys [Keyboard]
│ │ └── Left Stick [Gamepad]
│ ├── Look (Action)
│ │ ├── Delta [Mouse]
│ │ └── Right Stick [Gamepad]
│ ├── Jump (Action)
│ │ ├── Space [Keyboard]
│ │ └── Button South [Gamepad]
│ ├── Fire (Action)
│ │ ├── Left Button [Mouse]
│ │ └── Right Trigger [Gamepad]
│ └── Sprint (Action)
│ ├── Left Shift [Keyboard] (Hold)
│ └── Left Stick Press [Gamepad]
└── UI (Action Map)
├── Navigate
├── Submit
├── Cancel
└── Point
代码中使用 Input System
using UnityEngine;
using UnityEngine.InputSystem;
public class NewInputSystemExample : MonoBehaviour
{
// 生成的输入动作类
private PlayerInputActions inputActions;
// 缓存输入值
private Vector2 moveInput;
private Vector2 lookInput;
private bool isSprinting;
void Awake()
{
// 初始化输入动作
inputActions = new PlayerInputActions();
// ========== 订阅事件 ==========
// Move 动作(持续值)
inputActions.Player.Move.performed += ctx => moveInput = ctx.ReadValue<Vector2>();
inputActions.Player.Move.canceled += ctx => moveInput = Vector2.zero;
// Look 动作
inputActions.Player.Look.performed += ctx => lookInput = ctx.ReadValue<Vector2>();
inputActions.Player.Look.canceled += ctx => lookInput = Vector2.zero;
// Jump 动作(按钮)
inputActions.Player.Jump.performed += ctx => OnJump();
// Fire 动作
inputActions.Player.Fire.performed += ctx => OnFire();
inputActions.Player.Fire.canceled += ctx => OnFireRelease();
// Sprint 动作(按住)
inputActions.Player.Sprint.performed += ctx => isSprinting = true;
inputActions.Player.Sprint.canceled += ctx => isSprinting = false;
}
void OnEnable()
{
inputActions.Player.Enable();
}
void OnDisable()
{
inputActions.Player.Disable();
}
void Update()
{
// 使用缓存的输入值
Move();
Look();
}
void Move()
{
Vector3 movement = new Vector3(moveInput.x, 0, moveInput.y);
float speed = isSprinting ? sprintSpeed : moveSpeed;
transform.Translate(movement * speed * Time.deltaTime);
}
void Look()
{
// 鼠标视角控制
float yaw = lookInput.x * lookSensitivity;
float pitch = -lookInput.y * lookSensitivity;
transform.Rotate(Vector3.up * yaw);
cameraTransform.Rotate(Vector3.right * pitch);
}
void OnJump()
{
if (isGrounded)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
void OnFire()
{
// 开始射击
isFiring = true;
}
void OnFireRelease()
{
// 停止射击
isFiring = false;
}
}
使用 PlayerInput 组件
更简单的使用方式是通过 PlayerInput 组件:
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(PlayerInput))]
public class PlayerInputHandler : MonoBehaviour
{
private Vector2 moveInput;
private Vector2 lookInput;
// 这些方法会自动绑定到同名的 Input Action
public void OnMove(InputValue value)
{
moveInput = value.Get<Vector2>();
}
public void OnLook(InputValue value)
{
lookInput = value.Get<Vector2>();
}
public void OnJump(InputValue value)
{
if (value.isPressed)
{
Jump();
}
}
public void OnFire(InputValue value)
{
if (value.isPressed)
{
StartFiring();
}
else
{
StopFiring();
}
}
void Update()
{
// 处理移动
Vector3 movement = new Vector3(moveInput.x, 0, moveInput.y);
transform.Translate(movement * moveSpeed * Time.deltaTime);
}
}
输入交互(Interactions)
Input System 支持多种交互方式:
// 在代码中配置交互
var action = new InputAction(
type: InputActionType.Button,
binding: "<Keyboard>/space",
interactions: "tap(duration=0.2),slowTap(duration=0.5)"
);
// 常用交互类型:
// Press: 按下触发
// Hold: 按住一段时间触发
// Tap: 快速点击
// SlowTap: 长按
// MultiTap: 多次点击
// Swipe: 滑动
触摸输入
移动设备触摸
using UnityEngine;
using UnityEngine.InputSystem.EnhancedTouch;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
public class TouchInput : MonoBehaviour
{
void OnEnable()
{
EnhancedTouchSupport.Enable();
}
void OnDisable()
{
EnhancedTouchSupport.Disable();
}
void Update()
{
// 获取所有活跃触摸
foreach (var touch in Touch.activeTouches)
{
Debug.Log($"触摸 ID: {touch.touchId}");
Debug.Log($"位置: {touch.screenPosition}");
Debug.Log($"阶段: {touch.phase}");
Debug.Log($"压力: {touch.pressure}");
}
// 获取特定触摸
if (Touch.activeTouches.Count > 0)
{
Touch firstTouch = Touch.activeTouches[0];
switch (firstTouch.phase)
{
case UnityEngine.InputSystem.TouchPhase.Began:
Debug.Log("触摸开始");
break;
case UnityEngine.InputSystem.TouchPhase.Moved:
Debug.Log("触摸移动");
break;
case UnityEngine.InputSystem.TouchPhase.Ended:
Debug.Log("触摸结束");
break;
}
}
}
}
触摸手势
public class TouchGestures : MonoBehaviour
{
private Vector2 touchStartPos;
private float touchStartTime;
void Update()
{
if (Touchscreen.current == null) return;
var touch = Touchscreen.current.primaryTouch;
if (touch.press.wasPressedThisFrame)
{
touchStartPos = touch.position.ReadValue();
touchStartTime = Time.time;
}
if (touch.press.wasReleasedThisFrame)
{
Vector2 touchEndPos = touch.position.ReadValue();
float touchDuration = Time.time - touchStartTime;
Vector2 swipeDelta = touchEndPos - touchStartPos;
// 检测滑动手势
if (swipeDelta.magnitude > 100f && touchDuration < 0.5f)
{
DetectSwipe(swipeDelta);
}
// 检测点击
else if (swipeDelta.magnitude < 20f && touchDuration < 0.3f)
{
Debug.Log("点击");
}
}
}
void DetectSwipe(Vector2 delta)
{
if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
{
// 水平滑动
if (delta.x > 0)
Debug.Log("向右滑动");
else
Debug.Log("向左滑动");
}
else
{
// 垂直滑动
if (delta.y > 0)
Debug.Log("向上滑动");
else
Debug.Log("向下滑动");
}
}
}
游戏手柄输入
using UnityEngine;
using UnityEngine.InputSystem;
public class GamepadInput : MonoBehaviour
{
void Update()
{
// 检查是否有游戏手柄连接
if (Gamepad.current == null) return;
var gamepad = Gamepad.current;
// ========== 摇杆 ==========
Vector2 leftStick = gamepad.leftStick.ReadValue();
Vector2 rightStick = gamepad.rightStick.ReadValue();
// ========== 方向键 ==========
Vector2 dpad = gamepad.dpad.ReadValue();
// ========== 按钮 ==========
bool aButton = gamepad.buttonSouth.isPressed; // A / Cross
bool bButton = gamepad.buttonEast.isPressed; // B / Circle
bool xButton = gamepad.buttonWest.isPressed; // X / Square
bool yButton = gamepad.buttonNorth.isPressed; // Y / Triangle
// ========== 肩键和扳机 ==========
bool leftBumper = gamepad.leftShoulder.isPressed;
bool rightBumper = gamepad.rightShoulder.isPressed;
float leftTrigger = gamepad.leftTrigger.ReadValue();
float rightTrigger = gamepad.rightTrigger.ReadValue();
// ========== 功能键 ==========
bool startButton = gamepad.startButton.isPressed;
bool selectButton = gamepad.selectButton.isPressed;
bool leftStickPress = gamepad.leftStickButton.isPressed;
bool rightStickPress = gamepad.rightStickButton.isPressed;
// ========== 震动 ==========
gamepad.SetMotorSpeeds(0.5f, 0.5f); // 左马达, 右马达 (0-1)
// 停止震动
// gamepad.SetMotorSpeeds(0, 0);
// gamepad.ResetHaptics();
}
}
屏幕坐标转换
public class ScreenToWorld : MonoBehaviour
{
public Camera mainCamera;
void Update()
{
// ========== 屏幕坐标转世界坐标 ==========
// 方法1:使用射线
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
// 射线与平面相交
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(ray, out float enter))
{
Vector3 worldPoint = ray.GetPoint(enter);
Debug.Log($"地面上的世界坐标: {worldPoint}");
}
// 射线与物体碰撞检测
if (Physics.Raycast(ray, out RaycastHit hit))
{
Debug.Log($"击中: {hit.collider.name} 在 {hit.point}");
}
// 方法2:指定深度的世界坐标
Vector3 screenPos = new Vector3(
Input.mousePosition.x,
Input.mousePosition.y,
10f // 距离相机的深度
);
Vector3 worldPos = mainCamera.ScreenToWorldPoint(screenPos);
// ========== 世界坐标转屏幕坐标 ==========
Vector3 objectScreenPos = mainCamera.WorldToScreenPoint(transform.position);
// ========== 视口坐标 (0-1) ==========
Vector3 viewportPos = mainCamera.ScreenToViewportPoint(Input.mousePosition);
Vector3 worldFromViewport = mainCamera.ViewportToWorldPoint(
new Vector3(0.5f, 0.5f, 10f) // 屏幕中心
);
}
}
实践示例
第一人称控制器
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
public class FirstPersonController : MonoBehaviour
{
[Header("移动设置")]
public float walkSpeed = 5f;
public float sprintSpeed = 10f;
public float jumpForce = 8f;
public float gravity = -20f;
[Header("视角设置")]
public float lookSensitivity = 0.5f;
public float maxLookAngle = 80f;
[Header("组件引用")]
public Camera playerCamera;
private CharacterController controller;
private PlayerInputActions inputActions;
private Vector2 moveInput;
private Vector2 lookInput;
private float verticalVelocity;
private float cameraPitch;
private bool isSprinting;
void Awake()
{
controller = GetComponent<CharacterController>();
inputActions = new PlayerInputActions();
// 绑定输入
inputActions.Player.Move.performed += ctx => moveInput = ctx.ReadValue<Vector2>();
inputActions.Player.Move.canceled += ctx => moveInput = Vector2.zero;
inputActions.Player.Look.performed += ctx => lookInput = ctx.ReadValue<Vector2>();
inputActions.Player.Look.canceled += ctx => lookInput = Vector2.zero;
inputActions.Player.Jump.performed += ctx => Jump();
inputActions.Player.Sprint.performed += ctx => isSprinting = true;
inputActions.Player.Sprint.canceled += ctx => isSprinting = false;
}
void OnEnable() => inputActions.Player.Enable();
void OnDisable() => inputActions.Player.Disable();
void Update()
{
HandleLook();
HandleMovement();
}
void HandleLook()
{
// 水平旋转(角色)
transform.Rotate(Vector3.up * lookInput.x * lookSensitivity);
// 垂直旋转(相机)
cameraPitch -= lookInput.y * lookSensitivity;
cameraPitch = Mathf.Clamp(cameraPitch, -maxLookAngle, maxLookAngle);
playerCamera.transform.localRotation = Quaternion.Euler(cameraPitch, 0, 0);
}
void HandleMovement()
{
// 计算移动方向
Vector3 move = new Vector3(moveInput.x, 0, moveInput.y);
move = transform.TransformDirection(move); // 转换为世界坐标
// 应用速度
float speed = isSprinting ? sprintSpeed : walkSpeed;
move *= speed;
// 应用重力
if (controller.isGrounded && verticalVelocity < 0)
{
verticalVelocity = -0.5f; // 保持贴地
}
else
{
verticalVelocity += gravity * Time.deltaTime;
}
move.y = verticalVelocity;
// 移动
controller.Move(move * Time.deltaTime);
}
void Jump()
{
if (controller.isGrounded)
{
verticalVelocity = jumpForce;
}
}
}
最佳实践
- 使用新的 Input System:更灵活,支持多平台
- 缓存输入值:在事件回调中缓存,在 Update 中使用
- 启用/禁用动作:根据游戏状态启用不同的 Action Map
- 支持多种输入设备:键盘、手柄、触摸都应该支持
- 可配置的灵敏度:让玩家可以调整输入灵敏度
下一步
掌握输入系统后,你可以:
- 学习 物理系统 实现真实的角色移动和碰撞
- 了解 动画系统 让角色动作更生动(即将推出)
- 探索 UI 系统 创建游戏界面(即将推出)