12.PlayerInput组件行为执行模式

12.PlayerInput-PlayerInput行为执行模式


12.1 知识点

Send Messages

在自定义脚本中申明名为 “On+行为名” 的函数,没有参数或者参数类型为 InputValue。将该自定义脚本挂载到 PlayerInput 依附的对象上,当触发对应输入时会自动调用函数。

并且还有默认的 3 个和设备相关的函数可以调用:

  • 设备注册(当控制器从设备丢失中恢复并再次运行时会触发):OnDeviceRegained(PlayerInput input)
  • 设备丢失(玩家失去了分配给它的设备之一,例如,当无线设备耗尽电池时):OnDeviceLost(PlayerInput input)
  • 控制器切换:OnControlsChanged(PlayerInput input)
public void OnMove(InputValue value)
{
    print("Move");
    print("Move" + value.Get<Vector2>());
}

public void OnLook(InputValue value)
{
    print("Look");
    print("Look" + value.Get<Vector2>());
}

public void OnFire(InputValue value)
{
    print("Fire");
    if (value.isPressed)
    {
        print("Fire按下");
    }
}

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

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

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

挂载脚本在 Player 对象上后,这种模式下会自动寻找对象身上的脚本是否有对应名字的函数。

Broadcast Messages

基本和 SendMessage 规则一致,唯一的区别是,自定义脚本不仅可以挂载在 PlayerInput 依附的对象上,还可以挂载在其子对象下。

Invoke Unity Events

该模式可以让我们在 Inspector 窗口上通过拖拽的形式关联响应函数,但是注意:响应函数的参数类型需要改为 InputAction.CallbackContext

切换成Invoke Unity Events后 下方展开可以看到事件关联列表

在脚本中声明函数并拖拽关联进行测试:

public void MyFire(InputAction.CallbackContext context)
{
    print("开火1");
}

public void MyMove(InputAction.CallbackContext context)
{
    print("移动1");
}

public void MyLook(InputAction.CallbackContext context)
{
    print("Look1");
}


这种模式的好处是可以关联别的对象身上的脚本函数。

Invoke C Sharp Events

  1. 获取 PlayerInput 组件:
PlayerInput input = this.GetComponent<PlayerInput>();
  1. 获取对应事件进行委托函数添加:
input.onDeviceLost += OnDeviceLost;
input.onDeviceRegained += OnDeviceRegained;
input.onControlsChanged += OnControlsChanged;

// 输入触发事件,任何输入都会触发该事件
input.onActionTriggered += OnActionTrigger;

可以通过 PlayerInput 中的 currentActionMap 属性得到对应行为的值:

input.currentActionMap["Move"].ReadValue<Vector2>();
  1. 当触发输入时会自动触发事件调用对应函数:
public void OnActionTrigger(InputAction.CallbackContext context)
{
    print("触发OnActionTrigger");
    switch (context.action.name)
    {
        case "Fire":
            // 输入阶段的判断 触发阶段 才去做逻辑
            if (context.phase == InputActionPhase.Performed)
                print("开火");
            break;
        case "Look":
            print("看向");
            print(context.ReadValue<Vector2>());
            break;
        case "Move":
            print("移动");
            print(context.ReadValue<Vector2>());
            break;
    }
}

关键参数InputValue和InputAction.CallbackContext

InputValue

  • 判断是否按下单个按键或按钮:inputValue.isPressed
  • 获取 Vector2 类型的值:inputValue.Get<Vector2>()

InputAction.CallbackContext

  • 获取 Vector2 类型的值的 xy 属性:callbackContext.ReadValue<Vector2>().xcallbackContext.ReadValue<Vector2>().y
  • 获取输入行为的名字:callbackContext.action.name,例如 Fire、Move
  • 获取输入控件的名字:callbackContext.control.name,例如 wasd
  • 获取输入控件所属的设备的名字:callbackContext.control.device.name,例如 Keyboard
  • 获取输入阶段的枚举值:callbackContext.phase,例如 InputActionPhase.PerformedInputActionPhase.Started
  • 获取输入阶段的字符串表示:callbackContext.phase.ToString(),例如 “Performed”、”Started”

总结

建议使用Invoke Unity Events或Invoke CSharp Events的方式,效率更高。


