แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ

แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แ‰ 2015 แ‰ฐแˆˆแ‰‹แˆ แŠ แŒแŠ แˆญแ‹ฎ.แ‹ฎ. แ‹จแŠ แ‹ฒแˆฑ แ‹˜แ‹แŒ แ‰…แ‹ตแˆ˜ แŠ แ‹ซแ‰ต แˆ†แА แŒจแ‹‹แ‰ณแ‹Žแ‰ฝ .ioแŠจแ‹šแ‹ซแŠ• แŒŠแ‹œ แŒ€แˆแˆฎ แ‰ แ‰ณแ‹‹แ‰‚แАแ‰ต แ‹ซแ‹ฐแŒˆแ‹. แ‰ แŒแˆŒ แ‹จ.io แŒจแ‹‹แ‰ณแ‹Žแ‰ฝ แ‰ฐแ‹ˆแ‹ณแŒ…แАแ‰ต แˆ˜แŒจแˆ˜แˆญ แŠ แŒ‹แŒฅแˆžแŠ›แˆแก แ‰ฃแˆˆแ‰แ‰ต แˆถแˆตแ‰ต แŠ แˆ˜แ‰ณแ‰ต แ‹แˆตแŒฅแฃ แŠ แˆˆแŠ แ‹จแ‹šแˆ… แ‹˜แ‹แŒ แˆแˆˆแ‰ต แŒจแ‹‹แ‰ณแ‹Žแ‰ฝแŠ• แˆแŒ แˆจ แŠฅแŠ“ แ‰ฐแˆฝแŒงแˆแข.

แˆตแˆˆแŠฅแАแ‹šแˆ… แŒจแ‹‹แ‰ณแ‹Žแ‰ฝ แŠจแ‹šแˆ… แ‰ แŠแ‰ต แˆฐแˆแ‰ฐแˆ… แ‹จแˆ›แ‰ณแ‹แ‰€แ‹ แŠจแˆ†แАแฃ แˆˆแˆ˜แŒซแ‹ˆแ‰ต แ‰€แˆ‹แˆ แ‹จแˆ†แŠ‘ แАแŒป แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณแ‹Žแ‰ฝ แŠ“แ‰ธแ‹ (แˆแŠ•แˆ แˆ˜แˆˆแ‹ซ แŠ แ‹ซแˆตแˆแˆแŒแˆ)แข แ‰ฅแ‹™แ‹แŠ• แŒŠแ‹œ แ‰ แ‰ฐแˆ˜แˆณแˆณแ‹ญ แˆ˜แ‹ตแˆจแŠญ แ‰ฅแ‹™ แ‰ฐแ‰ƒแˆซแŠ’ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝแŠ• แ‹ญแŒ‹แˆแŒฃแˆ‰แข แˆŒแˆŽแ‰ฝ แ‰ณแ‹‹แ‰‚ แ‹จ.io แŒจแ‹‹แ‰ณแ‹Žแ‰ฝแก- Slither.io ะธ แ‹ฒแ’แ’แ‹ฎ.

แ‰ แ‹šแˆ… แŒฝแˆ‘แ แ‹แˆตแŒฅ, แŠฅแŠ•แ‹ดแ‰ต แŠฅแŠ•แ‹ฐแˆ†แА แŠฅแŠ•แˆ˜แˆจแˆแˆซแˆˆแŠ• แŠจแ‰ฃแ‹ถ แ‹จ.io แŒจแ‹‹แ‰ณ แ‹ญแแŒ แˆฉ. แˆˆแ‹šแˆ…, แ‹จแŒƒแ‰ซแˆตแŠญแˆชแ•แ‰ต แŠฅแ‹แ‰€แ‰ต แ‰ฅแ‰ป แ‰ แ‰‚ แ‹ญแˆ†แŠ“แˆ: แŠฅแŠ•แ‹ฐ แŠ แŒˆแ‰ฃแ‰ฅ แ‹ซแˆ‰ แАแŒˆแˆฎแ‰ฝแŠ• แˆ˜แˆจแ‹ณแ‰ต แ‹ซแˆตแˆแˆแŒแ‹Žแ‰ณแˆ ES6แฃ แ‰แˆแ แ‰ƒแˆ this ะธ แ‰ฐแˆตแ‹แ‹Žแ‰ฝ. แ‹จแŒƒแ‰ซแˆตแŠญแˆชแ•แ‰ต แŠฅแ‹แ‰€แ‰ตแ‹Ž แแแˆ แ‰ฃแ‹ญแˆ†แŠ•แˆ แŠ แ‰ฅแ‹›แŠ›แ‹แŠ• แˆแŒฅแ‰แŠ• แˆ˜แˆจแ‹ณแ‰ต แ‹ญแ‰ฝแˆ‹แˆ‰แข

.io แŒจแ‹‹แ‰ณ แˆแˆณแˆŒ

แˆˆแ‰ตแˆแˆ…แˆญแ‰ต แŠฅแˆญแ‹ณแ‰ณแฃ แŠฅแŠ•แŒ แ‰…แˆณแˆˆแŠ•แข .io แŒจแ‹‹แ‰ณ แˆแˆณแˆŒ. แŠฅแˆฑแŠ• แˆˆแˆ˜แŒซแ‹ˆแ‰ต แ‹ญแˆžแŠญแˆฉ!

แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แŒจแ‹‹แ‰ณแ‹ แ‰ แŒฃแˆ แ‰€แˆ‹แˆ แАแ‹แก แˆŒแˆŽแ‰ฝ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แ‰ฃแˆ‰แ‰ แ‰ต แˆ˜แ‹ตแˆจแŠญ แˆ‹แ‹ญ แˆ˜แˆญแŠจแ‰ฅแŠ• แ‰ตแ‰†แŒฃแŒ แˆซแˆ‹แ‰ฝแˆแข แ‹จแŠฅแˆญแˆตแ‹Ž แˆ˜แˆญแŠจแ‰ฅ แ‰ แˆซแˆต-แˆฐแˆญ แ•แˆฎแŒ„แŠญแ‰ฐแˆฎแ‰ฝแŠ• แ‹ซแ‰ƒแŒฅแˆ‹แˆ แŠฅแŠ“ แˆŒแˆŽแ‰ฝ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝแŠ• แ•แˆฎแŒ„แŠญแ‰ฐแˆฎแ‰ฝแŠ• แ‰ แˆ›แˆตแ‹ˆแŒˆแ‹ต แˆ‹แ‹ญ แŠฅแ‹ซแˆ‰ แˆˆแˆ˜แˆแ‰ณแ‰ต แ‹ญแˆžแŠญแˆซแˆ‰แข

1. แ‹จแ•แˆฎแŒ€แŠญแ‰ฑ แŠ แŒญแˆญ แˆ˜แŒแˆˆแŒซ / แˆ˜แ‹‹แ‰…แˆญ

แŠฅแŠ” แŠ แˆ˜แˆฐแŒแŠ“แˆˆแˆ แˆแŠ•แŒญ แŠฎแ‹ต แŠ แ‹แˆญแ‹ต แŠฅแŠ”แŠ• แˆ˜แŠจแ‰ฐแˆ แŠฅแŠ•แ‹ฒแ‰ฝแˆ‰ แ‹จแˆแˆณแˆŒ แŒจแ‹‹แ‰ณแข

แˆแˆณแˆŒแ‹ แ‹จแˆšแŠจแ‰ฐแˆ‰แ‰ตแŠ• แ‹ญแŒ แ‰€แˆ›แˆ:

  • แ‹ญแŒแˆˆแŒน แ‹จแŒจแ‹‹แ‰ณแ‹แŠ• แ‹จแ‹ตแˆญ แŠ แŒˆแˆแŒ‹แ‹ญ แ‹จแˆšแ‹ซแˆตแ‰ฐแ‹ณแ‹ตแˆญ แ‰ แŒฃแˆ แ‰ณแ‹‹แ‰‚แ‹ แ‹จ Node.js แ‹จแ‹ตแˆญ แˆ›แ‹•แ‰€แ แАแ‹แข
  • แˆถแŠฌแ‰ต.io - แ‰ แŠ แˆณแˆฝ แŠฅแŠ“ แ‰ แŠ แŒˆแˆแŒ‹แ‹ญ แˆ˜แŠซแŠจแˆ แ‹แˆ‚แ‰ฅ แˆˆแˆ˜แˆˆแ‹‹แ‹ˆแŒฅ แ‹จแ‹Œแ‰ฅแˆถแŠฌแ‰ต แ‰คแ‰ฐ-แˆ˜แŒฝแˆแแ‰ตแข
  • Webpackแข - แˆžแŒแˆ แŠ แˆตแ‰ฐแ‹ณแ‹ณแˆช. แˆˆแˆแŠ• Webpack แŠฅแŠ•แ‹ฐแˆšแŒ แ‰€แˆ™ แˆ›แŠ•แ‰ แ‰ฅ แ‹ญแ‰ฝแˆ‹แˆ‰. แŠฅแ‹šแˆ….

แ‹จแ•แˆฎแŒ€แŠญแ‰ต แˆ›แ‹แŒซ แˆ˜แ‹‹แ‰…แˆญ แˆแŠ• แŠฅแŠ•แ‹ฐแˆšแˆ˜แˆตแˆ แŠฅแАแˆ†แก-

แ‹จแˆ…แ‹แ‰ฅ / แŠ•แ‰ฅแˆจแ‰ถแ‰ฝ / ... src / แ‹ฐแŠ•แ‰ แŠ› / css / ... html/ index.html index.js ... แŠ แŒˆแˆแŒ‹แ‹ญ / แŠ แŒˆแˆแŒ‹แ‹ญ.js ... แ‹จแ‰ฐแŒ‹แˆซ / แ‰‹แˆšแ‹Žแ‰ฝ.js

แ‹จแˆ…แ‹แ‰ฅ/

แ‰ แŠ แ‰ƒแŠ แ‹แˆตแŒฅ แˆแˆ‰แˆ แАแŒˆแˆญ public/ แ‰ แŠ แŒˆแˆแŒ‹แ‹ฉ แ‰ แˆตแ‰ณแ‰ฒแˆตแ‰ฒแŠญแˆต แŒˆแ‰ข แ‹ญแ‹ฐแˆจแŒ‹แˆแข แ‹แˆตแŒฅ public/assets/ แ‰ แŠฅแŠ› แ•แˆฎแŒ€แŠญแ‰ต แŒฅแ‰…แˆ แˆ‹แ‹ญ แ‹จแ‹‹แˆ‰ แˆแˆตแˆŽแ‰ฝแŠ• แ‹ญแ‹Ÿแˆ.

src /

แˆแˆ‰แˆ แ‹จแˆแŠ•แŒญ แŠฎแ‹ต แ‰ แŠ แ‰ƒแŠแ‹ แ‹แˆตแŒฅ แАแ‹แข src/. แˆญแ‹•แˆถแ‰ฝ client/ ะธ server/ แˆˆแˆซแˆณแ‰ธแ‹ แ‹ญแŠ“แŒˆแˆฉ แŠฅแŠ“ shared/ แ‰ แ‹ฐแŠ•แ‰ แŠ›แ‹ แŠฅแŠ“ แ‰ แŠ แŒˆแˆแŒ‹แ‹ฉ แ‹จแˆšแˆ˜แŒฃ แ‰‹แˆš แ‹แ‹ญแˆ แ‹ญแ‹ญแ‹›แˆแข

2. แˆตแ‰ฅแˆฐแ‰ฃแ‹Žแ‰ฝ / แ‹จแ•แˆฎแŒ€แŠญแ‰ต แ‰…แŠ•แŒ…แ‰ถแ‰ฝ

แŠจแˆ‹แ‹ญ แŠฅแŠ•แ‹ฐแ‰ฐแŒ แ‰€แˆฐแ‹ แ•แˆฎแŒ€แŠญแ‰ฑแŠ• แˆˆแˆ˜แŒˆแŠ•แ‰ฃแ‰ต แˆžแŒแˆ‰แŠ• แˆฅแˆซ แŠ แˆตแŠชแ‹ซแŒ… แŠฅแŠ•แŒ แ‰€แˆ›แˆˆแŠ•. Webpackแข. แ‹จแŠฅแŠ›แŠ• แ‹จแ‹Œแ‰ฅแ“แŠญ แŠ แ‹ˆแ‰ƒแ‰€แˆฎแ‰ฝแŠ• แŠฅแŠ•แ‹ญแก-

webpack.common.jsแก

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: {
    game: './src/client/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
      {
        test: /.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          'css-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'src/client/html/index.html',
    }),
  ],
};

แ‰ แŒฃแˆ แŠ แˆตแˆแˆ‹แŒŠแ‹Žแ‰น แˆ˜แˆตแˆ˜แˆฎแ‰ฝ แŠฅแ‹šแˆ… แŠ แˆ‰-

  • src/client/index.js แ‹จแŒƒแ‰ซแˆตแŠญแˆชแ•แ‰ต (JS) แ‹ฐแŠ•แ‰ แŠ› แˆ˜แŒแ‰ขแ‹ซ แАแŒฅแ‰ฅ แАแ‹แข แ‹Œแ‰ฅแ“แŠญ แŠจแ‹šแˆ… แ‹ญแŒ€แˆแˆซแˆ แŠฅแŠ“ แˆŒแˆŽแ‰ฝ แ‹ˆแ‹ฐ แˆ€แŒˆแˆญ แ‹แˆตแŒฅ แ‹จแˆšแŒˆแ‰ก แ‹แ‹ญแˆŽแ‰ฝแŠ• แ‰ แ‹จแŒŠแ‹œแ‹ แ‹ญแˆแˆแŒ‹แˆแข
  • แ‹จแŠฅแŠ› แ‹จแ‹Œแ‰ฅแ“แŠญ แŒแŠ•แ‰ฃแ‰ณ แ‹แŒคแ‰ต JS แ‰ แˆ›แ‹แŒซแ‹ แ‹แˆตแŒฅ แ‹ญแ‰€แˆ˜แŒฃแˆ dist/. แ‹ญแˆ…แŠ•แŠ• แ‹แ‹ญแˆ แ‹จแŠ› แŠฅแ‹ฐแ‹แˆ‹แˆˆแˆแข js แŒฅแ‰…แˆ.
  • แŠฅแŠ› แŠฅแŠ•แŒ แ‰€แˆ›แˆˆแŠ• แ‰ฃแ‰คแˆ, แŠฅแŠ“ แ‰ แ‰ฐแˆˆแ‹ญแˆ แŠ แ‹ˆแ‰ƒแ‰€แˆฉ @babel/preset-env แˆˆแŠ แˆฎแŒŒ แŠ แˆณแˆพแ‰ฝ แ‹จแŠ›แŠ• JS แŠฎแ‹ต แˆˆแˆ˜แƒแแข
  • แ‰ JS แ‹แ‹ญแˆŽแ‰ฝ แ‹จแ‰ฐแŒ แ‰€แˆฑ แˆแˆ‰แŠ•แˆ CSS แˆˆแˆ›แ‹แŒฃแ‰ต แŠฅแŠ“ แ‰ แŠ แŠ•แ‹ต แ‰ฆแ‰ณ แˆˆแˆ›แŒฃแˆ˜แˆญ แ•แˆˆแŒŠแŠ• แŠฅแ‹จแ‰ฐแŒ แ‰€แˆแŠ• แАแ‹แข แ‹จแŠ› แŠฅแˆˆแ‹‹แˆˆแˆแข css แŒฅแ‰…แˆ.

แŠฅแŠ•แŒแ‹ณ แ‹จแŒฅแ‰…แˆ แ‹แ‹ญแˆ แˆตแˆžแ‰ฝแŠ• แŠ แˆตแ‰ฐแ‹แˆˆแˆ… แ‹ญแˆ†แŠ“แˆแข '[name].[contenthash].ext'. แ‹ญแ‹ญแ‹›แˆ‰ แ‹จแ‹แ‹ญแˆ แˆตแˆ แˆแ‰ตแŠญ แ‹จแ‹ตแˆญ แ‰ฆแˆญแˆณ [name] แ‰ แŒแ‰คแ‰ต แАแŒฅแ‰ก แˆตแˆ แ‹ญแ‰ฐแŠซแ‹‹แˆ (แ‰ แŠฅแŠ› แˆแŠ”แ‰ณ แ‹ญแˆ… game) แฃ แŠฅแŠ“ [contenthash] แ‰ แ‹แ‹ญแˆ‰ แ‹ญแ‹˜แ‰ต แˆƒแˆฝ แ‹ญแ‰ฐแŠซแˆแข แŠฅแŠ“แ‹ฐแˆญแŒˆแ‹‹แˆˆแŠ• แ•แˆฎแŒ€แŠญแ‰ฑแŠ• แˆˆ hashing แ‹ซแˆ˜แ‰ปแ‰น - แŠ แˆณแˆพแ‰ฝ แ‹จแŠฅแŠ›แŠ• JS แŒฅแ‰…แˆŽแ‰ฝ แˆ‹แˆแ‰ฐแ‹ˆแˆฐแА แŒŠแ‹œ แŠฅแŠ•แ‹ฒแˆธแŒŽแŒก แˆ˜แŠ•แŒˆแˆญ แ‹ญแ‰ฝแˆ‹แˆ‰แฃ แˆแŠญแŠ•แ‹ซแ‰ฑแˆ แŠ แŠ•แ‹ต แŒฅแ‰…แˆ แŠจแ‰ฐแˆˆแ‹ˆแŒ  แ‹จแ‹แ‹ญแˆ‰ แˆตแˆแˆ แ‹ญแˆˆแ‹ˆแŒฃแˆ (แ‹ญแˆˆแ‹แŒฃแˆ contenthash). แ‹จแˆ˜แŒจแˆจแˆปแ‹ แ‹แŒคแ‰ต แ‹จแŠฅแ‹ญแ‰ณ แ‹แ‹ญแˆ แˆตแˆ แ‹ญแˆ†แŠ“แˆ game.dbeee76e91a97d0c7207.js.

