Back to Question Center
0

Процедурно созданный игровой ландшафт с реакциями, PHP и WebSockets            Процедурно созданный игровой ландшафт с реакциями, PHP и WebSocketsRelated Topics: FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt

1 answers:
Обработанный игровой ландшафт с реакцией, PHP и WebSockets

Разработка игр с PHP и ReactJS

  • Разработка игр с помощью React и PHP: насколько они совместимы?
  • Обработанный игровой ландшафт с реакцией, PHP и WebSockets

Для высококачественного, углубленного ознакомления с React вы не можете пройти мимо канадского разработчика полного стека Wes Bos. Попробуйте свой курс здесь и используйте код SITEPOINT , чтобы получить 25% скидку и поддержать SitePoint.

В прошлый раз я начал рассказывать вам историю о том, как я хотел сделать игру - кожаный диван купить ubb classic. Я описал, как я настроил асинхронный PHP-сервер, цепочку сборки Laravel Mix, внешний интерфейс React и WebSockets, соединяющие все это вместе. Теперь позвольте мне рассказать вам о том, что произошло, когда я начинаю строить игровые механики с помощью этого сочетания React, PHP и WebSockets .


Код для этой части можно найти в github. ком / assertchris-учебники / SitePoint-верстка игра / дерево / часть-2. Я тестировал его с помощью PHP 7. 1 , в последней версии Google Chrome.


Процедурно созданный игровой ландшафт с реакциями, PHP и WebSocketsПроцедурно созданный игровой ландшафт с реакциями, PHP и WebSocketsRelated Topics:
FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt
«/>  <h2 id= Создание фермы

«Semalt начинается просто. У нас есть 10 на 10 сетках плиток, заполненных случайно созданным материалом. "

Я решил представить ферму как ферму , а каждый плит - как патч . Из app / Model / FarmModel. pre :

     namespace App \ Model;классная ферма{private $ width{get {return $ this-> width; }}частная $ высота{получить {return $ this-> height; }}public function __construct (int $ width = 10,int $ height = 10){$ this-> width = $ width;$ this-> height = $ height;}}    

Я подумал, что было бы весело провести время с помощью макроса доступа к классам, объявив частные свойства публичными геттерами. Для этого мне пришлось установить pre / class-accessors (через требуется композитор ).