12.2 知识点代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class Lesson12_PlayerInput_PlayerInput行为执行模式 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 Send Messages
        //在自定义脚本中
        //申明名为 "On+行为名" 的函数
        //没有参数 或者 参数类型为InputValue
        //将该自定义脚本挂载到PlayerInput依附的对象上
        //当触发对应输入时 会自动调用函数

        //并且还有默认的3个和设备相关的函数可以调用
        //设备注册(当控制器从设备丢失中恢复并再次运行时会触发):OnDeviceRegained(PlayerInput playerInput)
        //设备丢失(玩家失去了分配给它的设备之一,例如,当无线设备耗尽电池时):OnDeviceLost(PlayerInput playerInput)
        //控制器切换:OnControlsChanged(PlayerInput playerInput)
        #endregion

        #region 知识点二 Broadcast Messages	
        //基本和SendMessage规则一致
        //唯一的区别是,自定义脚本不仅可以挂载在PlayerInput依附的对象上
        //还可以挂载在其子对象下
        #endregion

        #region 知识点三 Invoke Unity Events
        //该模式可以让我们在Inspector窗口上通过拖拽的形式关联响应函数
        //但是注意:响应函数的参数类型 需要改为 InputAction.CallbackContext
        #endregion

        #region 知识点四 Invoke C Sharp Events

        //1.获取PlayerInput组件
        PlayerInput input = this.GetComponent<PlayerInput>();

        //2.获取对应事件进行委托函数添加

        input.onDeviceLost += OnDeviceLost;
        input.onDeviceRegained += OnDeviceRegained;
        input.onControlsChanged += OnControlsChanged;

        //输入触发事件 任何输入都会触发该事件
        input.onActionTriggered += OnActionTrigger;

        //可以通过PlayerInput中的currentActionMap属性得到对应行为的值
        input.currentActionMap["Move"].ReadValue<Vector2>();

        //3.当触发输入时会自动触发事件调用对应函数
        #endregion

        #region 知识点五 关键参数InputValue和InputAction.CallbackContext
        //InputValue
        //判断是否按下单个按键或按钮 inputValue.isPressed
        //获取Vector2类型的值 inputValue.Get<Vector2>()

        //InputAction.CallbackContext
        //获取Vector2类型的值的x和y属性 callbackContext.ReadValue<Vector2>().x callbackContext.ReadValue<Vector2>().y
        //获取输入行为的名字 callbackContext.action.name //例如Fire Move
        //获取输入控件的名字 callbackContext.control.name //例如wasd
        //获取输入控件所属的设备的名字 callbackContext.control.device.name //例如Keyboard
        //获取输入阶段的枚举值 callbackContext.phase //例如InputActionPhase.Performed InputActionPhase.Started
        //获取输入阶段的字符串表示 callbackContext.phase.ToString() //例如"Performed" "Started"
        #endregion

        #region 总结
        //建议使用第三第四中方式 效率更高
        #endregion
    }

    #region 知识点一 Send Messages / 知识点二 Broadcast Messages	
    public void OnMove(InputValue value)
    {
        print("Move");
        print("Move"+value.Get<Vector2>());
    }

    public void OnLook(InputValue value)
    {
        print("Look");
        print("Look" + value.Get<Vector2>());
    }
    public void OnFire(InputValue value)
    {
        print("Fire");
        if(value.isPressed)
        {
            print("Fire按下");
        }
    }

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

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

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

    #region 知识点三 Invoke Unity Events
    public void MyFire(InputAction.CallbackContext context)
    {
        print("开火1");
    }

    public void MyMove(InputAction.CallbackContext context)
    {
        print("移动1");
    }

    public void MyLook(InputAction.CallbackContext context)
    {
        print("Look1");
    }

    #endregion

    #region 知识点四 Invoke C Sharp Events

    public void OnActionTrigger(InputAction.CallbackContext context)
    {
        print("触发OnActionTrigger");
        switch (context.action.name)
        {
            case "Fire":
                //输入阶段的判断 触发阶段 才去做逻辑
                if(context.phase == InputActionPhase.Performed)
                    print("开火");
                break;
            case "Look":
                print("看向");
                print(context.ReadValue<Vector2>());
                break;
            case "Move":
                print("移动");
                print(context.ReadValue<Vector2>());
                break;
        }
    }

    #endregion
}

12.3 练习题

请使用上一道小球练习题制作的输入配置文件,用PlayerInput为一个对象处理移动、跳跃、开火的逻辑,分别用4种行为执行模式处理一次

移除之前的小球控制脚本,给小球添加PlayerInput组件,新创建一个配置文件进行关联

创建脚本,添加必要的变量,外部关联

public GameObject bullet; //子弹预制体

public Rigidbody body; //刚体组件 外部拖拽

private Vector3 dir; //移动方向

public PlayerInput playerInput; //玩家输入组件