แ‹แ‹ญแˆ webpack.common.js แ‹ˆแ‹ฐ แŒแŠ•แ‰ฃแ‰ณ แŠฅแŠ“ แ‹จแ‰ฐแŒ แŠ“แ‰€แ‰ แ‹จแ•แˆฎแŒ€แŠญแ‰ต แ‹แ‰…แˆฎแ‰ฝ แ‹จแˆแŠ“แˆตแŒˆแ‰ฃแ‹ แ‹จแˆ˜แˆ แˆจแ‰ต แ‹แ‰…แˆญ แ‹แ‹ญแˆ แАแ‹แข แ‹จแˆแˆ›แ‰ต แ‹แ‰…แˆญ แˆแˆณแˆŒ แ‹ญแŠธแ‹แŠ“แก

webpack.dev.js

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
});

แˆˆแ‹แŒคแ‰ณแˆ›แАแ‰ต, แ‰ แˆแˆ›แ‰ต แˆ‚แ‹ฐแ‰ต แ‹แˆตแŒฅ แŠฅแŠ•แŒ แ‰€แˆ›แˆˆแŠ• webpack.dev.js, แŠฅแŠ“ แ‹ˆแ‹ฐ แ‹ญแ‰€แ‹ญแˆซแˆ webpack.prod.jsแ‹ˆแ‹ฐ แˆแˆญแ‰ต แ‰ แˆšแˆฐแˆซแŒญแ‰ แ‰ต แŒŠแ‹œ แ‹จแŒฅแ‰…แˆ แˆ˜แŒ แŠ–แ‰ฝแŠ• แˆˆแˆ›แˆ˜แ‰ปแ‰ธแ‰ต.

แ‹จแŠ แŠซแ‰ฃแ‰ข แ‰…แŠ•แ‰ฅแˆญ

แ‰ แ‹šแˆ… แŒฝแˆ‘แ แ‹แˆตแŒฅ แ‹จแ‰ฐแ‹˜แˆจแ‹˜แˆฉแ‰ตแŠ• แ‹ฐแˆจแŒƒแ‹Žแ‰ฝ แˆ˜แŠจแ‰ฐแˆ แŠฅแŠ•แ‹ฒแ‰ฝแˆ‰ แ•แˆฎแŒ€แŠญแ‰ฑแŠ• แ‰ แŠ แŠซแ‰ฃแ‰ขแ‹ แˆ›แˆฝแŠ• แˆ‹แ‹ญ แŠฅแŠ•แ‹ฒแŒญแŠ‘ แŠฅแˆ˜แŠญแˆซแˆˆแˆ. แˆ›แ‹‹แ‰€แˆฉ แ‰€แˆ‹แˆ แАแ‹: แ‰ แˆ˜แŒ€แˆ˜แˆชแ‹ซ, แˆตแˆญแ‹“แ‰ฑ แˆ˜แŒซแŠ• แŠ แˆˆแ‰ แ‰ต แŠ แŠ•แŒ“ ะธ NPM. แ‰ แˆ˜แ‰€แŒ แˆ แˆ›แ‹ตแˆจแŒ แ‹ซแˆตแˆแˆแŒแ‹Žแ‰ณแˆ

$ git clone https://github.com/vzhou842/example-.io-game.git
$ cd example-.io-game
$ npm install

แŠฅแŠ“ แŠฅแˆญแˆตแ‹Ž แˆˆแˆ˜แˆ„แ‹ต แ‹แŒแŒ แАแ‹Žแ‰ต! แ‹จแˆแˆ›แ‰ต แŠ แŒˆแˆแŒ‹แ‹ฉแŠ• แˆˆแˆ˜แŒ€แˆ˜แˆญแฃ แ‰ แ‰€แˆ‹แˆ‰ แ‹ซแˆ‚แ‹ฑ

$ npm run develop

แŠฅแŠ“ แ‹ˆแ‹ฐ แ‹ตแˆญ แŠ แˆณแˆฝ แ‹ญแˆ‚แ‹ฑ แŠ แŠซแ‰ฃแ‰ขแ‹ซแ‹Š แˆ˜แŠ–แˆชแ‹ซ: 3000. แŠฎแ‹ฑ แˆฒแ‰€แ‹จแˆญ แ‹จแˆแˆ›แ‰ต แŠ แŒˆแˆแŒ‹แ‹ฉ แ‹จJS แŠฅแŠ“ CSS แ“แŠฌแŒ†แ‰ฝแŠ• แ‰ แˆซแˆต แˆฐแˆญ แˆ˜แˆแˆถ แ‹ญแŒˆแАแ‰ฃแˆ - แˆแˆ‰แŠ•แˆ แˆˆแ‹แŒฆแ‰ฝ แˆˆแˆ›แ‹จแ‰ต แŒˆแŒนแŠ• แ‰ฅแ‰ป แ‹ซแ‹ตแˆฑ!

3. แ‹จแ‹ฐแŠ•แ‰ แŠ› แˆ˜แŒแ‰ขแ‹ซ แАแŒฅแ‰ฆแ‰ฝ

แ‹ˆแ‹ฐ แŒจแ‹‹แ‰ณแ‹ แŠฎแ‹ต แŠฅแˆซแˆฑ แŠฅแŠ•แ‹แˆจแ‹ตแข แˆ˜แŒ€แˆ˜แˆชแ‹ซ แŒˆแŒฝ แŠฅแŠ•แˆแˆแŒ‹แˆˆแŠ• index.html, แŒฃแ‰ขแ‹ซแ‹แŠ• แˆฒแŒŽแ‰ แŠ™, แŠ แˆณแˆน แˆ˜แŒ€แˆ˜แˆชแ‹ซ แ‹ญแŒญแАแ‹‹แˆ. แŒˆแŒปแ‰ฝแŠ• แ‰ แŒฃแˆ แ‰€แˆ‹แˆ แ‹ญแˆ†แŠ“แˆแก-

index.html

แˆแˆณแˆŒ .io แŒจแ‹‹แ‰ณ  แ‰ฐแŒซแ‹ˆแ‰ต

แ‹ญแˆ… แ‹จแŠฎแ‹ต แˆแˆณแˆŒ แŒแˆแŒฝ แˆˆแˆ›แ‹ตแˆจแŒ แ‰ แ‰ตแŠ•แˆน แ‰€แˆˆแˆ แ‹ซแˆˆ แАแ‹แฃ แŠฅแŠ“ แŠจแ‰ฅแ‹™ แˆŒแˆŽแ‰ฝ แ‹จแˆแŒฅแ แˆแˆณแˆŒแ‹Žแ‰ฝ แŒ‹แˆญ แ‰ฐแˆ˜แˆณแˆณแ‹ญ แАแŒˆแˆญ แŠ แ‹ฐแˆญแŒ‹แˆˆแˆแข แˆ™แˆ‰ แŠฎแ‹ต แˆแˆ แŒŠแ‹œ แ‰  แˆ‹แ‹ญ แˆŠแ‰ณแ‹ญ แ‹ญแ‰ฝแˆ‹แˆแข แ‹จแŠแˆแˆ™.

แŠฅแŠ“ แŠ แˆˆแА:

  • HTML5 แˆธแˆซ แŠ แ‰ฃแˆ (<canvas>) แŒจแ‹‹แ‰ณแ‹แŠ• แˆˆแˆ˜แˆตแˆซแ‰ต แ‹จแˆแŠ•แŒ แ‰€แˆแ‰ แ‰ตแข
  • <link> แ‹จแŠฅแŠ›แŠ• CSS แŒฅแ‰…แˆ แˆˆแˆ˜แŒจแˆ˜แˆญแข
  • <script> แ‹จแŠฅแŠ›แŠ• แŒƒแ‰ซแˆตแŠญแˆชแ•แ‰ต แŒฅแ‰…แˆ แˆˆแˆ˜แŒจแˆ˜แˆญแข
  • แ‹‹แŠ“ แˆแŠ“แˆŒ แŠจแ‰ฐแŒ แ‰ƒแˆš แˆตแˆ แŒ‹แˆญ <input> แŠฅแŠ“ แ‹จ PLAY แ‰แˆแ (<button>).

แ‹จแˆ˜แАแˆป แŒˆแŒนแŠ• แŠจแŒซแŠ‘ แ‰ แŠ‹แˆ‹ แŠ แˆณแˆน แŠจแˆ˜แŒแ‰ขแ‹ซ แАแŒฅแ‰ฅ JS แ‹แ‹ญแˆ แŒ€แˆแˆฎ แ‹จแŒƒแ‰ซแˆตแŠญแˆชแ•แ‰ต แŠฎแ‹ตแŠ• แˆ˜แ‰ฐแŒแ‰ แˆญ แ‹ญแŒ€แˆแˆซแˆแข src/client/index.js.

index.js

import { connect, play } from './networking';
import { startRendering, stopRendering } from './render';
import { startCapturingInput, stopCapturingInput } from './input';
import { downloadAssets } from './assets';
import { initState } from './state';
import { setLeaderboardHidden } from './leaderboard';

import './css/main.css';

const playMenu = document.getElementById('play-menu');
const playButton = document.getElementById('play-button');
const usernameInput = document.getElementById('username-input');

Promise.all([
  connect(),
  downloadAssets(),
]).then(() => {
  playMenu.classList.remove('hidden');
  usernameInput.focus();
  playButton.onclick = () => {
    // Play!
    play(usernameInput.value);
    playMenu.classList.add('hidden');
    initState();
    startCapturingInput();
    startRendering();
    setLeaderboardHidden(false);
  };
});

แ‹ญแˆ… แ‹แˆตแ‰ฅแˆตแ‰ฅ แˆŠแˆ˜แˆตแˆ แ‹ญแ‰ฝแˆ‹แˆแฃ แАแŒˆแˆญ แŒแŠ• แŠฅแ‹šแˆ… แ‰ฅแ‹™ แŠฅแ‹จแ‰ฐแŠซแˆ„แ‹ฐ แŠ แ‹ญแ‹ฐแˆˆแˆแก

  1. แˆŒแˆŽแ‰ฝ แ‰ แˆญแŠซแ‰ณ JS แ‹แ‹ญแˆŽแ‰ฝแŠ• แ‰ แˆ›แˆตแˆ˜แŒฃแ‰ต แˆ‹แ‹ญแข
  2. CSS แˆ›แˆตแˆ˜แŒฃแ‰ต (แˆตแˆˆแ‹šแˆ… แ‹Œแ‰ฅแ“แŠญ แ‰ CSS แŒฅแ‰…แˆ แ‹แˆตแŒฅ แŠฅแŠ•แ‹ฐแˆšแ‹ซแŠซแ‰ตแ‰ณแ‰ธแ‹ แ‹ซแ‹แ‰ƒแˆ)แข
  3. ะ—ะฐะฟัƒัะบ connect() แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แŒ‹แˆญ แŒแŠ•แŠ™แАแ‰ต แˆˆแˆ˜แˆ˜แˆตแˆจแ‰ต แŠฅแŠ“ แˆˆแˆ›แˆ„แ‹ต downloadAssets() แŒจแ‹‹แ‰ณแ‹แŠ• แˆˆแˆ˜แˆตแˆซแ‰ต แ‹จแˆšแ‹ซแˆตแˆแˆแŒ‰ แˆแˆตแˆŽแ‰ฝแŠ• แˆˆแˆ›แ‹แˆจแ‹ตแข
  4. แ‹ฐแˆจแŒƒ 3 แŠจแ‰ฐแŒ แŠ“แ‰€แ‰€ แ‰ แŠ‹แˆ‹ แ‹‹แŠ“แ‹ แˆแŠ“แˆŒ แ‹ญแ‰ณแ‹ซแˆ (playMenu).
  5. แ‹จ"PLAY" แ‰แˆแแŠ• แˆˆแˆ˜แŒซแŠ• แ‰ฐแ‰†แŒฃแŒฃแˆชแ‹แŠ• แ‰ แˆ›แ‹˜แŒ‹แŒ€แ‰ต แˆ‹แ‹ญแข แ‰แˆแ‰ แˆฒแŒซแŠ• แŠฎแ‹ฑ แŒจแ‹‹แ‰ณแ‹แŠ• แ‹ญแŒ€แˆแˆซแˆ แŠฅแŠ“ แŠฅแŠ› แˆˆแˆ˜แŒซแ‹ˆแ‰ต แ‹แŒแŒ แˆ˜แˆ†แŠ“แ‰ฝแŠ•แŠ• แˆˆแŠ แŒˆแˆแŒ‹แ‹ฉ แ‹ญแАแŒแˆจแ‹‹แˆแข

แ‹จแŠฅแŠ› แ‹จแ‹ฐแŠ•แ‰ แŠ›-แŠ แŒˆแˆแŒ‹แ‹ญ แŠ แˆ˜แŠญแŠ•แ‹ฎ แ‹‹แŠ“แ‹ "แˆตแŒ‹" แ‰ แ‹แ‹ญแˆ‰ แ‹ˆแ‹ฐ แˆ€แŒˆแˆญ แ‹แˆตแŒฅ แ‰ แŒˆแ‰กแ‰ต แ‹แ‹ญแˆŽแ‰ฝ แ‹แˆตแŒฅ แАแ‹ index.js. แŠ แˆแŠ• แˆแˆ‰แŠ•แˆ แ‰ แ‰…แ‹ฐแˆ แ‰ฐแŠจแ‰ฐแˆ แŠฅแŠ•แˆ˜แˆˆแŠจแ‰ณแˆˆแŠ•.

4. แ‹จแ‹ฐแŠ•แ‰ แŠ› แ‹แˆ‚แ‰ฅ แˆ˜แˆˆแ‹‹แ‹ˆแŒฅ

แ‰ แ‹šแˆ… แŒจแ‹‹แ‰ณ แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แŒ‹แˆญ แˆˆแˆ˜แŒˆแŠ“แŠ˜แ‰ต แ‹จแ‰ณแ‹ˆแ‰€ แ‰คแ‰ฐ-แˆ˜แŒฝแˆแแ‰ตแŠ• แŠฅแŠ•แŒ แ‰€แˆ›แˆˆแŠ•แข แˆถแŠฌแ‰ต.io. Socket.io แ‰คแ‰ฐแŠ› แ‹ตแŒ‹แ แŠ แˆˆแ‹แข แ‹Œแ‰ฅแˆณแ‹ญแ‰ถแ‰ฝ, แˆˆแˆแˆˆแ‰ต แˆ˜แŠ•แŒˆแ‹ต แŒแŠ•แŠ™แАแ‰ต แ‰ แŒฃแˆ แ‰ฐแˆตแˆ›แˆš แ‹จแˆ†แŠ‘แ‰ต: แ‹ˆแ‹ฐ แŠ แŒˆแˆแŒ‹แ‹ฉ แˆ˜แˆแŠฅแŠญแ‰ต แˆ˜แˆ‹แŠญ แŠฅแŠ•แ‰ฝแˆ‹แˆˆแŠ• ะธ แŠ แŒˆแˆแŒ‹แ‹ฉ แ‰ แ‰ฐแˆ˜แˆณแˆณแ‹ญ แŒแŠ•แŠ™แАแ‰ต แˆ‹แ‹ญ แˆ˜แˆแŠฅแŠญแ‰ต แˆŠแˆแŠญแˆแŠ• แ‹ญแ‰ฝแˆ‹แˆแข

แŠ แŠ•แ‹ต แ‹แ‹ญแˆ แ‹ญแŠ–แˆจแŠ“แˆ src/client/networking.jsแˆ›แŠ• แ‹ญแŠ•แŠจแ‰ฃแŠจแ‰ฃแˆ แˆแˆ‰แˆ แˆฐแ‹ แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แŒ‹แˆญ แˆ˜แŒˆแŠ“แŠ˜แ‰ต;

แŠ แ‹แ‰ณแˆจ แˆ˜แˆจแ‰ฅ.js

import io from 'socket.io-client';
import { processGameUpdate } from './state';

const Constants = require('../shared/constants');

const socket = io(`ws://${window.location.host}`);
const connectedPromise = new Promise(resolve => {
  socket.on('connect', () => {
    console.log('Connected to server!');
    resolve();
  });
});

export const connect = onGameOver => (
  connectedPromise.then(() => {
    // Register callbacks
    socket.on(Constants.MSG_TYPES.GAME_UPDATE, processGameUpdate);
    socket.on(Constants.MSG_TYPES.GAME_OVER, onGameOver);
  })
);

export const play = username => {
  socket.emit(Constants.MSG_TYPES.JOIN_GAME, username);
};

export const updateDirection = dir => {
  socket.emit(Constants.MSG_TYPES.INPUT, dir);
};

แ‹ญแˆ… แŠฎแ‹ต แˆˆแŒแˆแŒฝแАแ‰ตแˆ แ‰ แ‰ตแŠ•แˆน แŠฅแŠ•แ‹ฒแ‰ณแŒ แˆญ แ‰ฐแ‹ฐแˆญแŒ“แˆแข

แ‰ แ‹šแˆ… แ‹แ‹ญแˆ แ‹แˆตแŒฅ แˆถแˆตแ‰ต แ‹‹แŠ“ แ‰ฐแŒแ‰ฃแˆซแ‰ต แŠ แˆ‰แก-

  • แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แŒ‹แˆญ แˆˆแˆ˜แŒˆแŠ“แŠ˜แ‰ต แŠฅแ‹จแˆžแŠจแˆญแŠ• แАแ‹แข connectedPromise แŒแŠ•แŠ™แАแ‰ต แˆตแŠ•แˆแŒฅแˆญ แ‰ฅแ‰ป แАแ‹ แ‹จแˆšแˆแ‰€แ‹ฐแ‹แข
  • แŒแŠ•แŠ™แАแ‰ฑ แˆตแŠฌแ‰ณแˆ› แŠจแˆ†แА แ‹จแˆ˜แˆแˆถ แˆ˜แ‹ฐแ‹ˆแˆ แ‰ฐแŒแ‰ฃแˆซแ‰ตแŠ• แŠฅแŠ•แˆ˜แ‹˜แŒแ‰ฃแˆˆแŠ• (processGameUpdate() ะธ onGameOver()) แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แˆแŠ•แ‰€แ‰ แˆ‹แ‰ธแ‹ แˆˆแˆšแ‰ฝแˆ‰ แˆ˜แˆแŠฅแŠญแ‰ถแ‰ฝแข
  • แ‹ˆแ‹ฐ แ‹แŒญ แŠฅแŠ•แˆแŠซแˆˆแŠ• play() ะธ updateDirection()แˆŒแˆŽแ‰ฝ แ‹แ‹ญแˆŽแ‰ฝแŠ• แˆ˜แŒ แ‰€แˆ แŠฅแŠ•แ‹ฒแ‰ฝแˆ‰.