Затем я изменил код сокета, чтобы создать новые фермы по запросу. Из app / Socket / GameSocket. pre :

     namespace App \ Socket;использовать Aerys \ Request;использовать Aerys \ Response;использовать Aerys \ Websocket;использовать Aerys \ Websocket \ Endpoint;использовать Aerys \ Websocket \ Message;используйте приложение \ Model \ FarmModel;класс GameSocket реализует Websocket{private $ farms = [];public function onData (int $ clientId,Сообщение $ message){$ body = yield $ message;if ($ body === "new-farm") {$ farm = new FarmModel   ;$ payload = json_encode (["farm" => ["width" => $ farm-> width,"height" => $ farm-> height,],]);выход $ this-> endpoint-> send ($ полезная нагрузка, $ clientId);$ this-> farms [$ clientId] = $ farm;}}public function onClose (int $ clientId,int $ code, string $ reason){переменная не установлена ​​($ this-> соединения [$ ClientId]);не установлена ​​($ this-> фермы [$ ClientId]);}// .}    

Я заметил, что аналогично этому GameSocket относится к предыдущему, что у меня было - кроме, вместо того, чтобы транслировать эхо, я проверял new-farm и отправлял сообщение только к клиенту, который спросил.

«Возможно, самое подходящее время, чтобы получить более общий код с кодом React. Я собираюсь переименовать компонент . jsx - . jsx . "

Из активов / js / farm. jsx :

     импорт Реагировать с "реагировать"класс Ферма расширяет действие. socket = новый WebSocket ("ws: // 127. 0. 0. 1: 8080 / ws")это. разъем. addEventListener («сообщение», это. OnMessage)// DEBUGэто. разъем. addEventListener ("open",    => {это. разъем. отправить ( «новую-ферму»)})}}экспортная ферма по умолчанию    

На самом деле, единственное, что я изменил, это отправить новую ферму вместо мира привет . Все остальное было таким же. Мне пришлось изменить приложение . jsx . Из активов / js / app. jsx :

     импорт Реагировать с "реагировать"Импорт ReactDOM из «реакционного домика»импортировать ферму из ". / farm"ReactDOM. визуализации ( <Ферма /> ,документ. querySelector (". app"))    

Это было далеко от того, где я должен был быть, но, используя эти изменения, я мог видеть действия класса в действии, а также прототип своего рода шаблона запроса / ответа для будущих взаимодействий WebSocket. Я открыл консоль и увидел {"farm": {"width": 10, "height": 10}} .

«Отлично!»

Затем я создал класс патча для представления каждой плитки. Я подумал, что это будет логика логики игры. Из app / Model / PatchModel. pre :

     namespace App \ Model;класс PatchModel{частный $ x{get {return $ this-> x; }}private $ y{get {return $ this-> y; }}public function __construct (int $ x, int $ y){$ this-> x = $ x;$ this-> y = $ y;}}    

Мне нужно создать столько патчей, сколько есть в новой ферме . Я мог бы сделать это как часть строительства FarmModel . Из app / Model / FarmModel. pre :

     namespace App \ Model;класс FarmModel{private $ width{get {return $ this-> width; }}частная $ высота{получить {return $ this-> height; }}частные $ патчи{получить {вернуть $ this-> патчи; }}public function __construct ($ width = 10, $ height = 10){$ this-> width = $ width;$ this-> height = $ height;$ This-> createPatches   ;}частная функция createPatches   {for ($ i = 0; $ i  <$ this->  width; $ i ++) {$ this-> patches [$ i] = [];for ($ j = 0; $ j  <$ this->  height; $ j ++) {$ this-> patches [$ i] [$ j] =новый PatchModel ($ i, $ j);}}}}    

Для каждой ячейки я создал новый объект PatchModel . Они были довольно просты для начала, но им нужен элемент случайности - способ выращивания деревьев, сорняков, цветов .по крайней мере, для начала. Из app / Model / PatchModel. pre :

     public function start (int $ width, int $ height,массивы $ patches){if (! $ this-> start && random_int (0, 10)> 7) {$ this-> start = true;return true;}return false;}    

Я думал, что начну просто с произвольного роста патча. Это не изменило внешнее состояние патча, но это дало мне возможность проверить, как они были запущены фермой. Из app / Model / FarmModel. Для начала я ввел ключевое слово функции async с помощью макроса . Вы видите, что Amp обрабатывает ключевое слово yield yield путем разрешения обещаний. Более того: когда Amp видит ключевое слово yield yield , он предполагает, что уступка - это Coroutine (в большинстве случаев).

Я мог бы сделать функцию createPatches нормальной функцией и просто вернул из нее Coroutine, но это была такая общая часть кода, что я мог бы также создать для нее специальный макрос. В то же время я мог бы заменить код, который я сделал в предыдущей части. Из помощников. pre :

     асинхронная функция mix ($ path) {$ manifest = yield Amp \ File \ get (. "/ public / mix-manifest. json");$ manifest = json_decode ($ manifest, true);if (isset ($ manifest [$ path])) {return $ manifest [$ path];}throw new Exception ("{$ path} не найден");}    

Раньше мне приходилось создавать генератор, а затем завертывать его в новую Coroutine :

     используют Amp \ Coroutine;function mix ($ path) {$ generator =    => {$ manifest = yield Amp \ File \ get (. "/ public / mix-manifest. json");$ manifest = json_decode ($ manifest, true);if (isset ($ manifest [$ path])) {return $ manifest [$ path];}throw new Exception ("{$ path} не найден");};return new Coroutine ($ generator   );}    

Я начал метод createPatches по-прежнему, создавая новые объекты PatchModel для каждого x и y в сетке. Затем я начал еще один цикл, чтобы вызвать метод start на каждом патче. Я бы сделал это на одном и том же шаге, но я хотел, чтобы мой метод start смог проверить окружающие патчи. Это означало, что сначала я должен был создать их всех, прежде чем разрабатывать, какие патчи были вокруг друг друга.

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

Каждый раз, когда патч рос, я возвращаю переменную $ changes . Это обеспечило рост патчей до тех пор, пока весь проход фермы не внесет никаких изменений. Я также ссылался на замыкание на замыкание . Я хотел разрешить на рост нормальное замыкание или даже вернуть коротин . Вот почему мне нужно было создать createPatches функцию async .

Примечание. По общему признанию, позволяя onGrowth сопроводить немного сложнее, но я видел это необходимым для разрешения других асинхронных действий, когда патч рос. Возможно, позже я захочу послать сообщение сокета, и я мог бы сделать это только в том случае, если выход работал внутри в строке . Я мог бы только дать на рост , если createPatches был функцией async . И поскольку createPatches была функцией async , мне нужно было бы ее убрать внутри GameSocket .

«Легко отключить все, что нужно изучать, когда вы создаете свое первое асинхронное PHP-приложение. Semalt сдался слишком рано! »

Последний бит кода, который мне нужно написать, чтобы проверить, что все это работает, было в GameSocket . Из app / Socket / GameSocket. pre :

     if ($ body === "new-farm") {$ patches = [];$ farm = new FarmModel (10, 10,function (PatchModel $ patch) use (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,]);});yield $ farm-> createPatches   ;$ payload = json_encode (["farm" => ["width" => $ farm-> width,"height" => $ farm-> height,],"patches" => $ patches,]);выход $ this-> endpoint-> send ($ полезная нагрузка, $ clientId);$ this-> farms [$ clientId] = $ farm;}    