修改PlayerInput为SendMessages模式,声明对应的On行为函数,移动代码要在Update调用,因为OnMove只会在改变时调用一次不会每帧调用

public void OnJump(InputValue value)
{
    body.AddForce(Vector3.up * 200); //给刚体添加向上的力
}

public void OnFire(InputValue value)
{
    //鼠标位置的射线检测
    RaycastHit info;
    if (Physics.Raycast(Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()), out info)) //从摄像机到鼠标位置发射一条射线,如果碰到物体,返回碰撞信息
    {
        //得到子弹飞出去的向量
        Vector3 point = info.point; //获取碰撞点坐标
        point.y = this.transform.position.y; //调整y轴坐标和自身一致
        Vector3 dir = point - this.transform.position; //计算子弹方向向量
        //创建子弹 飞出去
        Instantiate(bullet, this.transform.position, Quaternion.LookRotation(dir)); //实例化子弹,并设置位置和旋转角度
    }
}

//需要获取值的 这种函数 需要注意 只会在改变时进入函数
public void OnMove(InputValue value)
{
    dir = value.Get<Vector2>(); //获取Vector2类型的值
    dir.z = dir.y; //转换为Vector3类型的值
    dir.y = 0;
    Debug.Log("OnMove dir" + dir);
}

void Update()
{
    Debug.Log("Update前 dir" + dir);
    if (dir == Vector3.zero) return;
    Debug.Log("Update后 dir" + dir);
    body.AddForce(dir); //给刚体添加移动方向的力
}

修改PlayerInput为BroadcastMessages模式下 ,把脚本挂载到子对象上,也能实现输入。代码复用SendMessages的就行

修改PlayerInput为Invoke Unity Events模式,声明有不同参数的重载On行为函数,要手动拖对象进行脚本函数关联

public void OnJump(InputAction.CallbackContext context)
{
    if (context.phase != InputActionPhase.Performed) //判断输入阶段是否为Performed
        return;
    body.AddForce(Vector3.up * 200); //给刚体添加向上的力
}

public void OnFire(InputAction.CallbackContext context)
{
    if (context.phase != InputActionPhase.Performed) //判断输入阶段是否为Performed
        return;
    //鼠标位置的射线检测
    RaycastHit info;
    if (Physics.Raycast(Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()), out info)) //从摄像机到鼠标位置发射一条射线,如果碰到物体,返回碰撞信息
    {
        //得到子弹飞出去的向量
        Vector3 point = info.point; //获取碰撞点坐标
        point.y = this.transform.position.y; //调整y轴坐标和自身一致
        Vector3 dir = point - this.transform.position; //计算子弹方向向量
        //创建子弹 飞出去
        Instantiate(bullet, this.transform.position, Quaternion.LookRotation(dir)); //实例化子弹,并设置位置和旋转角度
    }
}

//需要获取值的 这种函数 需要注意 只会在改变时进入函数
public void OnMove(InputAction.CallbackContext context)
{
    dir = context.ReadValue<Vector2>(); //获取Vector2类型的值
    dir.z = dir.y; //转换为Vector3类型的值
    dir.y = 0;
}

修改PlayerInput为Invoke C Sharp Events模式,在Start时给onActionTriggered 注册监听,判断行为名字且在触发阶段才执行逻辑

playerInput.onActionTriggered += (context) =>
{
    switch (context.action.name)
    {
        case "Move":
            dir = context.ReadValue<Vector2>(); //获取Vector2类型的值
            dir.z = dir.y; //转换为Vector3类型的值
            dir.y = 0;
            break;
        case "Jump":
            if (context.phase == InputActionPhase.Performed) //判断输入阶段是否为Performed
                body.AddForce(Vector3.up * 200); //给刚体添加向上的力
            break;
        case "Fire":
            //鼠标位置的射线检测
            //不是按键触发事件 就不处理后面的内容了
            if (context.phase != InputActionPhase.Performed) //判断输入阶段是否为Performed
                return;
            RaycastHit info;
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()), out info)) //从摄像机到鼠标位置发射一条射线,如果碰到物体,返回碰撞信息
            {
                //得到子弹飞出去的向量
                Vector3 point = info.point; //获取碰撞点坐标
                point.y = this.transform.position.y; //调整y轴坐标和自身一致
                Vector3 dir = point - this.transform.position; //计算子弹方向向量
                //创建子弹 飞出去
                Instantiate(bullet, this.transform.position, Quaternion.LookRotation(dir)); //实例化子弹,并设置位置和旋转角度
            }
            break;
    }
};

