unity input system 使用记录(实例版)

Posted xkxsxkx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity input system 使用记录(实例版)相关的知识,希望对你有一定的参考价值。

目录

如果已经了解过基本概念了,请当我前面没写,直接跳至使用方法

前情提要

在unity中除了传统的通过Input来获取按键输入的方式外
还有一种新的控制输入方法,那就是Input system

但网上的记录感觉还是太少了,感觉不太能满足需求,所以自己就做了一点总结,来防止自己忘记

input system 主要完成的工作是作为一个中间层
将按键响应包装为事件响应,从而方便自己进行管理

易于使用和快速设置,从而方便快速添加基本控件。

当然,并不是说旧的不好,如果能够满足使用,完全没必要再刻意去替换,当然如果想支持新的设备,可以考虑使用input system

安装的过程就省略了,网上太多且简单

基本概念

input system文件可以直接通过create进行创建( Assets > Create > Input Actions),其内部如下

Control Schemes

用于定义用户控制游戏时使用的不同类型的设备或一组设备

比如:

  • 键盘鼠标
  • 游戏手柄
  • 方向盘等

可以通过设置多种类型的Control Schemes保持不同类型的控制系统分离
当然也可以不用,只用添加不同的Action,然后绑定好控制

使用多个Control Schemes的目的是,方便游戏切换设备操作模式,比如单人/双人模式
又或者不同设备的控制

Action Maps

包含一系列的相关Action

Action Maps 用来干什么呢?

Action Maps 包含特定类型行为的所有动作。

比如:你可以把所有角色的通用动作放在一起,像移动,跳跃,开火等
一个Action Maps 可以取名为Player 或者 Gameplay 只要你喜欢

那么就带来一个问题,似乎我只需要一个Action Maps就够了,那为什么要多个呢?

什么时候使用多个Action Maps

比如,你做了一个第一人称的游戏,包含射击和驾驶的功能

这时,你需要一个Action Maps去控制人物的移动和射击,但同时你又需要另一个Action Maps 用于驾驶控制

玩游戏多的人都知道,这两种操作之间很可能会发生按键间的冲突,开车的时候的跳跃键和射击时的肯定不一样

所以你可以在不同的模式下切换Action Maps从而做到满足两种游戏需求

切换Action Maps

在游戏中想要进行切换,可以通过脚本的

playerInput.SwitchCurrentActionMap("Menu");
Debug.Log(playerInput.currentActionMap);

Actions

在input system 中,Actions 连接了控制设备的物理输入和游戏内部的事件
Actions 是 Action maps的具体表现

添加Actions


通过点击+号即可添加
有三种不同类型的Action

  • Button 默认设置,包括按钮或者按键触发的一次性动作
  • Value 提供一种连续状态变化事件,如果设置了多个输入,就会切换到最主要的一个。用它进行模拟控制,比如移动。
  • Pass Through 和Value很相似,但它不会像Value一样(如果有多个输入时,不会只选择最主要的那个,而把其他的输入忽略)

对于Values和Pass Through的选择上,要看自己的设计需求

Values会优先考虑最大的输入值,而Pass Through会优先考虑最近的输入值,而不管它有多大。

input system 中的 Get Key 和 Get Key Down
如果使用老的input Manager, 判断一个键是按了一次还是按下了(例如移动),通过检查对按住按钮的Get key或对单次按下的Get key down来完成。
在 input system 中 Button Action Type 代表只触发一次,等价于 Get Key Down
Value Action Type 等价于 Get Key

GetKey 当通过名称指定的按键被用户按住时返回true
GetKeyDown 当用户按下指定名称的按键时的那一帧返回true。
GetKeyUp 在用户释放给定名字的按键的那一帧返回true。

在使用Value或者Pass Through Types时,你会看到一个额外的选项 Control Type

这允许您指定期望从输入中获得什么类型的输入,并影响哪些Bindings可用。
在完成上述的设置后,就可以添加Binding了

Bindings

简单来说就是将物理输入与事件进行绑定

添加bindings



除了通过界面绑定外,也可以直接通过代码在运行时绑定

using UnityEngine;
using UnityEngine.InputSystem;
public class BasicRebinding : MonoBehaviour

    public InputActionReference triggerAction;
    void ChangeBinding()
    
        InputBinding binding = triggerAction.action.bindings[0];
        binding.overridePath = "<Keyboard>/#(g)";
        triggerAction.action.ApplyBindingOverride(0, binding);
    

