Сохранен 32
https://2ch.hk/gd/res/619199.html
Прошлые домены не функционирует! Используйте адрес ARHIVACH.VC.
24 декабря 2023 г. Архивач восстановлен после серьёзной аварии. К сожалению, значительная часть сохранённых изображений и видео была потеряна. Подробности случившегося. Мы призываем всех неравнодушных помочь нам с восстановлением утраченного контента!

стейт-машина для мелких проектов на unity

 Аноним 23/10/19 Срд 18:43:18 #1 №619199 
hqdefault.jpg
Есть тут те кто дрочил простейшую стейт-машину на Unity? Я чего-то охуел от того какой это рак. Суть вот в чём. Замутил простой проект в котором с центре сцены куб. У куба есть компонент MonoBehaviour у которго 3 приватных переменных типа Color (понятное дело триколор), но они в [SerializeField] чтоб можно было устанавливать из инспектора.


Суть задачи в том чтоб куб находился постоянно в одном из трёх стояний: белый (элегантный), красный (красивый) или синий (свежий).

При входе в состояние выполняется какая-то логика. Между какими состояниями не важно, просто при переходе в элегантное состояние куб, например, перемещается немного влево.

Дальше состояние обрабатывается в каждом кадре (пердёж в консоль)

При выходе из состояния тоже выполняется логика


Что я нашёл so far.
Можно сделать 3 функции OnStateEnter, OnStateExit и OnStateUpdate и делать в них switch/case как полный, блядь, обмудок. Если добавишь ещё состояний 5, то получишь колбасу кода в миллион строк. Это не элегантно, не свежно и не особенно красиво.

Можно нахуярить FSM на миллион строк (в 50 раз больше чем сама задача) и прописывать всё в отдельных файлах. Один большой "бонус" в том что если ты хочешь чтоб твоё состояние "элегантный" знало какой у него цвет, то будь добр сделай из приватных переменных публичные всем на посмешище. Ну и конечно в стейтах не забывай делать GetComponent от того компонента что эти цвета держат.


Отсюда вопрос: эт чего действительно такой рак и нет никакого паттерна попроще для решения довольно тривиальной задачи? Я видел как один дурачок удалял компоненты с GO и приделывал новые. Но мне бы хотелось услышать варианты решения от разумных людей
Аноним 23/10/19 Срд 21:14:39 #2 №619220 
>>619199 (OP)
Да проще все ифами захуячить, мне кажется.
Аноним 23/10/19 Срд 23:40:43 #3 №619260 
>>619199 (OP)
Некоторые пользуются встроенным в animator конечным автоматом. Состояниям не обязательно присваивать анимации, и можно хуярить только логику. Не идеально, но для простых задач походит.
Аноним 24/10/19 Чтв 07:43:17 #4 №619332 
>>619260
Да, эту схему тоже пробовал. Из плюсов это то что всё визуально и ты в любой момент можешь поставить на паузу и посмотреть в каком стейте объект. Ещё не обязательно самому включать стейты, а достаточно передавать данные об объекте и если параметры стейтов настроены верно, то переключение произойдёт само.

НО! Здесь проблема в том что стейты ничего не знают о том кто их вызвал. Им в параметрах передаётся инстанс аниматора и ещё две ненужные херни. От инстенса аниматора можно получить GO и с него делать GetComponent. Но чтоб получить значения компонента (в моём случае это опять же цвета) их опять же нужно делать публичными. Мне не нужно иметь возможность менять их из других классов
Аноним 24/10/19 Чтв 08:49:38 #5 №619335 
>>619199 (OP)
> Можно сделать 3 функции OnStateEnter, OnStateExit и OnStateUpdate и делать в них switch/case как полный, блядь, обмудок.
А можно сделать четвёртую функцию SetState(State newState), которая выполняет несколько простых действий:
1. Берет активное состояние объекта. Состояние, допустим, это структ со ссылками на методы.
2. Отписывает если подписана функцию OnStateUpdate от реального Update.
3. Вызывает OnStateExit старого состояния.
4. Меняет состояние.
5. Вызывает OnStateEnter нового состояния.
И усё. Никакие ифы и свитчи не нужны.
Граф переходов между состояниями описан отдельно и является данными, но не логикой.
Аноним 24/10/19 Чтв 08:51:45 #6 №619336 
>>619335
Падажжи, второй пункт нинужна. Апдейт будет просто дёргать апдейт-функцию актуального стейта. Никакие подписки тоже ненужна.
Аноним 24/10/19 Чтв 09:27:02 #7 №619342 
imposibru.jpg
>>619335
> Берет активное состояние объекта. Состояние, допустим, это структ со ссылками на методы.