12.4 练习题代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class Lesson12_练习题 : MonoBehaviour
{
    public GameObject bullet; //子弹预制体

    public Rigidbody body; //刚体组件 外部拖拽

    private Vector3 dir; //移动方向

    public PlayerInput playerInput; //玩家输入组件

    void Start()
    {
        //body = this.GetComponent<Rigidbody>();

        #region Invoke C Sharp Events

        playerInput.onActionTriggered += (context) =>
        {
            switch (context.action.name)
            {
                case "Move":
                    dir = context.ReadValue<Vector2>(); //获取Vector2类型的值
                    dir.z = dir.y; //转换为Vector3类型的值
                    dir.y = 0;
                    break;
                case "Jump":
                    if (context.phase == InputActionPhase.Performed) //判断输入阶段是否为Performed
                        body.AddForce(Vector3.up * 200); //给刚体添加向上的力
                    break;
                case "Fire":
                    //鼠标位置的射线检测
                    //不是按键触发事件 就不处理后面的内容了
                    if (context.phase != InputActionPhase.Performed) //判断输入阶段是否为Performed
                        return;
                    RaycastHit info;
                    if (Physics.Raycast(Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()), out info)) //从摄像机到鼠标位置发射一条射线,如果碰到物体,返回碰撞信息
                    {
                        //得到子弹飞出去的向量
                        Vector3 point = info.point; //获取碰撞点坐标
                        point.y = this.transform.position.y; //调整y轴坐标和自身一致
                        Vector3 dir = point - this.transform.position; //计算子弹方向向量
                        //创建子弹 飞出去
                        Instantiate(bullet, this.transform.position, Quaternion.LookRotation(dir)); //实例化子弹,并设置位置和旋转角度
                    }
                    break;
            }
        };

        #endregion
    }

    void Update()
    {
        Debug.Log("Update前 dir" + dir);
        if (dir == Vector3.zero) return;
        Debug.Log("Update后 dir" + dir);
        body.AddForce(dir); //给刚体添加移动方向的力
    }

    #region Send Messages 和 Broadcast Messages

    public void OnJump(InputValue value)
    {
        body.AddForce(Vector3.up * 200); //给刚体添加向上的力
    }

    public void OnFire(InputValue value)
    {
        //鼠标位置的射线检测
        RaycastHit info;
        if (Physics.Raycast(Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()), out info)) //从摄像机到鼠标位置发射一条射线,如果碰到物体,返回碰撞信息
        {
            //得到子弹飞出去的向量
            Vector3 point = info.point; //获取碰撞点坐标
            point.y = this.transform.position.y; //调整y轴坐标和自身一致
            Vector3 dir = point - this.transform.position; //计算子弹方向向量
            //创建子弹 飞出去
            Instantiate(bullet, this.transform.position, Quaternion.LookRotation(dir)); //实例化子弹,并设置位置和旋转角度
        }
    }

    //需要获取值的 这种函数 需要注意 只会在改变时进入函数
    public void OnMove(InputValue value)
    {
        dir = value.Get<Vector2>(); //获取Vector2类型的值
        dir.z = dir.y; //转换为Vector3类型的值
        dir.y = 0;
        Debug.Log("OnMove dir" + dir);
    }

    #endregion

    #region Invoke Unity Events

    public void OnJump(InputAction.CallbackContext context)
    {
        if (context.phase != InputActionPhase.Performed) //判断输入阶段是否为Performed
            return;
        body.AddForce(Vector3.up * 200); //给刚体添加向上的力
    }

    public void OnFire(InputAction.CallbackContext context)
    {
        if (context.phase != InputActionPhase.Performed) //判断输入阶段是否为Performed
            return;
        //鼠标位置的射线检测
        RaycastHit info;
        if (Physics.Raycast(Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue()), out info)) //从摄像机到鼠标位置发射一条射线,如果碰到物体,返回碰撞信息
        {
            //得到子弹飞出去的向量
            Vector3 point = info.point; //获取碰撞点坐标
            point.y = this.transform.position.y; //调整y轴坐标和自身一致
            Vector3 dir = point - this.transform.position; //计算子弹方向向量
            //创建子弹 飞出去
            Instantiate(bullet, this.transform.position, Quaternion.LookRotation(dir)); //实例化子弹,并设置位置和旋转角度
        }
    }

    //需要获取值的 这种函数 需要注意 只会在改变时进入函数
    public void OnMove(InputAction.CallbackContext context)
    {
        dir = context.ReadValue<Vector2>(); //获取Vector2类型的值
        dir.z = dir.y; //转换为Vector3类型的值
        dir.y = 0;
    }

    #endregion

}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