1D Axis Composite

2D Axis Composite,提供一个Vector2值



在2D Axis Composite中,从两个axes中获取了值信息
然而这两个值会因为Composite mode 的不同而影响彼此

  • Digital


比如你推动上和右,那么得到的Vector2 就是 (1,1),一般用于两个不相关的控制
在人物移动时,往往会需要上下左右组合按,但如果同时按上右,这样得到的速度就会很大,与正常的逻辑相违背,这时就要使用Digital Normalized

  • Digital Normalized


默认使用,返回一个归一化的向量,这时如果按上右,那么得到的就是(0.7, 0.7)
限制大家应该也看出来了,那就是digital只能给两个方向的输入使用,如果是更多方向呢
比如遥杆控制、控制器的左右操纵杆,那么就需要新的方式

  • The Analogue Mode

如何一次性识别两个按钮被按下?

这种场景很常见,比如经常会有CTRL+Z等快捷组合键需要实现
在inputsystem中,可以创建Button with One Modifier Composite 或者 Button with Two Modifiers Composite

但是要注意的是,input system 并不能屏蔽其他的输入
举个例子,如果你绑定jump动作和B键,而另一个动作Dive绑定的是Left Trigger + B
那么两个动作都会触发,那么应该如何避免呢?

如何避免两个动作的同时触发

就是在触发时,做一个检测判断,看另一个按键有没有被触发,再决定下一个动作。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class InputTester : MonoBehaviour

    bool modifierPressed;
    void OnModifier(InputValue value)
    
        modifierPressed = value.isPressed;
    
    void OnTrigger()
    
        if(modifierPressed)
        
            // Do one thing...
        
        else 
        
            // Do a different thing...
        
    

Interactions and Processors

当设置你的游戏控制器时,可以使用Interactions 和 Processors 改变输入的解释方法

比如,轻点和长按,就是对按钮行为的不同解释

Interactions

用户在按压按钮时,可以有多种方法
可以快速的轻按,或者长时间的重按
也可以在按下和释放时,触发不同的Action
也可以快速双击

  • Hold
    之后按压一段时间后,才会触发事件
  • Multi Tap
    重复使用tap, 可以用这个方式实现双击操作
  • Press
    按压或者释放时触发事件
  • Slow Tap
    只是和Tap相比较而言具有更长的时间周期
  • Tap
    快速按压和释放

Processors


//挖坑,有时间继续补

使用方法

input system 为了方便用户使用,具有灵活性和可扩展性,提供了多种方式使用
其中最简单的使用方法是使用Player Input Component

如何使用Player Input Component

切换 Action Maps

playerInput.SwitchCurrentActionMap("Menu");

Behaviour 设置

Behaviour 有四种选项

Send Message

使用Send Message时,每次的触发会盗用一个对应的函数
比如Input Action 名为 Jump,那么对应的函数即为 OnJump

就是在前面加个On-

获取输入时的数据

可以通过Get 获取设置的对应类型的数据

using UnityEngine;
using UnityEngine.InputSystem;
public class MovePlayer : MonoBehaviour

    public Vector2 moveVal;
    public float moveSpeed;
    void OnMove(InputValue value)
    
        moveVal = value.Get<Vector2>();
    
    void Update()
    
        transform.Translate(new Vector3(moveVal.x, moveVal.y, 0) * moveSpeed * Time.deltaTime);
    
    void OnFire(InputValue value) 
     
        float triggerVal = value.Get<float>(); 
    
    bool modifierPressed;
    void OnModifier(InputValue value)
    
         modifierPressed = value.isPressed;
     

这种方式很适合对于单个游戏对象操控,对于多个对象可以使用

Broadcast Messages

Broadcast Messages 与 send Message 很相似,只是broadcast 会通过object hierarchy 向下传递

Invoke Unity Events

public void Move(InputAction.CallbackContext value)
    
        moveVal = value.ReadValue<Vector2>();
        transform.Translate(new Vector3(moveVal.x, moveVal.y, 0));
    

Invoke C Sharp Events