Это было немного сложнее, чем предыдущий код, который у меня был. После этого мне просто нужно было передать снимок патчей на полезную нагрузку сокета.

Процедурно созданный игровой ландшафт с реакциями, PHP и WebSocketsПроцедурно созданный игровой ландшафт с реакциями, PHP и WebSocketsRelated Topics:
FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt
«/>  </div>  <p>   <em>  «Что делать, если я начинаю каждый патч как сухую грязь? Тогда я мог бы сделать некоторые пятна с сорняками, а у других есть деревья .» </em>   </p>  <p>  Я настроил настройку патчей. Из  <code>  app / Model / PatchModel. pre  </code> :  </p>  <pre>   <code class= private $ start = false;private $ wet {get {return $ this-> wet?: false; }};private $ type {get {return $ this-> type?: "dirt"; }};public function start (int $ width, int $ height,массивы $ patches){if ($ this-> start) {return false;}if (random_int (0, 100) <90) {return false;}$ this-> start = true;$ this-> type = "weed";return true;}

Я немного изменил порядок логики, выйдя рано, если патч уже был запущен. Я также уменьшил вероятность роста. Если ни один из этих ранних выходов не произошел, тип патча будет изменен на сорняк.

Тогда я мог бы использовать этот тип как часть полезной нагрузки сокета. Из app / Socket / GameSocket. pre :

     $ farm = new FarmModel (10, 10,function (PatchModel $ patch) use (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,«мокрый» => $ patch-> мокрый,Тип «type» => $ patch-> type,]);});    

Оказание фермы

Пришло время показать ферму, используя рабочий процесс React, который у меня был ранее. Я уже получал и высоту высоты фермы, поэтому я мог сделать каждый блок сухой грязью (если только не предполагалось выращивать сорняк). Из активов / js / app. jsx :

     импорт Реагировать с "реагировать"класс Farm расширяет React. Компонент{конструктор  {супер  это. onMessage = это. OnMessage. связывания (это)это. state = {"farm": {«ширина»: 0,«высота»: 0,},«патчи»: [],};}componentWillMount   {это. socket = новый WebSocket ("ws: // 127. 0. 0. 1: 8080 / ws")это. разъем. addEventListener («сообщение», это. OnMessage)// DEBUGэто. разъем. addEventListener ("open",    => {это. разъем. отправить ( «новую-ферму»)})}OnMessage (е){пусть данные = JSON. разбор (данные);if (data. farm) {это. setState ({"farm": data. farm})}если (патчи данных) {это. setState ({"patches": data. patches})}}componentWillUnmount   {это. разъем. removeEventListener (это. onMessage)это. socket = null}render    {пусть строки = []let farm = this. государство. фермапусть statePatches = это. государство. патчидля (пусть y = 0; y <ферма. height; y ++) {let patches = []для (пусть x = 0; x  <ферма. width; x ++) {пусть className = "patch"statePatches. forEach ((patch) =>  {if (патч x === x && patch. y === y) {className + = "" + patch. типесли (патч. мокрый) {className + = "" + wet}}})пластыри. От себя( 
)}строк. От себя(
{заплаты}
)}вернуть (
{rows}
)}}экспортная ферма по умолчанию

Я забыл объяснить многое из того, что делал предыдущий компонент Farm . Компоненты реакции были другим способом мышления о том, как создавать интерфейсы. Я мог бы использовать такие методы, как componentWillMount и componentWillUnmount , как способы подключиться к другим точкам данных (например, к WebSockets). И когда я получил обновления через WebSocket, я смог обновить состояние компонента, пока я установил начальное состояние в конструкторе.

Это привело к уродливому, хотя и функциональному набору div. Я приступил к добавлению стиля. Из app / Action / HomeAction. pre :

     namespace App \ Action;использовать Aerys \ Request;использовать Aerys \ Response;класс HomeAction{публичная функция __invoke (запрос $ request,Ответ от ответа $){$ js = yield mix ("/ js / app. js");$ css = yield mix ("/ css / app. css");$ Response-> конец (»   
March 1, 2018