C# универсальный с небольшой разницей для типов?
Обратите внимание на два расширения: одно для float, другое для Vector3.
Обратите внимание, что есть только небольшая разница в var(
вызов.
В C# они могут быть записаны как один как универсальный??
Суть моего вопроса такова:
в общем, вы можете перейти на природу типа?
public static IEnumerator Tweeng( this float duration,
System.Action<float> vary, float aa, float zz )
{
float sT = Time.time;
float eT = sT + duration;
while (Time.time < eT)
{
float t = (Time.time-sT)/duration;
vary( Mathf.SmoothStep(aa,zz, t) ); // slight difference here
yield return null;
}
vary(zz);
}
public static IEnumerator Tweeng( this float duration,
System.Action<Vector3> vary, Vector3 aa, Vector3 zz )
{
float sT = Time.time;
float eT = sT + duration;
while (Time.time < eT)
{
float t = (Time.time-sT)/duration;
vary( Vector3.Lerp(aa,zz, t) ); // slight difference here
yield return null;
}
vary(zz);
}
(Кстати, для любого чтения гуру C# пример кода в Unity, где вы получаете доступ к системе кадров в сопрограмме.)
Для любого читателя Unity, примеры того, как вы называете Tweeng
// tweeng z to 20 degrees in .12 seconds
StartCoroutine(.12f.Tweeng( (t)=>transform.Eulers(0f,0f,t), 0f,20f) );
// fade in alpha in .75 seconds
StartCoroutine(.75f.Tweeng( (u)=>{c.a=u;s.color=c;}, 0f,1f) );
(Если вы новичок в Unity и не знакомы с основной концепцией расширений, вот вступление.)
3 ответа
Вы можете сделать это, если вы делаете дополнительный Func<T,T>
который выполняет преобразование перед вызовом var
действие (которое вы должны переименовать, потому что var
является ключевым словом C#).
Вот один из подходов, который вы могли бы использовать:
public static IEnumerator Tweeng<T>(
this float duration
, System.Action<T> varAction
, T aa
, T zz
) {
Func<T,T,float,T> transform = MakeTransform<T>();
float sT = Time.time;
float eT = sT + duration;
while (Time.time < eT) {
float t = (Time.time-sT)/duration;
varAction(transform(aa, zz, t));
yield return null;
}
varAction(zz);
}
private static Func<T,T,float,T> MakeTransform<T>() {
if (typeof(T) == typeof(float)) {
Func<float, float, float, float> f = Mathf.SmoothStep;
return (Func<T,T,float,T>)(Delegate)f;
}
if (typeof(T) == typeof(Vector3)) {
Func<Vector3, Vector3, float, Vector3> f = Vector3.Lerp;
return (Func<T,T,float,T>)(Delegate)f;
}
throw new ArgumentException("Unexpected type "+typeof(T));
}
Это может быть даже сделано inline:
public static IEnumerator DasTweeng<T>( this float duration, System.Action<T> vary, T aa, T zz )
{
float sT = Time.time;
float eT = sT + duration;
Func<T,T,float,T> step;
if (typeof(T) == typeof(float))
step = (Func<T,T,float,T>)(Delegate)(Func<float, float, float, float>)Mathf.SmoothStep;
else if (typeof(T) == typeof(Vector3))
step = (Func<T,T,float,T>)(Delegate)(Func<Vector3, Vector3, float, Vector3>)Vector3.Lerp;
else
throw new ArgumentException("Unexpected type "+typeof(T));
while (Time.time < eT)
{
float t = (Time.time-sT)/duration;
vary( step(aa,zz, t) );
yield return null;
}
vary(zz);
}
Возможно, более естественная идиома
Delegate d;
if (typeof(T) == typeof(float))
d = (Func<float, float, float, float>)Mathf.SmoothStep;
else if (typeof(T) == typeof(Vector3))
d = (Func<Vector3, Vector3, float, Vector3>)Vector3.Lerp;
else
throw new ArgumentException("Unexpected type "+typeof(T));
Func<T,T,float,T> step = (Func<T,T,float,T>)d;
Вы можете определить свой метод следующим образом:
public static IEnumerator Tweeng<T>(this float duration,
System.Action<T> var, T aa, T zz, Func<T,T,float,T> thing)
{
float sT = Time.time;
float eT = sT + duration;
while (Time.time < eT)
{
float t = (Time.time - sT) / duration;
var(thing(aa, zz, t));
yield return null;
}
var(zz);
}
А затем с помощью этого:
float a = 5;
float b = 0;
float c = 0;
a.Tweeng(q => {}, b, c, Mathf.SmoothStep);
Или же:
float a = 0;
Vector3 b = null;
Vector3 c = null;
a.Tweeng(q => {}, b, c, Vector3.Lerp);
В качестве альтернативы, если вы хотите избавиться от передачи метода, у вас могут быть простые перегрузки для его обработки:
public static IEnumerator Tweeng(this float duration, System.Action<float> var, float aa, float zz)
{
return Tweeng(duration, var, aa, zz, Mathf.SmoothStep);
}
public static IEnumerator Tweeng(this float duration, System.Action<Vector3> var, Vector3 aa, Vector3 zz)
{
return Tweeng(duration, var, aa, zz, Vector3.Lerp);
}
private static IEnumerator Tweeng<T>(this float duration,
System.Action<T> var, T aa, T zz, Func<T,T,float,T> thing)
{
float sT = Time.time;
float eT = sT + duration;
while (Time.time < eT)
{
float t = (Time.time - sT) / duration;
var(thing(aa, zz, t));
yield return null;
}
var(zz);
}
А затем с помощью этого:
float a = 5;
float b = 0;
float c = 0;
a.Tweeng(q => {}, b, c);
Или же:
float a = 0;
Vector3 b = null;
Vector3 c = null;
a.Tweeng(q => {}, b, c);
Методы-заглушки для компиляции в LINQPad/ без единицы:
public class Mathf { public static float SmoothStep(float aa, float zz, float t) => 0; }
public class Time { public static float time => DateTime.Now.Ticks; }
public class Vector3 { public static Vector3 Lerp(Vector3 aa, Vector3 zz, float t) => null; }
Мне понравилась вещь Tweeng, но зачем расширять float, если Coroutine можно использовать только на MonoBehaviours? Вы должны сделать расширения для MonoBehaviour, например, я сделал расширение для интерполяции:
public static void _Interpolate(this MonoBehaviour monoBehaviour, float duration,
Action<float, bool> callback, float from, float to, Interpolator interpolator)
{
monoBehaviour.StartCoroutine(ExecuteInterpolation(interpolator, duration, callback, from, to));
}
Поэтому я только запустил сопрограмму внутри расширения:
private static IEnumerator ExecuteInterpolation(Interpolator interpolator, float duration,
Action<float, bool> callback, float from, float to)
{
float sT = Time.time;
float eT = sT + duration;
bool hasFinished = false;
while (Time.time < eT)
{
float t = (Time.time - sT) / duration;
// ----> my logic here with callback(to, false)
yield return null;
}
hasFinished = true;
callback(to, hasFinished);
}
Обратите внимание, что у меня есть логическое значение, чтобы сказать, что интерполяция завершена, это происходит потому, что не рекомендуется полагаться на сравнение с плавающей точкой для проверки конца потока, если он округляет для максимального результата результат до того, как мы получим последний результат. перезвоните для последнего взаимодействия, вызванного дважды.