Input输入系统 (旧版)

Input类常用API

1. 鼠标在屏幕上的位置

屏幕坐标的原点 是在 屏幕的左下角 往右是X轴正方向 往上时Y轴正方向

返回值时Vector3 但是只有 x和y有值 z一直是0 是因为屏幕本来就是2D的 不存在Z轴

Input.mousePosition

2. 检测鼠标输入

// 鼠标按下一瞬间进入(0左键 1右键 2中键)
Input.GetMouseButtonDown(0);

// 鼠标抬起一瞬间 进入
Input.GetMouseButtonUp(0);

// 鼠标长按按下抬起都会进入
Input.GetMouseButton(1);

// 返回值的 y -1往下滚 0没有滚 1往上滚, 返回值 是Vector2 我们鼠标中键滚动 会改变其中的Y值
print(Input.mouseScrollDelta);

3. 检测键盘输入

// 键盘按下:它只在key的状态从未按下到按的帧里才会返回true
Input.GetKeyDown(KeyCode.W);

// 传入字符串的重载,这里传入的 字符串 不能是大写的 不然会报错
Input.GetKeyDown("q");

// 键盘抬起
Input.GetKeyUp(KeyCode.W);

// 键盘长按
Input.GetKey(KeyCode.W);

4. 检测默认轴输入

// 键盘AD按下时 返回 -1到1之间的变换
float h = Input.GetAxis("Horizontal");

// 键盘SW按下时 返回 -1到1之间的变换
Input.GetAxis("Vertical");

// 鼠标横向移动时 -1 到 1 左 右
Input.GetAxis("Mouse X");

// 鼠标竖向移动时 -1 到 1 下 上
Input.GetAxis("Mouse Y");

Input.GetAxis和Input.GetAxisRow的区别

* Input.GetAxis: 这个函数用于获取一个特定轴的输入。在 Unity 中,输入轴可以看作是一个从-1到1或者从0到1的数值,表示了用户输入的方向或强度。例如,如果你有一个“垂直移动”轴,`Input.GetAxis("Vertical")` 将返回一个值,表示用户是否按下了垂直方向键,或者他们在垂直方向上移动了多大的距离。

* Input.GetAxisRow: 这个函数用于获取一个特定行(或按钮)的输入。在 Unity 中,行通常对应于键盘或游戏控制器的按钮。例如,`Input.GetAxisRow(0)` 将返回一个值,表示用户是否按下了键盘上的任意键(如果用户按下了任意键,返回的值通常为1)

input System(新版)

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Installation.html

官方文档

https://gamedevbeginner.com/input-in-unity-made-easy-complete-guide-to-the-new-system/#input_system_get_mouse_position

参考文章

1. 导入Input System

1. 打开Unity的软件包管理器(菜单:Window > Package Manager)。从列表中选择input system包,然后点击安装。

2. 启用新的输入后端。(当你安装input system软件包时,Unity会问你是否要启用新的后端。如果你点击是,Unity将启用新的后端并禁用旧的后端,编辑器将重新启动)

3. 你可以在 Player settings(menu: Edit > Project Settings > Player->Active Input Handling)去修改输入后端,你也可以设置new、old或者两者兼容。

2. 键盘输入

获取当前键盘设备需要引入命名空间UnityEngine.InputSystem

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 键盘输入
/// 
/// 作者: DY
/// 创建日期: 2025-01-09
/// 版本: 1.0
/// </summary>
public class 键盘输入 : MonoBehaviour
{
    Keyboard keyboard = null;
    private void Start()
    {
        // 1. 获得当前键盘对象
        keyboard = Keyboard.current;

        // 3. 通过事件监听按下,可以获得按下的值
        keyboard.onTextInput += (value) =>
        {
            // 注意点:如果长按,就会一直调用
            print(value);
        };
    }

