Глава II в серията от уроци за това как да създадете игра от нулата с TypeScript и приложни програмни интерфейси на браузъра

Добре дошъл обратно! Това е поредицата от статии, в които обсъждаме как да създадем проста походова игра с TypeScript и собствени API на браузъра! Глава II е посветена на изграждането на игрови цикъл за тази игра, други глави са достъпни тук:

„Последния път“ завършваме с въпроса: как можем да стартираме Game Loop, без да усложняваме конструктора. Един от подходите, които можем да предприемем, е да го направим „буден“.

Чувствайте се свободни да превключите към клона game-loop-1 на хранилище. Той съдържа работния резултат от предишните публикации и е чудесна отправна точка за тази.

Съдържание

  1. В
  2. Существо се пробужда
  3. Стартиране на цикъла
  4. Вложен обект
  5. Тестване на игровия обект
  6. Заключение

В

Ако си спомняте, постигнахме значителен напредък при настройването на обекта game. Ние дори подготвихме неговия Update метод, който рекурсивно актуализира всички компоненти на Game. Това обаче не е от голяма полза, тъй като никой не прави първоначално повикване, за да започне цикъла!

Също така установихме, че е възможно да стартираме цикъла с помощта на конструктор:

По-добър подход би бил да се осигури специален метод за инициализация. Този метод може да стартира цикъла и да направи много други настройки, докато конструкторът остава слаб. Можем да наричаме този метод както желаем, например Init или Awake. Ще използвам най-новото, за да имитирам Unity3d API.

Существо се пробужда

Всички обекти и компоненти вече ще станат „събудени“. Тоест: те ще имат публичния Awake метод.

Имайте предвид, че този метод на жизнения цикъл няма силна връзка с конструктор. Един обект може да бъде конструиран само един, докато Awake може да бъде изпълнен многократно за продължителността на живота на обекта. Може да „заспи“ и след това да се събуди отново. Един от типичните примери е повторно използване на обекти, за да се избегнат разходите за разпределение на паметта за създаване на обекта (да се чете: изпълняващ конструктор).

Изкушаващо е просто да добавите нов метод към абстрахиране на Entity и интерфейс IComponent. Но точно както Update, Awake може да бъде част от други елементи на нашата игра, не само ECS. По-разумно е да създадете специален IAwake интерфейс и след това да го внедрите.

Интерфейсът е удивително прост. Дори не очакваме да бъдат предадени конкретни данни:

Остава само да го внедрим с IComponent:

и Entity:

Имайте предвид, че този обект събужда всичките си компоненти веднага щом се събуди. Така е, тъй като Update, е поведение по подразбиране. Отделни субекти са свободни да го разширят или променят, ако е необходимо.

Освен това, тъй като така или иначе сме в този квартал, нека да направим малко домакинство. Ще се присъединя към Awake and Update под един помощен модул и ще го нарека „жизнен цикъл“. Ако имаме нужда от още подобни събития, можем да ги добавим тук:

Сега можем да изтрием остарелите update.h.ts и awake.h.ts. Освен това поддържайте необходимите файлове за варели, за да реекспортирате правилно този модул:

И след това актуализирайте потребителите на тези интерфейси:

Ах! Но счупихме тестовете! Това е така, защото не изпълняваме обещанието: „всеки обект и компонент трябва да има Awake метод“. И присмехулниците явно го нямат. Нека поправим това много бързо:

И определено трябва да тестваме Awake. Ще използвам същия подход като този, който използвахме, докато тествахме Update: първо шпионирам съответните методи, добавям фалшиви компоненти към фалшив Entity, изпълнявам метода Awake на обекта и ще очаквам тези на Component да бъдат извикани също:

Ако стартирате npm start в този момент, вашият код трябва да се компилира без грешки. Ако стартирате тестове от npm t, всички те също трябва да са успешни:

Стартиране на цикъла

С всичко това на място можем да се възползваме от нов метод на жизнения цикъл в обекта Game:

Веднага щом играта и всички нейни компоненти се събудят, започваме цикъла на играта:

