1. Unity新输入系统全面解析
Unity的新输入系统(Input System)是Unity Technologies官方推出的现代化输入解决方案,旨在取代传统的Input Manager。作为一名使用Unity开发过多款跨平台游戏的开发者,我深刻体会到这套新系统带来的变革性优势。
重要提示:新输入系统需要Unity 2019.4或更高版本,并通过Package Manager安装。建议使用Unity 2021 LTS或更新版本以获得最完整的功能支持。
1.1 为什么需要新输入系统?
传统Input Manager存在几个致命缺陷:
- 硬编码严重:输入键位直接写在代码中,难以适应不同设备
- 扩展性差:添加新输入设备需要修改大量代码
- 跨平台支持弱:不同平台需要单独处理输入逻辑
新输入系统的核心优势在于:
- 设备无关性:通过抽象层将具体输入设备与游戏逻辑解耦
- 可视化配置:所有输入映射可通过编辑器界面配置,无需硬编码
- 多设备支持:自动处理键盘、鼠标、手柄、触屏等多种输入设备
- 动态重绑定:运行时允许玩家自定义按键设置
2. 环境准备与基础配置
2.1 安装Input System Package
- 打开Unity编辑器,选择Window > Package Manager
- 在Packages列表中找到"Input System"并安装
- 安装完成后会提示重启编辑器
csharp复制// 安装后需在代码中添加命名空间引用
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
2.2 切换输入处理模式
重启编辑器后需要进行关键配置:
- 菜单栏选择Edit > Project Settings > Player
- 找到"Active Input Handling"选项
- 根据项目情况选择:
- Input System Package (New):全新项目推荐
- Both:需要兼容旧系统的项目
经验分享:如果选择"Both",可以通过
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown和Input.GetMouseButton(0)同时监听新旧系统的触摸/点击事件,但要注意避免逻辑冲突。
3. 两种核心使用模式详解
3.1 直接设备查询模式
适合快速原型开发或简单游戏场景,代码结构与旧Input Manager类似:
csharp复制public class DirectInputExample : MonoBehaviour
{
public float moveSpeed = 5f;
void Update()
{
// 键盘输入检测
var keyboard = Keyboard.current;
if (keyboard != null)
{
Vector2 moveInput = Vector2.zero;
if (keyboard.wKey.isPressed) moveInput.y += 1;
if (keyboard.sKey.isPressed) moveInput.y -= 1;
if (keyboard.aKey.isPressed) moveInput.x -= 1;
if (keyboard.dKey.isPressed) moveInput.x += 1;
transform.Translate(moveInput * moveSpeed * Time.deltaTime);
}
// 鼠标输入检测
var mouse = Mouse.current;
if (mouse != null && mouse.leftButton.wasPressedThisFrame)
{
Debug.Log($"点击位置:{mouse.position.ReadValue()}");
}
// 手柄输入检测
var gamepad = Gamepad.current;
if (gamepad != null)
{
Vector2 stickInput = gamepad.leftStick.ReadValue();
transform.Translate(new Vector3(stickInput.x, 0, stickInput.y) *
moveSpeed * Time.deltaTime);
if (gamepad.buttonSouth.wasPressedThisFrame)
{
Debug.Log("手柄A键按下");
}
}
}
}
优缺点分析:
- 优点:编码简单直观,适合快速测试
- 缺点:设备依赖性强,难以支持按键重绑定
3.2 基于Input Actions的推荐模式
这是新输入系统的核心用法,通过创建Input Actions资源文件来定义所有输入逻辑。
3.2.1 创建Input Actions资源
- 在Project窗口右键 > Create > Input Actions
- 命名为"PlayerControls"(建议命名规范)
- 双击打开Input Actions编辑器
3.2.2 配置Action Maps和Actions
- Action Maps:逻辑输入分组
- 例如:"Gameplay"、"UI"、"Vehicle"等
- Actions:抽象输入动作
- 类型:Value(连续值)、Button(按钮)、PassThrough(直通)
典型配置示例:
- 创建"Gameplay" Action Map
- 添加以下Actions:
- Move (Value/Vector2):角色移动
- Look (Value/Vector2):视角控制
- Jump (Button):跳跃
- Fire (Button):开火
- Sprint (Button):冲刺
3.2.3 绑定物理输入
为每个Action添加Bindings:
- 对于Move动作:
- 添加"2D Vector Composite"
- 分别绑定WASD键和手柄左摇杆
- 对于Jump动作:
- 绑定空格键和手柄A键
- 对于Fire动作:
- 绑定鼠标左键和手柄RT键
专业技巧:可以为同一个Action添加多个Bindings,系统会自动选择当前活动的输入设备。
3.2.4 生成C#脚本
- 选中Input Actions资源
- 在Inspector中勾选"Generate C# Class"
- 点击Apply生成包装类
生成的类提供了强类型访问接口,例如:
csharp复制var controls = new PlayerControls();
controls.Gameplay.Move.performed += OnMove;
4. 代码实现与架构设计
4.1 基础实现模式
csharp复制public class PlayerController : MonoBehaviour
{
private PlayerControls controls;
private Vector2 moveInput;
private Vector2 lookInput;
private bool isJumping;
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float lookSensitivity = 1f;
void Awake()
{
controls = new PlayerControls();
}
void OnEnable()
{
// 注册输入回调
controls.Gameplay.Move.performed += ctx => moveInput = ctx.ReadValue<Vector2>();
controls.Gameplay.Move.canceled += _ => moveInput = Vector2.zero;
controls.Gameplay.Look.performed += ctx => lookInput = ctx.ReadValue<Vector2>();
controls.Gameplay.Look.canceled += _ => lookInput = Vector2.zero;
controls.Gameplay.Jump.performed += _ => isJumping = true;
controls.Gameplay.Jump.canceled += _ => isJumping = false;
controls.Enable();
}
void OnDisable()
{
controls.Disable();
}
void Update()
{
// 处理移动
Vector3 movement = new Vector3(moveInput.x, 0, moveInput.y) *
moveSpeed * Time.deltaTime;
transform.Translate(movement);
// 处理视角旋转
Vector3 rotation = new Vector3(-lookInput.y, lookInput.x, 0) *
lookSensitivity;
transform.Rotate(rotation);
// 处理跳跃
if (isJumping)
{
// 跳跃逻辑...
}
}
}
4.2 进阶架构设计
对于复杂项目,推荐采用分层架构:
- 输入层:只负责收集原始输入
csharp复制public class InputProvider : MonoBehaviour
{
public event Action<Vector2> OnMove;
public event Action<Vector2> OnLook;
public event Action OnJump;
private PlayerControls controls;
void Awake() => controls = new PlayerControls();
void OnEnable()
{
controls.Gameplay.Move.performed += ctx => OnMove?.Invoke(ctx.ReadValue<Vector2>());
controls.Gameplay.Look.performed += ctx => OnLook?.Invoke(ctx.ReadValue<Vector2>());
controls.Gameplay.Jump.performed += _ => OnJump?.Invoke();
controls.Enable();
}
void OnDisable() => controls.Disable();
}
- 逻辑层:处理游戏逻辑
csharp复制public class PlayerMovement : MonoBehaviour
{
[SerializeField] private InputProvider inputProvider;
[SerializeField] private float moveSpeed = 5f;
void OnEnable()
{
inputProvider.OnMove += HandleMove;
}
void OnDisable()
{
inputProvider.OnMove -= HandleMove;
}
void HandleMove(Vector2 input)
{
Vector3 movement = new Vector3(input.x, 0, input.y) * moveSpeed * Time.deltaTime;
transform.Translate(movement);
}
}
5. 高级功能与实战技巧
5.1 交互(Interactions)配置
Interactions可以定义复杂的输入行为模式:
- Hold:长按
- 配置参数:Hold Time(秒)、Press Point(阈值)
- Tap:点击
- 配置参数:Tap Time(秒)、Press Point(阈值)
- Multi Tap:多次点击
- 配置参数:Tap Time、Tap Count(次数)
- Slow Tap/Fast Tap:慢速/快速点击
- Press:按压
应用示例:
- 为Fire动作添加Hold Interaction实现蓄力射击
- 为Jump动作添加Multi Tap实现二段跳
5.2 处理器(Processors)应用
Processors用于实时处理输入值:
- Invert:反转轴
- Scale:缩放输入值
- Stick Deadzone:摇杆死区
- Normalize:标准化向量
csharp复制// 代码中应用Processor
controls.Gameplay.Move.ApplyParameterOverride(
"scale",
0.5f, // 缩放因子
InputDevice.AllDevices
);
5.3 控制方案(Control Schemes)
- 在Input Actions编辑器中定义多个Control Scheme
- 例如:"KeyboardMouse"、"Gamepad"、"Touch"
- 运行时切换:
csharp复制// 获取所有可用方案
var schemes = controls.controlSchemes;
// 切换方案
controls.bindingMask = InputBinding.MaskByGroup("Gamepad");
5.4 输入调试技巧
- 使用Input Debugger:
- Window > Analysis > Input Debugger
- 查看当前活动设备
- 监控输入值和触发事件
- 设备模拟测试
6. 性能优化与常见问题
6.1 性能最佳实践
- 避免每帧查询设备状态:优先使用事件回调
- 合理使用Action Maps:只启用当前需要的Map
- 减少Processor复杂度:复杂计算放在游戏逻辑中
- 批量处理输入事件:对于高频输入(如鼠标移动),可以考虑累积处理
6.2 常见问题排查
问题1:输入没有响应
- 检查设备是否被正确识别
- 确认Input Actions资源已保存并应用
- 验证controls.Enable()是否被调用
问题2:多个输入同时触发
- 检查Action的"Action Type"设置是否正确
- 考虑使用"Pass Through"类型或调整交互设置
问题3:移动平台输入延迟
- 减少Input System的更新频率
- 使用EnhancedTouch代替标准Touch输入
csharp复制// 在Awake中初始化EnhancedTouch
EnhancedTouchSupport.Enable();
6.3 多平台适配建议
- UI提示适配:
csharp复制string GetBindingText(InputAction action)
{
return action.GetBindingDisplayString(
InputBinding.DisplayStringOptions.DontIncludeInteractions);
}
- 输入设备检测:
csharp复制// 检测当前主要输入设备类型
public InputDeviceClass GetCurrentDeviceClass()
{
if (Mouse.current != null && Mouse.current.delta.ReadValue() != Vector2.zero)
return InputDeviceClass.Mouse;
if (Keyboard.current != null && Keyboard.current.anyKey.isPressed)
return InputDeviceClass.Keyboard;
if (Gamepad.current != null && Gamepad.current.IsActuated())
return InputDeviceClass.Gamepad;
return InputDeviceClass.Touch;
}
- 动态控制提示:
csharp复制public Sprite GetControlIcon(InputAction action)
{
var control = action.activeControl;
if (control == null) return null;
// 根据设备类型返回对应图标
if (control.device is Gamepad)
return gamepadButtonSprites[control.path];
else if (control.device is Keyboard)
return keyboardKeySprites[control.path];
return null;
}
7. 项目迁移策略
7.1 从旧Input Manager迁移
-
并行运行阶段:
- 设置Active Input Handling为"Both"
- 逐步替换旧输入代码
- 使用#IF UNITY_INPUT_SYSTEM条件编译
-
输入映射转换:
- 创建对应的Input Actions
- 保持相同的输入语义
-
代码重构:
- 将直接输入查询改为事件驱动
- 抽象输入接口以支持多种实现
7.2 多人游戏输入处理
- 玩家输入分离:
csharp复制// 为每个玩家创建独立的InputActions实例
public class PlayerInput : MonoBehaviour
{
public PlayerControls controls { get; private set; }
public int playerIndex { get; set; }
void Awake()
{
controls = new PlayerControls();
controls.devices = InputSystem.devices
.Where(d => d is Gamepad)
.Skip(playerIndex)
.FirstOrDefault();
}
}
- 输入重定向:
csharp复制// 动态切换输入设备
public void AssignDevice(InputDevice device)
{
controls.devices = new[] { device };
}
8. 实战案例:完整角色控制器
csharp复制[RequireComponent(typeof(CharacterController))]
public class AdvancedPlayerController : MonoBehaviour
{
[Header("References")]
[SerializeField] private Camera playerCamera;
[SerializeField] private Transform groundCheck;
[Header("Movement")]
[SerializeField] private float walkSpeed = 5f;
[SerializeField] private float sprintSpeed = 8f;
[SerializeField] private float jumpHeight = 2f;
[SerializeField] private float gravity = -9.81f;
[SerializeField] private float groundDistance = 0.4f;
[SerializeField] private LayerMask groundMask;
[Header("Look")]
[SerializeField] private float lookSensitivity = 1f;
[SerializeField] private float lookClamp = 80f;
private PlayerControls controls;
private CharacterController controller;
private Vector3 velocity;
private bool isGrounded;
private float xRotation = 0f;
private float currentSpeed;
void Awake()
{
controller = GetComponent<CharacterController>();
controls = new PlayerControls();
currentSpeed = walkSpeed;
// 锁定光标
Cursor.lockState = CursorLockMode.Locked;
}
void OnEnable()
{
// 移动输入
controls.Gameplay.Move.performed += ctx =>
{
Vector2 input = ctx.ReadValue<Vector2>();
Vector3 move = transform.right * input.x + transform.forward * input.y;
controller.Move(move * currentSpeed * Time.deltaTime);
};
// 视角输入
controls.Gameplay.Look.performed += ctx =>
{
Vector2 input = ctx.ReadValue<Vector2>();
xRotation -= input.y * lookSensitivity;
xRotation = Mathf.Clamp(xRotation, -lookClamp, lookClamp);
playerCamera.transform.localRotation = Quaternion.Euler(xRotation, 0, 0);
transform.Rotate(Vector3.up * input.x * lookSensitivity);
};
// 跳跃
controls.Gameplay.Jump.performed += _ =>
{
if (isGrounded)
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
};
// 冲刺
controls.Gameplay.Sprint.performed += _ => currentSpeed = sprintSpeed;
controls.Gameplay.Sprint.canceled += _ => currentSpeed = walkSpeed;
controls.Enable();
}
void OnDisable()
{
controls.Disable();
}
void Update()
{
// 重力处理
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
}
这个控制器实现了:
- 基于物理的角色移动
- 鼠标视角控制
- 跳跃和重力系统
- 冲刺功能
- 地面检测
9. 输入系统扩展与自定义
9.1 创建自定义Interactions
csharp复制[Serializable]
public class DoubleTapInteraction : IInputInteraction
{
public float maxTapSpacing = 0.5f;
public float pressPoint = 0.5f;
private double firstPressTime;
private bool firstPressReceived;
public void Process(ref InputInteractionContext context)
{
switch (context.phase)
{
case InputActionPhase.Waiting:
if (context.ControlIsActuated(pressPoint))
{
if (!firstPressReceived)
{
firstPressTime = context.time;
firstPressReceived = true;
context.Started();
}
else if (context.time - firstPressTime <= maxTapSpacing)
{
context.Performed();
Reset();
}
}
break;
case InputActionPhase.Started:
if (context.time - firstPressTime > maxTapSpacing)
{
context.Canceled();
Reset();
}
break;
}
}
public void Reset()
{
firstPressReceived = false;
firstPressTime = 0;
}
}
// 注册自定义Interaction
[InitializeOnLoad]
public static class RegisterCustomInteractions
{
static RegisterCustomInteractions()
{
InputSystem.RegisterInteraction<DoubleTapInteraction>();
}
}
9.2 创建自定义Controls
csharp复制[InputControlLayout(displayName = "Flight Stick")]
public class FlightStick : InputDevice
{
public static FlightStick current { get; private set; }
public AxisControl pitch { get; private set; }
public AxisControl roll { get; private set; }
public AxisControl yaw { get; private set; }
public ButtonControl fire { get; private set; }
protected override void FinishSetup()
{
base.FinishSetup();
pitch = GetChildControl<AxisControl>("pitch");
roll = GetChildControl<AxisControl>("roll");
yaw = GetChildControl<AxisControl>("yaw");
fire = GetChildControl<ButtonControl>("fire");
}
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
}
10. 项目架构建议
10.1 输入系统与游戏逻辑的解耦
推荐采用观察者模式实现输入与逻辑的松耦合:
csharp复制// 输入事件中心
public class InputEvents : MonoBehaviour
{
public static InputEvents current;
void Awake() => current = this;
// 移动事件
public event Action<Vector2> OnMove;
public void Move(Vector2 direction) => OnMove?.Invoke(direction);
// 视角事件
public event Action<Vector2> OnLook;
public void Look(Vector2 delta) => OnLook?.Invoke(delta);
// 跳跃事件
public event Action OnJump;
public void Jump() => OnJump?.Invoke();
}
// 输入处理器
public class InputProcessor : MonoBehaviour
{
private PlayerControls controls;
void Awake()
{
controls = new PlayerControls();
}
void OnEnable()
{
controls.Gameplay.Move.performed += ctx =>
InputEvents.current.Move(ctx.ReadValue<Vector2>());
controls.Gameplay.Look.performed += ctx =>
InputEvents.current.Look(ctx.ReadValue<Vector2>());
controls.Gameplay.Jump.performed += _ =>
InputEvents.current.Jump();
controls.Enable();
}
void OnDisable()
{
controls.Disable();
}
}
10.2 基于ScriptableObject的输入配置
csharp复制[CreateAssetMenu(menuName = "Input/InputConfig")]
public class InputConfig : ScriptableObject
{
public InputActionReference moveAction;
public InputActionReference lookAction;
public InputActionReference jumpAction;
public float lookSensitivity = 1f;
public float moveSpeed = 5f;
}
// 使用配置
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private InputConfig inputConfig;
void Update()
{
Vector2 moveInput = inputConfig.moveAction.action.ReadValue<Vector2>();
// 使用moveInput...
}
}
11. 性能监控与优化
11.1 输入系统性能分析
-
使用Profiler:
- 监控"Input System"开销
- 关注"ProcessEvents"耗时
-
关键指标:
- 每帧处理的输入事件数量
- 回调函数执行时间
- 输入设备轮询频率
11.2 优化策略
- 减少回调复杂度:
csharp复制// 不推荐 - 复杂逻辑直接放在回调中
controls.Gameplay.Move.performed += ctx => {
// 复杂计算...
};
// 推荐 - 只记录输入值,在Update中处理
private Vector2 moveInput;
controls.Gameplay.Move.performed += ctx => moveInput = ctx.ReadValue<Vector2>();
- 批量处理高频输入:
csharp复制private List<Vector2> mouseDeltas = new List<Vector2>();
void OnEnable()
{
controls.Gameplay.Look.performed += ctx =>
mouseDeltas.Add(ctx.ReadValue<Vector2>());
}
void Update()
{
if (mouseDeltas.Count > 0)
{
Vector2 combinedDelta = Vector2.zero;
foreach (var delta in mouseDeltas)
{
combinedDelta += delta;
}
mouseDeltas.Clear();
// 使用combinedDelta...
}
}
- 选择性启用Action Maps:
csharp复制// 只在需要时启用特定Action Map
controls.UI.Enable();
controls.Gameplay.Disable();
12. 测试与调试策略
12.1 单元测试输入系统
csharp复制[TestFixture]
public class InputTests
{
private PlayerControls controls;
[SetUp]
public void Setup()
{
controls = new PlayerControls();
controls.Enable();
}
[Test]
public void MoveAction_WhenWSADPressed_ReturnsCorrectVector()
{
// 模拟键盘输入
var keyboard = InputSystem.AddDevice<Keyboard>();
// 按下W键
Press(keyboard.wKey);
Assert.AreEqual(Vector2.up, controls.Gameplay.Move.ReadValue<Vector2>());
// 同时按下W和D键
Press(keyboard.dKey);
Assert.AreEqual(new Vector2(1, 1).normalized,
controls.Gameplay.Move.ReadValue<Vector2>());
}
[TearDown]
public void Teardown()
{
controls.Disable();
InputSystem.RemoveAllDevices();
}
private void Press(ButtonControl button)
{
InputSystem.QueueStateEvent(button.device,
new ButtonState { press = true });
InputSystem.Update();
}
}
12.2 输入模拟测试
csharp复制public class InputSimulator : MonoBehaviour
{
void Start()
{
// 添加虚拟设备
var mouse = InputSystem.AddDevice<Mouse>();
var keyboard = InputSystem.AddDevice<Keyboard>();
var gamepad = InputSystem.AddDevice<Gamepad>();
// 模拟输入
StartCoroutine(SimulateInput());
}
IEnumerator SimulateInput()
{
while (true)
{
// 模拟W键按下
InputSystem.QueueStateEvent(Keyboard.current,
new KeyboardState(Key.W));
InputSystem.Update();
yield return new WaitForSeconds(1f);
// 模拟鼠标移动
InputSystem.QueueStateEvent(Mouse.current,
new MouseState { delta = new Vector2(10, 5) });
InputSystem.Update();
yield return new WaitForSeconds(1f);
}
}
}
13. 平台特定处理
13.1 移动平台适配
- 触屏输入优化:
csharp复制public class TouchInput : MonoBehaviour
{
public float touchSensitivity = 0.1f;
void OnEnable()
{
EnhancedTouchSupport.Enable();
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown += OnFingerDown;
}
void OnDisable()
{
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown -= OnFingerDown;
}
void OnFingerDown(Finger finger)
{
// 处理触屏输入...
}
}
- 虚拟摇杆实现:
csharp复制public class VirtualJoystick : MonoBehaviour
{
public RectTransform handle;
public float maxRadius = 50f;
private Vector2 inputVector;
private InputAction moveAction;
void Awake()
{
moveAction = new InputAction("VirtualMove", InputActionType.Value, "Vector2");
}
void Update()
{
if (Input.touchCount > 0)
{
// 处理触摸输入...
moveAction.ApplyValue(inputVector);
}
else
{
moveAction.ApplyValue(Vector2.zero);
}
}
}
13.2 主机平台优化
- 手柄震动反馈:
csharp复制public void TriggerHapticFeedback(float lowFrequency, float highFrequency, float duration)
{
if (Gamepad.current != null)
{
Gamepad.current.SetMotorSpeeds(lowFrequency, highFrequency);
StartCoroutine(StopHaptics(duration));
}
}
IEnumerator StopHaptics(float delay)
{
yield return new WaitForSeconds(delay);
Gamepad.current.SetMotorSpeeds(0, 0);
}
- 手柄按键提示适配:
csharp复制public Sprite GetButtonSprite(InputAction action)
{
var control = action.activeControl;
if (control == null) return null;
string path = control.path;
if (path.Contains("buttonSouth")) return aButtonSprite;
if (path.Contains("buttonEast")) return bButtonSprite;
// 其他按钮...
return null;
}
14. 输入重绑定系统实现
14.1 基本重绑定流程
csharp复制public class InputRebinding : MonoBehaviour
{
private InputAction actionToRebind;
private int bindingIndex;
public void StartRebinding(InputAction action, int bindingIndex)
{
actionToRebind = action;
this.bindingIndex = bindingIndex;
// 禁用动作
action.Disable();
// 执行重绑定
var rebindOperation = action.PerformInteractiveRebinding(bindingIndex)
.WithControlsExcluding("<Mouse>/position")
.WithControlsExcluding("<Mouse>/delta")
.OnMatchWaitForAnother(0.1f)
.OnComplete(operation => RebindComplete(operation))
.Start();
}
private void RebindComplete(InputActionRebindingExtensions.RebindingOperation operation)
{
actionToRebind.Enable();
operation.Dispose();
// 保存绑定
var rebinds = actionToRebind.SaveBindingOverridesAsJson();
PlayerPrefs.SetString("rebinds", rebinds);
}
public static void LoadRebinds()
{
if (PlayerPrefs.HasKey("rebinds"))
{
var rebinds = PlayerPrefs.GetString("rebinds");
new PlayerControls().LoadBindingOverridesFromJson(rebinds);
}
}
}
14.2 高级重绑定功能
- 复合绑定重绑定:
csharp复制public void RebindCompositePart(InputAction action, string partName)
{
int bindingIndex = action.bindings
.IndexOf(b => b.isPartOfComposite && b.name == partName);
StartRebinding(action, bindingIndex);
}
- 绑定冲突检测:
csharp复制private bool CheckForConflicts(InputAction action, InputBinding newBinding)
{
foreach (var otherAction in controls.asset.actionMaps.SelectMany(m => m.actions))
{
if (otherAction == action) continue;
foreach (var binding in otherAction.bindings)
{
if (binding.effectivePath == newBinding.effectivePath)
{
Debug.LogWarning($"冲突检测:{otherAction.name} 已使用 {newBinding.effectivePath}");
return true;
}
}
}
return false;
}
15. 输入系统与UI集成
15.1 UI导航控制
csharp复制public class UINavigation : MonoBehaviour
{
private PlayerControls controls;
void Awake()
{
controls = new PlayerControls();
}
void OnEnable()
{
controls.UI.Navigate.performed += OnNavigate;
controls.UI.Submit.performed += OnSubmit;
controls.UI.Cancel.performed += OnCancel;
controls.UI.Enable();
}
void OnDisable()
{
controls.UI.Disable();
}
void OnNavigate(InputAction.CallbackContext context)
{
Vector2 direction = context.ReadValue<Vector2>();
// 处理UI导航...
}
void OnSubmit(InputAction.CallbackContext context)
{
// 处理确认按钮...
}
void OnCancel(InputAction.CallbackContext context)
{
// 处理取消/返回按钮...
}
}
15.2 输入提示系统
csharp复制public class InputPromptSystem : MonoBehaviour
{
[System.Serializable]
public class DevicePromptSet
{
public Sprite keyboardPrompt;
public Sprite gamepadPrompt;
public Sprite touchPrompt;
}
public DevicePromptSet[] promptSets;
private InputDeviceClass currentDeviceClass;
void Update()
{
currentDeviceClass = DetectCurrentDevice();
UpdateAllPrompts();
}
InputDeviceClass DetectCurrentDevice()
{
// 设备检测逻辑...
}
void UpdateAllPrompts()
{
foreach (var prompt in FindObjectsOfType<InputPrompt>())
{
prompt.UpdatePrompt(currentDeviceClass);
}
}
}
16. 网络游戏中的输入处理
16.1 客户端输入预测
csharp复制public class ClientInputPrediction : MonoBehaviour
{
private PlayerControls controls;
private NetworkPlayer networkPlayer;
private List<InputSnapshot> inputBuffer = new List<InputSnapshot>();
void Awake()
{
controls = new PlayerControls();
networkPlayer = GetComponent<NetworkPlayer>();
}
void OnEnable()
{
controls.Gameplay.Move.performed += RecordInput;
controls.Enable();
}
void OnDisable()
{
controls.Disable();
}
void RecordInput(InputAction.CallbackContext context)
{
var snapshot = new InputSnapshot {
timestamp = NetworkTime.time,
moveInput = context.ReadValue<Vector2>(),
sequenceNumber = networkPlayer.NextSequenceNumber()
};
inputBuffer.Add(snapshot);
PredictMovement(snapshot);
// 发送到服务器
networkPlayer.SendInput(snapshot);
}
void PredictMovement(InputSnapshot snapshot)
{
// 客户端预测逻辑...
}
public void ServerReconciliation(uint ackSequenceNumber)
{
// 移除已确认的输入
inputBuffer.RemoveAll(s => s.sequenceNumber <= ackSequenceNumber);
// 重新模拟未确认的输入
foreach (var snapshot in inputBuffer)
{
PredictMovement(snapshot);
}
}
}
16.2 服务器输入验证
csharp复制public class ServerInputValidator : MonoBehaviour
{
public float maxMoveDistance = 5f;
public float maxInputRate = 10f;
private Dictionary<int, InputSnapshot> lastInputs = new Dictionary<int, InputSnapshot>();
public bool ValidateInput(NetworkPlayer player, InputSnapshot input)
{
// 检查输入频率
if (lastInputs.TryGetValue(player.id, out var lastInput))
{
float timeDelta = input.timestamp - lastInput.timestamp;
if (timeDelta < 1f / maxInputRate)
return false;
}
// 检查移动合理性
Vector2 delta = input.moveInput - lastInput.moveInput;
if (delta.magnitude > maxMoveDistance)
return false;
// 更新最后有效输入
lastInputs[player.id] = input;
return true;
}
}
17. 输入系统与动画系统集成
17.1 动画参数驱动
csharp复制public class InputAnimator : MonoBehaviour
{
public Animator animator;
private PlayerControls controls;
void Awake()
{
controls = new PlayerControls();
}
void OnEnable()
{
controls.Gameplay.Move.performed += OnMove;
controls.Gameplay.Jump.performed += OnJump;
controls.Enable();
}
void OnDisable()
{
controls.Disable();
}
void OnMove(InputAction.CallbackContext context)
{
Vector2 input = context.ReadValue<Vector2>();
animator.SetFloat("MoveX", input.x);
animator.SetFloat("MoveY", input.y);
animator.SetBool("IsMoving", input.magnitude > 0.1f);
}
void OnJump(InputAction.CallbackContext context)
{
animator.SetTrigger("Jump");
}
}
17.2 动画事件触发输入
csharp复制public class AnimationInputHandler : MonoBehaviour
{
private PlayerControls controls;
void Awake()
{
controls = new PlayerControls();
}
// 动画事件调用的方法
public void OnAnimationAttackStart()
{
controls.Gameplay.Attack.Disable();
}
public void OnAnimationAttackEnd()
{
controls.Gameplay.Attack.Enable();
}
}
18. 输入系统与物理系统集成
18.1 基于力的角色控制
csharp复制[RequireComponent(typeof(Rigidbody))]
public class PhysicsCharacterController : MonoBehaviour
{
public float moveForce = 10f;
public float jumpForce = 5f;
public ForceMode forceMode = ForceMode.Force;
private Rigidbody rb;
private PlayerControls controls;