5. แ‹จแ‹ฐแŠ•แ‰ แŠ› แŠ แ‰€แˆซแˆจแ‰ฅ

แˆแˆตแˆ‰แŠ• แ‰ แˆตแŠญแˆชแŠ‘ แˆ‹แ‹ญ แˆˆแˆ›แˆณแ‹จแ‰ต แŒŠแ‹œแ‹ แŠ แˆแŠ• แАแ‹!

แАแŒˆแˆญ แŒแŠ• แ‹ซแŠ•แŠ• แŠจแˆ›แ‹ตแˆจแŒ‹แ‰ฝแŠ• แ‰ แŠแ‰ตแฃ แˆˆแ‹šแˆ… โ€‹โ€‹แ‹จแˆšแ‹ซแˆตแˆแˆแŒ‰แ‰ตแŠ• แˆแˆ‰แŠ•แˆ แˆแˆตแˆŽแ‰ฝ (แˆƒแ‰ฅแ‰ถแ‰ฝ) แˆ›แ‹แˆจแ‹ต แŠ แˆˆแ‰ฅแŠ•แข แ‹จแˆ€แ‰ฅแ‰ต แŠ แˆตแ‰ฐแ‹ณแ‹ณแˆชแŠ• แŠฅแŠ•แƒแแก-

แŠ•แ‰ฅแˆจแ‰ถแ‰ฝ.js

const ASSET_NAMES = ['ship.svg', 'bullet.svg'];

const assets = {};
const downloadPromise = Promise.all(ASSET_NAMES.map(downloadAsset));

function downloadAsset(assetName) {
  return new Promise(resolve => {
    const asset = new Image();
    asset.onload = () => {
      console.log(`Downloaded ${assetName}`);
      assets[assetName] = asset;
      resolve();
    };
    asset.src = `/assets/${assetName}`;
  });
}

export const downloadAssets = () => downloadPromise;
export const getAsset = assetName => assets[assetName];

แ‹จแˆ€แ‰ฅแ‰ต แŠ แˆตแ‰ฐแ‹ณแ‹ฐแˆญ แˆˆแˆ˜แ‰ฐแŒแ‰ แˆญ แ‹ซแŠ• แ‹ซแˆ…แˆ แŠจแ‰ฃแ‹ต แŠ แ‹ญแ‹ฐแˆˆแˆ! แ‹‹แŠ“แ‹ แˆ€แˆณแ‰ฅ แŠฅแ‰ƒ แˆ›แŠจแˆ›แ‰ธแ‰ต แАแ‹ assets, แ‹ญแˆ…แˆ แ‹จแ‹แ‹ญแˆ แˆตแˆ แ‰แˆแแŠ• แŠจแŠฅแ‰ƒแ‹ แ‹‹แŒ‹ แŒ‹แˆญ แ‹ซแˆตแˆซแˆ Image. แˆ€แ‰ฅแ‰ฑ แˆฒแŒซแŠ• แ‰ แŠ แŠ•แ‹ต แ‹•แ‰ƒ แ‹แˆตแŒฅ แŠฅแŠ“แŠจแˆ›แ‰ปแˆˆแŠ• assets แˆˆแ‹ˆแ‹ฐแŠแ‰ฑ แˆแŒฃแŠ• แˆ˜แ‹ณแˆจแˆป. แŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แŒแˆˆแˆฐแ‰ฅ แˆ˜แŒˆแˆแŒˆแ‹ซ แˆ˜แ‰ผ แˆ›แ‹แˆจแ‹ต แ‹ญแˆแ‰€แ‹ตแˆˆแ‰ณแˆ (แˆ›แˆˆแ‰ตแˆแฃ แˆแˆ‰แˆ แˆ€แ‰ฅแ‰ถแ‰ฝ) แŠฅแŠ•แˆแ‰…แ‹ณแˆˆแŠ•แข downloadPromise.

แŠ•แ‰ฅแˆจแ‰ถแ‰นแŠ• แŠซแ‹ˆแˆจแ‹ฑ แ‰ แŠ‹แˆ‹ แˆ›แˆณแ‹จแ‰ต แˆ˜แŒ€แˆ˜แˆญ แ‹ญแ‰ฝแˆ‹แˆ‰แข แ‰€แ‹ฐแˆ แˆฒแˆ แŠฅแŠ•แ‹ฐแ‰ฐแŠ“แŒˆแˆจแ‹, แ‰ แ‹ตแˆจ-แŒˆแŒฝ แˆ‹แ‹ญ แˆˆแˆ˜แˆณแˆ, แŠฅแŠ•แŒ แ‰€แˆ›แˆˆแŠ• HTML5 แˆธแˆซ (<canvas>). แ‹จแŠ› แŒจแ‹‹แ‰ณ แ‰ แŒฃแˆ แ‰€แˆ‹แˆ แАแ‹ แˆตแˆˆแ‹šแˆ… แ‹จแˆšแŠจแ‰ฐแˆˆแ‹แŠ• แˆ˜แˆณแˆ แ‰ฅแ‰ป แ‹ซแˆตแˆแˆแŒˆแŠ“แˆแข

  1. แŒ€แˆญแ‰ฃ
  2. แ‹จแ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แˆญแŠจแ‰ฅ
  3. แ‰ แŒจแ‹‹แ‰ณแ‹ แ‹แˆตแŒฅ แ‹ซแˆ‰ แˆŒแˆŽแ‰ฝ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ
  4. แ‹›แŒŽแˆŽแ‰ฝ

แŠ แˆตแˆแˆ‹แŒŠแ‹Žแ‰น แ‰…แŠ•แŒฅแ‰ฆแ‰ฝ แŠฅแ‹šแˆ… แŠ แˆ‰แข src/client/render.jsแŠจแˆ‹แ‹ญ แ‹จแ‰ฐแ‹˜แˆจแ‹˜แˆฉแ‰ตแŠ• แŠ แˆซแ‰ต แАแŒˆแˆฎแ‰ฝ แ‰ แ‰ตแŠญแŠญแˆ แ‹จแˆšแ‹ซแ‰€แˆญแ‰ฅแก-

แˆ›แ‰…แˆจแ‰ฅ.js

import { getAsset } from './assets';
import { getCurrentState } from './state';

const Constants = require('../shared/constants');
const { PLAYER_RADIUS, PLAYER_MAX_HP, BULLET_RADIUS, MAP_SIZE } = Constants;

// Get the canvas graphics context
const canvas = document.getElementById('game-canvas');
const context = canvas.getContext('2d');

// Make the canvas fullscreen
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

function render() {
  const { me, others, bullets } = getCurrentState();
  if (!me) {
    return;
  }

  // Draw background
  renderBackground(me.x, me.y);

  // Draw all bullets
  bullets.forEach(renderBullet.bind(null, me));

  // Draw all players
  renderPlayer(me, me);
  others.forEach(renderPlayer.bind(null, me));
}

// ... Helper functions here excluded

let renderInterval = null;
export function startRendering() {
  renderInterval = setInterval(render, 1000 / 60);
}
export function stopRendering() {
  clearInterval(renderInterval);
}

แ‹ญแˆ… แŠฎแ‹ต แŒแˆแŒฝ แˆˆแˆ›แ‹ตแˆจแŒแˆ แŠ แŒ แˆจแข

render() แ‹จแ‹šแˆ… แ‹แ‹ญแˆ แ‹‹แŠ“ แ‰ฐแŒแ‰ฃแˆญ แАแ‹แข startRendering() ะธ stopRendering() แ‹จ render loop แŠ• แŠฅแŠ•แ‰…แˆตแ‰ƒแˆด แ‰ 60 FPS แ‹ญแ‰†แŒฃแŒ แˆฉแข

แ‹จแŒแˆˆแˆฐแ‰ฃแ‹Š แˆจแ‹ณแ‰ต แ‰ฐแŒแ‰ฃแˆซแ‰ต แ‰ฐแŒจแ‰ฃแŒญ แ‰ตแŒแ‰ แˆซแ‹Žแ‰ฝ (แˆˆแˆแˆณแˆŒ แฃ renderBullet()) แ‹ซแŠ• แ‹ซแˆ…แˆ แŠ แˆตแˆแˆ‹แŒŠ แŠ แ‹ญแ‹ฐแˆ‰แˆแฃ แŒแŠ• แŠ แŠ•แ‹ต แ‰€แˆ‹แˆ แˆแˆณแˆŒ แŠฅแ‹šแˆ… แŠ แˆˆแก-

แˆ›แ‰…แˆจแ‰ฅ.js

function renderBullet(me, bullet) {
  const { x, y } = bullet;
  context.drawImage(
    getAsset('bullet.svg'),
    canvas.width / 2 + x - me.x - BULLET_RADIUS,
    canvas.height / 2 + y - me.y - BULLET_RADIUS,
    BULLET_RADIUS * 2,
    BULLET_RADIUS * 2,
  );
}

แ‹˜แ‹ดแ‹แŠ• แŠฅแ‹จแ‰ฐแŒ แ‰€แˆแŠ• แˆ˜แˆ†แŠ‘แŠ• แˆแ‰ฅ แ‹ญแ‰ แˆ‰ getAsset(), แ‰€แ‹ฐแˆ แˆฒแˆ แ‰  แ‹แˆตแŒฅ แ‰ณแ‹ญแ‰ทแˆ asset.js!

แˆตแˆˆ แˆŒแˆŽแ‰ฝ แŠ แŒ‹แ‹ฅ แˆจแ‹ณแ‰ถแ‰ฝ แˆˆแˆ˜แˆ›แˆญ แแˆ‹แŒŽแ‰ต แŠซแˆŽแ‰ต แ‹จแ‰€แˆจแ‹แŠ• แ‹ซแŠ•แ‰ฅแ‰กแข src/แ‹ฐแŠ•แ‰ แŠ›/render.js.

6. แ‹จแ‹ฐแŠ•แ‰ แŠ› แŒแ‰คแ‰ต

แŒจแ‹‹แ‰ณ แˆˆแˆ˜แˆตแˆซแ‰ต แŒŠแ‹œแ‹ แŠ แˆแŠ• แАแ‹แข แˆŠแŒซแ‹ˆแ‰ต แ‹จแˆšแ‰ฝแˆ! แ‹จแ‰แŒฅแŒฅแˆญ แˆ˜แˆญแˆƒแŒแ‰ฅแˆฉ แ‰ แŒฃแˆ แ‰€แˆ‹แˆ แ‹ญแˆ†แŠ“แˆ-แ‹จแŠฅแŠ•แ‰…แˆตแ‰ƒแˆดแ‹แŠ• แŠ แ‰…แŒฃแŒซ แˆˆแˆ˜แˆˆแ‹ˆแŒฅ, แŠ แ‹ญแŒคแ‹แŠ• (แ‰ แŠฎแˆแ’แ‰ฐแˆญ แˆ‹แ‹ญ) แˆ˜แŒ แ‰€แˆ แ‹ˆแ‹ญแˆ แˆ›แ‹ซ แŒˆแŒนแŠ• (แ‰ แˆžแ‰ฃแ‹ญแˆ แˆ˜แˆณแˆชแ‹ซ แˆ‹แ‹ญ) แˆ˜แŠ•แŠซแ‰ต แ‹ญแ‰ฝแˆ‹แˆ‰. แ‹ญแˆ…แŠ•แŠ• แ‰ฐแŒแ‰ฃแˆซแ‹Š แˆˆแˆ›แ‹ตแˆจแŒ แŠฅแŠ•แˆ˜แ‹˜แŒแ‰ฃแˆˆแŠ•แข แ‹จแ‹แŒแŒ…แ‰ต แŠ แ‹ตแˆ›แŒฎแ‰ฝ แˆˆ Mouse แŠฅแŠ“ Touch แŠญแˆตแ‰ฐแ‰ถแ‰ฝ.
แ‹ญแˆ…แŠ•แŠ• แˆแˆ‰ แ‹ญแŠ•แŠจแ‰ฃแŠจแ‰ฃแˆ src/client/input.js:

แŒแ‰ฅแ‹“แ‰ต.js

import { updateDirection } from './networking';

function onMouseInput(e) {
  handleInput(e.clientX, e.clientY);
}

function onTouchInput(e) {
  const touch = e.touches[0];
  handleInput(touch.clientX, touch.clientY);
}

function handleInput(x, y) {
  const dir = Math.atan2(x - window.innerWidth / 2, window.innerHeight / 2 - y);
  updateDirection(dir);
}

export function startCapturingInput() {
  window.addEventListener('mousemove', onMouseInput);
  window.addEventListener('touchmove', onTouchInput);
}

export function stopCapturingInput() {
  window.removeEventListener('mousemove', onMouseInput);
  window.removeEventListener('touchmove', onTouchInput);
}

onMouseInput() ะธ onTouchInput() แŒฅแˆช แ‹จแˆšแ‹ซแ‹ฐแˆญแŒ‰ แ‹จแŠญแˆตแ‰ฐแ‰ต แŠ แ‹ตแˆ›แŒฎแ‰ฝ แŠ“แ‰ธแ‹แข updateDirection() (แ‹จ networking.js) แ‹จแŒแ‰คแ‰ต แŠญแˆตแ‰ฐแ‰ต แˆฒแŠจแˆฐแ‰ต (แˆˆแˆแˆณแˆŒ, แŠ แ‹ญแŒฅ แˆฒแŠ•แ‰€แˆณแ‰€แˆต). updateDirection() แ‹จแŒแ‰ฅแŠ แ‰ต แŠญแˆตแ‰ฐแ‰ตแŠ• แ‹จแˆšแ‹ซแˆตแ‰ฐแŠ“แŒแ‹ต แŠฅแŠ“ แ‹จแŒจแ‹‹แ‰ณแ‹แŠ• แˆแŠ”แ‰ณ แ‹จแˆšแ‹ซแˆปแˆฝแˆˆแ‹ แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แŒ‹แˆญ แˆ˜แˆแŠฅแŠญแ‰ตแŠ• แ‹ซแˆตแ‰ฐแŠ“แŒแ‹ณแˆแข

7. แ‹จแ‹ฐแŠ•แ‰ แŠ› แˆแŠ”แ‰ณ

แ‹ญแˆ… แŠญแแˆ แ‰ แ–แˆตแ‰ณแ‹ แ‹จแˆ˜แŒ€แˆ˜แˆชแ‹ซ แŠญแแˆ แ‹แˆตแŒฅ แ‰ แŒฃแˆ แŠ แˆตแ‰ธแŒ‹แˆชแ‹ แАแ‹. แˆˆแˆ˜แŒ€แˆ˜แˆชแ‹ซ แŒŠแ‹œ แˆตแ‰ณแАแ‰กแ‰ต แŠซแˆแ‰ฐแˆจแ‹ณแ‰ฝแˆแ‰ต แ‰ฐแˆตแ‹ แŠ แ‰ตแ‰แˆจแŒก! แŠฅแŠ•แ‹ฒแ‹ซแ‹แˆ แˆ˜แ‹แˆˆแˆ แ‰ตแ‰ฝแˆ‹แˆˆแˆ… แŠฅแŠ“ แ‰ แŠ‹แˆ‹ แ‹ˆแ‹ฐ แŠฅแˆฑ แˆ˜แˆ˜แˆˆแˆต แ‰ตแ‰ฝแˆ‹แˆˆแˆ…แข

แ‹จแ‹ฐแŠ•แ‰ แŠ›แ‹/แ‹จแŠ แŒˆแˆแŒ‹แ‹ญ แŠฎแ‹ต แˆˆแˆ›แŒ แŠ“แ‰€แ‰… แ‹จแˆšแ‹ซแˆตแˆแˆแŒˆแ‹ แ‹จแˆ˜แŒจแˆจแˆปแ‹ แ‹จแŠฅแŠ•แ‰†แ‰…แˆแˆฝ แŠญแแˆ แАแ‹แข แŒแ‹›แ‰ต. แŠจแ‹ฐแŠ•แ‰ แŠ› แŠ แ‰ฐแˆจแŒ“แŒŽแˆ แŠญแแˆ แ‹จแ‰ฐแŒˆแŠ˜แ‹แŠ• แ‹จแŠฎแ‹ต แ‰…แŠ•แŒฃแ‰ข แŠ แˆตแ‰ณแ‹แˆต?

แˆ›แ‰…แˆจแ‰ฅ.js

import { getCurrentState } from './state';

function render() {
  const { me, others, bullets } = getCurrentState();

  // Do the rendering
  // ...
}

getCurrentState() แ‰ แ‹ฐแŠ•แ‰ แŠ›แ‹ แ‹แˆตแŒฅ แ‹จแŒจแ‹‹แ‰ณแ‹แŠ• แ‹ˆแ‰…แ‰ณแ‹Š แˆแŠ”แ‰ณ แˆŠแˆฐแŒ แŠ• แ‹ญแŒˆแ‰ฃแˆ แ‰ แˆ›แŠ•แŠ›แ‹แˆ แŒŠแ‹œ แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แ‰ แ‰ฐแ‰€แ‰ แˆ‰แ‰ต แ‹แˆ˜แŠ“แ‹Žแ‰ฝ แˆ‹แ‹ญ แ‰ แˆ˜แˆ˜แˆตแˆจแ‰ตแข แŠ แŒˆแˆแŒ‹แ‹ฉ แˆŠแˆแŠญ แ‹จแˆšแ‰ฝแˆˆแ‹ แ‹จแŒจแ‹‹แ‰ณ แˆ›แˆปแˆปแ‹ซ แˆแˆณแˆŒ แ‹ญแŠธแ‹แŠ“แก