Это значит что у меня на 3 состояния должно быть условно 9 методов прописано в том же классе. Это не сильно отличается от switch/case или if'ов. Те же проблемы. Очень много кода который лучше всего отделить.
Реализовать говнокодом проблем нет, у меня он изначально уже готов и работает говнокодом. Идеально если решение будет подходить под 3 пункта:

1. каждый стейт куба это отдельный класс
2. стейт имеет доступ к приватным переменным куба (это исключительно чтоб нельзя было извне менять значения кубу)
3. стейт можно переключать как из куба, так и из самого стейта

Из того что я знаю это не очень возможно хотя бы по той причине что переменные приватны и следовательно один стейт не может знать о существовании другого, и, как итог, на него переключиться. Но я надеюсь что я тупой и что за 60 лет существования геймдева такие тривиальные задачи научились решать красиво
Аноним 24/10/19 Чтв 09:45:48 #8 №619344 
>>619342
>>619335
> Состояние, допустим, это структ со ссылками на методы.
Ну тогда делай не структ, а класс.
Только тогда у тебя будет:
>>619199 (OP)
> Я видел как один дурачок удалял компоненты с GO и приделывал новые.
Почему? Потому что эти стейты станут именно што полноценными компонентами.
Аноним 24/10/19 Чтв 11:25:26 #9 №619357 
>>619344
> Почему?
Потому что он добавлял компонент, а потом в него запихивал все необходимые данные типа:

newComp.a = oldComp.a;
newComp.b = oldComp.b;

а потом ещё и в новом компоненте делал GetComponent на всё остальное. Нужно чтоб такие вещи кэшировались.

И нет смысла делать стейты компонентами, только самостоятельными классами
Аноним 24/10/19 Чтв 11:43:25 #10 №619360 
>>619357
Нет смысла делать стейты классами. Делай их структурами.
Аноним 24/10/19 Чтв 12:59:52 #11 №619370 
>>619199 (OP)
Нахуя тебе стейтмашина, ебашь сразу behavior tree да и все.
Аноним 24/10/19 Чтв 13:00:55 #12 №619371 
>>619370
И вообще читни gameprogrammingpatterns для начала.
Аноним 24/10/19 Чтв 13:04:47 #13 №619373 
>>619342
> на 3 состояния должно быть условно 9 методов
Это фундаментальная проблема простых fsm, никуда от нее не деться без иерархических стейтмашин или деревьев.
Аноним 24/10/19 Чтв 13:28:20 #14 №619378 
>>619371
> читни gameprogrammingpatterns для начала
Я то почитал, но было бы отлично если б ты конкретно указал какую идею оттуда упустил


>>619373
> Это фундаментальная проблема простых fsm
Меня не беспокоит много методов, меня беспокоит что мой класс становитмя жирным и я бы их отделил

>не деться без иерархических стейтмашин или деревьев
Если знаешь как лучше всего гуглить или где конкретно читать в максимально базовом виде, то буду благодарен
Аноним 24/10/19 Чтв 18:13:47 #15 №619435 
1571929985795.png
>>619378
> мой класс становитмя жирным и я бы их отделил
Куда класс денется без методов "писоть, какоть, кушоть"?
Аноним 25/10/19 Птн 09:52:56 #16 №619539 
Сделал схему со struct по рекомендациям. Прошу её на обсуждение. Это ли имелось в виду?

public struct CharacterState
{
public delegate void CharacterEvent();

public CharacterEvent OnEnter;
public CharacterEvent OnUpdate;
public CharacterEvent OnExit;

public CharacterState(CharacterEvent OnEnter = null, CharacterEvent OnUpdate = null, CharacterEvent OnExit = null)
{
this.OnEnter = OnEnter;
this.OnUpdate = OnUpdate;
this.OnExit = OnExit;
}
}

public class Character : MonoBehaviour
{
private new Renderer renderer;

private CharacterState currentState;

private CharacterState idleState;
private CharacterState angryState;
private CharacterState scaredState;

[Header("State colors")]
[SerializeField] private Color idleColor;
[SerializeField] private Color angryColor;
[SerializeField] private Color scaredColor;

private void Start()
{
renderer = GetComponent<Renderer>();

idleState = new CharacterState(OnEnter: IdleEnter);
angryState = new CharacterState(OnEnter: AngryEnter);
scaredState = new CharacterState(OnEnter: ScaredEnter);

SwitchState(angryState);
}

private void Update()
{
currentState.OnUpdate?.Invoke();
}

private void SwitchState(CharacterState newState)
{
currentState.OnExit?.Invoke();
currentState = newState;
currentState.OnEnter?.Invoke();
}

private void IdleEnter()
{
renderer.material.color = idleColor;
}

private void AngryEnter()
{
renderer.material.color = angryColor;
}

private void ScaredEnter()
{
renderer.material.color = scaredColor;
}
}