    private void Update()
    {
        // 2. 单个按键 按下抬起长按
        if (keyboard.aKey.wasPressedThisFrame)
        {
            print("键盘a键按下");
        }
        if (keyboard.aKey.wasReleasedThisFrame)
        {
            print("键盘a键抬起");
        }
        if (keyboard.aKey.isPressed)
        {
            print("键盘a键长按");
        }

        // 4. 任意键按下监听
        if (keyboard.anyKey.wasPressedThisFrame)
        {
            print("任意键的按下");
        }
        if (keyboard.anyKey.wasReleasedThisFrame)
        {
            print("任意键的抬下");
        }
        if (keyboard.anyKey.isPressed)
        {
            print("任意键的长按");
        }
    }
}

3. 鼠标输入

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 鼠标输入  
/// 
/// 作者: DY
/// 创建日期: 2025-01-09
/// 版本: 1.0
/// </summary>
public class 鼠标输入 : MonoBehaviour
{
    Mouse mouse = null;
    private void Start()
    {
        // 1. 获得当前鼠标设备
        mouse = Mouse.current;
    }

    private void Update()
    {
        // 2. 鼠标的抬起、按下、长按
        // leftButton、rightButton、middleButton、forwardButton、backButton
        // 鼠标左键的抬起、按下、长按
        if (mouse.leftButton.wasPressedThisFrame)
        {
            print("鼠标左键的按下");
        }
        if (mouse.leftButton.wasReleasedThisFrame)
        {
            print("鼠标左键的抬起");
        }
        if (mouse.leftButton.isPressed)
        {
            print("鼠标左键的长按");
        }


        // 3. 鼠标位置相关
        // 获得鼠标当前位置
        if (mouse.rightButton.wasPressedThisFrame)
        {
            print(mouse.position.ReadValue());
        }

        // 获得鼠标两帧之间的偏移量
        // print(mouse.delta.ReadValue());

        // 获得鼠标滚轮方向向量
        print(mouse.scroll.ReadValue());

    }

}

练习题1

场景中创建一个立方体,鼠标点中立方体,立方体变成红色,表示选中状态,此时可以缩放鼠标滑轮,让立方体放大和缩小。鼠标点击空白,立方体变为白色,表示没有选中。

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 练习题_物体选中  
/// 
/// 作者: DY
/// 创建日期: 2025-01-09
/// 版本: 1.0
/// </summary>
public class 练习题_物体选中 : MonoBehaviour
{
    RaycastHit hit;
    GameObject currentObj;

    private void Update()
    {
        if(Mouse.current.leftButton.wasPressedThisFrame)
        {
            Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
            if(Physics.Raycast(ray, out hit, 100, 
                1 << LayerMask.NameToLayer("Object"), QueryTriggerInteraction.UseGlobal))
            {
                if(hit.collider != null)
                {
                    currentObj = hit.collider.gameObject;
                    currentObj.GetComponent<MeshRenderer>().material.color = Color.red;
                }
            }
            else
            {
                if (currentObj != null)
                {
                    currentObj.GetComponent<MeshRenderer>().material.color = Color.white;
                    currentObj = null;
                }
            }

        }

        if(currentObj != null)
        {
            if(Mouse.current.scroll.ReadValue().y > 0)
            {
                currentObj.transform.localScale += Vector3.one;
            }else if(Mouse.current.scroll.ReadValue().y < 0)
            {
                if (currentObj.transform.localScale.x <= 1) return;
                currentObj.transform.localScale -= Vector3.one;
            }
            
        }
    }

}

4. 触屏输入

要想有触碰效果的话,需要将Game窗口改为Simulator

using UnityEngine;
using UnityEngine.InputSystem;

public class 触碰输入练习 : MonoBehaviour
{
    Touchscreen ts;
    private void Start()
    {
        // 触屏输入
        ts = Touchscreen.current;
    }

