react兼容性
“我想制作一款基于经济的多人游戏。 像Stardew Valley之类的东西,但是没有任何友好的方面和基于玩家的经济。”
当我决定尝试使用PHP和React构建游戏时,我就开始考虑这一点。 问题是,我对多人游戏的动态性或如何考虑和实施基于玩家的经济一无所知。
我什至不确定我是否对React足够了解,可以证明使用它是合理的。 我的意思是,初始界面(我主要关注服务器和游戏的经济方面)非常适合React。 但是,当我开始进行耕作/互动方面时呢? 我喜欢在经济体系周围建立等距界面的想法。
我曾经看过dead_lugosi的演讲 ,她在其中描述了用PHP构建中世纪游戏。 玛格丽特激发了我的灵感,而那次演讲是促使我写一本关于JS游戏开发的书的原因之一 。 我决心写自己的经历。 在这种情况下,也许其他人也可以从我的错误中学到东西。
该部分的代码可以在以下网址找到: github.com/assertchris-tutorials/sitepoint-making-games/tree/part-1 。 我已经在PHP 7.1
和最新版本的Google Chrome中对其进行了测试。
设置后端
我搜索的第一件事是建立多人游戏经济的指南。 我发现了一个出色的Stack Overflow线程 ,人们在其中解释了要考虑的各种事情。 在意识到可能是从错误的地方开始之前,我已经走了一半。
首先,我需要一台PHP服务器。 我将拥有一堆React客户端,所以我想要一些具有高并发性的东西(甚至WebSockets)。 它必须是持久的:即使玩家不在身边,事情也必须发生。”
我开始设置异步PHP服务器-处理高并发性并支持WebSockets。 我添加了最近与PHP预处理器一起使用的工作,以使事情更整洁,并创建了头两个端点。
从config.pre
:
$host = new Aerys\Host();
$host->expose("*", 8080);
$host->use($router = Aerys\router());
$host->use($root = Aerys\root(.."/public"));
$web = process .."/routes/web.pre";
$web($router);
$api = process .."/routes/api.pre";
$api($router);
我决定将Aerys用于应用程序的HTTP和WebSocket部分。 这段代码看起来与Aerys文档完全不同,但这是因为我对所需内容有很好的了解。
运行Aerys应用程序的通常过程是使用如下命令:
vendor/bin/aerys -d -c config.php
有很多代码需要重复,并且没有处理我要使用PHP预处理的事实。 我创建了一个加载器文件。
从loader.php
:
return Pre\processAndRequire(__DIR__ . "/config.pre");
然后安装我的依赖项。 这是来自composer.json
:
"require": {
"amphp/aerys": "dev-amp_v2",
"amphp/parallel": "dev-master",
"league/container": "^2.2",
"league/plates": "^3.3",
"pre/short-closures": "^0.4.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
我想使用amphp/parallel
来将阻止代码移出异步服务器,但它不会与amphp/aerys
的稳定标记amphp/aerys
。 这就是为什么我选择dev-amp_v2
分支。
我认为最好包含某种模板引擎和服务定位器。 我选择了每个PHP League版本。 最后,我添加了pre/short-closures
,既可以处理config.pre
的自定义语法,又可以计划在after之后使用的简短闭包……
然后,我着手创建路由文件。 从routes/web.pre
:
use Aerys\Router;
use App\Action\HomeAction;
return (Router $router) => {
$router->route(
"GET", "/", new HomeAction
);
};
并且,从routes/api.pre
:
use Aerys\Router;
use App\Action\Api\HomeAction;
return (Router $router) => {
$router->route(
"GET", "/api", new HomeAction
);
};
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
尽管路由很简单,但这些帮助我测试了config.pre
的代码。 我决定使这些路由文件返回闭包,因此我可以将它们传递给类型化的$router
,它们可以在其中添加自己的路由。 最后,我创建了两个(类似)动作。
从app/Actions/HomeAction.pre
:
namespace App\Action;
use Aerys\Request;
use Aerys\Response;
class HomeAction
{
public function __invoke(Request $request,
Response $response)
{
$response->end("hello world");
}
}
最后一步是添加快捷方式脚本,以启动Aerys服务器的开发和生产版本。
从composer.json
:
"scripts": {
"dev": "vendor/bin/aerys -d -c loader.php",
"prod": "vendor/bin/aerys -c loader.php"
},
"config": {
"process-timeout": 0
},
完成所有这些操作后,我可以启动新服务器,只需键入以下内容即可访问http://127.0.0.1:8080
。
composer dev
设置前端
“好吧,现在我对PHP的了解相对稳定了; 我将如何构建ReactJS文件? 也许我可以使用Laravel Mix ……?
我并不热衷于创建一个全新的构建链,并且Mix已被重建以在非Laravel项目中也能很好地工作。 尽管配置和扩展相对容易,但是默认情况下它偏爱VueJS。
我要做的第一件事是安装一些NPM依赖项。 从package.json
:
"devDependencies": {
"babel-preset-react": "^6.23.0",
"bootstrap-sass": "^3.3.7",
"jquery": "^3.1.1",
"laravel-mix": "^0.7.5",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"webpack": "^2.2.1"
},
混合使用Webpack预处理和捆绑JS和CSS文件。 我还需要安装React和相关的Babel库来构建jsx
文件。 最后,我添加了Bootstrap文件,以获取一些默认样式。
Mix自动加载了自定义配置文件,因此我添加了以下内容。 从webpack.mix.js
:
let mix = require("laravel-mix")
// load babel presets for jsx files
mix.webpackConfig({
"module": {
"rules": [
{
"test": /jsx$/,
"exclude": /(node_modules)/,
"loader": "babel-loader" + mix.config.babelConfig(),
"query": {
"presets": [
"react",
"es2015",
],
},
},
],
},
})
// set up front-end assets
mix.setPublicPath("public")
mix.js("assets/js/app.jsx", "public/js/app.js")
mix.sass("assets/scss/app.scss", "public/css/app.css")
mix.version()
我需要告诉Mix如何处理jsx
文件,因此我添加了通常可能放在.babelrc
的相同配置。 我计划将单个JS和CSS入口点插入到应用程序的各种细节中。
注意:Mix的未来版本将附带对构建ReactJS资产的内置支持。 发生这种情况时,可以删除mix.webpackConfig
代码。
再次,我创建了一些快捷方式脚本,以节省认真的打字时间。 从package.json
:
"scripts": {
"dev": "$npm_package_config_webpack",
"watch": "$npm_package_config_webpack -w",
"prod": "$npm_package_config_webpack -p"
},
"config": {
"webpack": "webpack --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
所有这三个脚本都使用了Webpack variable命令,但是它们的不同之处在于它们。 dev
构建了JS和CSS文件的调试版本。 -w
开关启动了Webpack监视程序(以便可以部分重建捆绑软件)。 -p
开关启用了捆绑软件的精益生产版本。
由于我使用的是捆绑软件版本控制,因此我需要一种在不知道哈希值的情况下引用/js/app.60795d5b3951178abba1.js
文件的方法。 我注意到Mix喜欢创建清单文件,因此我做了一个辅助函数来查询它。 来自helpers.pre
:
use 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} not found");
};
return new Coroutine($generator());
}
当Aerys以$val = yield $promise
的形式出现时,Aerys知道如何处理它们,因此我使用了Amp的Promise实现。 读取和解码文件后,我可以寻找匹配的文件路径。 我调整了HomeAction
。 从app/Actions/HomeAction.pre
:
public function __invoke(Request $request,
Response $response)
{
$path = yield mix("/js/app.js");
$response->end("
<div class='app'></div>
<script src='{$path}'></script>
");
}
我意识到我可以继续创建返回promise的函数,并以这种方式使用它们使我的代码保持异步。 这是我的JS代码,来自assets/js/component.jsx
:
import React from "react"
class Component extends React.Component
{
render() {
return <div>hello world</div>
}
}
export default Component
…并且,从assets/js/app.jsx
:
import React from "react"
import ReactDOM from "react-dom"
import Component from "./component"
ReactDOM.render(
<Component />,
document.querySelector(".app")
)
毕竟,我只是想看看Mix是否会编译我的jsx
文件,以及是否可以使用异步mix
函数再次找到它们。 原来它起作用了!
注意:每次都使用mix
函数非常昂贵,尤其是在加载相同文件的情况下。 相反,我们可以在服务器引导阶段加载所有模板,并在需要时从我们的操作内部引用它们。 我们启动Aerys时使用的配置文件可以返回承诺(例如Amp\all
给我们的那种),因此我们可以在服务器启动之前解析所有模板。
与WebSockets连接
我快要准备好了。 最后要做的是通过WebSockets连接后端和前端。 我发现使用新课程相对来说比较简单。 从app/Socket/GameSocket.pre
:
namespace App\Socket;
use Aerys\Request;
use Aerys\Response;
use Aerys\Websocket;
use Aerys\Websocket\Endpoint;
use Aerys\Websocket\Message;
class GameSocket implements Websocket
{
private $endpoint;
private $connections = [];
public function onStart(Endpoint $endpoint)
{
$this->endpoint = $endpoint;
}
public function onHandshake(Request $request,
Response $response)
{
$origin = $request->getHeader("origin");
if ($origin !== "http://127.0.0.1:8080") {
$response->setStatus(403);
$response->end("<h1>origin not allowed</h1>");
return null;
}
$info = $request->getConnectionInfo();
return $info["client_addr"];
}
public function onOpen(int $clientId, $address)
{
$this->connections[$clientId] = $address;
}
public function onData(int $clientId,
Message $message)
{
$body = yield $message;
yield $this->endpoint->broadcast($body);
}
public function onClose(int $clientId,
int $code, string $reason)
{
unset($this->connections[$clientId]);
}
public function onStop()
{
// nothing to see here…
}
}
…并对网络路由进行了一些修改(来自routes/web.pre
):
use Aerys\Router;
use App\Action\HomeAction;
use App\Socket\GameSocket;
return (Router $router) => {
$router->route(
"GET", "/", new HomeAction
);
$router->route(
"GET", "/ws", Aerys\websocket(new GameSocket)
);
};
现在,我可以更改JS以连接到该WebSocket,并向与其连接的每个人发送一条消息。 从assets/js/component.jsx
:
import React from "react"
class Component extends React.Component
{
constructor()
{
super()
this.onMessage = this.onMessage.bind(this)
}
componentWillMount()
{
this.socket = new WebSocket(
"ws://127.0.0.1:8080/ws"
)
this.socket.addEventListener(
"message", this.onMessage
)
// DEBUG
this.socket.addEventListener("open", () => {
this.socket.send("hello world")
})
}
onMessage(e)
{
console.log("message: " + e.data)
}
componentWillUnmount()
{
this.socket.removeEventListener(this.onMessage)
this.socket = null
}
render() {
return <div>hello world</div>
}
}
export default Component
创建新的Component
对象时,它将连接到WebSocket服务器,并为新消息添加事件侦听器。 我添加了一些调试代码,以确保其正确连接并向后发送新消息。
稍后,我们将介绍PHP和WebSocket的细节,请不要担心。
摘要
在这一部分中,我们研究了如何设置简单的异步PHP Web服务器,如何在非Laravel项目中使用Laravel Mix,以及如何将后端和前端与WebSockets连接在一起。
! 这涉及很多方面,我们还没有编写任何游戏代码。 和我一起进入第二部分 ,当我们开始构建游戏逻辑和React接口时。
本文由Niklas Keller同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!
翻译自: https://www.sitepoint.com/game-development-with-reactjs-and-php-how-compatible-are-they/
react兼容性