{
  "t": 1555960373725,
  "me": {
    "x": 2213.8050880413657,
    "y": 1469.370893425012,
    "direction": 1.3082443894581433,
    "id": "AhzgAtklgo2FJvwWAADO",
    "hp": 100
  },
  "others": [],
  "bullets": [
    {
      "id": "RUJfJ8Y18n",
      "x": 2354.029197099604,
      "y": 1431.6848318262666
    },
    {
      "id": "ctg5rht5s",
      "x": 2260.546457727445,
      "y": 1456.8088728920968
    }
  ],
  "leaderboard": [
    {
      "username": "Player",
      "score": 3
    }
  ]
}

แŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“ แŠ แˆแˆตแ‰ต แ‰ฐแˆ˜แˆณแˆณแ‹ญ แˆ˜แˆตแŠฎแ‰ฝแŠ• แ‹ญแ‹ญแ‹›แˆแข

  • tแ‹ญแˆ… แ‹แˆ›แŠ” แˆ˜แ‰ผ แŠฅแŠ•แ‹ฐแ‰ฐแˆแŒ แˆจ แ‹จแˆšแŒ แ‰แˆ แ‹จแŠ แŒˆแˆแŒ‹แ‹ญ แ‹จแŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆแข
  • meแ‹ญแˆ…แŠ•แŠ• แ‹แˆ˜แŠ“ แˆตแˆˆแ‰ฐแ‰€แ‰ แˆˆแ‹ แ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แˆจแŒƒแข
  • แˆŒแˆŽแ‰ฝ: แˆŒแˆŽแ‰ฝ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แ‰ แ‰ฐแˆ˜แˆณแˆณแ‹ญ แŒจแ‹‹แ‰ณ แ‹แˆตแŒฅ แˆตแˆˆแˆšแˆณแ‰ฐแ‰ แˆ˜แˆจแŒƒ แ‹ตแˆญแ‹ตแˆญแข
  • แŒฅแ‹ญแ‰ถแ‰ฝแ‰ แŒจแ‹‹แ‰ณแ‹ แ‹แˆตแŒฅ แˆตแˆˆ แ•แˆฎแŒ„แŠญแ‰ถแ‰ฝ แˆ˜แˆจแŒƒ แ‹ตแˆญแ‹ตแˆญแข
  • แ‹จแˆ˜แˆชแ‹Žแ‰ฝ แˆฐแˆŒแ‹ณแŠ แˆแŠ• แ‹ซแˆˆแ‹ แ‹จแˆ˜แˆชแ‹Žแ‰ฝ แˆฐแˆŒแ‹ณ แ‹แˆ‚แ‰ฅแข แ‰ แ‹šแˆ… แŒฝแˆ‘แ แ‹แˆตแŒฅ, แŠฅแŠ› แŠ แŠ•แˆ˜แˆˆแŠจแ‰ณแ‰ธแ‹แˆ.

7.1 Naive แ‹ฐแŠ•แ‰ แŠ› แˆแŠ”แ‰ณ

แ‹จแ‹‹แˆ… แŠ แ‰ฐแŒˆแ‰ฃแ‰ แˆญ getCurrentState() แ‰ แ‰…แˆญแ‰ก แ‹จแ‰ฐแ‰€แ‰ แˆˆแ‹แŠ• แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“ แ‹แˆ‚แ‰ฅ แ‰ฅแ‰ป แАแ‹ แˆ˜แˆ˜แˆˆแˆต แ‹จแˆšแ‰ฝแˆˆแ‹แข

naive-state.js

let lastGameUpdate = null;

// Handle a newly received game update.
export function processGameUpdate(update) {
  lastGameUpdate = update;
}

export function getCurrentState() {
  return lastGameUpdate;
}

แ‰†แŠ•แŒ† แŠฅแŠ“ แŒแˆแŒฝ! แŒแŠ• แ‹ซแŠ• แ‹ซแˆ…แˆ แ‰€แˆ‹แˆ แ‰ขแˆ†แŠ• แŠ–แˆฎแข แ‹จแ‹šแˆ… แŠ แ‰ฐแŒˆแ‰ฃแ‰ แˆญ แŠ แŠ•แ‹ฑ แˆแŠญแŠ•แ‹ซแ‰ต แ‰ฝแŒแˆญ แ‹ซแˆˆแ‰ แ‰ต แАแ‹แก- แ‹จแแˆฌแˆ แแŒฅแАแ‰ฑแŠ• แ‰ แŠ แŒˆแˆแŒ‹แ‹ญ แˆฐแ‹“แ‰ต แแŒฅแАแ‰ต แ‹ญแŒˆแ‹ตแ‰ฃแˆ.

แ‹จแแˆฌแˆ แˆ˜แŒ แŠ•แ‹จแŠญแˆแŽแ‰ฝ แ‰ฅแ‹›แ‰ต (แˆ›แˆˆแ‰ตแˆ แŒฅแˆชแ‹Žแ‰ฝ render()) แ‰ แˆฐแŠจแŠ•แ‹ต แ‹ˆแ‹ญแˆ FPS. แŒจแ‹‹แ‰ณแ‹Žแ‰ฝ แŠ แ‰ฅแ‹›แŠ›แ‹แŠ• แŒŠแ‹œ แ‰ขแ‹ซแŠ•แˆต 60 FPS แˆˆแˆ›แŒแŠ˜แ‰ต แ‹ญแŒฅแˆซแˆ‰.

แ‹จแˆแˆแŠญแ‰ต แˆ˜แŒ แŠ•: แŠ แŒˆแˆแŒ‹แ‹ฉ แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“แ‹Žแ‰ฝแŠ• แˆˆแ‹ฐแŠ•แ‰ แŠžแ‰ฝ แ‹จแˆšแˆแŠญแ‰ แ‰ต แ‹ตแŒแŒแˆžแˆฝแข แ‰ฅแ‹™แ‹แŠ• แŒŠแ‹œ แŠจแŠญแˆแ‰ แแŒฅแАแ‰ต แ‹ซแАแˆฐ แАแ‹. แ‰ แŠฅแŠ› แŒจแ‹‹แ‰ณ แŠ แŒˆแˆแŒ‹แ‹ฉ แ‰ แˆฐแŠจแŠ•แ‹ต แ‰ 30 แ‹‘แ‹ฐแ‰ถแ‰ฝ แ‹ตแŒแŒแˆžแˆฝ แ‹ญแˆฐแˆซแˆแข

แ‹จแŒจแ‹‹แ‰ณแ‹แŠ• แ‹จแ‰…แˆญแ‰ฅ แŒŠแ‹œ แ‹แˆ˜แŠ“ แ‰ฅแ‰ป แŠซแ‰€แˆจแ‰ฅแŠ•แฃ FPS แ‰ แˆ˜แˆ แˆจแ‰ฑ แŠจ 30 แ‰ แˆ‹แ‹ญ แŠ แ‹ญแ‰ แˆแŒฅแˆ แฃ แˆแŠญแŠ•แ‹ซแ‰ฑแˆ แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แ‰ แˆฐแŠจแŠ•แ‹ต แŠจ30 แ‰ แˆ‹แ‹ญ แ‹แˆ›แŠ”แ‹Žแ‰ฝแŠ• แŠ แŠ“แŒˆแŠแˆแข. แ‰ฅแŠ•แŒ แˆซแˆ render() แ‰ แˆฐแŠจแŠ•แ‹ต 60 แŒŠแ‹œแฃ แŠจแ‹šแ‹ซ แŠจแŠฅแАแ‹šแˆ… แŒฅแˆชแ‹Žแ‰ฝ แ‹แˆตแŒฅ แŒแˆ›แˆพแ‰น แŠ แŠ•แ‹ต แŠ แ‹ญแАแ‰ต แАแŒˆแˆญ แŠฅแŠ•แ‹ฐแŒˆแŠ“ แ‹ญแ‰€แ‹ญแˆซแˆ‰แฃ แ‰ แˆ˜แˆ แˆจแ‰ฑ แˆแŠ•แˆ แАแŒˆแˆญ แŠ แ‹ซแ‹ฐแˆญแŒ‰แˆแข แˆŒแˆ‹แ‹ แ‹จแ‹‹แˆ… แŠ แ‰ฐแŒˆแ‰ฃแ‰ แˆญ แ‰ฝแŒแˆญ แАแ‹แข แˆˆแˆ˜แ‹˜แŒแ‹จแ‰ต แ‹จแ‰ฐแŒ‹แˆˆแŒ . แ‰ แŒฅแˆฉ แ‹จแŠขแŠ•แ‰ฐแˆญแŠ”แ‰ต แแŒฅแАแ‰ตแฃ แ‹ฐแŠ•แ‰ แŠ›แ‹ แ‰ แ‹จ33แˆšแˆด (แ‰ แˆดแŠฎแŠ•แ‹ต 30 แ‰ แˆฐแŠจแŠ•แ‹ต) แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ›แŠ” แ‹ญแ‰€แ‰ แˆ‹แˆแข

แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แ‰ แˆšแ‹ซแˆณแ‹แŠ• แˆแŠ”แ‰ณ, แˆแŠ•แˆ แАแŒˆแˆญ แแŒนแˆ แŠ แ‹ญแ‹ฐแˆˆแˆ. แ‹จแ‰ แˆˆแŒ  แ‰ตแŠญแŠญแˆˆแŠ› แˆแˆตแˆ แ‹จแˆšแŠจแ‰ฐแˆˆแ‹ แ‹ญแˆ†แŠ“แˆ-
แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แ‹จแ‹‹แˆ…แАแ‰ต แŠ แ‰ฐแŒˆแ‰ฃแ‰ แˆญ แ‹ˆแ‹ฐ แˆ˜แ‹˜แŒแ‹จแ‰ต แˆฒแˆ˜แŒฃ แ‰ แ‰ฐแŒแ‰ฃแˆญ แŠฅแŒ…แŒ แ‹จแŠจแ‹ แАแ‹แข แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“ แ‰  50ms แˆ˜แ‹˜แŒแ‹จแ‰ต แŠจแ‹ฐแˆจแˆฐ แฃ แŠจแ‹šแ‹ซ แ‹จแ‹ฐแŠ•แ‰ แŠ› แˆ˜แˆธแŒซแ‹Žแ‰ฝ แ‰ฐแŒจแˆ›แˆช 50 แˆšแˆด แˆแŠญแŠ•แ‹ซแ‰ฑแˆ แŠ แˆแŠ•แˆ แ‹จแŒจแ‹‹แ‰ณแ‹แŠ• แˆแŠ”แ‰ณ แŠจแ‰€แ‹ฐแˆ˜แ‹ แ‹แˆ›แŠ” แŠฅแ‹ซแ‰€แˆจแ‰  แАแ‹แข แ‹ญแˆ… แˆˆแ‰ฐแŒซแ‹‹แ‰น แˆแŠ• แ‹ซแˆ…แˆ แ‹จแˆ›แ‹ญแˆ˜แ‰ฝ แŠฅแŠ•แ‹ฐแˆ†แА แˆ˜แŒˆแˆ˜แ‰ต แ‰ตแ‰ฝแˆ‹แˆ‹แ‰ฝแˆแก แ‹จแ‹˜แˆแ‰€แ‹ฐ แ‰ฅแˆฌแŠชแŠ•แŒ แŒจแ‹‹แ‰ณแ‹แŠ• แ‹ซแˆธแ‰ แˆจแ‰€ แŠฅแŠ“ แ‹ซแˆแ‰ฐแˆจแŒ‹แŒ‹ แ‹ซแ‹ฐแˆญแŒˆแ‹‹แˆแข

7.2 แ‹จแ‰ฐแˆปแˆปแˆˆ แ‹จแ‹ฐแŠ•แ‰ แŠ› แˆแŠ”แ‰ณ

แ‰ แŠ แˆแƒแ€แˆ™ แˆ‹แ‹ญ แŠ แŠ•แ‹ณแŠ•แ‹ต แˆ›แˆปแˆปแ‹ซแ‹Žแ‰ฝแŠ• แŠฅแŠ“แ‹ฐแˆญแŒ‹แˆˆแŠ•แข แ‰ แˆ˜แŒ€แˆ˜แˆชแ‹ซ, แŠฅแŠ•แŒ แ‰€แˆ›แˆˆแŠ• แˆ˜แ‹˜แŒแ‹จแ‰ต แˆ›แˆณแ‹จแ‰ต แˆˆ 100 ms. แ‹ญแˆ… แˆ›แˆˆแ‰ต แ‹จแ‹ฐแŠ•แ‰ แŠ›แ‹ "แ‹จแŠ แˆแŠ‘" แˆแŠ”แ‰ณ แˆแˆ แŒŠแ‹œ แ‰ แŠ แŒˆแˆแŒ‹แ‹ฉ แˆ‹แ‹ญ แŠซแˆˆแ‹ แ‹จแŒจแ‹‹แ‰ณ แˆแŠ”แ‰ณ แ‰ 100 แˆš.แˆด. แˆˆแˆแˆณแˆŒ, แ‰ แŠ แŒˆแˆแŒ‹แ‹ฉ แˆ‹แ‹ญ แ‹ซแˆˆแ‹ แŒŠแ‹œ แŠจแˆ†แА 150, แŠจแ‹šแ‹ซแˆ แ‹ฐแŠ•แ‰ แŠ›แ‹ แŠ แŒˆแˆแŒ‹แ‹ฉ แ‰ แ‹ˆแ‰…แ‰ฑ แ‹จแАแ‰ แˆจแ‹แŠ• แˆแŠ”แ‰ณ แ‹ซแ‰€แˆญแ‰ฃแˆ 50:

แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แ‹ญแˆ… แ‹จแˆ›แ‹ญแŒˆแˆ˜แ‰ต แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“ แŒŠแ‹œแ‹Žแ‰ฝแŠ• แˆˆแˆ˜แ‰ตแˆจแ แ‹จ100ms แ‰‹แ‰ต แ‹ญแˆฐแŒ แŠ“แˆแก

แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แˆˆแ‹šแˆ… แ‹ซแˆˆแ‹ แŠญแแ‹ซ แ‹˜แˆ‹แ‰‚ แ‹ญแˆ†แŠ“แˆ แ‹จแŒแ‰คแ‰ต แˆ˜แ‹˜แŒแ‹จแ‰ต แˆˆ 100 ms. แ‹ญแˆ… แˆˆแˆตแˆ‹แˆณ แŠ แŒจแ‹‹แ‹ˆแ‰ต แ‰€แˆ‹แˆ แˆ˜แˆตแ‹‹แŠฅแ‰ตแАแ‰ต แАแ‹ - แŠ แ‰ฅแ‹›แŠžแ‰น แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ (แ‰ แ‰ฐแˆˆแ‹ญ แ‰ฐแˆซ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ) แ‹ญแˆ…แŠ• แˆ˜แ‹˜แŒแ‹จแ‰ต แŠฅแŠ•แŠณแŠ• แŠ แ‹ซแˆตแ‰ฐแ‹แˆ‰แˆแข แ‰ฃแˆแ‰ฐแŒ แ‰ แ‰€ แˆ˜แ‹˜แŒแ‹จแ‰ต แŠจแˆ˜แŒซแ‹ˆแ‰ต แ‹ญแˆแ‰… แˆฐแ‹Žแ‰ฝ แ‰‹แˆš แ‹จแˆ†แА แ‹จ100 แˆš.แˆœ แˆ˜แ‹˜แŒแ‹จแ‰ตแŠ• แˆ›แˆตแ‰ฐแŠซแŠจแˆ แ‰€แˆ‹แˆ แАแ‹แข

แˆŒแˆ‹ แ‹จแˆšแ‰ฃแˆ แ‰ดแŠญแŠ’แŠญ แˆ˜แŒ แ‰€แˆ แŠฅแŠ•แ‰ฝแˆ‹แˆˆแŠ• แ‹จแ‹ฐแŠ•แ‰ แŠ›-แŒŽแŠ• แ‰ตแŠ•แ‰ แ‹ซ, แ‹ญแˆ…แˆ แ‹จแ‰ณแˆฐแ‰ แ‹แŠ• แˆ˜แ‹˜แŒแ‹จแ‰ตแŠ• แ‰ แˆ˜แ‰€แАแˆต แŒฅแˆฉ แˆตแˆซ แ‹ญแˆฐแˆซแˆ, แАแŒˆแˆญ แŒแŠ• แ‰ แ‹šแˆ… แŒฝแˆ‘แ แ‹แˆตแŒฅ แŠ แ‹ญแŠซแ‰ฐแ‰ตแˆ.

แˆŒแˆ‹แ‹ แŠฅแ‹จแ‰ฐแŒ แ‰€แˆแŠ•แ‰ แ‰ต แ‹ซแˆˆแ‹ แˆ›แˆปแˆปแ‹ซ แАแ‹แข แˆ˜แˆตแˆ˜แˆซแ‹Š แŒฃแˆแ‰ƒแŒˆแ‰ฅแАแ‰ต. แ‰ แˆ›แˆณแ‹จแ‰ต แˆ˜แ‹˜แŒแ‹จแ‰ต แˆแŠญแŠ•แ‹ซแ‰ตแฃ แŠฅแŠ› แ‰ฅแ‹™แ‹แŠ• แŒŠแ‹œ แ‰ แ‹ฐแŠ•แ‰ แŠ›แ‹ แ‹แˆตแŒฅ แŠซแˆˆแ‹ แ‹จแŠ แˆแŠ‘ แŒŠแ‹œ แ‰ แŠแ‰ต แ‰ขแ‹ซแŠ•แˆต แŠ แŠ•แ‹ต แ‹แˆ˜แŠ“ แŠฅแŠ•แˆ†แŠ“แˆˆแŠ•แข แˆฒแŒ แˆซ getCurrentState(), แˆ˜แˆแŒธแˆ แŠฅแŠ•แ‰ฝแˆ‹แˆˆแŠ• แˆ˜แˆตแˆ˜แˆซแ‹Š แŒฃแˆแ‰ƒแŒˆแ‰ฅแАแ‰ต แ‰ แ‹ฐแŠ•แ‰ แŠ›แ‹ แ‹แˆตแŒฅ แŠซแˆˆแ‹ แ‹จแŠ แˆแŠ‘ แŒŠแ‹œ แ‰ แŠแ‰ต แŠฅแŠ“ แ‰ แŠ‹แˆ‹ แ‰ฃแˆˆแ‹ แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“แ‹Žแ‰ฝ แˆ˜แŠซแŠจแˆแก-

แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แ‹ญแˆ„ แ‹จแแˆฌแˆ แ‰ฐแˆ˜แŠ• แ‰ฝแŒแˆญแŠ• แ‹ญแˆแ‰ณแˆแก แŠ แˆแŠ• แˆแ‹ฉ แแˆฌแˆžแ‰ฝแŠ• แ‰ แˆแŠ•แˆแˆแŒˆแ‹ แ‹จแแˆฌแˆ แแŒฅแАแ‰ต แˆ˜แˆตแˆซแ‰ต แŠฅแŠ•แ‰ฝแˆ‹แˆˆแŠ•!

7.3 แ‹จแ‰ฐแˆปแˆปแˆˆ แ‹จแ‹ฐแŠ•แ‰ แŠ› แˆแŠ”แ‰ณแŠ• แ‰ แˆ˜แ‰ฐแŒแ‰ แˆญ แˆ‹แ‹ญ

แ‹จแ‰ตแŒแ‰ แˆซ แˆแˆณแˆŒ แ‰  src/client/state.js แˆแˆˆแ‰ฑแŠ•แˆ render lag แŠฅแŠ“ linear interpolation แ‹ญแŒ แ‰€แˆ›แˆแฃ แŒแŠ• แˆˆแˆจแŒ…แˆ แŒŠแ‹œ แŠ แ‹ญแ‹ฐแˆˆแˆแข แŠฎแ‹ฑแŠ• แ‰ แˆแˆˆแ‰ต แŠญแแˆŽแ‰ฝ แŠฅแŠ•แŠจแ‹แแˆแข แ‹จแˆ˜แŒ€แˆ˜แˆชแ‹ซแ‹ แ‹ญแŠธแ‹แŠ“แก-

state.js แŠญแแˆ 1

const RENDER_DELAY = 100;

const gameUpdates = [];
let gameStart = 0;
let firstServerTimestamp = 0;

export function initState() {
  gameStart = 0;
  firstServerTimestamp = 0;
}

export function processGameUpdate(update) {
  if (!firstServerTimestamp) {
    firstServerTimestamp = update.t;
    gameStart = Date.now();
  }
  gameUpdates.push(update);

  // Keep only one game update before the current server time
  const base = getBaseUpdate();
  if (base > 0) {
    gameUpdates.splice(0, base);
  }
}

function currentServerTime() {
  return firstServerTimestamp + (Date.now() - gameStart) - RENDER_DELAY;
}

// Returns the index of the base update, the first game update before
// current server time, or -1 if N/A.
function getBaseUpdate() {
  const serverTime = currentServerTime();
  for (let i = gameUpdates.length - 1; i >= 0; i--) {
    if (gameUpdates[i].t <= serverTime) {
      return i;
    }
  }
  return -1;
}

แ‹จแˆ˜แŒ€แˆ˜แˆชแ‹ซแ‹ แŠฅแˆญแˆแŒƒ แˆแŠ• แŠฅแŠ•แ‹ฐแˆ†แА แˆ›แ‹ˆแ‰… แАแ‹ currentServerTime(). แ‰€แ‹ฐแˆ แˆฒแˆ แŠฅแŠ•แ‹ณแ‹จแАแ‹ แŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“ แ‹จแŠ แŒˆแˆแŒ‹แ‹ญ แŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆแŠ• แ‹ซแŠซแ‰ตแ‰ณแˆแข แˆแˆตแˆ‰แŠ• แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แŒ€แˆญแ‰ฃ 100ms แˆˆแˆ›แ‰…แˆจแ‰ฅ แ‹จแˆแˆตแˆ แˆตแˆซ แˆ˜แ‹˜แŒแ‹จแ‰ตแŠ• แˆ˜แŒ แ‰€แˆ แŠฅแŠ•แˆแˆแŒ‹แˆˆแŠ• แАแŒˆแˆญ แŒแŠ• แ‰ แŠ แŒˆแˆแŒ‹แ‹ฉ แˆ‹แ‹ญ แ‹ซแˆˆแ‹แŠ• แŒŠแ‹œ แˆแŒฝแˆž แŠ แŠ“แ‹แ‰…แˆแฃ แˆแŠญแŠ•แ‹ซแ‰ฑแˆ แˆ›แˆปแˆปแ‹ซแ‹Žแ‰น แ‹ˆแ‹ฐ แŠฅแŠ› แˆˆแˆ˜แ‹ตแˆจแˆต แˆแŠ• แ‹ซแˆ…แˆ แŒŠแ‹œ แŠฅแŠ•แ‹ฐแ‹ˆแˆฐแ‹ฐ แˆ›แ‹ˆแ‰… แŠ แŠ•แ‰ฝแˆแˆแข แ‰ แ‹ญแАแˆ˜แˆจแ‰ก แ‹จแˆ›แ‹ญแ‰ณแ‹ˆแ‰… แŠฅแŠ“ แแŒฅแАแ‰ฑ แ‰ แŒฃแˆ แˆŠแˆˆแ‹ซแ‹ญ แ‹ญแ‰ฝแˆ‹แˆ!

แ‰ แ‹šแˆ… แ‰ฝแŒแˆญ แ‹™แˆชแ‹ซ แˆˆแˆ›แŒแŠ˜แ‰ต, แŠฅแŠ› แˆแŠญแŠ•แ‹ซแ‰ณแ‹Š approximation แˆ˜แŒ แ‰€แˆ แ‹ญแ‰ฝแˆ‹แˆ‰: แŠฅแŠ› แ‹จแˆ˜แŒ€แˆ˜แˆชแ‹ซแ‹ แ‹แˆ›แŠ” แ‹ˆแ‹ฒแ‹ซแ‹แŠ‘ แŠฅแŠ•แ‹ฐแ‹ฐแˆจแˆฐ แŠ แˆตแˆ˜แˆตแˆŽ. แ‹ญแˆ… แŠฅแ‹แАแ‰ต แŠจแˆ†แАแฃ แ‰ แ‹šแˆ… แŒŠแ‹œ แ‹จแŠ แŒˆแˆแŒ‹แ‹ฉแŠ• แŒŠแ‹œ แŠฅแŠ“แ‹แ‰… แАแ‰ แˆญ! แ‹จแŠ แŒˆแˆแŒ‹แ‹ฉแŠ• แ‹จแŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆ แŠฅแŠ“แŠจแˆ›แ‰ปแˆˆแŠ•แข firstServerTimestamp แŠฅแŠ“ แ‹จแŠฅแŠ›แŠ• แŒ แ‰ฅแ‰… แŠ แŠซแ‰ฃแ‰ขแ‹ซแ‹Š (แ‹ฐแŠ•แ‰ แŠ›) แ‹จแŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆ แ‰ แ‰ฐแˆ˜แˆณแˆณแ‹ญ แ‰…แŒฝแ‰ แ‰ต แ‹แˆตแŒฅ gameStart.

แ‰†แ‹ญ. แ‹จแŠ แŒˆแˆแŒ‹แ‹ญ แŒŠแ‹œ = แ‹จแ‹ฐแŠ•แ‰ แŠ› แŒŠแ‹œ แˆ˜แˆ†แŠ• แ‹จแˆˆแ‰ แ‰ตแˆ? แˆˆแˆแŠ•แ‹ตแŠ• แАแ‹ แ‰ "แŠ แŒˆแˆแŒ‹แ‹ญ แŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆ" แŠฅแŠ“ "แ‹ฐแŠ•แ‰ แŠ› แ‹จแŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆ" แˆ˜แŠซแŠจแˆ แ‹จแˆแŠ•แˆˆแ‹จแ‹? แ‹ญแˆ… แ‰ตแˆแ‰… แŒฅแ‹ซแ‰„ แАแ‹! แŠ แŠ•แ‹ต แŠ แ‹ญแАแ‰ต แАแŒˆแˆญ แŠฅแŠ•แ‹ณแˆแˆ†แŠ‘ แ‰ณแ‹ˆแ‰€แข Date.now() แ‰ แ‹ฐแŠ•แ‰ แŠ›แ‹ แŠฅแŠ“ แ‰ แŠ แŒˆแˆแŒ‹แ‹ฉ แ‹แˆตแŒฅ แ‹จแ‰ฐแˆˆแ‹ซแ‹ฉ แ‹จแŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆžแ‰ฝแŠ• แ‹ญแˆ˜แˆแˆณแˆ แฃ แŠฅแŠ“ แ‰ แŠฅแАแ‹šแˆ… แˆ›แˆฝแŠ–แ‰ฝ แŠ แŠซแ‰ฃแ‰ขแ‹ซแ‹Š แˆแŠ”แ‰ณแ‹Žแ‰ฝ แˆ‹แ‹ญ แ‹จแ‰ฐแˆ˜แˆ แˆจแ‰ฐ แАแ‹แข แ‹จแŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆžแ‰ฝ แ‰ แˆแˆ‰แˆ แˆ›แˆฝแŠ–แ‰ฝ แˆ‹แ‹ญ แŠ แŠ•แ‹ต แŠ แ‹ญแАแ‰ต แ‹ญแˆ†แŠ“แˆ‰ แ‰ฅแˆ‹แ‰ฝแˆ แŠ แ‰ณแˆตแ‰กแข

แŠ แˆแŠ• แˆแŠ• แŠฅแŠ•แ‹ฐแˆšแˆฐแˆซ แ‰ฐแˆจแ‹ตแ‰ฐแŠ“แˆ currentServerTime(): แ‹ญแˆ˜แˆˆแˆณแˆ แ‹จแŠ แˆแŠ‘ แ‹จแˆแˆตแˆ แŒŠแ‹œ แ‹จแŠ แŒˆแˆแŒ‹แ‹ญ แŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆ. แ‰ แˆŒแˆ‹ แŠ แАแŒ‹แŒˆแˆญ แ‹ญแˆ… แ‹จแŠ แŒˆแˆแŒ‹แ‹ฉ แ‹จแŠ แˆแŠ‘ แŒŠแ‹œ แАแ‹ (firstServerTimestamp <+ (Date.now() - gameStart)แ‹จแ‹˜แŒˆแ‹จ แˆ˜แ‹˜แŒแ‹จแ‰ต ()RENDER_DELAY).

แŠ แˆแŠ• แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“แ‹Žแ‰ฝแŠ• แŠฅแŠ•แ‹ดแ‰ต แŠฅแŠ•แ‹ฐแˆแŠ•แ‹ญแ‹ แŠฅแŠ•แˆ˜แˆแŠจแ‰ตแข แŠจแ‹แˆ›แŠ” แŠ แŒˆแˆแŒ‹แ‹ญ แˆฒแ‹ฐแˆญแˆฐแ‹ แ‹ญแ‰ฃแˆ‹แˆ processGameUpdate()แŠฅแŠ“ แŠ แ‹ฒแˆฑแŠ• แ‹แˆ˜แŠ“ แ‹ˆแ‹ฐ แ‹ตแˆญแ‹ตแˆญ แŠฅแŠ“แˆตแ‰€แˆแŒ แ‹‹แˆˆแŠ• gameUpdates. แŠจแ‹šแ‹ซ แ‹จแˆ›แˆ…แ‹ฐแˆจ แ‰ตแ‹แˆตแ‰ณแ‹แŠ• แŠ แŒ แ‰ƒแ‰€แˆ แˆˆแˆ˜แˆแ‰ฐแˆฝ แŠจแ‹šแˆ… แ‰ แŠแ‰ต แˆแˆ‰แŠ•แˆ แ‹จแ‰†แ‹ฉ แ‹แˆ˜แŠ“แ‹Žแ‰ฝแŠ• แŠฅแŠ“แˆตแ‹ˆแŒแ‹ณแˆˆแŠ•แข แ‹จแˆ˜แˆ แˆจแ‰ต แˆ›แˆปแˆปแ‹ซแˆแŠญแŠ•แ‹ซแ‰ฑแˆ แŠฅแŠ› แŠจแŠฅแŠ•แŒแ‹ฒแˆ… แŠ แŠ•แˆแˆแŒ‹แ‰ธแ‹แˆแข

"แˆ˜แˆฐแˆจแ‰ณแ‹Š แˆ›แˆปแˆปแ‹ซ" แˆแŠ•แ‹ตแŠ• แАแ‹? แ‹ญแˆ… แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แ‹ˆแ‰…แ‰ณแ‹Š แŒŠแ‹œ แ‹ˆแ‹ฐ แŠ‹แˆ‹ แ‰ แˆ˜แŠ•แ‰€แˆณแ‰€แˆต แ‹จแˆแŠ“แŒˆแŠ˜แ‹ แ‹จแˆ˜แŒ€แˆ˜แˆชแ‹ซแ‹ แˆ›แˆปแˆปแ‹ซ. แ‹ญแˆ…แŠ• แˆฅแ‹•แˆ‹แ‹Š แˆ˜แŒแˆˆแŒซ แŠ แˆตแ‰ณแ‹แˆต?

แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แ‹จแŒจแ‹‹แ‰ณแ‹ แˆ›แˆปแˆปแ‹ซ แ‰ แ‰€แŒฅแ‰ณ แ‰  "แ‹จแ‹ฐแŠ•แ‰ แŠ› แˆ˜แˆตแŒซ แŒŠแ‹œ" แ‰ แˆตแ‰ฐแŒแˆซ แ‹จแˆ˜แˆ แˆจแ‰ต แˆ›แˆปแˆปแ‹ซ แАแ‹แข

แ‹จแˆ˜แˆ แˆจแ‰ต แ‹แˆ›แŠ”แ‹ แˆˆแˆแŠ• แŒฅแ‰…แˆ แˆ‹แ‹ญ แ‹ญแ‹แˆ‹แˆ? แˆˆแˆแŠ•แ‹ตแАแ‹ แˆ›แˆปแˆปแ‹ซแ‹Žแ‰ฝแŠ• แ‹ˆแ‹ฐ แˆ˜แАแˆป แˆ˜แˆตแˆ˜แˆญ แˆ˜แŒฃแˆ แ‹จแˆแŠ•แ‰ฝแˆˆแ‹? แ‹ญแˆ…แŠ•แŠ• แˆˆแˆ›แ‹ˆแ‰… แŠฅแŠ•แˆžแŠญแˆญ แ‰ แˆ˜แŒจแˆจแˆป แŠ แ‰ฐแŒˆแ‰ฃแ‰ แˆฉแŠ• แŠ แˆตแ‰กแ‰ แ‰ต getCurrentState():

state.js แŠญแแˆ 2

export function getCurrentState() {
  if (!firstServerTimestamp) {
    return {};
  }

  const base = getBaseUpdate();
  const serverTime = currentServerTime();

  // If base is the most recent update we have, use its state.
  // Else, interpolate between its state and the state of (base + 1).
  if (base < 0) {
    return gameUpdates[gameUpdates.length - 1];
  } else if (base === gameUpdates.length - 1) {
    return gameUpdates[base];
  } else {
    const baseUpdate = gameUpdates[base];
    const next = gameUpdates[base + 1];
    const r = (serverTime - baseUpdate.t) / (next.t - baseUpdate.t);
    return {
      me: interpolateObject(baseUpdate.me, next.me, r),
      others: interpolateObjectArray(baseUpdate.others, next.others, r),
      bullets: interpolateObjectArray(baseUpdate.bullets, next.bullets, r),
    };
  }
}

แˆถแˆตแ‰ต แŒ‰แ‹ณแ‹ฎแ‰ฝแŠ• แŠฅแŠ•แ‹ญแ‹›แˆˆแŠ•-

  1. base < 0 แ‹ญแˆ… แˆ›แˆˆแ‰ต แŠ แˆแŠ• แŠฅแˆตแŠซแˆˆแ‰ แ‰ต แŒŠแ‹œ แ‹ตแˆจแˆต แˆแŠ•แˆ แ‹แˆ›แŠ”แ‹Žแ‰ฝ แ‹จแˆ‰แˆ (แŠจแˆ‹แ‹ญ แ‹ซแˆˆแ‹แŠ• แ‰ตแŒแ‰ แˆซ แ‹ญแˆ˜แˆแŠจแ‰ฑ getBaseUpdate()). แ‹ญแˆ… แ‰ แŒจแ‹‹แ‰ณแ‹ แˆ˜แŒ€แˆ˜แˆชแ‹ซ แˆ‹แ‹ญ แ‰ แˆ‚แ‹ฐแ‰ต แˆ˜แ‹˜แŒแ‹จแ‰ต แˆแŠญแŠ•แ‹ซแ‰ต แˆŠแŠจแˆฐแ‰ต แ‹ญแ‰ฝแˆ‹แˆแข แ‰ แ‹šแˆ… แŠ แŒ‹แŒฃแˆš, แ‹จแ‰ฐแ‰€แ‰ แˆˆแ‹แŠ• แ‹จแ‰…แˆญแ‰ฅ แŒŠแ‹œ แ‹แˆ˜แŠ“ แŠฅแŠ•แŒ แ‰€แˆ›แˆˆแŠ•.
  2. base แ‹ซแˆˆแŠ• แ‹จแ‰…แˆญแ‰ฅ แŒŠแ‹œ แ‹แˆ˜แŠ“ แАแ‹แข แ‹ญแˆ… แ‰ แŠ”แ‰ตแ‹ˆแˆญแŠญ แˆ˜แ‹˜แŒแ‹จแ‰ต แ‹ˆแ‹ญแˆ แ‹ฐแŠซแˆ› แ‹จแ‰ แ‹ญแАแˆ˜แˆจแ‰ฅ แŒแŠ•แŠ™แАแ‰ต แˆแŠญแŠ•แ‹ซแ‰ต แˆŠแˆ†แŠ• แ‹ญแ‰ฝแˆ‹แˆ. แ‰ แ‹šแˆ… แŠ แŒ‹แŒฃแˆšแฃ แ‹ซแˆˆแŠ•แŠ• แ‹จแ‰…แˆญแ‰ฅ แŒŠแ‹œ แ‹แˆ˜แŠ“ แŠฅแ‹จแ‰ฐแŒ แ‰€แˆแŠ• แАแ‹แข
  3. แŠจแŠ แˆแŠ‘ แ‹จแˆ˜แˆตแˆชแ‹ซ แŒŠแ‹œ แ‰ แŠแ‰ตแˆ แˆ†แА แ‰ แŠ‹แˆ‹ แˆ›แˆปแˆปแ‹ซ แŠ แˆˆแŠ•แฃ แˆตแˆˆแ‹šแˆ… แŠฅแŠ•แ‰ฝแˆ‹แˆˆแŠ• แŒฃแˆแ‰ƒ แˆ˜แŒแ‰ฃแ‰ต!