using UnityEngine;
using UnityEngine.InputSystem;
public class CSharpEvent : MonoBehaviour

    public PlayerInput playerInput;
    void OnEnable()
    
        playerInput.onActionTriggered += MyEventFunction;
    
    void OnDisable()
    
        playerInput.onActionTriggered -= MyEventFunction;
    
    void MyEventFunction(InputAction.CallbackContext value)
    
        Debug.Log(value.action.name + (" was triggered"));
    

直接使用

预备知识


鼠标主要有四个属性
Delta代表鼠标相对运动的坐标改变值,与上一帧的偏移

The current window-space motion delta of the pointer.

Postion代表鼠标当前绝对位置, 在空间中的坐标

The current pointer coordinates in window space.

Radius代表与屏幕的接触半径

Window-space radius of the pointer contact with the surface.
Usually, only touch input has radius detection

Sroll 代表滚轮

引用
context.phase中的内容,其中包含了按键信息的详细情况,
对应的case有

  • Disabled 禁用
  • Waiting 等待输入
  • Started 开始输入
  • Performed 完成输入事件(例如hold了足够的时间)
  • Canceled 未完成输入事件(例如hold的时间不够)

InputAction包括
wasPressedThisFrame,
wasReleasedThisFrame,
isPressed

if (keyboard.wKey.wasPressedThisFrame)
    Debug.Log("w键按下(一直按住w键的话,也只执行一次)");
if (keyboard.wKey.wasReleasedThisFrame)
    Debug.Log("w键松开");
Debug.Log("是否按住w键:" + keyboard.wKey.isPressed);

直接读取设备的状态

在使用方法上,可以和老的输入方式类似,直接读取设备的状态

void Update()

    Vector2 mousePosition = Mouse.current.position.ReadValue();
    if(Keyboard.current.anyKey.wasPressedThisFrame)
    
        Debug.Log("A key was pressed");
    
    if (Gamepad.current.aButton.wasPressedThisFrame)
    
        Debug.Log("A button was pressed");
    

Keyboard keyboard;

keyboard = InputSystem.GetDevice<Keyboard>(); 

if (keyboard.wKey.isPressed)

    direction = new Vector2(0, 1);
    Vector3 delta = new Vector3(direction.x, direction.y, 0) * Time.deltaTime * 5f;
    transform.position = transform.position + delta;


// 按键按下
Keyboard.current.spaceKey.isPressed
// 按键按住
Keyboard.current.spaceKey.wasPressedThisFrame

直接读取输入

在使用时,有时并不用各种Get,也可以直接使用,button就是bool

pitch是围绕X轴旋转,也叫做俯仰角
yaw是围绕Y轴旋转,也叫偏航角
roll是围绕Z轴旋转,也叫翻滚角

 private void CameraRotation()
        
            // if there is an input and camera position is not fixed
            if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition)
            
                //Don't multiply mouse input by Time.deltaTime;
                float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;

                _cinemachineTargetYaw += _input.look.x * deltaTimeMultiplier;
                _cinemachineTargetPitch += _input.look.y * deltaTimeMultiplier;
            

            // clamp our rotations so our values are limited 360 degrees
            _cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
            _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);

            // Cinemachine will follow this target
            CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride,
                _cinemachineTargetYaw, 0.0f);
        

private void Move()
        
            // set target speed based on move speed, sprint speed and if sprint is pressed
            float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;

            // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon

            // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
            // if there is no input, set the target speed to 0
            if (_input.move == Vector2.zero) targetSpeed = 0.0f;

            // a reference to the players current horizontal velocity
            float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;

            float speedOffset = 0.1f;
            float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;

            // accelerate or decelerate to target speed
            if (currentHorizontalSpeed < targetSpeed - speedOffset ||
                currentHorizontalSpeed > targetSpeed + speedOffset)
            
                // creates curved result rather than a linear one giving a more organic speed change
                // note T in Lerp is clamped, so we don't need to clamp our speed
                _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
                    Time.deltaTime * SpeedChangeRate);

                // round speed to 3 decimal places
                _speed = Mathf.Round(_speed * 1000f) / 1000f;
            
            else
            
                _speed = targetSpeed;
            

            _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
            if (_animationBlend < 0.01f) _animationBlend = 0f;

            // normalise input direction
            Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;

            // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
            // if there is a move input rotate player when the player is moving
            if (_input.move != Vector2.zero)
            
                _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
                                  _mainCamera.transform.eulerAngles.y;
                float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
                    RotationSmoothTime);

                // rotate to face input direction relative to camera position
                transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
            


            Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;

            // move the player
            _controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
                             new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);

            // update animator if using character
            if (_hasAnimator)
            
                _animator.SetFloat(_animIDSpeed, _animationBlend);
                _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
            
        