    private void Update()
    {
        // 判空
        if(ts == null) return;

        // 2. 得到触屏手指信息
        print(ts.touches.Count);

        // 3. 手指按下,抬起,点击
        if (ts.touches[0].press.wasPressedThisFrame)
        {
            print("手指按下");
        }

        if (ts.touches[0].press.wasReleasedThisFrame)
        {
            print("第一个手指抬起");
        }

        if (ts.touches[0].press.isPressed)
        {
            print("第一个手指长按");
        }

        if (ts.touches[0].tap.isPressed)
        {
            print("点击手势");
        }

        print("连续点击次数:" + ts.touches[0].tapCount);


        // 3. 手指位置信息
        print(ts.touches[0].position.ReadValue());

        // 手指刚按下是的位置
        print(ts.touches[0].startPosition.ReadValue());

        // 手指偏移位置
        print(ts.touches[0].delta.ReadValue());


    }
}

5. 手柄输入

6. 其他输入

像键盘、鼠标、触屏、手柄这些输入,还有摇杆、电子笔、传感器、陀螺仪、重力传感器、加速传感器、光照传感器等

遇到了在说,下面的连接是官方文档

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.2/manual/Sensors.html#accelerometer

7. InputAction

InputAction是Input System帮助我们封装的动作类,我们可以直接在Inspector窗口编辑想要处理的输入类型,当输入触发时,只需要把精力放在输入的逻辑处理上。

当我们再脚本中申明变量时,在Inspector窗口中,就可以选择你要输入的类型

[Header("Binding")]
public InputAction move;
[Header("1D Axis")]
public InputAction axis;
[Header("2D Vector")]
public InputAction vector2D;
[Header("3D Vector")]
public InputAction vector3D;
[Header("Button With One")]
public InputAction btnOne;

当我们点击齿轮时

其中Action表示设置哪些输入

  • ActionType动作类型

    • Value:值类型(只是想要得到某个值,选择它)

    • Button:按钮类型(只是想要触发某个行为,选择它)

    • Pass Through:直通类型,与值类型一致。区别在与多个类型绑定Action时,值类型只能处理一个设备(主设备),而直通类型可以处理多个设备,它将得到的值发送给所有的设备

  • Control Type:筛选输入设备。 比如选择Vector2,则只有能够返回Vector2的输入设备才会被我们看到。

其中Interactions表示相互输入设置,用来处理特殊输入,比如长按、多次点击等。

当满足条件时才会触发这个行为(设置长按事件,点击次数等)

当我们按下右边的➕时,有5个输入效果:Hold、Multi Tap、Press、Slow Tap、Tap

我们点击后,可以通过代码中的3个函数(开始started、触发performed、结束canceled)得到触发相应事件

  • Hold:长按。当按钮按下时会触发started,若在松开按钮前,按住时间大于等于 Hold Time则会触发performed(时间一到就触发),否则触发canceled

  • Tap:在一段时间内松开触发。当按钮按下会触发started,若在Max Tap Duration时间内松开按钮,则会触发performed,否则触发canceled

  • SlowTap:按下时间大于等于Max Tap Duration时,并不会立刻触发performed,则是在松开的时候才会触发performed

  • MultiTap:用于多次点击,例如双击或者三连击。

    • TapCount为点击次数。

    • Max Tap Spacing为每次点击之间的间隔

    • Max Tap Duration为每次点击的持续时间(按下的持续时间)

  • Press:类似按钮的操作。

    • Press Only:按下的时候触发started和preformed。不触发canceled

    • Release Only:按下时触发started,松开时触发performed

    • Press And Release:按下时触发started和preformed,松开时再次触发started和preformed。不触发canceled。

    • Press Point:每个按钮都有对于的浮点值。比如 (未按下0 和 按下1之间)我们可以通过值得到按下的深度,当按下的值大于等于Press Point值则认为按钮按下。

其中Processors表示对得到的值进行一次加工

  • Clamp:将输入值钳制到[min,max]范围

  • invert:反转控件中的值(将值乘以-1)

  • Stick Deadzone:它会设置一个摇杆误触范围,在这个范围内,会把这个值归为0。当摇杆无法达到最大值,比如摇杆拉到底只能得到0.99,我们也可以设置死区,让它在这个范围内归为1。

