Пишу простой tweener на Unity. Движок предоставляет классы (компоненты) для рендера, и у тех, что я использую есть свойство color, но эти классы не наследуются от чего то, что имеет это свойство и не реализуют единого интерфейса, что обязывал бы реализовывать свойство цвета. В итоге мне приходиться писать повторяющийся код:
foreach (var mesh in texts)
{
Color delta = (endColor - mesh.color) / frames;
for (int i = 0; i < frames; i++)
{
mesh.color += delta;
yield return new WaitForEndOfFrame();
}
}
foreach (var mesh in images)
{
Color delta = (endColor - mesh.color) / frames;
for (int i = 0; i < frames; i++)
{
mesh.color += delta;
yield return new WaitForEndOfFrame();
}
}
foreach (var mesh in sprites)
{
Color delta = (endColor - mesh.color) / frames;
for (int i = 0; i < frames; i++)
{
mesh.color += delta;
yield return new WaitForEndOfFrame();
}
}
Казалось бы, очевидным решением было бы использовать dynamic тип, но как известно Microsoft немного накосячили и у нас в .Net Framework 4.0 кривой RuntimeBinder и использовать dynamic тип я не могу (так-то могу, но это затратно по ресурсам и времени написания и вообще это всё были бы костыли). Так же не хочу использовать явное приведение внутри цикла, так как это более нечитаемый код и менее эффективно по ресурсам, хоть и ненамного.
Так как же всё таки сократить это?
Полный код функции:
public static GameObject Paint(this GameObject obj, Color endColor, int frames, float wait = float.NaN)
{
IEnumerator AwaitPaint()
{
var texts = new List<TextMesh>();
var images = new List<Image>();
var sprites = new List<SpriteRenderer>();
void AddComponents()
{
var childrens = new List<GameObject>();
void AddChildrens(GameObject _obj)
{
var newChildrens = new List<GameObject>();
foreach (Transform tr in _obj.transform)
{
childrens.Add(tr.gameObject);
newChildrens.Add(tr.gameObject);
}
foreach (GameObject __obj in newChildrens.ToArray())
{
AddChildrens(__obj);
}
}
AddChildrens(obj);
foreach (var text in obj.GetComponents<TextMesh>())
{
texts.Add(text);
}
foreach (var image in obj.GetComponents<Image>())
{
images.Add(image);
}
foreach (var sprite in obj.GetComponents<SpriteRenderer>())
{
sprites.Add(sprite);
}
}
AddComponents();
if (!float.IsNaN(wait))
{
yield return new WaitForSeconds(wait);
}
foreach (var mesh in texts)
{
Color delta = (endColor - (Color)mesh.color) / frames;
for (int i = 0; i < frames; i++)
{
mesh.color += delta;
yield return new WaitForEndOfFrame();
}
}
foreach (var mesh in images)
{
Color delta = (endColor - (Color)mesh.color) / frames;
for (int i = 0; i < frames; i++)
{
mesh.color += delta;
yield return new WaitForEndOfFrame();
}
}
foreach (var mesh in sprites)
{
Color delta = (endColor - (Color)mesh.color) / frames;
for (int i = 0; i < frames; i++)
{
mesh.color += delta;
yield return new WaitForEndOfFrame();
}
}
called.Remove(called.Same(obj));
}
if (called.FindIndex((x) => x.obj == obj) >= 0)
{
CoroutineContainer.Stop(called.Same(obj).coroutine);
called.Remove(called.Same(obj));
}
called.Add((obj, CoroutineContainer.Start(AwaitPaint())));
return obj;
}
Вызов:
gameObject.Paint(new Color(1, 1, 1, 1), 30);
Есть такая замечательная вещь - паттерн проектирования "Адаптер", который позволяет совместить несовместимое.
Помимо того, что это подход сильно упростит метод, он также позволит поддерживать не только указанные в вопросе компоненты, но в целом любые компоненты, которые так или иначе имеют какой-то изменяемый цвет, написав соответствующий адаптер.
Для начала нужно определиться, что нам вообще нужно получить от всех этих объектов:
Для чего реализуем интерфейс:
public interface IColorChanger {
void ChangeColor(Color delta);
Color GetColor();
}
Реализуем этот интерфейс для конкретного типа TextMesh
(все остальное в самом конце):
public class TextMeshColorAdapter : IColorChanger {
private TextMesh _target;
public TextMeshColorAdapter(TextMesh target) {
_target = target;
}
public void ChangeColor(Color delta) {
_target.color += delta;
}
public Color GetColor() {
return _target.color;
}
}
Не совсем относится к вопросу, но решил и это исправить:
Не стоит писать свои велосипеды по рекурсивному поиску всех потомков объекта, метод GetComponentsInChildren
сделает все сам.
Адаптеры разных компонентов можно объединить в один список и вместо кучи циклов сделать парочку LINQ запросов:
var targets = new List<IColorChanger>();
targets.AddRange(
obj.GetComponentsInChildren<TextMesh>().Select(txt => new TextMeshColorAdapter(txt))
);
targets.AddRange(
obj.GetComponentsInChildren<Image>().Select(img => new ImageColorAdapter(img))
);
targets.AddRange(
obj.GetComponentsInChildren<SpriteRenderer>().Select(sprite => new SpriteRendererColorAdapter(sprite))
);
Благодаря общему интерфейсу также можно один раз пройтись по общему массиву и поменять цвет каждого элемента:
foreach (var target in targets) {
var deltaColor = (endColor - target.GetColor()) / frames;
for (int i = 0; i < frames; i++)
{
target.ChangeColor(deltaColor);
yield return new WaitForEndOfFrame();
}
}
Адаптеры
public interface IColorChanger {
void ChangeColor(Color delta);
Color GetColor();
}
public class TextMeshColorAdapter : IColorChanger {
private TextMesh _target;
public TextMeshColorAdapter(TextMesh target) {
_target = target;
}
public void ChangeColor(Color delta) {
_target.color += delta;
}
public Color GetColor() {
return _target.color;
}
}
public class ImageColorAdapter : IColorChanger {
private Image _target;
public ImageColorAdapter(Image target) {
_target = target;
}
public void ChangeColor(Color delta) {
_target.color += delta;
}
public Color GetColor() {
return _target.color;
}
}
public class SpriteRendererColorAdapter : IColorChanger {
private SpriteRenderer _target;
public SpriteRendererColorAdapter(SpriteRenderer target) {
_target = target;
}
public void ChangeColor(Color delta) {
_target.color += delta;
}
public Color GetColor() {
return _target.color;
}
}
Метод
public static GameObject Paint(this GameObject obj, Color endColor, int frames, float wait = float.NaN)
{
IEnumerator AwaitPaint()
{
var targets = new List<IColorChanger>();
targets.AddRange(obj.GetComponentsInChildren<TextMesh>().Select(txt => new TextMeshColorAdapter(txt)));
targets.AddRange(obj.GetComponentsInChildren<Image>().Select(img => new ImageColorAdapter(img)));
targets.AddRange(obj.GetComponentsInChildren<SpriteRenderer>().Select(sprite => new SpriteRendererColorAdapter(sprite)));
if (!float.IsNaN(wait))
{
yield return new WaitForSeconds(wait);
}
foreach (var target in targets) {
var deltaColor = (endColor - target.GetColor()) / frames;
for (int i = 0; i < frames; i++)
{
target.ChangeColor(deltaColor);
yield return new WaitForEndOfFrame();
}
}
called.Remove(called.Same(obj));
}
if (called.FindIndex((x) => x.obj == obj) >= 0)
{
CoroutineContainer.Stop(called.Same(obj).coroutine);
called.Remove(called.Same(obj));
}
called.Add((obj, CoroutineContainer.Start(AwaitPaint())));
return obj;
}
Можно написать универсальный обработчик типа
static public IEnumerator ColorForList<T> (this List<T> list, Color endColor, float frames) {
foreach (var element in list) {
Color delta = (endColor-element.GetColor())/frames;
for (int i = 0; i < frames; i++) {
element.SetColor(delta);
yield return new WaitForEndOfFrame();
}
}
}
static Color GetColor<T> (this T someType) { ... }
static Color SetColor<T> (this T someType, Color color) { ... }
Чтобы потом просто вызвать
StartCoroutine(texts.ColorForList(endColor, frames));
StartCoroutine(images.ColorForList(endColor, frames));
StartCoroutine(sprites.ColorForList(endColor, frames));
ну, в принципе, можна попробовать так
способ крайне экзотический
да и кода кажись больше вышло, аахахахахахахахахахха
но всё же )
но лучше оставь своё, это тот случай когда повтор кода уместен
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
class S
{
delegate void VoidObjectColor(object o, Color c);
delegate Color ColorObject(object o);
delegate void VoidColorInt(Color c, int n);
VoidColorInt dgFinished;
VoidObjectColor dgColorChanged;
ColorObject dgGetColor;
List<TextMesh> texts;
List<Image> images;
List<SpriteRenderer> sprites;
/// <summary>
/// к этому обьекту присоединим сопрограммы
/// </summary>
MonoBehaviour behaviour;
public static GameObject Paint(this GameObject obj, Color endColor,
int frames, float wait = float.NaN)
{
//
// ну вот , вызываешь, все анимации затем выполнятся автоматически
//
TextAnimations(endColor, frames);
}
IEnumerator ChangeColorS(IEnumerator ie, Color targetColor, int fframes)
{
//
// желательно
// но не обязательно
//
if (ie == null) yield break;
ie.Reset();
//////
Color color, deltaColor;
while(ie.MoveNext())
{
color = dgGetColor(ie.Current);
deltaColor = (targetColor - color) / fframes;
for(int i = 0; i < fframes; i++)
{
color += deltaColor;
dgColorChanged(ie.Current, color);
yield return null;
}
}
if(dgFinished != null) dgFinished(targetColor, fframes);
}
void TextAnimations(Color targetColor, int frames)
{
dgGetColor = (oobject) => ((TextMesh)oobject).color;
dgColorChanged = (oobject, color) => ((TextMesh)oobject).color = color;
dgFinished = ImageAnimations;
behaviour.StartCoroutine(
ChangeColorS(texts.GetEnumerator(), targetColor, frames));
}
void ImageAnimations(Color targetColor, int frames)
{
dgGetColor = (oobject) => ((Image)oobject).color;
dgColorChanged = (oobject, color) => ((Image)oobject).color = color;
dgFinished = SpriteAnimations;
behaviour.StartCoroutine(
ChangeColorS(images.GetEnumerator(), targetColor, frames));
}
void SpriteAnimations(Color targetColor, int frames)
{
dgGetColor = (oobject) => ((SpriteRenderer)oobject).color;
dgColorChanged = (oobject, color) => ((SpriteRenderer)oobject).color = color;
dgFinished = null;
behaviour.StartCoroutine(
ChangeColorS(sprites.GetEnumerator(), targetColor, frames));
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Как в laravel сделать если не авторизован пользователь и вызвать Auth::user()->id; то возвращается ошибка Trying to get property of non-object как ее заменить на свое...
Подскажите, пожалуйста, есть ли возможность задать условие когда определенный атрибут данного товара равен чему-то, то вывести изображение,...
И выдает собака: ok ok ok ok ok ok ok ok ok ok ok ok ok okNONONONONONONONONONONO
Использую плагин ACF для настройки вывода пользовательского контента через произвольные поляПомогите, пожалуйста, с решением задачи — вывод...