Есть ли какие-нибудь рекомендации по улучшению?
Аноним 25/10/19 Птн 12:51:24 #17 №619555 
Тред не читал, но в чем проблема использовать паттерн статус?
Аноним 25/10/19 Птн 12:55:50 #18 №619557 
https://gameprogrammingpatterns.com/state.html
Аноним 25/10/19 Птн 12:56:15 #19 №619558 
>>619555
Пардон, состояние. Ссылка выше
Аноним 25/10/19 Птн 14:41:02 #20 №619579 
>>619555
>>619557
>>619558
Помимо того что ответ на это находится в OP, дык я ещё и отвечал на него.

Лучше сделайте code review >>619539
Жаль отступов только не сохранилось
Аноним 25/10/19 Птн 17:49:34 #21 №619601 
>>619579
В оппосте я видел что-то про свич и дальше не читал. Я предлагаю сделать по классу для каждого состояния, который дальше уже будет производить актуальные действия
Аноним 25/10/19 Птн 18:29:15 #22 №619604 
>>619579
Если у тебя увеличится количество состояний или действий, завязанных на состояния, ты охуеешь.
Короче: создаёшь общий для всех статусов интерфейс, в классе куба создаёшь поле с этим типом. Создаёшь класс для каждого состояния, которые реализуют интерфейс.
Когда нужно поменять состояние, просто создаёшь экземпляр конкретного класса, и берешь из него цвет для рендера. Не надо для каждого перехода в статус отдельный метод создавать
Аноним 25/10/19 Птн 19:24:35 #23 №619612 
>>619604
> Когда нужно поменять состояние, просто создаёшь экземпляр конкретного класса, и берешь из него цвет для рендера

Что значит беру ИЗ него? То о чём ты говоришь не работает потому что экзеспляр класса состояния не знает переменных того экземпляря который это состояние носит. У меня IdleState не знает в какой цвет ему менять. Или ты где-то пропустил какой-то момент в этой схеме или я чего-то не понимаю
Аноним 25/10/19 Птн 19:34:45 #24 №619614 
>>619612
А, вот в чем трабла, эт я затупил. Ну классам статусов нужна ссылка на экземпляр объекта. Можно пихать в конструктор, но лучше реализовать, как тут https://m.habr.com/ru/post/281783/
Если я верно понимаю, то ты все равно создаёшь по три метода на каждый стэйт - лучше держать методы в классах стэйтов, а данные брать из модели куба(ну или экземпляра класса, который их вызывает в простом случае).
Аноним 26/10/19 Суб 19:54:38 #25 №619715 
Оп, ты решил проблему или что там?
Аноним 28/10/19 Пнд 16:05:04 #26 №620049 
>>619614
Кудос за вброс, обязательно ознакомлюсь

>>619715
Сегодня ночью буду пробовать всё что тут описали, обязательно отпишусь. Пока завалы рабочие нет возможности вернуться к проекту
Аноним 29/10/19 Втр 00:07:36 #27 №620140 
>>620049
А кем работаешь?
Аноним 30/10/19 Срд 20:04:20 #28 №620295 
>>620140
full stack


>обязательно отпишусь
В общем я попробовал все варианты что здесь и ни один из них не выглядит хоть как либо нормально. У каждого какая-то кривизна.

Со struct на маленьком проекте получилось хорошо, но когда перешёл в проект побольше, то 5 состояний у меня сожрало очень много строк и выглядело невозможно криво

С MVC получилась огромная мешанина кода где на 3 события мне пришлось делать и отдельные файлы и контроллер для каждого типа персонажа. Каждая модель при инициализации делает GameObject.FindObjectOfType<КлассКонтроллера>() что настоящая задница. Особенно учитывая что жалуется в той статье человек на безобидный, по сравнению с FindObjectOfType, GetComponent. Можно конечно это решить при помощи синглтонов и factory, но это самый настоящий overkill для такой простой задачи.

Тем временем открыл FPS Microgame и Creator Kit и посмотрел как у них реализован AI. И везде они делают enum и switch и прописывают в один длинный файл все события при всех возможных стейтах. Выглядит ужасно
Аноним 30/10/19 Срд 21:29:09 #29 №620303 
>>620295
Выглядит может и ужасно, зато, скорее всего хорошо читается и работает. Говна в коде не избежать, поэтому лучше обходиться читабельным говном
Аноним 31/10/19 Чтв 13:43:59 #30 №620379 
>>620295
Ну а если без Мвк иметь классы статусов и передавать им вызывающий объект (this) из которого они будут тянуть значения, заданные в инспекторе?
Аноним 01/11/19 Птн 14:07:01 #31 №620586 
>>620379

this передаст инстенс этого объекта. private переменные будут недоступны
Аноним 02/11/19 Суб 17:50:36 #32 №620710 
>>620586
Сделай публичные свойства (геттеры)
comments powered by Disqus