แ‹จแ‰€แˆจแ‹ แˆแˆ‰ state.js แ‰€แˆ‹แˆ (แАแŒˆแˆญ แŒแŠ• แŠ แˆฐแˆแ‰บ) แˆ’แˆณแ‰ฅ แ‹จแˆ†แА แ‹จแˆ˜แˆตแˆ˜แˆซแ‹Š แŒฃแˆแ‰ƒแŒˆแ‰ฅแАแ‰ต แ‰ตแŒแ‰ แˆซ แАแ‹แข แŠฅแˆซแˆตแ‹Ž แˆ›แˆฐแˆต แŠจแˆแˆˆแŒ‰ แŠจแ‹šแ‹ซ แ‹ญแŠญแˆแ‰ฑแ‰ตแข state.js แˆ‹แ‹ญ แ‹จแŠแˆแˆ™.

แŠญแแˆ 2. แ‹จแŒ€แˆญแ‰ฃ แŠ แŒˆแˆแŒ‹แ‹ญ

แ‰ แ‹šแˆ… แŠญแแˆ แ‹จแŠฅแŠ›แŠ• แ‹จแˆšแ‰†แŒฃแŒ แˆจแ‹แŠ• แ‹จ Node.js แŒ€แˆญแ‰ฃ แŠฅแŠ•แˆ˜แˆˆแŠจแ‰ณแˆˆแŠ• .io แŒจแ‹‹แ‰ณ แˆแˆณแˆŒ.

1. แ‹จแŠ แŒˆแˆแŒ‹แ‹ญ แˆ˜แŒแ‰ขแ‹ซ แАแŒฅแ‰ฅ

แ‹จแ‹ตแˆญ แŠ แŒˆแˆแŒ‹แ‹ฉแŠ• แˆˆแˆ›แˆตแ‰ฐแ‹ณแ‹ฐแˆญ แˆˆ Node.js แ‹จแˆšแ‰ณแ‹ˆแ‰… แ‰ณแ‹‹แ‰‚ แ‹จแ‹ตแˆญ แˆ›แ‹•แ‰€แ แŠฅแŠ•แŒ แ‰€แˆ›แˆˆแŠ•แข แ‹ญแŒแˆˆแŒน. แ‰ แŠฅแŠ› แŠ แŒˆแˆแŒ‹แ‹ญ แˆ˜แŒแ‰ขแ‹ซ แАแŒฅแ‰ฅ แ‹แ‹ญแˆ แ‹ญแ‹‹แ‰€แˆซแˆแข src/server/server.js:

แŠ แŒˆแˆแŒ‹แ‹ญ.js แŠญแแˆ 1

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackConfig = require('../../webpack.dev.js');

// Setup an Express server
const app = express();
app.use(express.static('public'));

if (process.env.NODE_ENV === 'development') {
  // Setup Webpack for development
  const compiler = webpack(webpackConfig);
  app.use(webpackDevMiddleware(compiler));
} else {
  // Static serve the dist/ folder in production
  app.use(express.static('dist'));
}

// Listen on port
const port = process.env.PORT || 3000;
const server = app.listen(port);
console.log(`Server listening on port ${port}`);

แ‰ แˆ˜แŒ€แˆ˜แˆชแ‹ซแ‹ แŠญแแˆ แˆตแˆˆ Webpack แŠฅแŠ•แ‹ฐแ‰ฐแАแŒ‹แŒˆแˆญแŠ• แŠ แˆตแ‰ณแ‹แˆต? แ‹จแŠ›แŠ• แ‹จแ‹Œแ‰ฅแ“แŠญ แŠ แ‹ˆแ‰ƒแ‰€แˆฎแ‰ฝแŠ• แ‹จแˆแŠ•แŒ แ‰€แˆแ‰ แ‰ต แ‹ญแˆ… แАแ‹แข แ‰ แˆแˆˆแ‰ต แˆ˜แŠ•แŒˆแ‹ถแ‰ฝ แŠฅแŠ•แŒ แ‰€แˆ›แ‰ธแ‹‹แˆˆแŠ•แก-

  • แ‰ฐแŒ แ‰€แˆ แ‹จแ‹Œแ‰ฅแ“แŠญ-แ‹ดแ‰ญ-แˆšแ‹ตแˆแ‹Œแˆญ แ‹จแŠฅแŠ›แŠ• แ‹จแŠฅแ‹ตแŒˆแ‰ต แ“แŠฌแŒ†แ‰ฝแŠ• แ‰ แˆซแˆต แˆฐแˆญ แŠฅแŠ•แ‹ฐแŒˆแŠ“ แˆˆแˆ˜แŒˆแŠ•แ‰ฃแ‰ต แ‹ˆแ‹ญแˆ
  • แ‰ แˆตแ‰ณแ‰ฒแˆตแ‰ฒแŠญแˆต แŠ แ‰ƒแŠ แ‹ซแˆตแ‰ฐแˆ‹แˆแ‰ dist/แŠจแˆแˆญแ‰ฑ แŒแŠ•แ‰ฃแ‰ณ แ‰ แŠ‹แˆ‹ แ‹แ‹ญแˆŽแ‰ปแ‰ฝแŠ•แŠ• แ‰ แ‹จแ‰ตแŠ›แ‹ แ‹จแ‹Œแ‰ฅแ“แŠญ แŠฅแŠ•แŒฝแ‹แˆˆแŠ•แข

แˆŒแˆ‹แ‹ แŠ แˆตแˆแˆ‹แŒŠ แ‰ฐแŒแ‰ฃแˆญ server.js แŠ แŒˆแˆแŒ‹แ‹ฉแŠ• แˆ›แ‹‹แ‰€แˆญ แАแ‹แข แˆถแŠฌแ‰ต.ioแŠจแŠคแŠญแˆตแ•แˆจแˆต แŠ แŒˆแˆแŒ‹แ‹ญ แŒ‹แˆญ แ‹จแˆšแŒˆแŠ“แŠ˜แ‹แก-

แŠ แŒˆแˆแŒ‹แ‹ญ.js แŠญแแˆ 2

const socketio = require('socket.io');
const Constants = require('../shared/constants');

// Setup Express
// ...
const server = app.listen(port);
console.log(`Server listening on port ${port}`);

// Setup socket.io
const io = socketio(server);

// Listen for socket.io connections
io.on('connection', socket => {
  console.log('Player connected!', socket.id);

  socket.on(Constants.MSG_TYPES.JOIN_GAME, joinGame);
  socket.on(Constants.MSG_TYPES.INPUT, handleInput);
  socket.on('disconnect', onDisconnect);
});

แ‹จ socket.io แŒแŠ•แŠ™แАแ‰ต แŠจแŠ แŒˆแˆแŒ‹แ‹ฉ แŒ‹แˆญ แ‰ แ‰ฐแˆณแŠซ แˆแŠ”แ‰ณ แŠซแ‰‹แ‰‹แˆแŠ• แ‰ แŠ‹แˆ‹ แˆˆแŠ แ‹ฒแˆฑ แˆถแŠฌแ‰ต แ‹จแŠญแˆตแ‰ฐแ‰ต แ‰ฐแ‰†แŒฃแŒฃแˆชแ‹Žแ‰ฝแŠ• แŠ แ‹˜แŒ‹แŒ…แ‰ฐแŠ“แˆแข แ‹จแŠญแˆตแ‰ฐแ‰ต แ‰ฐแ‰†แŒฃแŒฃแˆชแ‹Žแ‰ฝ แŠจแ‹ฐแŠ•แ‰ แŠžแ‰ฝ แ‹จแ‰ฐแ‰€แ‰ แˆ‰แ‰ตแŠ• แˆ˜แˆแŠฅแŠญแ‰ต แˆˆแŠ แŠ•แ‹ต แАแŒ แˆ‹ แ‹•แ‰ƒ แ‰ แˆ›แˆตแ‰ฐแˆ‹แˆˆแ แ‹ซแˆตแ‰ฐแŠ“แŒแ‹ณแˆ‰แข game:

แŠ แŒˆแˆแŒ‹แ‹ญ.js แŠญแแˆ 3

const Game = require('./game');

// ...

// Setup the Game
const game = new Game();

function joinGame(username) {
  game.addPlayer(this, username);
}

function handleInput(dir) {
  game.handleInput(this, dir);
}

function onDisconnect() {
  game.removePlayer(this);
}

แ‹จ.io แŒจแ‹‹แ‰ณ แŠฅแ‹จแˆแŒ แˆญแŠ• แАแ‹แฃ แˆตแˆˆแ‹šแˆ… แŠ แŠ•แ‹ต แ‰…แŒ‚ แ‰ฅแ‰ป แŠฅแŠ•แˆแˆแŒ‹แˆˆแŠ• Game ("แŒจแ‹‹แ‰ณ") - แˆแˆ‰แˆ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แ‰ แŠ แŠ•แ‹ต แˆ˜แ‹ตแˆจแŠญ แ‹แˆตแŒฅ แ‹ญแŒซแ‹ˆแ‰ณแˆ‰! แ‰ แˆšแ‰€แŒฅแˆˆแ‹ แŠญแแˆ แ‹ญแˆ… แŠญแแˆ แŠฅแŠ•แ‹ดแ‰ต แŠฅแŠ•แ‹ฐแˆšแˆฐแˆซ แŠฅแŠ•แˆ˜แˆˆแŠจแ‰ณแˆˆแŠ•. Game.

2. แ‹จแŒจแ‹‹แ‰ณ แŠ แŒˆแˆแŒ‹แ‹ฎแ‰ฝ

แŠญแแˆ Game แ‰ แŠ แŒˆแˆแŒ‹แ‹ฉ แ‰ แŠฉแˆ แ‰ แŒฃแˆ แŠ แˆตแˆแˆ‹แŒŠ แ‹จแˆ†แАแ‹แŠ• แŠ แˆ˜แŠญแŠ•แ‹ฎ แ‹ญแ‹Ÿแˆ. แˆแˆˆแ‰ต แ‹‹แŠ“ แ‰ฐแŒแ‰ฃแˆซแ‰ต แŠ แˆ‰แ‰ตแก- แ‹จแ‰ฐแŒซแ‹‹แ‰ฝ แŠ แˆตแ‰ฐแ‹ณแ‹ฐแˆญ ะธ แ‹จแŒจแ‹‹แ‰ณ แˆ›แˆตแˆ˜แˆฐแˆ.

แŠจแˆ˜แŒ€แˆ˜แˆชแ‹ซแ‹ แ‰ฐแŒแ‰ฃแˆญ แˆ›แˆˆแ‰ตแˆ แ‹จแ‰ฐแŒซแ‹‹แ‰ฝ แŠ แˆตแ‰ฐแ‹ณแ‹ฐแˆญ แŠฅแŠ•แŒ€แˆแˆญแข

game.js แŠญแแˆ 1

const Constants = require('../shared/constants');
const Player = require('./player');

class Game {
  constructor() {
    this.sockets = {};
    this.players = {};
    this.bullets = [];
    this.lastUpdateTime = Date.now();
    this.shouldSendUpdate = false;
    setInterval(this.update.bind(this), 1000 / 60);
  }

  addPlayer(socket, username) {
    this.sockets[socket.id] = socket;

    // Generate a position to start this player at.
    const x = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
    const y = Constants.MAP_SIZE * (0.25 + Math.random() * 0.5);
    this.players[socket.id] = new Player(socket.id, username, x, y);
  }

  removePlayer(socket) {
    delete this.sockets[socket.id];
    delete this.players[socket.id];
  }

  handleInput(socket, dir) {
    if (this.players[socket.id]) {
      this.players[socket.id].setDirection(dir);
    }
  }

  // ...
}

แ‰ แ‹šแˆ… แŒจแ‹‹แ‰ณ แ‰ฐแŒซแ‹‹แ‰พแ‰นแŠ• แ‰ แˆœแ‹ณ แŠฅแŠ•แˆˆแ‹ซแ‰ธแ‹‹แˆˆแŠ• id their socket.io socket (แŒแˆซ แŠจแ‰ฐแŒ‹แ‰กแฃ แŠจแ‹šแ‹ซ แ‹ญแˆ˜แˆˆแˆฑ server.js). Socket.io แˆซแˆฑ แˆˆแŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แˆถแŠฌแ‰ต แˆแ‹ฉ แ‹ญแˆ˜แ‹ตแ‰ฃแˆ idแˆตแˆˆแ‹šแˆ… แˆตแˆˆแ‹šแˆ… แŒ‰แ‹ณแ‹ญ แˆ˜แŒจแАแ‰… แŠ แ‹ซแˆตแˆแˆแŒˆแŠ•แˆ. แŠฅแ‹ฐแ‹แˆแˆˆแ‰ณแˆˆแˆแข แ‹จแ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แ‰ณแ‹ˆแ‰‚แ‹ซ.

แ‹ซแŠ•แŠ• แ‰ แŠ แŠฅแˆแˆฏแ‰ฝแŠ• แ‹ญแ‹˜แŠ•แฃ แ‰ แŠ แŠ•แ‹ต แŠญแแˆ แ‹แˆตแŒฅ แ‹ซแˆ‰ แ‹จแŠ แ‰ฅแАแ‰ต แ‰ฐแˆˆแ‹‹แ‹‹แŒฎแ‰ฝแŠ• แŠฅแŠ•แˆ˜แˆญแˆแˆญ Game:

  • sockets แ‹จแ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แ‰ณแ‹ˆแ‰‚แ‹ซแ‹แŠ• แŠจแ‰ฐแŒซแ‹‹แ‰น แŒ‹แˆญ แŠจแ‰ฐแŒˆแŠ“แŠ˜แ‹ แˆถแŠฌแ‰ต แŒ‹แˆญ แ‹จแˆšแ‹ซแŒˆแŠ“แŠ แАแŒˆแˆญ แАแ‹แข แ‰ แ‰‹แˆš แŒŠแ‹œ แ‹แˆตแŒฅ แˆถแŠฌแ‰ถแ‰ฝแŠ• แ‰ แ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แ‰ณแ‹ˆแ‰‚แ‹ซแ‰ธแ‹ แŠฅแŠ•แ‹ตแŠ•แ‹ฐแˆญแˆต แ‹ซแˆตแ‰ฝแˆˆแŠ“แˆแข
  • players แ‹จแ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แ‰ณแ‹ˆแ‰‚แ‹ซแ‹แŠ• แŠจแŠฎแ‹ฑ>แ‰ฐแŒซแ‹‹แ‰ฝ แАแŒˆแˆญ แŒ‹แˆญ แ‹จแˆšแ‹ซแŒˆแŠ“แŠ แ‹•แ‰ƒ แАแ‹แข

bullets แ‹จแАแŒˆแˆฎแ‰ฝ แˆตแ‰ฅแˆตแ‰ฅ แАแ‹แข Bullet, แŠฅแˆฑแˆ แ‹จแ‰ฐแ‹ˆแˆฐแА แ‰…แ‹ฐแˆ แ‰ฐแŠจแ‰ฐแˆ แ‹จแˆŒแˆˆแ‹.
lastUpdateTime แŒจแ‹‹แ‰ณแ‹ แˆˆแˆ˜แŒจแˆจแˆป แŒŠแ‹œ แ‹จแ‰ฐแ‹˜แˆ˜แАแ‰ แ‰ต แ‹จแŒŠแ‹œ แˆ›แˆ…แ‰ฐแˆ แАแ‹แข แŠฅแŠ•แ‹ดแ‰ต แŒฅแ‰…แˆ แˆ‹แ‹ญ แŠฅแŠ•แ‹ฐแˆšแ‹แˆ แ‰ แ‰…แˆญแ‰ก แŠฅแŠ•แˆ˜แˆˆแŠจแ‰ณแˆˆแŠ•แข
shouldSendUpdate แˆจแ‹ณแ‰ต แ‰ฐแˆˆแ‹‹แ‹‹แŒญ แАแ‹. แŠ แŒ แ‰ƒแ‰€แˆ™แŠ•แˆ แ‰ แ‰…แˆญแ‰ก แŠฅแŠ•แˆ˜แˆˆแŠจแ‰ณแˆˆแŠ•แข
แ‹˜แ‹ดแ‹Žแ‰ฝ addPlayer(), removePlayer() ะธ handleInput() แˆ›แ‰ฅแˆซแˆซแ‰ต แŠ แ‹ซแˆตแˆแˆแŒแˆ, แ‰  แ‹แˆตแŒฅ แŒฅแ‰…แˆ แˆ‹แ‹ญ แ‹ญแ‹แˆ‹แˆ‰ server.js. แ‹จแˆ›แˆตแ‰ณแ‹ˆแˆต แ‰ฝแˆŽแ‰ณแ‹ŽแŠ• แˆ›แ‹ฐแˆต แŠจแˆแˆˆแŒ‰ แ‰ตแŠ•แˆฝ แŠจแ แ‰ฅแˆˆแ‹ แ‹ญแˆ˜แˆˆแˆฑแข

แ‹จแˆ˜แŒจแˆจแˆปแ‹ แˆ˜แˆตแˆ˜แˆญ constructor() แ‹ญแŒ€แˆแˆซแˆ แ‹จแ‹แˆ›แŠ” แ‹‘แ‹ฐแ‰ต แŒจแ‹‹แ‰ณแ‹Žแ‰ฝ (แŠจ60 แ‹แˆ˜แŠ“แ‹Žแ‰ฝ แ‹ตแŒแŒแˆžแˆฝ แŒ‹แˆญ)

game.js แŠญแแˆ 2

const Constants = require('../shared/constants');
const applyCollisions = require('./collisions');

class Game {
  // ...