Едно малко подобрение: искам да се уверя, че цикълът на играта не започва, преди всички компоненти и дъщерни обекти да се събудят. Правя го, като забавям актуализацията до следващия кадър:

Вложен обект

Казах ли „дъщерни обекти?“. Точно така: Играта е root Entity, но не е единствената. Това е много удобен начин да имате обекти, организирани в йерархията. Освен всичко друго, това ни позволява да актуализираме всички тези деца от играта, като извикаме Update на всяко едно от тях.

Ще започнем да добавяме дъщерни обекти в следващата глава, но засега нека просто да настроим етап и да се уверим, че те ще бъдат актуализирани.

Добавям публично свойство, което съдържа масив от всички дъщерни обекти:

Като следим всички дъщерни обекти, можем лесно да извикаме всички методи на жизнения цикъл на тях. Освен това някои от тях (или дори всички!) може да имат свои деца и да извикат Awake/Update и на тях.

Първо, трябва да събудим всички деца и да направим това, преди да започнем цикъла:

И след това ги актуализирайте на всяка итерация:

И сега най-накрая имаме напълно функциониращ Game Loop, който повтаря всеки кадър и актуализира всички обекти и всички компоненти.

Последно докосване: нека стартираме този двигател, като инстанцираме и събудим самата игра:

Страхотно! Ако стартирате кода си, като изпълните npm start, той трябва да се компилира без проблеми.

Тестване на игровия обект

Все още нямаме визуализации, които да докажат, че сме успели в мисията си, така че тестът на модула е единствената надежда.

За щастие, тестването на обекта Game в този момент е лесно. Има пет неща, които трябва да тестваме:

И за да направим това, трябва да направим няколко приготовления. Първо, нека настроим фалшиви деца и компоненти:

Създадох и инстанцирах компоненти, точно както направих за теста за Entity в предишната глава. Също така създадох празни обекти, инстанцирах ги и ги прикачих към играта.

Използваме requestAnimationFrame в играта. Това е асинхронно обратно извикване и трябва да му се подиграваме правилно. Един от начините да направите това е да го замените с jest.fn и да се уверите, че извиква обратно извикване веднага:

Това ще ми позволи да тествам дали цикълът за актуализиране работи правилно:

Тук просто шпионирам game.Update, събуждам играта и очаквам game.Update наистина да бъде изпълнена.

Тестването на останалите четири сценария е доста подобно на тестването на Entity, което направихме в предишната глава:

Започвам, като шпионирам Awake или Update на всяко дете/компонент и очаквам те да бъдат извикани съответно след game.Awake или game.Update.

Защо да тестваме Awake/Update на компоненти? Вече тествахме същото нещо върху абстрактно предприятие. Защо да се повтаря?

Причината е, че можем да заменим тази функционалност в Awake/Update на играта. Не забравяйте, че абстрактният Entity предоставя само поведение по подразбиране и ако отменим метода (както направихме), тогава трябва да сме сигурни, че не губим нищо.

Ако стартирате тестовете си с npm t, всички те трябва да са успешни:

Можете да намерите пълния изходен код на тази публикация в клонаgame-loop-2 на хранилище.

Заключение

Трябва да се гордеете със себе си, постигнахте МНОГО: научихте за Game Loop и как се играе в един отбор с Entity и Components, как да изпълнявате код на всеки кадър, открихте методите на жизнения цикъл на Entity и как да ги използвате с Game Loop и, разбира се, да изградите основния Game Entity и да го покриете с тестове! Удивителен напредък!

„Следващия път“ най-накрая ще нарисуваме нещо на екрана и ще видим красотата на компонентите в действие.

Ако имате коментари, предложения, въпроси или други отзиви, не се колебайте да ми изпратите лично съобщение или да оставите коментар по-долу! Благодаря ви за четенето и ще се видим следващия път!

Това е глава II от поредицата от уроци „Създаване на игра с TypeScript“. Други глави са достъпни тук:

Кодиране на ниво нагоре

Благодарим ви, че сте част от нашата общност! Абонирайте се за нашия канал в YouTube или се присъединете към курса за интервю за кодиране Skilled.dev.