当我们点击➕时

  • Add Binding:处理单按键输入的(比如:鼠标按下,键盘按下等)

  • Add Positive\Negative Binding:1D轴(比如:Input老输入系统中获得水平竖直热键,返回-1~1之间的一个值)

  • Add Up\Down\Left\Right Composite:2D轴(比如:老输入系统中水平和竖直热键组合在了一起,得到的Vector中的x、y分别表示两个轴)

  • Add Binding With One Modifier:双键组合(比如:Ctrl + C)

  • Add Binding With Two Modifier Composite:三键组合(比如:Ctrl + K + U)

当我们创建之后,通过Path来绑定映射。

代码控制

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// InputAction研究  
/// 
/// 作者: DY
/// 创建日期: 2025-01-09
/// 版本: 1.0
/// </summary>
public class InputAction研究 : MonoBehaviour
{
    public InputAction move;

    private void Start()
    {
        //  1. 启用输入检测
        move.Enable();

        // 2. 操作监听相关
        move.started += (callback) =>
        {
            
        };
        move.performed += (callback) =>
        {
            // 3. CallbackContext的信息

            // 当前状态:没有启用 Disabled、等待 Waiting、触发 Performed、结束 Canceled
            print(callback.phase);
            // 获取值
            // print(callback.ReadValue<float>())
            // 当前设备名字
            print(callback.control.name);

        };
        move.canceled += (callback) =>
        {

        };

    }



}

代码结合Add Binding

功能:实现鼠标左键按下,得到打印信息。键盘A键,也可以得到打印信息

Inspect面板中添加两个按键

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 代码结合AddBinding  
/// 
/// 作者: DY
/// 创建日期: 2025-01-09
/// 版本: 1.0
/// </summary>
public class 代码结合AddBinding : MonoBehaviour
{
    // 实现鼠标左键按下,得到打印信息。键盘A键,也可以得到打印信息
    public InputAction action;

    private void Start()
    {
        action.Enable();
        action.performed += (callback) =>
        {
            print(callback.control.name);
        };
    }
}

代码结合Add Positive\Negative Binding

创建后,双击1DAxis,会出现一些属性

  • Negative:负面按键,例如 0~-1

  • Positive:正向按键,例如 0~1

  • MinValue:最小值

  • MaxValue:最大值

  • Which Side Wins:当同时按下时如何处理

    • Neither:返回MinValue和MaxValue的中间值

    • Positeive:返回maxValue

    • Negative:返回minValue

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 代码结合AddPositiveNegativeBinding  
/// 
/// 作者: DY
/// 创建日期: 2025-01-09
/// 版本: 1.0
/// </summary>
public class 代码结合AddPositiveNegativeBinding : MonoBehaviour
{
    public InputAction axis;

    private void Start()
    {
        axis.Enable();
    }

    private void Update()
    {
        print(axis.ReadValue<float>());
    }

}

代码结合Add Binding With One Modifier

  • Modifier:第一个按键

  • Binding:第二个按键

图中表示,按下左shift + 鼠标左键组合,可以触发事件

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 代码结合AddBindingWithOneModifier  
/// 
/// 作者: DY
/// 创建日期: 2025-01-09
/// 版本: 1.0
/// </summary>
public class 代码结合AddBindingWithOneModifier : MonoBehaviour
{
    public InputAction action;

    private void Start()
    {
        action.Enable();

        action.performed += (callback) =>
        {
            // 打印的是第二个按键的设备名
            print(callback.control.name);
        };
    }

}

8. 输入配置文件

它的本质是一个Json文件,有一套自己的规范。里面存储的是多个InputAction的集合。

结合PlayerInput组件,解析或编辑这个配置文件。通过这个配置文件统一管理用户的所有输入。

创建和打开配置文件

  1. 在Project面板上,右键创建Input Action文件。

  2. 这个文件是以 xx 为后最的json文件。

  3. 双击打开它,就可以对其进行编辑。

注意:一开始创建时,它的内容为空 {} ,当我们在窗口中编辑时,他就会把数据写入这个文件中。

ActionMaps行动地图窗口

为配置文件中的行为进行分组配置,可以配置多套操作规范。