  update() {
    // Calculate time elapsed
    const now = Date.now();
    const dt = (now - this.lastUpdateTime) / 1000;
    this.lastUpdateTime = now;

    // Update each bullet
    const bulletsToRemove = [];
    this.bullets.forEach(bullet => {
      if (bullet.update(dt)) {
        // Destroy this bullet
        bulletsToRemove.push(bullet);
      }
    });
    this.bullets = this.bullets.filter(
      bullet => !bulletsToRemove.includes(bullet),
    );

    // Update each player
    Object.keys(this.sockets).forEach(playerID => {
      const player = this.players[playerID];
      const newBullet = player.update(dt);
      if (newBullet) {
        this.bullets.push(newBullet);
      }
    });

    // Apply collisions, give players score for hitting bullets
    const destroyedBullets = applyCollisions(
      Object.values(this.players),
      this.bullets,
    );
    destroyedBullets.forEach(b => {
      if (this.players[b.parentID]) {
        this.players[b.parentID].onDealtDamage();
      }
    });
    this.bullets = this.bullets.filter(
      bullet => !destroyedBullets.includes(bullet),
    );

    // Check if any players are dead
    Object.keys(this.sockets).forEach(playerID => {
      const socket = this.sockets[playerID];
      const player = this.players[playerID];
      if (player.hp <= 0) {
        socket.emit(Constants.MSG_TYPES.GAME_OVER);
        this.removePlayer(socket);
      }
    });

    // Send a game update to each player every other time
    if (this.shouldSendUpdate) {
      const leaderboard = this.getLeaderboard();
      Object.keys(this.sockets).forEach(playerID => {
        const socket = this.sockets[playerID];
        const player = this.players[playerID];
        socket.emit(
          Constants.MSG_TYPES.GAME_UPDATE,
          this.createUpdate(player, leaderboard),
        );
      });
      this.shouldSendUpdate = false;
    } else {
      this.shouldSendUpdate = true;
    }
  }

  // ...
}

แ‹˜แ‹ด update() แˆแŠ“แˆแ‰ฃแ‰ต แ‰ แŒฃแˆ แŠ แˆตแˆแˆ‹แŒŠ แ‹จแˆ†แАแ‹แŠ• แ‹จแŠ แŒˆแˆแŒ‹แ‹ญ-แŒŽแŠ• แŠ แˆ˜แŠญแŠ•แ‹ฎ แ‹ญแ‹Ÿแˆแข แ‹จแˆšแˆ แˆซแ‹ แ‹ญแŠธแ‹แŠ“แฃ แ‰ แ‰…แ‹ฐแˆ แ‰ฐแŠจแ‰ฐแˆแก-

  1. แˆˆแˆแŠ• แ‹ซแˆ…แˆ แŒŠแ‹œ แ‹ซแˆฐแˆ‹แˆ dt แŠซแˆˆแˆแ‹ แŒ€แˆแˆฎ แŠ แˆแแˆ update().
  2. แŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑแŠ• แ•แˆฎแŒ€แŠญแ‰ต แ‹ซแ‹ตแˆณแˆ แŠฅแŠ“ แŠ แˆตแˆแˆ‹แŒŠ แŠจแˆ†แА แ‹ซแŒ แ‹แ‰ธแ‹‹แˆ. แ‹จแ‹šแˆ…แŠ• แ‰ฐแŒแ‰ฃแˆญ แŠ แ‰ฐแŒˆแ‰ฃแ‰ แˆญ แ‰ แŠ‹แˆ‹ แŠฅแŠ•แˆ˜แˆˆแŠจแ‰ณแˆˆแŠ•. แˆˆแŠ แˆแŠ‘ แ‹ญแˆ…แŠ• แˆ›แ‹ˆแ‰… แ‰ แ‰‚แ‹ซแ‰ฝแŠ• แАแ‹แข bullet.update() แ‹ญแˆ˜แˆˆแˆณแˆ trueแ•แˆฎแŒ€แŠญแ‰ฑ แˆ˜แŒฅแ‹แ‰ต แŠซแˆˆแ‰ แ‰ต (แŠจแˆ˜แ‹ตแˆจแŠฉ แ‹ˆแŒฃ)แข
  3. แŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑแŠ• แ‰ฐแŒซแ‹‹แ‰ฝ แ‹ซแ‹˜แˆแŠ“แˆ แŠฅแŠ“ แŠ แˆตแˆแˆ‹แŒŠ แŠจแˆ†แА แ•แˆฎแŒ„แŠญแ‰ตแŠ• แ‹ซแ‰ แ‰…แˆ‹แˆแข แ‹ญแˆ…แŠ•แŠ• แ‰ตแŒแ‰ แˆซ แ‰ แŠ‹แˆ‹ แŠฅแŠ•แˆ˜แˆˆแŠจแ‰ณแˆˆแŠ• - player.update() แ‹•แ‰ƒ แˆ˜แˆ˜แˆˆแˆต แ‹ญแ‰ฝแˆ‹แˆแข Bullet.
  4. แ‰ แ•แˆฎแŒ€แŠญแ‰ถแ‰ฝ แŠฅแŠ“ แ‰ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แˆ˜แŠซแŠจแˆ แŒแŒญแ‰ถแ‰ฝแŠ• แ‹ญแˆแ‰ตแˆปแˆ applyCollisions(), แ‹ญแˆ…แˆ แ‰ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แˆ‹แ‹ญ แ‹จแˆšแ‹ฐแˆญแˆฑ แ‹จแ•แˆฎแŒ€แŠญแ‰ถแ‰ฝ แˆตแ‰ฅแˆตแ‰ฅ แ‹ญแˆ˜แˆแˆณแˆ. แˆˆแŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แ‹จแ‰ฐแˆ˜แˆˆแˆฐ แ•แˆฎแŒ€แŠญแ‰ตแฃ แ‹จแ‰ฐแŠฎแˆฐแ‹แŠ• แ‰ฐแŒซแ‹‹แ‰ฝ แАแŒฅแ‰ฆแ‰ฝแŠ• แŠฅแŠ•แŒจแˆแˆซแˆˆแŠ• (แ‰ แˆ˜แŒ แ‰€แˆ player.onDealtDamage()) แŠฅแŠ“ แŠจแ‹šแ‹ซแˆ แ•แˆฎแŒ„แŠญแ‰ฑแŠ• แŠจแ‹ตแˆญแ‹ตแˆฉ แˆ‹แ‹ญ แ‹ซแˆตแ‹ˆแŒแ‹ฑแ‰ต bullets.
  5. แˆแˆ‰แŠ•แˆ แ‹จแ‰ฐแŒˆแ‹ฐแˆ‰ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แ‹ซแˆณแ‹แ‰ƒแˆ แŠฅแŠ“ แ‹ซแŒ แ‹แˆแข
  6. แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ›แŠ” แˆˆแˆแˆ‰แˆ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แ‹ญแˆแŠซแˆ แ‰ แ‹จแˆฐแŠจแŠ•แ‹ฑ แ‰ แˆšแŒ แˆฉแ‰ แ‰ต แŒŠแ‹œ แŒŠแ‹œแ‹ซแ‰ต update(). แ‹ญแˆ… แŠจแˆ‹แ‹ญ แ‹จแ‰ฐแŒ แ‰€แˆฐแ‹แŠ• แˆจแ‹ณแ‰ต แ‰ฐแˆˆแ‹‹แ‹‹แŒญ แˆˆแˆ˜แŠจแ‰ณแ‰ฐแˆ แ‹ญแˆจแ‹ณแŠ“แˆ. shouldSendUpdate. แˆแŠญแŠ•แ‹ซแ‰ฑแˆ update() 60 แŒŠแ‹œ / แˆฐ แ‹ญแ‰ฃแˆ‹แˆ แฃ แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“แ‹Žแ‰ฝแŠ• 30 แŒŠแ‹œ / แˆฐ แŠฅแŠ•แˆแŠซแˆˆแŠ•แข แˆตแˆˆแ‹šแˆ…แˆ แ‹จแˆฐแ‹“แ‰ต แ‹ตแŒแŒแˆžแˆฝ แ‹จแŠ แŒˆแˆแŒ‹แ‹ญ แˆฐแ‹“แ‰ต 30 แˆฐแ‹“แ‰ต / แˆฐ แАแ‹ (แ‰ แˆ˜แŒ€แˆ˜แˆชแ‹ซแ‹ แŠญแแˆ แ‹แˆตแŒฅ แˆตแˆˆ แ‹จแˆฐแ‹“แ‰ต แ‹‹แŒ‹แ‹Žแ‰ฝ แ‰ฐแАแŒ‹แŒˆแˆญแŠ•).

แˆˆแˆแŠ• แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“แ‹Žแ‰ฝแŠ• แ‰ฅแ‰ป แ‹ญแˆ‹แŠฉ แ‰ แŒŠแ‹œ แ‰ แŠฉแˆ ? แ‰ปแŠ“แˆ แˆˆแˆ›แˆตแ‰€แˆ˜แŒฅแข แ‰ แˆฐแŠจแŠ•แ‹ต 30 แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“แ‹Žแ‰ฝ แ‰ฅแ‹™ แŠ“แ‰ธแ‹!

แˆˆแˆแŠ• แ‹แˆ แ‰ฅแˆˆแˆ… แŠ แ‰ตแ‹ฐแ‹แˆแข update() แ‰ แˆฐแŠจแŠ•แ‹ต 30 แŒŠแ‹œ? แ‹จแŒจแ‹‹แ‰ณแ‹แŠ• แˆ›แˆตแˆ˜แˆฐแˆ แˆˆแˆ›แˆปแˆปแˆแข แ‰ฅแ‹™ แŒŠแ‹œ แ‹จแˆšแŒ แˆซแ‹แข update()แฃ แ‹จแŒจแ‹‹แ‰ณแ‹ แˆ›แˆตแˆ˜แˆฐแˆ แ‹จแ‰ แˆˆแŒ  แ‰ตแŠญแŠญแˆˆแŠ› แ‹ญแˆ†แŠ“แˆแข แАแŒˆแˆญ แŒแŠ• แ‰ แˆแ‰ฐแŠ“แ‹Žแ‰ฝ แ‰ฅแ‹›แ‰ต แ‰ฅแ‹™ แŠ แ‰ตแ‹ˆแˆฐแ‹ฑแข update(), แˆแŠญแŠ•แ‹ซแ‰ฑแˆ แ‹ญแˆ… แˆตแˆŒแ‰ต แ‹แ‹ต แ‹จแˆ†แА แˆตแˆซ แАแ‹ - 60 แ‰ แˆฐแŠจแŠ•แ‹ต แ‰ แ‰‚ แАแ‹.

แ‹จแ‰€แˆจแ‹ แŠญแแˆ Game แ‹แˆตแŒฅ แŒฅแ‰…แˆ แˆ‹แ‹ญ แ‹จแ‹‹แˆ‰ แ‹จแˆจแ‹ณแ‰ต แ‹˜แ‹ดแ‹Žแ‰ฝแŠ• แ‹ซแŠซแ‰ตแ‰ณแˆ update():

game.js แŠญแแˆ 3

class Game {
  // ...

  getLeaderboard() {
    return Object.values(this.players)
      .sort((p1, p2) => p2.score - p1.score)
      .slice(0, 5)
      .map(p => ({ username: p.username, score: Math.round(p.score) }));
  }

  createUpdate(player, leaderboard) {
    const nearbyPlayers = Object.values(this.players).filter(
      p => p !== player && p.distanceTo(player) <= Constants.MAP_SIZE / 2,
    );
    const nearbyBullets = this.bullets.filter(
      b => b.distanceTo(player) <= Constants.MAP_SIZE / 2,
    );

    return {
      t: Date.now(),
      me: player.serializeForUpdate(),
      others: nearbyPlayers.map(p => p.serializeForUpdate()),
      bullets: nearbyBullets.map(b => b.serializeForUpdate()),
      leaderboard,
    };
  }
}

getLeaderboard() แ‰ แŒฃแˆ แ‰€แˆ‹แˆ - แ‰ฐแŒซแ‹‹แ‰พแ‰นแŠ• แ‰ แ‹แŒคแ‰ต แ‹ญแˆ˜แ‹ตแ‰ฃแˆแฃ แŠ แˆแˆตแ‰ฑแŠ• แ‹ญแ‹ˆแˆตแ‹ณแˆ แŠฅแŠ“ แ‹จแ‰ฐแŒ แ‰ƒแˆš แˆตแˆ แŠฅแŠ“ แˆˆแŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แАแŒฅแ‰ฅ แ‹ญแˆ˜แˆแˆณแˆแข

createUpdate() แ‹แˆตแŒฅ แŒฅแ‰…แˆ แˆ‹แ‹ญ แ‹แˆแˆ update() แˆˆแ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แ‹จแˆšแŠจแ‹แˆแˆ‰ แ‹จแŒจแ‹‹แ‰ณ แ‹แˆ˜แŠ“แ‹Žแ‰ฝแŠ• แˆˆแˆ˜แแŒ แˆญแข แ‹‹แŠ“แ‹ แˆฅแˆซแ‹ แ‹˜แ‹ดแ‹Žแ‰ฝแŠ• แˆ˜แŒฅแˆซแ‰ต แАแ‹ serializeForUpdate()แˆˆแŠญแแˆŽแ‰ฝ แ‹จแ‰ฐแ‰ฐแŒˆแ‰ แˆจ Player ะธ Bullet. แˆˆแŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แˆจแŒƒแŠ• แ‰ฅแ‰ป แŠฅแŠ•แ‹ฐแˆšแ‹ซแˆตแ‰ฐแˆ‹แˆแ แˆแ‰ฅ แ‹ญแ‰ แˆ‰ แ‰…แˆญแ‰ฅ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แŠฅแŠ“ แ•แˆฎแŒ„แŠญแ‰ถแ‰ฝ - แŠจแ‰ฐแŒซแ‹‹แ‰น แˆญแ‰€แ‹ แˆตแˆˆแˆšแŒˆแŠ™ แ‹จแŒจแ‹‹แ‰ณ แ‹•แ‰ƒแ‹Žแ‰ฝ แˆ˜แˆจแŒƒ แˆ›แˆตแ‰ฐแˆ‹แˆˆแ แŠ แ‹ซแˆตแˆแˆแŒแˆ!

3. แ‰ แŠ แŒˆแˆแŒ‹แ‹ฉ แˆ‹แ‹ญ แ‹จแŒจแ‹‹แ‰ณ แŠฅแ‰ƒแ‹Žแ‰ฝ

แ‰ แŠฅแŠ› แŒจแ‹‹แ‰ณ แ‹แˆตแŒฅ แ•แˆฎแŒ„แŠญแ‰ฐแˆฎแ‰ฝ แŠฅแŠ“ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แ‰ แŒฃแˆ แ‰ฐแˆ˜แˆณแˆณแ‹ญ แŠ“แ‰ธแ‹แก แŠฅแАแˆฑ แˆจแ‰‚แ‰…แฃ แŠญแ‰ฅแฃ แ‰ฐแŠ•แ‰€แˆณแ‰ƒแˆฝ แ‹จแŒจแ‹‹แ‰ณ แŠฅแ‰ƒแ‹Žแ‰ฝ แŠ“แ‰ธแ‹แข แ‰ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แŠฅแŠ“ แ‰ แ•แˆฎแŒ€แŠญแ‰ถแ‰ฝ แˆ˜แŠซแŠจแˆ แ‹ซแˆˆแ‹แŠ• แ‰ฐแˆ˜แˆณแˆณแ‹ญแАแ‰ต แˆˆแˆ˜แŒ แ‰€แˆ แ‹จแˆ˜แˆ แˆจแ‰ต แŠญแแˆ‰แŠ• แ‰ แˆ˜แ‰ฐแŒแ‰ แˆญ แŠฅแŠ•แŒ€แˆแˆญ Object:

แАแŒˆแˆญ.js

class Object {
  constructor(id, x, y, dir, speed) {
    this.id = id;
    this.x = x;
    this.y = y;
    this.direction = dir;
    this.speed = speed;
  }

  update(dt) {
    this.x += dt * this.speed * Math.sin(this.direction);
    this.y -= dt * this.speed * Math.cos(this.direction);
  }

  distanceTo(object) {
    const dx = this.x - object.x;
    const dy = this.y - object.y;
    return Math.sqrt(dx * dx + dy * dy);
  }

  setDirection(dir) {
    this.direction = dir;
  }

  serializeForUpdate() {
    return {
      id: this.id,
      x: this.x,
      y: this.y,
    };
  }
}

แŠฅแ‹šแˆ… แˆแŠ•แˆ แ‹จแ‰ฐแ‹ˆแˆณแˆฐแ‰  แАแŒˆแˆญ แ‹จแˆˆแˆ. แ‹ญแˆ… แŠญแแˆ แˆˆแ‰…แŒฅแ‹ซแ‹ แŒฅแˆฉ แˆ˜แˆแˆ…แ‰… แ‹ญแˆ†แŠ“แˆ. แŠญแแˆ‰ แŠฅแŠ•แ‹ดแ‰ต แŠฅแŠ•แ‹ฐแˆ†แА แŠฅแŠ•แ‹ญ Bullet แ‹ญแŒ แ‰€แˆ›แˆ Object:

แŒฅแ‹ญแ‰ต.js

const shortid = require('shortid');
const ObjectClass = require('./object');
const Constants = require('../shared/constants');

class Bullet extends ObjectClass {
  constructor(parentID, x, y, dir) {
    super(shortid(), x, y, dir, Constants.BULLET_SPEED);
    this.parentID = parentID;
  }

  // Returns true if the bullet should be destroyed
  update(dt) {
    super.update(dt);
    return this.x < 0 || this.x > Constants.MAP_SIZE || this.y < 0 || this.y > Constants.MAP_SIZE;
  }
}

