转载请注明出处:https://blog.csdn.net/weixin_44013533/article/details/135746035
作者:CSDN@|Ringleader|
本文节选自我的Unity New Input System博客:Unity New Input System 及其系统结构和源码浅析【Unity学习笔记·第十二】
PlayerInput.cs类内部使用GameObject.SendMessage()
或者GameObject.BroadcastMessage()
方法,本质都是使用反射方式实现方法调用的。
void CacheMessageNames()
{
if (m_Actions == null)
return;
if (m_ActionMessageNames != null)
m_ActionMessageNames.Clear();
else m_ActionMessageNames = new Dictionary<string, string>();
foreach (var action in m_Actions)
{
action.MakeSureIdIsInPlace();
var name = CSharpCodeHelpers.MakeTypeName(action.name);
m_ActionMessageNames[action.m_Id] = "On" + name;
}
}
特别注意:
Send/Broadcast Messages方式,目前只处理performed
或者type为value的canceled
的回调,所以对于这两种方式,Button类型Action的按键释放通知是不处理的。
反射对方法名大小写敏感,方法大小写不匹配时方法不触发,但不报异常
方法可以带InputValue
参数,但无参和有参同时存在只会调用首先声明的那一个
MissingMethodException
,比如你像另两个behavior一样带CallbackContext
是不行的。//哪个写在前面调用谁
public void OnFireCube(InputValue value)
{
Debug.Log("父对象触发fire action,value=" + value.Get<float>());
}
public void OnFireCube()
{
Debug.Log("父对象触发fire action");
}
使用原生c#事件方式,利用PlayerInput.onActionTriggered事件间接监听action事件触发:
private void Start()
{
var playerInput = GetComponent<PlayerInput>();
playerInput.onActionTriggered += context =>
{
switch (context.action.name)
{
case "fireCube":
OnFire(context);
break;
}
};
}
public void OnFire(InputAction.CallbackContext context)
{
switch (context.phase)
{
case InputActionPhase.Performed:
Debug.Log("c#Event performed:value="+context.ReadValue<float>());
break;
case InputActionPhase.Canceled:
Debug.Log("c#Event canceled:value="+context.ReadValue<float>());
break;
case InputActionPhase.Started:
Debug.Log("c#Event start:value="+context.ReadValue<float>());
break;
}
}
当然你也可以不用PlayerInput的事件,直接用InputSystem、ActionMap或者Action注册回调。
class MyPlayerInputScript : MonoBehaviour
{
private void Awake()
{
// 需要访问PlayerInput组件和相关的Action
PlayerInput playerInput = GetComponent<PlayerInput>();
InputAction hit = playerInput.actions["Fire"];
// 手动注册回调函数
hit.started += OnFireStarted;
hit.performed += OnFirePerformed;
hit.canceled += OnFireCanceled;
}
void OnFireStarted(InputAction.CallbackContext context)
{
var v = context.ReadValue<float>();
Debug.Log(string.Format("Fire Started:{0}", v));
}
void OnFirePerformed(InputAction.CallbackContext context)
{
var v = context.ReadValue<float>();
Debug.Log(string.Format("Fire Performed:{0}", v));
}
void OnFireCanceled(InputAction.CallbackContext context)
{
var v = context.ReadValue<float>();
Debug.Log(string.Format("Fire Canceled:{0}", v));
}
}
Invoke Unity Event方式,和原生c#逻辑是类似的,只不过是用UI操作代替code罢了。因为需要UI操作,所以用了Unity Event,在PlayerInput组件引入ActionEvent[] m_ActionEvents
即可,Inspector中就会多出Events列表,对ActionAsset中每个action都可以注册一个回调方法,注册的方法是
前一个候选框选择方法所在的对象(或对象上的任意组件,不能单是脚本!)
选择方法所在的脚本,并选择此方法
这种behavior的回调方法也可以带CallbackContext参数,使用和c#event相同:
public void OnFire(InputAction.CallbackContext context)
{
if(context.phase == InputActionPhase.Performed)
{
Debug.Log("c#Event performed:value="+context.ReadValue<float>());
}
}
sendMessage / boardcast
这种方式虽然使用简单,但需要搜索一个潜在的庞大组件列表,以找到那些包含匹配方法的组件,这引入了大量的开销。更糟糕的是,由于它们使用字符串作为方法名称,因此它们使用反射来标识匹配的方法。在这种情况下,反射是在运行时与类型系统交互和修改类型系统的能力,但通过反射调用方法比以正常方式调用方法慢。如果你使用一次或两次反射,这很好,但如果你经常使用,那么这些小的性能影响就会加起来。不仅如此,由于所有这些都发生在运行时,因此根本没有编译时错误检查。这使得方法名称中的拼写错误等小错误更容易需要很长时间才能调试。 (参见: Unity Tips | Part 7 - Events and Messaging)
PlayerInput的输入触发通知的本质就是给Action的三个事件performed、canceled、started
注册回调,只不过差别在,C#Event需要手动通过代码方式注册,而sendMessage 用的约定、UnityEvent用的UI绑定默认注册罢了。
总的来说:sendMessage 最方便,但性能最差、灵活性不佳;UnityEvent最直观,性能一般;C#Event 最灵活、性能最好,但操作稍麻烦。根据需要选择适合的Behavior。