301 lines
9.8 KiB
C#
301 lines
9.8 KiB
C#
// (c) copyright Hutong Games, LLC 2010-2012. All rights reserved.
|
|
|
|
using System;
|
|
using System.Reflection;
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using Object = UnityEngine.Object;
|
|
using HutongGames.PlayMaker;
|
|
|
|
namespace HutongGames.PlayMaker.Actions
|
|
{
|
|
[ActionCategory(ActionCategory.ScriptControl)]
|
|
[Tooltip("Call a method in a component on a GameObject.")]
|
|
public class CallMethod : FsmStateAction
|
|
{
|
|
[ObjectType(typeof(Component))]
|
|
[Tooltip("The behaviour on a GameObject that has the method you want to call. " +
|
|
"Drag the script component from the Unity inspector into this slot. " +
|
|
"HINT: Use Lock if the script is on another GameObject." +
|
|
"\n\nNOTE: Unity Object fields only show the GameObject name, " +
|
|
"so for clarity we show the Behaviour name in a readonly field below.")]
|
|
public FsmObject behaviour;
|
|
|
|
//[UIHint(UIHint.Method)]
|
|
[Tooltip("Name of the method to call on the component")]
|
|
public FsmString methodName;
|
|
|
|
[Tooltip("Method parameters. NOTE: these must match the method's signature!")]
|
|
public FsmVar[] parameters;
|
|
|
|
[ActionSection("Store Result")]
|
|
|
|
[UIHint(UIHint.Variable)]
|
|
[Tooltip("Store the result of the method call.")]
|
|
public FsmVar storeResult;
|
|
|
|
[Tooltip("Repeat every frame.")]
|
|
public bool everyFrame;
|
|
|
|
[Tooltip("Use the old manual editor UI.")]
|
|
public bool manualUI;
|
|
|
|
private FsmObject cachedBehaviour;
|
|
private FsmString cachedMethodName;
|
|
private Type cachedType;
|
|
private MethodInfo cachedMethodInfo;
|
|
private ParameterInfo[] cachedParameterInfo;
|
|
private object[] parametersArray;
|
|
private string errorString;
|
|
|
|
public override void Reset()
|
|
{
|
|
behaviour = null;
|
|
methodName = null;
|
|
parameters = null;
|
|
storeResult = null;
|
|
everyFrame = false;
|
|
}
|
|
|
|
public override void OnEnter()
|
|
{
|
|
parametersArray = new object[parameters.Length];
|
|
|
|
DoMethodCall();
|
|
|
|
if (!everyFrame)
|
|
{
|
|
Finish();
|
|
}
|
|
}
|
|
|
|
public override void OnUpdate()
|
|
{
|
|
DoMethodCall();
|
|
}
|
|
|
|
private void DoMethodCall()
|
|
{
|
|
if (behaviour.Value == null)
|
|
{
|
|
Finish();
|
|
return;
|
|
}
|
|
|
|
if (NeedToUpdateCache())
|
|
{
|
|
if (!DoCache())
|
|
{
|
|
Debug.LogError(errorString);
|
|
Finish();
|
|
return;
|
|
}
|
|
}
|
|
|
|
object result;
|
|
if (cachedParameterInfo.Length == 0)
|
|
{
|
|
result = cachedMethodInfo.Invoke(cachedBehaviour.Value, null);
|
|
}
|
|
else
|
|
{
|
|
for (var i = 0; i < parameters.Length; i++)
|
|
{
|
|
var parameter = parameters[i];
|
|
parameter.UpdateValue();
|
|
if (parameter.Type == VariableType.Array)
|
|
{
|
|
parameter.UpdateValue();
|
|
var objectArray = parameter.GetValue() as object[];
|
|
var realType = cachedParameterInfo[i].ParameterType.GetElementType();
|
|
var convertedArray = Array.CreateInstance(realType, objectArray.Length);
|
|
for (int index = 0; index < objectArray.Length; index++)
|
|
convertedArray.SetValue(objectArray[index], index);
|
|
parametersArray[i] = convertedArray;
|
|
}
|
|
else
|
|
{
|
|
parameter.UpdateValue();
|
|
parametersArray[i] = parameter.GetValue();
|
|
}
|
|
}
|
|
|
|
result = cachedMethodInfo.Invoke(cachedBehaviour.Value, parametersArray);
|
|
}
|
|
|
|
if (storeResult != null && !storeResult.IsNone && storeResult.Type != VariableType.Unknown)
|
|
{
|
|
storeResult.SetValue(result);
|
|
}
|
|
}
|
|
|
|
// TODO: Move tests to helper function in core
|
|
private bool NeedToUpdateCache()
|
|
{
|
|
return cachedBehaviour == null || cachedMethodName == null || // not cached yet
|
|
cachedBehaviour.Value != behaviour.Value || // behavior value changed
|
|
cachedBehaviour.Name != behaviour.Name || // behavior variable name changed
|
|
cachedMethodName.Value != methodName.Value || // methodName value changed
|
|
cachedMethodName.Name != methodName.Name; // methodName variable name changed
|
|
}
|
|
|
|
private void ClearCache()
|
|
{
|
|
cachedBehaviour = null;
|
|
cachedMethodName = null;
|
|
cachedType = null;
|
|
cachedMethodInfo = null;
|
|
cachedParameterInfo = null;
|
|
}
|
|
|
|
private bool DoCache()
|
|
{
|
|
//Debug.Log("DoCache");
|
|
|
|
ClearCache();
|
|
errorString = string.Empty;
|
|
cachedBehaviour = new FsmObject(behaviour);
|
|
cachedMethodName = new FsmString(methodName);
|
|
|
|
if (cachedBehaviour.Value == null)
|
|
{
|
|
if (behaviour.UsesVariable && !Application.isPlaying)
|
|
{
|
|
// Value might be set at runtime
|
|
// Display/Log this info...?
|
|
}
|
|
else
|
|
{
|
|
errorString += "Behaviour is invalid!\n";
|
|
}
|
|
Finish();
|
|
return false;
|
|
}
|
|
|
|
cachedType = behaviour.Value.GetType();
|
|
|
|
var types = new List<Type>(parameters.Length);
|
|
foreach (var each in parameters)
|
|
{
|
|
types.Add(each.RealType);
|
|
}
|
|
|
|
#if NETFX_CORE
|
|
var methods = cachedType.GetTypeInfo().GetDeclaredMethods(methodName.Value);
|
|
foreach (var method in methods)
|
|
{
|
|
if (TestMethodSignature(method, types))
|
|
{
|
|
cachedMethodInfo = method;
|
|
}
|
|
}
|
|
#else
|
|
cachedMethodInfo = cachedType.GetMethod(methodName.Value, types.ToArray());
|
|
#endif
|
|
if (cachedMethodInfo == null)
|
|
{
|
|
errorString += "Invalid Method Name or Parameters: " + methodName.Value + "\n";
|
|
Finish();
|
|
return false;
|
|
}
|
|
|
|
cachedParameterInfo = cachedMethodInfo.GetParameters();
|
|
|
|
return true;
|
|
}
|
|
|
|
#if NETFX_CORE
|
|
private bool TestMethodSignature(MethodInfo method, List<Type> parameterTypes)
|
|
{
|
|
if (method == null) return false;
|
|
var methodParameters = method.GetParameters();
|
|
if (methodParameters.Length != parameterTypes.Count) return false;
|
|
for (var i = 0; i < methodParameters.Length; i++)
|
|
{
|
|
if (!ReferenceEquals(methodParameters[i].ParameterType, parameterTypes[i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
public override string ErrorCheck()
|
|
{
|
|
/* We could only error check if when we re-cache,
|
|
* however NeedToUpdateCache() is not super robust
|
|
* So for now we just re cache every frame in editor
|
|
* Need to test editor perf...
|
|
if (!NeedToUpdateCache())
|
|
{
|
|
return errorString; // last error message
|
|
}*/
|
|
|
|
if (Application.isPlaying)
|
|
{
|
|
return errorString; // last error message
|
|
}
|
|
|
|
if (!DoCache())
|
|
{
|
|
return errorString;
|
|
}
|
|
|
|
if (parameters.Length != cachedParameterInfo.Length)
|
|
{
|
|
return "Parameter count does not match method.\nMethod has " + cachedParameterInfo.Length + " parameters.\nYou specified " + parameters.Length + " paramaters.";
|
|
}
|
|
|
|
for (var i = 0; i < parameters.Length; i++)
|
|
{
|
|
var p = parameters[i];
|
|
var paramType = p.RealType;
|
|
var paramInfoType = cachedParameterInfo[i].ParameterType;
|
|
if (!ReferenceEquals(paramType, paramInfoType))
|
|
{
|
|
return "Parameters do not match method signature.\nParameter " + (i + 1) + " (" + paramType + ") should be of type: " + paramInfoType;
|
|
}
|
|
}
|
|
|
|
if (ReferenceEquals(cachedMethodInfo.ReturnType, typeof(void)))
|
|
{
|
|
if (!string.IsNullOrEmpty(storeResult.variableName))
|
|
{
|
|
return "Method does not have return.\nSpecify 'none' in Store Result.";
|
|
}
|
|
}
|
|
else if (!ReferenceEquals(cachedMethodInfo.ReturnType, storeResult.RealType))
|
|
{
|
|
return "Store Result is of the wrong type.\nIt should be of type: " + cachedMethodInfo.ReturnType;
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public override string AutoName()
|
|
{
|
|
var name = methodName + "(";
|
|
for (int i = 0; i < parameters.Length; i++)
|
|
{
|
|
var param = parameters[i];
|
|
name += ActionHelpers.GetValueLabel(param.NamedVar);
|
|
if (i < parameters.Length - 1)
|
|
{
|
|
name += ",";
|
|
}
|
|
}
|
|
name += ")";
|
|
|
|
if (!storeResult.IsNone)
|
|
{
|
|
name = storeResult.variableName + "=" + name;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
#endif
|
|
}
|
|
}
|