使用React和PHP开发游戏:它们的兼容性如何?

“我想制作一款基于经济的多人游戏。 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
  );
};

尽管路由很简单,但这些帮助我测试了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的形式处理承诺,因此我使用了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内容达到最佳状态!

From: https://www.sitepoint.com/game-development-with-reactjs-and-php-how-compatible-are-they/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值