当我们在窗口中修改内容后,并保存。相应的,JSON文件也会保存。

下面代码中,这个行动窗口对应的就是maps数组,其中action数组对于的就是Actions窗口,bindings数组对于的就是 Binding Properties窗口。

{
    "name": "MyInput",
    "maps": [
        {
            "name": "Player",
            "id": "550b4697-2bf5-432e-afa9-56548221fc4a",
            "actions": [
            ],
            "bindings": [
            ]
        },
        {
            "name": "UI",
            "id": "b8988e80-00bf-4718-b026-47898d2e3c0b",
            "actions": [
            ],
            "bindings": [
            ]
        }
    ],
    "controlSchemes": [
        {
            "name": "New Control Scheme",
            "bindingGroup": "New Control Scheme",
            "devices": []
        }
    ]
}

Actions窗口

和InputAction的内容一样

Binding Properties窗口

和 InputAction的内容一样

顶部菜单窗口

Control Scheme:控制在该主题下显示那些信息。比如我们添加一个 Control Scheme,并把这个主题中添加键盘,那么只有在

Binding Properties下勾选了Using in Control Scheme中键盘,我才会显示出来。否则则会被隐藏。

9. 输入配置文件生成c#代码

根据配置配置文件生成代码:当我们点击InputAction配置后,在Inspector上可以选择生成c#代码。我们可以自定义生成文件的类名、文件路径、命名空间。点击Apply就可以为我们生成c#代码。其中Revert是重置按钮。

生成完成后,可以通过代码去使用

using UnityEngine;
/// <summary>
/// 配置文件获得输入  
/// 
/// 作者: DY
/// 创建日期: 2025-01-10
/// 版本: 1.0
/// </summary>
public class 配置文件获得输入 : MonoBehaviour
{
    MyInput inputActions = null;
    private void Start()
    {
        // 1. 创建我们刚刚在配置文件中生成的c#代码
        inputActions = new MyInput();

        // 2. 启用它
        inputActions.Enable();

        // 3. 使用
        inputActions.Newactionmap.开火.performed += (callback) =>
        {
            print("开火");
        };

        inputActions.Newactionmap.跳跃.performed += (callback) =>
        {
            print("跳跃");
        };

    }

    private void Update()
    {
        print(inputActions.Newactionmap.移动.ReadValue<Vector2>());
    }

}

10. Player Input

关联输入配置文件,它会自动解析这个配置文件,不需要手动生成c#代码然后去new。

PlayerInput组件面板参数

Actions:所关联的配置文件。当我们创建配置文件后,会出现下面3个选项

  • Default Scheme:显示那个主题。any表示所有主题都可以显示。

  • Auto-Switch:当设备切换时,是否自动切换设备输入

  • Default Map:当前选择哪套规范

UI Input Module:关联的UI输入模块

Camera:关联的摄像机(多人玩家分屏的时候,考虑此选项)

Behavior:行为(会在下文行为执行模式专门说明)

11. Player Input行为执行模式

我们接下来统一设置一下输入配置文件,这样能够很好理解下面的几种行为执行模式

1. Send Message

采用这种模式,需要将脚本挂载到与Player Input同一个对象上,当输入触发,它会去调用这个脚本相对应的函数。

这个函数的命名格式必须是 On + 行为名。

这种模式也提供了3种方法:当设备丢失、设备注册、控制器切换,它会去调用相应的函数

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// SendMessages练习  
/// 
/// 作者: DY
/// 创建日期: 2025-01-10
/// 版本: 1.0
/// </summary>
public class SendMessages1 : MonoBehaviour
{
    public void On移动(InputValue value)
    {
        print("触发了移动输入");
    }

    public void On开火(InputValue value)
    {
        print("触发了开火输入");
    }

    public void On跳跃(InputValue value)
    {
        print("触发了跳跃输入");
    }

    public void OnDeviceLost(PlayerInput playerInput)
    {
        print("设备丢失");
    }

    public void OnDeviceRegained(PlayerInput playerInput)
    {
        print("设备注册");
    }