private void JumpAndGravity()
        
            if (Grounded)
            
                // reset the fall timeout timer
                _fallTimeoutDelta = FallTimeout;

                // update animator if using character
                if (_hasAnimator)
                
                    _animator.SetBool(_animIDJump, false);
                    _animator.SetBool(_animIDFreeFall, false);
                

                // stop our velocity dropping infinitely when grounded
                if (_verticalVelocity < 0.0f)
                
                    _verticalVelocity = -2f;
                

                // Jump
                if (_input.jump && _jumpTimeoutDelta <= 0.0f)
                
                    // the square root of H * -2 * G = how much velocity needed to reach desired height
                    _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);

                    // update animator if using character
                    if (_hasAnimator)
                    
                        _animator.SetBool(_animIDJump, true);
                    
                

                // jump timeout
                if (_jumpTimeoutDelta >= 0.0f)
                
                    _jumpTimeoutDelta -= Time.deltaTime;
                
            
            else
            
                // reset the jump timeout timer
                _jumpTimeoutDelta = JumpTimeout;

                // fall timeout
                if (_fallTimeoutDelta >= 0.0f)
                
                    _fallTimeoutDelta -= Time.deltaTime;
                
                else
                
                    // update animator if using character
                    if (_hasAnimator)
                    
                        _animator.SetBool(_animIDFreeFall, true);
                    
                

                // if we are not grounded, do not jump
                _input.jump = false;
            

            // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
            if (_verticalVelocity < _terminalVelocity)
            
                _verticalVelocity += Gravity * Time.deltaTime;
            
        

检查空格是否在当前帧按压

检查游戏手柄的a键是否被按压

Keyboard.current.space.wasPressedThisFrame;
Gamepad.current.aButton.wasPressedThisFrame

查找所有的已连接设备

var allGamepads = Gamepad.all;

// Go through all devices and select gamepads.
InputSystem.devices.Select(x => x is Gamepad);

// Query everything that is using the gamepad template or based on that template.
InputSystem.GetControls("/<gamepad>");

// Fetch all devices with "gamepad" in their names (not a good idea; no guarantee
// a gamepad is actually named that way).
InputSystem.GetControls("/gamepad*");

何时有新设备加入

InputSystem.onDeviceChange +=
        (device, change) =>
        
            if (change == InputDeviceChange.Added)
                /* New Device */;
            else if (change == InputDeviceChange.Disconnected)
                /* Device got unplugged */;
            else if (change == InputDeviceChange.Connected)
                /* Plugged back in */;
            else if (change 

Unity错误提示大全(遇到问题就更新)

记录下使用Unity中遇到的所有错误提示

1.Unhandled Exception: System.Reflection.ReflectionTypeLoadException: The classes in the module cannot be loaded.

  at (wrapper managed-to-native) System.Reflection.Assembly:GetTypes (bool)

   at System.Reflection.Assembly.GetTypes () [0x00000] in <filename unknown>:0

问题描述:

添加dllunity立马报错

问题根源:

dll的类库项目使用的是.NET 4.5 , unity 只支持.NET 2.0

解决方案:

修改dll的类库项目属性->应用程序->目标框架 为.NET 2.0

 

2.NullReferenceException: Object reference not set to an instance of an object

问题根源:

未将对象引用设置到对象的实例

定义了个TweenPosition变量tween,忘记赋值,后面直接调用tween.playforward方法

解决方案:

Awake里获取组件的引用,对变量tween进行赋值

tween = GetComponent<TweenPosition>();

 

以上是关于unity input system 使用记录(实例版)的主要内容,如果未能解决你的问题,请参考以下文章

Unity新版输入系统 new input system

Unity——新输入系统Input System

Unity3D New Input System 鼠标左键单击双击长按配置及实现接口多态用法

怎么在 Unity3d 中模拟touch事件

unity3D中的Input按键方法检测

Unity错误提示大全(遇到问题就更新)