แ‰ตแŒแ‰ แˆซ Bullet แ‰ แŒฃแˆ แŠ แŒญแˆญ! แŒจแˆแˆจแŠ“แˆแข Object แ‹จแˆšแŠจแ‰ฐแˆ‰แ‰ต แ‰…แŒฅแ‹ซแ‹Žแ‰ฝ แ‰ฅแ‰ป:

  • แŒฅแ‰…แˆ‰แŠ• แ‰ แˆ˜แŒ แ‰€แˆ shortid แˆˆแŠ แŒ‹แŒฃแˆš แ‰ตแ‹แˆแ‹ต id แ•แˆฎแŒ„แŠญแ‰ต.
  • แˆ˜แˆตแŠญ แˆ˜แŒจแˆ˜แˆญ parentIDแ‹ญแˆ…แŠ•แŠ• แ•แˆฎแŒ€แŠญแ‰ต แ‹จแˆแŒ แˆจแ‹แŠ• แ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แŠจแ‰ณแ‰ฐแˆ แŠฅแŠ•แ‹ตแ‰ตแ‰ฝแˆ‰แข
  • แ‹จแˆ˜แˆ˜แˆˆแˆป แŠฅแˆดแ‰ต แ‰ แˆ›แŠจแˆ แˆ‹แ‹ญ update(), แ‹ญแˆ…แˆ แŒ‹แˆญ แŠฅแŠฉแˆ แАแ‹ trueแ•แˆฎแŒ€แŠญแ‰ฑ แŠจแˆ˜แ‹ตแˆจแŠฉ แ‹แŒญ แŠจแˆ†แА (แ‰ แˆ˜แŒจแˆจแˆปแ‹ แŠญแแˆ แˆ‹แ‹ญ แˆตแˆˆแ‹šแˆ… แŒ‰แ‹ณแ‹ญ แŠฅแŠ•แ‹ฐแ‰ฐแАแŒ‹แŒˆแˆญแŠ• แ‹ซแˆตแ‰ณแ‹แˆฑ?)

แ‹ˆแ‹ฐแ‹šแˆ… แŠฅแŠ•แˆ‚แ‹ต Player:

แ‰ฐแŒซแ‹‹แ‰ฝ.js

const ObjectClass = require('./object');
const Bullet = require('./bullet');
const Constants = require('../shared/constants');

class Player extends ObjectClass {
  constructor(id, username, x, y) {
    super(id, x, y, Math.random() * 2 * Math.PI, Constants.PLAYER_SPEED);
    this.username = username;
    this.hp = Constants.PLAYER_MAX_HP;
    this.fireCooldown = 0;
    this.score = 0;
  }

  // Returns a newly created bullet, or null.
  update(dt) {
    super.update(dt);

    // Update score
    this.score += dt * Constants.SCORE_PER_SECOND;

    // Make sure the player stays in bounds
    this.x = Math.max(0, Math.min(Constants.MAP_SIZE, this.x));
    this.y = Math.max(0, Math.min(Constants.MAP_SIZE, this.y));

    // Fire a bullet, if needed
    this.fireCooldown -= dt;
    if (this.fireCooldown <= 0) {
      this.fireCooldown += Constants.PLAYER_FIRE_COOLDOWN;
      return new Bullet(this.id, this.x, this.y, this.direction);
    }
    return null;
  }

  takeBulletDamage() {
    this.hp -= Constants.BULLET_DAMAGE;
  }

  onDealtDamage() {
    this.score += Constants.SCORE_BULLET_HIT;
  }

  serializeForUpdate() {
    return {
      ...(super.serializeForUpdate()),
      direction: this.direction,
      hp: this.hp,
    };
  }
}

แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แŠจแ•แˆฎแŒ€แŠญแ‰ถแ‰ฝ แ‹จแ‰ แˆˆแŒ  แ‹แˆตแ‰ฅแˆตแ‰ฅ แŠ“แ‰ธแ‹, แˆตแˆˆแ‹šแˆ… แŒฅแ‰‚แ‰ต แ‰ฐแŒจแˆ›แˆช แˆ˜แˆตแŠฎแ‰ฝ แ‰ แ‹šแˆ… แŠญแแˆ แ‹แˆตแŒฅ แˆ˜แ‰€แˆ˜แŒฅ แŠ แˆˆแ‰ฃแ‰ธแ‹. แ‹จแŠฅแˆฑ แ‹˜แ‹ด update() แ‰ฅแ‹™ แˆตแˆซแ‹Žแ‰ฝแŠ• แ‹ญแˆฐแˆซแˆ, แ‰ แ‰ฐแˆˆแ‹ญแˆ, แˆแŠ•แˆ แŠซแˆแ‰€แˆฉ แŠ แ‹ฒแˆต แ‹จแ‰ฐแˆแŒ แˆจแ‹แŠ• แ•แˆฎแŒ€แŠญแ‰ต แ‹ญแˆ˜แˆแˆณแˆ fireCooldown (แ‰ แ‰€แ‹ฐแˆ˜แ‹ แŠญแแˆ แˆตแˆˆแ‹šแˆ… แŒ‰แ‹ณแ‹ญ แŠฅแŠ•แ‹ฐแ‰ฐแАแŒ‹แŒˆแˆญแŠ• แŠ แˆตแ‰ณแ‹แˆต?) แŠฅแŠ•แ‹ฒแˆแˆ แ‹˜แ‹ดแ‹แŠ• แ‹ซแˆซแ‹แˆ˜แ‹‹แˆ serializeForUpdate(), แˆแŠญแŠ•แ‹ซแ‰ฑแˆ แ‰ แŒจแ‹‹แ‰ณ แ‹แˆ›แŠ” แ‹แˆตแŒฅ แˆˆแ‰ฐแŒซแ‹‹แ‰น แ‰ฐแŒจแˆ›แˆช แˆ˜แˆตแŠฎแ‰ฝแŠ• แˆ›แŠซแ‰ฐแ‰ต แŠ แˆˆแ‰ฅแŠ•.

แ‹จแˆ˜แˆ แˆจแ‰ต แŠญแแˆ แˆ˜แŠ–แˆญ Object - แŠฎแ‹ตแŠ• แˆ˜แ‹ตแŒˆแˆ แˆˆแˆ›แˆตแ‹ˆแŒˆแ‹ต แŠ แˆตแˆแˆ‹แŒŠ แŠฅแˆญแˆแŒƒ. แˆˆแˆแˆณแˆŒ, แŠญแแˆ แ‹จแˆˆแˆ Object แŠฅแ‹ซแŠ•แ‹ณแŠ•แ‹ฑ แ‹จแŒจแ‹‹แ‰ณ แАแŒˆแˆญ แŠ แŠ•แ‹ต แŠ แ‹ญแАแ‰ต แŠ แ‰ฐแŒˆแ‰ฃแ‰ แˆญ แˆŠแŠ–แˆจแ‹ แ‹ญแŒˆแ‰ฃแˆ distanceTo(), แŠฅแŠ“ แŠฅแАแ‹šแˆ…แŠ• แˆแˆ‰ แ‰ตแŒแ‰ แˆซแ‹Žแ‰ฝ แ‰ แ‰ แˆญแŠซแ‰ณ แ‹แ‹ญแˆŽแ‰ฝ แˆ‹แ‹ญ แˆ˜แ‰…แ‹ณแ‰ต แ‰…แ‹ แ‰ต แ‹ญแˆ†แŠ“แˆ. แ‹ญแˆ… แ‰ แ‰ฐแˆˆแ‹ญ แˆˆแ‰ตแˆ‹แˆแ‰… แ•แˆฎแŒ€แŠญแ‰ถแ‰ฝ แŠ แˆตแˆแˆ‹แŒŠ แ‹ญแˆ†แŠ“แˆ.แ‹จแˆšแˆฐแ‹แ‹ แ‰แŒฅแˆญ แˆฒแŒจแˆแˆญ Object แŠญแแˆŽแ‰ฝ แŠฅแ‹ซแ‹ฐแŒ‰ แŠ“แ‰ธแ‹.

4. แŒแŒญแ‰ตแŠ• แˆ˜แˆˆแ‹จแ‰ต

แˆˆแŠฅแŠ› แ‹จแ‰€แˆจแŠ• แ‰ฅแ‰ธแŠ›แ‹ แАแŒˆแˆญ แ•แˆฎแŒ€แŠญแ‰ฑ แ‰ฐแŒซแ‹‹แ‰พแ‰นแŠ• แˆฒแˆ˜แ‰ฑ แˆ˜แˆˆแ‹จแ‰ต แАแ‹! แ‹ญแˆ…แŠ•แŠ• แŠฎแ‹ต แŠจแˆตแˆแ‰ฑ แ‹แˆตแŒฅ แ‹ซแˆตแ‰ณแ‹แˆฑ update() แ‰ แŠญแแˆ แ‹แˆตแŒฅ Game:

แŒจแ‹‹แ‰ณ.js

const applyCollisions = require('./collisions');

class Game {
  // ...

  update() {
    // ...

    // Apply collisions, give players score for hitting bullets
    const destroyedBullets = applyCollisions(
      Object.values(this.players),
      this.bullets,
    );
    destroyedBullets.forEach(b => {
      if (this.players[b.parentID]) {
        this.players[b.parentID].onDealtDamage();
      }
    });
    this.bullets = this.bullets.filter(
      bullet => !destroyedBullets.includes(bullet),
    );

    // ...
  }
}

แ‹˜แ‹ดแ‹แŠ• แ‰ฐแŒแ‰ฃแˆซแ‹Š แˆ›แ‹ตแˆจแŒ แŠ แˆˆแ‰ฅแŠ• applyCollisions(), แ‹ญแˆ…แˆ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝแŠ• แ‹จแˆšแˆ˜แ‰ฑแ‰ตแŠ• แˆแˆ‰แŠ•แˆ แ•แˆฎแŒ„แŠญแ‰ถแ‰ฝ แ‹ญแˆ˜แˆแˆณแˆ. แŠฅแŠ•แ‹ฐ แŠฅแ‹ตแˆ แˆ†แŠ–, แ‹ญแˆ…แŠ• แˆ›แ‹ตแˆจแŒ แŠจแ‰ฃแ‹ต แŠ แ‹ญแ‹ฐแˆˆแˆ แˆแŠญแŠ•แ‹ซแ‰ฑแˆ

  • แˆแˆ‰แˆ แ‹จแˆšแŒ‹แŒฉ แАแŒˆแˆฎแ‰ฝ แŠญแ‰ แ‰ฆแ‰ฝ แŠ“แ‰ธแ‹, แ‹ญแˆ…แˆ แ‹จแŒแŒญแ‰ต แˆ›แ‹ˆแ‰‚แ‹ซแŠ• แˆˆแˆ˜แ‰ฐแŒแ‰ แˆญ แ‰€แˆ‹แˆ‰ แ‰…แˆญแŒฝ แАแ‹.
  • แŠ แˆตแ‰€แ‹ตแˆ˜แŠ• แ‹˜แ‹ด แŠ แˆˆแŠ• distanceTo(), แ‰ แŠญแแˆ แ‹แˆตแŒฅ แ‰ฃแˆˆแˆแ‹ แŠญแแˆ แ‹แˆตแŒฅ แ‰ฐแŒแ‰ฃแˆซแ‹Š แ‹ซแ‹ฐแˆจแŒแАแ‹ Object.

แ‹จแŠฅแŠ› แ‹จแŒแŒญแ‰ต แˆ›แ‹ˆแ‰‚แ‹ซ แŠ แ‰ฐแŒˆแ‰ฃแ‰ แˆญ แˆแŠ• แ‹ญแˆ˜แˆตแˆ‹แˆแข

แŒแŒญแ‰ถแ‰ฝ.js

const Constants = require('../shared/constants');

// Returns an array of bullets to be destroyed.
function applyCollisions(players, bullets) {
  const destroyedBullets = [];
  for (let i = 0; i < bullets.length; i++) {
    // Look for a player (who didn't create the bullet) to collide each bullet with.
    // As soon as we find one, break out of the loop to prevent double counting a bullet.
    for (let j = 0; j < players.length; j++) {
      const bullet = bullets[i];
      const player = players[j];
      if (
        bullet.parentID !== player.id &&
        player.distanceTo(bullet) <= Constants.PLAYER_RADIUS + Constants.BULLET_RADIUS
      ) {
        destroyedBullets.push(bullet);
        player.takeBulletDamage();
        break;
      }
    }
  }
  return destroyedBullets;
}

แ‹ญแˆ… แ‰€แˆ‹แˆ แ‹จแŒแŒญแ‰ต แŒแŠแ‰ต แ‰ แŠฅแ‹แАแ‰ณแ‹ แˆ‹แ‹ญ แ‹จแ‰ฐแˆ˜แˆฐแˆจแ‰ฐ แАแ‹ แ‰ แˆ›แ‹•แŠจแˆŽแ‰ปแ‰ธแ‹ แˆ˜แŠซแŠจแˆ แ‹ซแˆˆแ‹ แˆญแ‰€แ‰ต แŠจ แˆซแ‹ฒแ‹ฎแ‰ปแ‰ธแ‹ แ‹ตแˆแˆญ แ‹ซแАแˆฐ แŠจแˆ†แА แˆแˆˆแ‰ต แŠญแ‰ แ‰ฆแ‰ฝ แ‹ญแŒ‹แŒซแˆ‰. แ‰ แˆแˆˆแ‰ต แŠญแ‰ แ‰ฆแ‰ฝ แˆ›แ‹•แŠจแˆŽแ‰ฝ แˆ˜แŠซแŠจแˆ แ‹ซแˆˆแ‹ แˆญแ‰€แ‰ต แ‰ แ‰ตแŠญแŠญแˆ แŠจแˆซแ‹ฒแ‹ซแ‰ธแ‹ แ‹ตแˆแˆญ แŒ‹แˆญ แŠฅแŠฉแˆ แ‹จแˆ†แАแ‰ แ‰ต แˆแŠ”แ‰ณ แŠฅแ‹šแˆ… แŠ แˆˆแข

แ‰ฃแˆˆแ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰ฝ .io แ‹จแ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆ˜แแŒ แˆญ
แŠฅแ‹šแˆ… แˆŠแ‰ณแˆฐแ‰ฅแ‰ฃแ‰ธแ‹ แ‹จแˆšแŒˆแ‰ก แˆแˆˆแ‰ต แ‰ฐแŒจแˆ›แˆช แŒˆแŒฝแ‰ณแ‹Žแ‰ฝ แŠ แˆ‰-

  • แ•แˆฎแŒ€แŠญแ‰ฑ แ‹จแˆแŒ แˆจแ‹แŠ• แ‰ฐแŒซแ‹‹แ‰ฝ แˆ˜แˆแ‰ณแ‰ต แ‹จแˆˆแ‰ แ‰ตแˆแข แ‹ญแˆ… แ‰ แˆ›แАแƒแ€แˆญ แˆŠแŒˆแŠ แ‹ญแ‰ฝแˆ‹แˆ bullet.parentID ั player.id.
  • แ‰ฅแ‹™ แ‰ฐแŒซแ‹‹แ‰พแ‰ฝ แ‰ แ‰ฐแˆ˜แˆณแˆณแ‹ญ แŒŠแ‹œ แˆฒแŒ‹แŒฉ แ•แˆฎแŒ€แŠญแ‰ฑ แŠ แŠ•แ‹ต แŒŠแ‹œ แ‰ฅแ‰ป แˆ˜แˆแ‰ณแ‰ต แŠ แˆˆแ‰ แ‰ตแข แŠฆแ•แˆฌแ‰ฐแˆฉแŠ• แ‰ แˆ˜แŒ แ‰€แˆ แ‹ญแˆ…แŠ•แŠ• แ‰ฝแŒแˆญ แŠฅแŠ•แˆแ‰ณแ‹‹แˆˆแŠ• break: แŠจแ•แˆฎแŒ€แŠญแ‰ฑ แŒ‹แˆญ แ‹จแˆšแŒ‹แŒญ แ‰ฐแŒซแ‹‹แ‰น แŠฅแŠ•แ‹ฐแ‰ฐแŒˆแŠ˜ แแˆˆแŒ‹แ‹แŠ• แŠ แ‰แˆ˜แŠ• แ‹ˆแ‹ฐแˆšแ‰€แŒฅแˆˆแ‹ แ•แˆฎแŒ€แŠญแ‰ต แŠฅแŠ•แ‰€แŒฅแˆ‹แˆˆแŠ•แข

แˆ˜แŒจแˆจแˆปแ‹

แ‹ญแŠผแ‹ แАแ‹! แ‹จ.io แ‹ตแˆญ แŒจแ‹‹แ‰ณ แˆˆแˆ˜แแŒ แˆญ แˆ›แ‹ˆแ‰… แ‹ซแˆˆแ‰ฅแ‹Žแ‰ตแŠ• แˆแˆ‰แŠ•แˆ แАแŒˆแˆญ แˆธแแАแŠ“แˆแข แ‰€แŒฅแˆŽ แˆแŠ• แŠ แˆˆ? แ‹จแˆซแˆตแ‹ŽแŠ• แ‹จ.io แŒจแ‹‹แ‰ณ แ‹ญแŒˆแŠ•แ‰ก!

แˆแˆ‰แˆ แ‹จแŠ“แˆ™แŠ“ แŠฎแ‹ต แŠญแแ‰ต แˆแŠ•แŒญ แŠฅแŠ“ แ‹จแ‰ฐแˆˆแŒ แˆ แАแ‹แข แ‹จแŠแˆแˆ™.

แˆแŠ•แŒญ: hab.com

แ‰ DDoS แŒฅแ‰ แ‰ƒแฃ VPS VDS แŠ แŒˆแˆแŒ‹แ‹ฎแ‰ฝ แˆˆแŒฃแ‰ขแ‹ซแ‹Žแ‰ฝ แŠ แˆตแ‰ฐแˆ›แˆ›แŠ แˆ›แˆตแ‰ฐแŠ“แŒˆแŒƒ แ‹ญแŒแ‹™ ๐Ÿ”ฅ แŠ แˆตแ‰ฐแˆ›แˆ›แŠ แ‹จแ‹ตแˆญ แŒฃแ‰ขแ‹ซ แˆ›แˆตแ‰ฐแŠ“แŒˆแŒƒ แ‰ แ‹ฒแ‹ถแŠคแˆต แŒฅแ‰ แ‰ƒแฃ แ‰ แ‰ชแ’แŠคแˆต แ‰ชแ‹ฒแŠคแˆต แŠ แŒˆแˆแŒ‹แ‹ฎแ‰ฝ แ‹ญแŒแ‹™ | ProHoster