    public void OnControlsChanged(PlayerInput playerInput)
    {
        print("控制器切换");
    }
}

2. BroadCast Messages

与Send Message是一致的,只不过采用BroadCast Messages模式,它会去找挂载了PlayerInput组件对象的子对象的脚本文件中的方式。

而采用Send Message模式,它只会在与挂载了PlayerInput组件对象下去查找脚本文件。

3. Invoke Unity Eventes

通过拖拽的方式去调用相对于的函数

它会events视图,我们可以自定义脚本,把相对于的脚本进行拖拽即可。



using UnityEngine;
/// <summary>
/// InvokeUnityEvents练习  
/// 
/// 作者: DY
/// 创建日期: 2025-01-10
/// 版本: 1.0
/// </summary>
public class InvokeUnityEvents练习 : MonoBehaviour
{
    public void 移动逻辑()
    {
        print("移动");
    }

    public void 开火逻辑()
    {
        print("开火");
    }

    public void 跳跃逻辑()
    {
        print("跳跃");
    }

    public void 控制器切换()
    {
        print("控制器切换");
    }
}

4. Invoke C sharp Evetns

通过写脚本监听的方式去触发输入事件

using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// InvokeCSharpEvents练习  
/// 
/// 作者: DY
/// 创建日期: 2025-01-10
/// 版本: 1.0
/// </summary>
public class InvokeCSharpEvents练习 : MonoBehaviour
{
    PlayerInput playerInput;

    private void Start()
    {
        // 1. 先要获得PlayerInput
        playerInput = GetComponent<PlayerInput>();

        // 2. 监听设备相关
        playerInput.onDeviceRegained += (callback) =>
        {
            print("设备注册");
        };

        playerInput.onControlsChanged += (callback) =>
        {
            print("控制器切换");
        };

        playerInput.onDeviceLost += (callback) =>
        {
            print("设备丢失");
        };

        // 3. 监听输入相关
        playerInput.onActionTriggered += (InputAction.CallbackContext context) =>
        {
            switch (context.action.name)
            {
                case "移动":
                    // print(context.ReadValue<Vector2>());
                    break;
                case "跳跃":
                    // print(context.ReadValue<Vector2>());
                    break;
                case "开火":
                    if (context.performed)
                    {
                        print("开火");
                    }
                    break;
                default:
                    break;
            }


        };
    }

    private void Update()
    {
        // 4. 可以通过update直接获得
        print(playerInput.currentActionMap["移动"].ReadValue<Vector2>());
    }
}

12. PlayerInputManager

主要用于制作多人游戏的,遇到了再去学习。不然学了就忘记了。

13. UGUI配合使用

新输入系统对UGUI是支持的(需要Input System UI Input Module),新输入系统不支持GUI

OnScreen组件相关

当我们想要是UI上制作一个摇杆,控制人物移动,我们可以直接给这个摇杆添加On Screen Stick组件,就可以实现摇杆的功能,这个组件下有一个映射关系。就是说当我们移动摇杆时,就会触发映射按键的值。

On Screen Button组件也是一个按键映射,当我们点击按钮后,相当于触发了 按键映射 的事件

14. Input Debug

新输入系统案例练习

1. InputSystem中专门用于任意键按下的方案

void Start()
{
    InputSystem.onAnyButtonPress.CallOnce((control) =>
    {
        print(control.path);
        print(control.name);
    });

}

2. 通过Json手动加载输入配置文件

public PlayerInput input;
void Start()
{
    // 1. 读取Json文件
    string json = Resources.Load<TextAsset>("PlayerInput").text;
    // 2. 将JSON文件解析为InputActionAsset
    InputActionAsset asset = InputActionAsset.FromJson(json);
    // 3. 指定actions
    input.actions = asset;
    // 4. 使用actions
    input.onActionTriggered += (context) =>
    {
        if (context.phase == InputActionPhase.Performed)
        {
            switch (context.action.name)
            {
                case "Move":
                    print("移动");
                    break;
                case "Scroll":
                    print("Scroll");
                    break;
            }
        }
    };

}