ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ

ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ื‘ืืคืจื™ื™ื˜ ืื™ืŸ 2015 Agar.io ืื™ื– ื’ืขื•ื•ืึธืจืŸ ื“ืขืจ ืึธื ืคึฟืจืขื’ ืคึฟื•ืŸ ืึท ื ืฒึทืขื ื–ืฉืึทื ืขืจ games.io, ื•ื•ืขืžืขื ืก ืคึผืึธืคึผื•ืœืึทืจื™ื˜ืขื˜ ืื™ื– ื–ื™ื™ืขืจ ื’ืขื•ื•ืืงืกืŸ ื–ื™ื ื˜ ื“ืขืžืึธืœื˜. ืื™ืš ื”ืึธื‘ืŸ ื™ืงืกืคึผื™ืจื™ืึทื ืกื˜ ื“ื™ ื”ืขื›ืขืจื•ื ื’ ืื™ืŸ ืคึผืึธืคึผื•ืœืึทืจื™ื˜ืขื˜ ืคื•ืŸ .io ืฉืคึผื™ืœืขืจื™ื™ึท ื–ื™ืš: ืื™ืŸ ื“ื™ ืœืขืฆื˜ืข ื“ืจื™ื™ ื™ืึธืจ, ืื™ืš ื‘ืืฉืืคืŸ ืื•ืŸ ืคืืจืงื•ื™ืคื˜ ืฆื•ื•ื™ื™ ืฉืคึผื™ืœืขืจื™ื™ึท ืื™ืŸ ื“ืขื ื–ืฉืึทื ืจืึท..

ืื•ื™ื‘ ืื™ืจ ื”ืึธื˜ ืงื™ื™ื ืžืึธืœ ื’ืขื”ืขืจื˜ ื•ื•ืขื’ืŸ ื“ื™ ืฉืคึผื™ืœืขืจื™ื™ึท ืคืจื™ืขืจ, ื–ื™ื™ ื–ืขื ืขืŸ ืคืจื™ื™ ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ ื•ื•ืขื‘ ืฉืคึผื™ืœืขืจื™ื™ึท ื•ื•ืึธืก ื–ืขื ืขืŸ ื’ืจื™ื ื’ ืฆื• ืฉืคึผื™ืœืŸ (ืงื™ื™ืŸ ื—ืฉื‘ื•ืŸ ืคืืจืœืื ื’ื˜). ื–ื™ื™ ื™ื•ื–ืฉืึทื•ื•ืึทืœื™ ื’ืจื•ื‘ ืคื™ืœืข ืึทืคึผืึธื•ื–ื™ื ื’ ืคึผืœื™ื™ึทืขืจืก ืื™ืŸ ืื™ื™ืŸ ืืจืขื ืข. ืื ื“ืขืจืข ื‘ืึทืจื™ืžื˜ .io ืฉืคึผื™ืœืขืจื™ื™ึท: Slither.io ะธ Diep.io.

ืื™ืŸ ื“ืขื ืึทืจื˜ื™ืงืœ ืžื™ืจ ื•ื•ืขืœืŸ ื’ืขืคึฟื™ื ืขืŸ ืื•ื™ืก ื•ื•ื™ ืฉืึทืคึฟืŸ ืึทืŸ .io ืฉืคึผื™ืœ ืคึฟื•ืŸ ืงืจืึทืฆืŸ. ืฆื• ื˜ืึธืŸ ื“ืึธืก, ื‘ืœื•ื™ื– ื•ื•ื™ืกืŸ ืคื•ืŸ ื“ื–ืฉืึทื•ื•ืึทืกืงืจื™ืคึผื˜ ื•ื•ืขื˜ ื–ื™ื™ืŸ ื’ืขื ื•ื’: ืื™ืจ ื“ืึทืจืคึฟืŸ ืฆื• ืคึฟืึทืจืฉื˜ื™ื™ืŸ ื–ืื›ืŸ ื•ื•ื™ ืกื™ื ื˜ืึทืงืก ืขืกืงืกื ื•ืžืงืก, ืงื™ื•ื•ืขืจื“ this ะธ ืคึผืจืึธืžื™ืกืขืก. ืืคื™ืœื• ืื•ื™ื‘ ืื™ืจ ื˜ืึธืŸ ื ื™ื˜ ื•ื•ื™ืกืŸ Javascript ื‘ื™ืฉืœื™ื™ืžืขืก, ืื™ืจ ืงืขื ืขืŸ ื ืึธืš ืคึฟืึทืจืฉื˜ื™ื™ืŸ ืจื•ื‘ึฟ ืคื•ืŸ ื“ื™ ืคึผืึธืกื˜ืŸ.

ื‘ื™ื™ึทืฉืคึผื™ืœ ืคื•ืŸ ืึท .ื™ืึธ ืฉืคึผื™ืœ

ืคึฟืึทืจ ื˜ืจื™ื™ื ื™ื ื’ ื”ื™ืœืฃ ืžื™ืจ ื•ื•ืขืœืŸ ืึธืคึผืฉื™ืงืŸ ืฆื• ื‘ื™ื™ึทืฉืคึผื™ืœ ืฉืคึผื™ืœ .ื™ืึธ. ืคึผืจื•ื‘ื™ืจืŸ ืฆื• ืฉืคึผื™ืœืŸ ืขืก!

ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ื“ืขืจ ืฉืคึผื™ืœ ืื™ื– ื’ืึทื ืฅ ืคึผืฉื•ื˜: ืื™ืจ ืงืึธื ื˜ืจืึธืœ ืึท ืฉื™ืฃ ืื™ืŸ ืึทืŸ ืืจืขื ืข ืžื™ื˜ ืื ื“ืขืจืข ืคึผืœื™ื™ึทืขืจืก. ื“ื™ื™ืŸ ืฉื™ืฃ ืคื™ื™ืขืจื– ืื•ื™ื˜ืึธืžืึทื˜ื™ืฉ ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœื– ืื•ืŸ ืื™ืจ ืคึผืจื•ื‘ื™ืจืŸ ืฆื• ืฉืœืึธื’ืŸ ืื ื“ืขืจืข ืคึผืœื™ื™ึทืขืจืก ื‘ืฉืขืช ื•ื™ืกืžื™ื™ื“ืŸ ื–ื™ื™ืขืจ ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœื–.

1. ืงื•ืจืฅ ืื™ื‘ืขืจื‘ืœื™ืง / ืคึผืจื•ื™ืขืงื˜ ืกื˜ืจื•ืงื˜ื•ืจ

ืจืขืงืึธืžืขื ื“ื™ืจืŸ ืึธืคึผืœืึธื“ื™ืจืŸ ืžืงื•ืจ ืงืึธื“ ื‘ื™ื™ึทืฉืคึผื™ืœ ืฉืคึผื™ืœ ืึทื–ื•ื™ ืื™ืจ ืงืขื ืขืŸ ื ืึธื›ืคืึธืœื’ืŸ ืžื™ืจ.

ื“ืขืจ ื‘ื™ื™ืฉืคึผื™ืœ ื ื™ืฆื˜ ื“ื™ ืคืืœื’ืขื ื“ืข:

  • ืขืงืกืคึผืจืขืกืก ืื™ื– ื“ื™ ืžืขืจืกื˜ ืคืึธืœืงืก ื•ื•ืขื‘ ืคืจื™ื™ืžื•ื•ืขืจืง ืคึฟืึทืจ Node.js ื•ื•ืึธืก ืžืึทื ื™ื“ื–ืฉื™ื– ื“ื™ ื•ื•ืขื‘ ืกืขืจื•ื•ืขืจ ืคื•ืŸ ื“ื™ ืฉืคึผื™ืœ.
  • socket.io - ื•ื•ืขื‘ืกืึธืงืงืขื˜ ื‘ื™ื‘ืœื™ืึธื˜ืขืง ืคึฟืึทืจ ื™ืงืกื˜ืฉื™ื™ื ื“ื–ืฉื™ื ื’ ื“ืึทื˜ืŸ ืฆื•ื•ื™ืฉืŸ ื“ืขื ื‘ืœืขื˜ืขืจืขืจ ืื•ืŸ ื“ื™ ืกืขืจื•ื•ืขืจ.
  • ื•ื•ืขื‘ืคึผืึทืงืง - ืžืึธื“ื•ืœืข ืคืึทืจื•ื•ืึทืœื˜ืขืจ. ืื™ืจ ืงืขื ื˜ ืœื™ื™ืขื ืขืŸ ื•ื•ืขื’ืŸ ื•ื•ืึธืก ืฆื• ื ื•ืฆืŸ Webpack ื“ืึธ.

ื“ืึธืก ืื™ื– ื•ื•ื™ ื“ื™ ืคึผืจื•ื™ืขืงื˜ ื•ื•ืขื’ื•ื•ื™ื™ึทื–ืขืจ ืกื˜ืจื•ืงื˜ื•ืจ ืงื•ืงื˜ ื•ื•ื™:

public/
    assets/
        ...
src/
    client/
        css/
            ...
        html/
            index.html
        index.js
        ...
    server/
        server.js
        ...
    shared/
        constants.js

ืขืคื ื˜ืœืขืš/

ืึทืœืฅ ืื™ื– ืื™ืŸ ื“ืขืจ ื˜ืขืงืข public/ ื•ื•ืขื˜ ื–ื™ื™ืŸ ืกื˜ืึทื˜ื™ืงืœื™ ื˜ืจืึทื ืกืžื™ื˜ื˜ืขื“ ื“ื•ืจืš ื“ื™ ืกืขืจื•ื•ืขืจ. ืื™ืŸ public/assets/ ื›ึผื•ืœืœ ื‘ื™ืœื“ืขืจ ื’ืขื ื™ืฆื˜ ื“ื•ืจืš ืื•ื ื“ื–ืขืจ ืคึผืจื•ื™ืขืงื˜.

src /

ื›ืœ ืžืงื•ืจ ืงืึธื“ ืื™ื– ืื™ืŸ ื“ืขืจ ื˜ืขืงืข src/. ื˜ื™ื˜ืœืขืŸ client/ ะธ server/ ืจืขื“ืŸ ืคึฟืึทืจ ื–ื™ืš ืื•ืŸ shared/ ื›ึผื•ืœืœ ืึท ืงืึทื ืกื˜ืึทื ืฅ ื˜ืขืงืข ื™ืžืคึผืึธืจื˜ื™ื“ ื“ื•ืจืš ื“ื™ ืงืœื™ืขื ื˜ ืื•ืŸ ืกืขืจื•ื•ืขืจ.

2. ืึทืกืขืžื‘ืœื™ / ืคึผืจื•ื™ืขืงื˜ ืคึผืึทืจืึทืžืขื˜ืขืจืก

ื•ื•ื™ ืกื˜ื™ื™ื˜ื™ื“ ืื•ื™ื‘ืŸ, ืžื™ืจ ื ื•ืฆืŸ ืึท ืžืึธื“ื•ืœืข ืคืึทืจื•ื•ืึทืœื˜ืขืจ ืฆื• ื‘ื•ื™ืขืŸ ื“ืขื ืคึผืจื•ื™ืขืงื˜ ื•ื•ืขื‘ืคึผืึทืงืง. ืœืึธืžื™ืจ ื ืขืžืขืŸ ืึท ืงื•ืง ืื™ืŸ ืื•ื ื“ื–ืขืจ ื•ื•ืขื‘ืคึผืึทืงืง ืงืึทื ืคื™ื’ื™ืขืจื™ื™ืฉืึทืŸ:

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 ืื™ื– ื“ื™ ืคึผืึธื–ื™ืฆื™ืข ืคื•ื ื˜ ืคื•ืŸ ื“ื™ Javascript (JS) ืงืœื™ืขื ื˜. ื•ื•ืขื‘ืคึผืึทืงืง ื•ื•ืขื˜ ืึธื ื”ื™ื™ื‘ืŸ ืคึฟื•ืŸ ื“ืึธ ืื•ืŸ ืจืขืงื•ืจืกื™ื•ื•ืœื™ ืงื•ืงืŸ ืคึฟืึทืจ ืื ื“ืขืจืข ื™ืžืคึผืึธืจื˜ื™ื“ ื˜ืขืงืขืก.
  • ื“ืขืจ ืจืขื–ื•ืœื˜ืึทื˜ JS ืคื•ืŸ ืื•ื ื“ื–ืขืจ ื•ื•ืขื‘ืคึผืึทืงืง ื‘ื•ื™ืขืŸ ื•ื•ืขื˜ ื–ื™ื™ืŸ ืœื™ื’ืŸ ืื™ืŸ ื“ื™ ื•ื•ืขื’ื•ื•ื™ื™ึทื–ืขืจ dist/. ืื™ืš ื•ื•ืขืœ ืจื•ืคืŸ ื“ืขื ื˜ืขืงืข ืื•ื ื“ื–ืขืจ JS ืคึผืขืงืœ.
  • ืžื™ืจ ื ื•ืฆืŸ ื‘ืึทื‘ืขืœ, ืื•ืŸ ืกืคึผืขืฆื™ืขืœ ื“ื™ ืงืึทื ืคื™ื’ื™ืขืจื™ื™ืฉืึทืŸ @babel/preset-env ืฆื• ื˜ืจืึทื ืกืคึผื™ื™ืœ ืื•ื ื“ื–ืขืจ JS ืงืึธื“ ืคึฟืึทืจ ืขืœื˜ืขืจืข ื‘ืจืึทื•ื–ืขืจื–.
  • ืžื™ืจ ื ื•ืฆืŸ ืึท ืคึผืœื•ื’ื™ืŸ ืฆื• ืขืงืกื˜ืจืึทืงื˜ ืึทืœืข ื“ื™ CSS ืจืขืคืขืจืขื ืกื˜ ื“ื•ืจืš JS ื˜ืขืงืขืก ืื•ืŸ ืคืึทืจื‘ื™ื ื“ืŸ ื–ื™ื™ ืื™ืŸ ืื™ื™ืŸ ืึธืจื˜. ืื™ืš ื•ื•ืขืœ ืขืก ืจื•ืคืŸ ืื•ื ื“ื–ืขืจ CSS ืคึผืขืงืœ.

ืื™ืจ ืงืขืŸ ื”ืึธื‘ืŸ ื‘ืืžืขืจืงื˜ ืžืึธื“ื ืข ืคึผืขืงืœ ื˜ืขืงืข ื ืขืžืขืŸ '[name].[contenthash].ext'. ื–ื™ื™ ืึทื ื˜ื”ืึทืœื˜ืŸ ื˜ืขืงืข ื ืึธืžืขืŸ ืกืึทื‘ืกื˜ื™ื˜ื•ืฉืึทืŸ ื•ื•ืขื‘ืคึผืึทืง: [name] ื•ื•ืขื˜ ื–ื™ื™ืŸ ืจื™ืคึผืœื™ื™ืกื˜ ืžื™ื˜ ื“ื™ ื ืึธืžืขืŸ ืคื•ืŸ ื“ื™ ืึทืจื™ื™ึทื ืฉืจื™ื™ึทื‘ ืคื•ื ื˜ (ืื™ืŸ ืื•ื ื“ื–ืขืจ ืคืึทืœ ืขืก ืื™ื– game), ืึท [contenthash] ื•ื•ืขื˜ ื–ื™ื™ืŸ ืจื™ืคึผืœื™ื™ืกื˜ ืžื™ื˜ ืึท ื”ืึทืฉ ืคื•ืŸ ื“ื™ ื˜ืขืงืข ืื™ื ื”ืึทืœื˜. ืžื™ืจ ื˜ืึธืŸ ื“ืึธืก ืฆื• ืึทืคึผื˜ืึทืžื™ื™ื– ื“ื™ ืคึผืจื•ื™ืขืงื˜ ืคึฟืึทืจ ื›ืึทืฉื™ื ื’ - ืžื™ืจ ืงืขื ืขืŸ ื–ืึธื’ืŸ ื‘ืจืึทื•ื–ืขืจื– ืฆื• ืงืึทืฉ ืื•ื ื“ื–ืขืจ 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, ืฆื• ืึทืคึผื˜ืึทืžื™ื™ื– ืคึผืขืงืœ ืกื™ื–ืขืก ื•ื•ืขืŸ ื“ื™ืคึผืœื•ื™ื™ื ื’ ืฆื• ืคึผืจืึธื“ื•ืงืฆื™ืข.

ืœืืงืืœืข ืกืขื˜ืึทืคึผ

ืื™ืš ืจืขืงืึธืžืขื ื“ื™ืจืŸ ื™ื ืกื˜ืึธืœื™ื ื’ ื“ื™ ืคึผืจื•ื™ืขืงื˜ ืื•ื™ืฃ ื“ื™ื™ืŸ ื”ื™ื’ืข ืžืึทืฉื™ืŸ ืึทื–ื•ื™ ืื™ืจ ืงืขื ืขืŸ ื ืึธื›ื’ื™ื™ืŸ ื“ื™ ืกื˜ืขืคึผืก ืœื™ืกื˜ืขื“ ืื™ืŸ ื“ืขื ืคึผืึธืกื˜ืŸ. ืกืขื˜ืึทืคึผ ืื™ื– ืคึผืฉื•ื˜: ืขืจืฉื˜ืขืจ, ื“ื™ ืกื™ืกื˜ืขื ืžื•ื–ืŸ ื”ืึธื‘ืŸ ื ืึธื“ืข ะธ ื ืคึผื. ื•ื•ื™ื™ึทื˜ืขืจ ืื™ืจ ื“ืึทืจืคึฟืŸ ืฆื• ื˜ืึธืŸ

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

ืื•ืŸ ืื™ืจ ื–ืขื ื˜ ื’ืจื™ื™ื˜ ืฆื• ื’ื™ื™ืŸ! ืฆื• ืึธื ื”ื™ื™ื‘ืŸ ื“ื™ ืึทื ื˜ื•ื•ื™ืงืœื•ื ื’ ืกืขืจื•ื•ืขืจ, ื ืึธืจ ืœื•ื™ืคืŸ

$ npm run develop

ืื•ืŸ ื’ื™ื™ืŸ ืฆื• ื“ื™ื™ืŸ ื•ื•ืขื‘ ื‘ืœืขื˜ืขืจืขืจ ืœืึธืงืึทืœื”ืึธืกื˜: ืงืกื ื•ืžืงืก. ื“ืขืจ ืึทื ื˜ื•ื•ื™ืงืœื•ื ื’ ืกืขืจื•ื•ืขืจ ื•ื•ืขื˜ ืื•ื™ื˜ืึธืžืึทื˜ื™ืฉ ืจื™ื‘ื™ืœื“ ื“ื™ JS ืื•ืŸ CSS ืคึผืึทืงืึทื“ื–ืฉืึทื– ื•ื•ืขืŸ ืงืึธื“ ืขื ื“ืขืจื•ื ื’ืขืŸ ืคึผืึทืกื™ืจืŸ - ื ืึธืจ ื“ืขืจืคืจื™ืฉืŸ ื“ื™ ื‘ืœืึทื˜ ืฆื• ื–ืขืŸ ืึทืœืข ืขื ื“ืขืจื•ื ื’ืขืŸ!

3. ืงืœื™ืขื ื˜ ืคึผืึธื–ื™ืฆื™ืข ื•ื•ื™ื™ื–ื˜

ื–ืืœ ืก ื‘ืึทืงื•ืžืขืŸ ืึทืจืึธืคึผ ืฆื• ื“ื™ ืฉืคึผื™ืœ ืงืึธื“ ื–ื™ืš. ืขืจืฉื˜ืขืจ ืžื™ืจ ื“ืึทืจืคึฟืŸ ืึท ื‘ืœืึทื˜ index.html, ื•ื•ืขืŸ ืื™ืจ ื‘ืึทื–ื•ื›ืŸ ื“ืขื ืคึผืœืึทืฅ, ื“ืขืจ ื‘ืœืขื˜ืขืจืขืจ ื•ื•ืขื˜ ืœืึธื“ืŸ ืขืก ืขืจืฉื˜ืขืจ. ืื•ื ื“ื–ืขืจ ื‘ืœืึทื˜ ื•ื•ืขื˜ ื–ื™ื™ืŸ ื’ืึทื ืฅ ืคึผืฉื•ื˜:

ื™ื ื“ืขืงืก.ื”ื˜ืžืœ

ื ื‘ื™ื™ืฉืคึผื™ืœ .ื™ืึธ ืฉืคึผื™ืœ  ืฉืคึผื™ืœ

ื“ืขื ืงืึธื“ ื‘ื™ื™ึทืฉืคึผื™ืœ ืื™ื– ืกื™ืžืคึผืœืึทืคื™ื™ื“ ืึท ื‘ื™ืกืœ ืคึฟืึทืจ ืงืœืขืจื™ื˜ื™, ืื•ืŸ ืื™ืš ื•ื•ืขืœ ื˜ืึธืŸ ื“ื™ ื–ืขืœื‘ืข ืžื™ื˜ ืคื™ืœืข ืคื•ืŸ โ€‹โ€‹ื“ื™ ืื ื“ืขืจืข ื‘ื™ื™ืฉืคื™ืœืŸ ืื™ืŸ ื“ืขื ืคึผืึธืกื˜ืŸ. ืื™ืจ ืงืขื ื˜ ืฉื˜ืขื ื“ื™ืง ืงื•ืงืŸ ืื™ืŸ ื“ื™ ืคื•ืœ ืงืึธื“ ืื™ืŸ ื’ื™ื˜ื”ื•ื‘.

ืžื™ืจ ื”ืื‘ืŸ:

  • 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. ื‘ืึทืฉื˜ืขื˜ื™ืงืŸ ื“ื™ "ืฉืคึผื™ืœ" ืงื ืขืคึผืœ ื’ื™ื˜ ื”ืึทื ื“ืœืขืจ. ื•ื•ืขืŸ ื“ื™ ืงื ืขืคึผืœ ืื™ื– ื’ืขื“ืจื™ืงื˜, ื“ื™ ืงืึธื“ ื™ื ื™ื˜ื™ืึทืœื™ื™ื–ื™ื– ื“ื™ ืฉืคึผื™ืœ ืื•ืŸ ื“ืขืจืฆื™ื™ืœื˜ ื“ื™ ืกืขืจื•ื•ืขืจ ืึทื– ืžื™ืจ ื–ืขื ืขืŸ ื’ืจื™ื™ื˜ ืฆื• ืฉืคึผื™ืœืŸ.

ื“ื™ ื”ื•ื™ืคึผื˜ "ืคืœื™ื™ืฉ" ืคื•ืŸ ืื•ื ื“ื–ืขืจ ืงืœื™ืขื ื˜-ืกืขืจื•ื•ืขืจ ืœืึธื’ื™ืง ืื™ื– ืื™ืŸ ื“ื™ ื˜ืขืงืขืก ื•ื•ืึธืก ื–ืขื ืขืŸ ื™ืžืคึผืึธืจื˜ื™ื“ ื“ื•ืจืš ื“ื™ ื˜ืขืงืข index.js. ืื™ืฆื˜ ืžื™ืจ ื•ื•ืขืœืŸ ืงื•ืงืŸ ื‘ื™ื™ึท ื–ื™ื™ ืึทืœืข ืื™ืŸ ืกื“ืจ.

4. ื•ื•ืขืงืกืœ ืคื•ืŸ ืงืœื™ืขื ื˜ ื“ืึทื˜ืŸ

ืื™ืŸ ื“ืขื ืฉืคึผื™ืœ ืžื™ืจ ื ื•ืฆืŸ ืึท ื’ืขื–ื•ื ื˜-ื‘ืืงืื ื˜ ื‘ื™ื‘ืœื™ืึธื˜ืขืง ืฆื• ื™ื‘ืขืจื’ืขื‘ืŸ ืžื™ื˜ ื“ื™ ืกืขืจื•ื•ืขืจ socket.io. Socket.io ื”ืื˜ ืึท ื’ืขื‘ื•ื™ื˜-ืื™ืŸ ืฉื˜ื™ืฆืŸ ื•ื•ืขื‘ืกืึธืงืงืขืฅ, ื•ื•ืึธืก ื–ืขื ืขืŸ ื’ืขื–ื•ื ื˜ ืคึผืึทืกื™ืง ืคึฟืึทืจ ืฆื•ื•ื™ื™-ื•ื•ืขื’ ืงืึธืžื•ื ื™ืงืึทืฆื™ืข: ืžื™ืจ ืงืขื ืขืŸ ืฉื™ืงืŸ ืึทืจื˜ื™ืงืœืขืŸ ืฆื• ื“ื™ ืกืขืจื•ื•ืขืจ ะธ ื“ืขืจ ืกืขืจื•ื•ืขืจ ืงืขื ืขืŸ ืฉื™ืงืŸ ืึทืจื˜ื™ืงืœืขืŸ ืฆื• ืื•ื ื“ื– ืื™ื‘ืขืจ ื“ืขืจ ื–ืขืœื‘ื™ืงืขืจ ืงืฉืจ.

ืžื™ืจ ื•ื•ืขืœืŸ ื”ืึธื‘ืŸ ืื™ื™ืŸ ื˜ืขืงืข src/client/networking.jsื•ื•ืขืจ ื•ื•ืขื˜ ื ืขืžืขืŸ ืงืขื™ืจ ืคื•ืŸ ืึทืœืขืžืขืŸ ืงืึธืžื•ื ื™ืงืึทืฆื™ืข ืžื™ื˜ ื“ื™ ืกืขืจื•ื•ืขืจ:

networking.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. ืงืœื™ืขื ื˜ ืจืขื ื“ืขืจื™ื ื’

ืขืก ืื™ื– ืฆื™ื™ื˜ ืฆื• ื•ื•ื™ื™ึทื–ืŸ ื“ื™ ื‘ื™ืœื“ ืื•ื™ืฃ ื“ืขื ืขืงืจืึทืŸ!

... ืึธื‘ืขืจ ืื™ื™ื“ืขืจ ืžื™ืจ ืงืขื ืขืŸ ื˜ืึธืŸ ื“ืึธืก, ืžื™ืจ ื“ืึทืจืคึฟืŸ ืฆื• ืึธืคึผืœืึธื“ื™ืจืŸ ืึทืœืข ื“ื™ ื‘ื™ืœื“ืขืจ (ืจืขืกื•ืจืกืŸ) ื•ื•ืึธืก ื–ืขื ืขืŸ ื“ืืจืฃ ืคึฟืึทืจ ื“ืขื. ื–ืืœ ืก ืฉืจื™ื™ึทื‘ืŸ ืึท ืžื™ื˜ืœ ืคืึทืจื•ื•ืึทืœื˜ืขืจ:

assets.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, ื•ื•ืึธืก ืฆื™ืขืŸ ืคึผื•ื ืงื˜ ื“ื™ ืคื™ืจ ืคื•ื ืงื˜ืŸ ืœื™ืกื˜ืขื“ ืื•ื™ื‘ืŸ:

render.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() ืงืึธื ื˜ืจืึธืœื™ืจืŸ ื“ื™ ืึทืงื˜ืึทื•ื•ื™ื™ืฉืึทืŸ ืคื•ืŸ ื“ื™ ืจืขื ื“ืขืจื™ื ื’ ืฆื™ืงืœ ื‘ื™ื™ 60 FPS.

ืกืคึผืขืฆื™ืคื™ืฉ ื™ืžืคึผืœืึทืžืึทื ืฅ ืคื•ืŸ ื™ื—ื™ื“ ืจืขื ื“ืขืจื™ื ื’ ื”ืขืœืคึผืขืจ ืคืึทื ื’ืงืฉืึทื ื– (ืœืžืฉืœ renderBullet()) ื–ืขื ืขืŸ ื ื™ืฉื˜ ืึทื–ื•ื™ ื•ื•ื™ื›ื˜ื™ืง, ืึธื‘ืขืจ ื“ืึธ ืื™ื– ืื™ื™ืŸ ืคึผืฉื•ื˜ ื‘ื™ื™ึทืฉืคึผื™ืœ:

render.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/client/render.js.

6. ืงืœื™ืขื ื˜ ืึทืจื™ื™ึทื ืฉืจื™ื™ึทื‘

ืขืก ืื™ื– ืฆื™ื™ื˜ ืฆื• ืžืึทื›ืŸ ืึท ืฉืคึผื™ืœ ืคึผืœื™ื™ึทืึทื‘ืœืข! ื“ื™ ืงืึธื ื˜ืจืึธืœ ืกื›ืขืžืข ื•ื•ืขื˜ ื–ื™ื™ืŸ ื–ื™ื™ืขืจ ืคึผืฉื•ื˜: ืฆื• ื˜ื•ื™ืฉืŸ ื“ื™ ืจื™ื›ื˜ื•ื ื’ ืคื•ืŸ ื‘ืึทื•ื•ืขื’ื•ื ื’, ืื™ืจ ืงืขื ืขืŸ ื ื•ืฆืŸ ื“ื™ ืžื•ื™ื– (ืื•ื™ืฃ ืึท ืงืึธืžืคึผื™ื•ื˜ืขืจ) ืึธื“ืขืจ ืคืึทืจื‘ื™ื ื“ืŸ ื“ื™ ืคืึทืจืฉื˜ืขืœืŸ (ืื•ื™ืฃ ืึท ืจื™ืจืขื•ื•ื“ื™ืง ืžื™ื˜ืœ). ืฆื• ื™ื ืกื˜ืจื•ืžืขื ื˜ ื“ืขื ืžื™ืจ ื•ื•ืขืœืŸ ืจืขื’ื™ืกื˜ืจื™ืจืŸ ื’ืขืฉืขืขื ื™ืฉ ืœื™ืกื ืขืจืก ืคึฟืึทืจ ืžืึทื•ืก ืื•ืŸ ืจื™ืจ ื’ืขืฉืขืขื ื™ืฉืŸ.
ื•ื•ืขื˜ ื ืขืžืขืŸ ืงืขื™ืจ ืคื•ืŸ ืึทืœืข ื“ืขื src/client/input.js:

input.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. ืงืœื™ืขื ื˜ ืกื˜ืึทื˜ื•ืก

ื“ืขืจ ืึธืคึผื˜ื™ื™ืœื•ื ื’ ืื™ื– ื“ื™ ืžืขืจืกื˜ ืฉื•ื•ืขืจ ืื™ืŸ ื“ืขืจ ืขืจืฉื˜ืขืจ ื˜ื™ื™ืœ ืคื•ืŸ ื“ืขื ืคึผืึธืกื˜ืŸ. ื“ื• ื–ืืœืกื˜ ื ื™ืฉื˜ ื–ื™ื™ืŸ ื“ื™ืกืงืขืจื™ื“ื–ืฉื“ ืื•ื™ื‘ ืื™ืจ ื˜ืึธืŸ ื ื™ื˜ ืคึฟืึทืจืฉื˜ื™ื™ืŸ ืขืก ื“ื™ ืขืจืฉื˜ืขืจ ืžืึธืœ ืื™ืจ ืœื™ื™ืขื ืขืŸ ืขืก! ืื™ืจ ืงืขื ืขืŸ ืืคื™ืœื• ื”ืึธืคึผืงืขืŸ ืขืก ืื•ืŸ ืงื•ืžืขืŸ ืฆื•ืจื™ืง ืฆื• ืขืก ืฉืคึผืขื˜ืขืจ.

ื“ื™ ืœืขืฆื˜ืข ืฉื˜ื™ืง ืคื•ืŸ ื“ื™ ืจืขื˜ืขื ื™ืฉ ื“ืืจืฃ ืฆื• ืคืึทืจืขื ื“ื™ืงืŸ ื“ืขื ืงืœื™ืขื ื˜-ืกืขืจื•ื•ืขืจ ืงืึธื“ ืื™ื– ื–ืขื ืขืŸ. ื’ืขื“ืขื ืงื˜ ื“ื™ ืงืึธื“ ืกื ื™ืคึผืึทื˜ ืคื•ืŸ ื“ื™ ืงืœื™ืขื ื˜ ืจืขื ื“ืขืจื™ื ื’ ืึธืคึผื˜ื™ื™ืœื•ื ื’?

render.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 ืงืœื™ืขื ื˜ ืก ื ืึทื™ื•ื• ืฉื˜ืึทื˜

ื ืึทื™ื•ื• ื™ืžืคึผืœืึทืžืขื ื˜ื™ื™ืฉืึทืŸ getCurrentState() ืงืขื ืขืŸ ื‘ืœื•ื™ื– ื’ืœื™ื™ืš ืฆื•ืจื™ืงืงื•ืžืขืŸ ื“ืึทื˜ืŸ ืคื•ืŸ ื“ื™ ืžืขืจืกื˜ ืœืขืฆื˜ื ืก ื‘ืืงื•ืžืขืŸ ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ.

ื ืึทื™ื•ื•-ืฉื˜ืึทื˜.ื“ื–ืฉืก

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 ืคึผืขืจ ืกืขืงื•ื ื“ืข):

ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ืฆื•ื ื‘ืึทื“ื•ื™ืขืจืŸ, ื’ืึธืจื ื™ืฉื˜ ืื™ื– ื’ืื ืฅ. ื ืžืขืจ ืจืขืึทืœื™ืกื˜ื™ืฉ ื‘ื™ืœื“ ื•ื•ืึธืœื˜ ื–ื™ื™ืŸ:
ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ื ื ืึทื™ื•ื• ื™ืžืคึผืœืึทืžืขื ื˜ื™ื™ืฉืึทืŸ ืื™ื– ืฉื™ื™ืŸ ืคื™ืœ ื“ื™ ืขืจื’ืกื˜ ืคืึทืœ ื•ื•ืขืŸ ืขืก ืงื•ืžื˜ ืฆื• ืœื™ื™ื˜ืึทื ืกื™. ืื•ื™ื‘ ืึท ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ืื™ื– ื‘ืืงื•ืžืขืŸ ืžื™ื˜ ืึท ืคืึทืจื”ืึทืœื˜ืŸ ืคื•ืŸ 50ms, ื“ืขืžืึธืœื˜ ื“ืขืจ ืงืœื™ืขื ื˜ ืื™ื– ืกืœืึธื•ื“ ืึทืจืึธืคึผ ืžื™ื˜ ืึทืŸ ืขืงืกื˜ืจืข 50ms ื•ื•ื™ื™ึทืœ ืขืก ืื™ื– ื ืึธืš ืจืขื ื“ืขืจื™ื ื’ ื“ื™ ืฉืคึผื™ืœ ืฉื˜ืึทื˜ ืคื•ืŸ ื“ื™ ืคืจื™ืขืจื“ื™ืงืข ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ. ืื™ืจ ืงืขื ืขืŸ ื™ืžืึทื“ื–ืฉืึทืŸ ื•ื•ื™ ื•ืžื‘ืึทืงื•ื•ืขื ื“ืึธืก ืื™ื– ืคึฟืึทืจ ื“ื™ ืฉืคึผื™ืœืขืจ: ืจืขื›ื˜ ืฆื• ืึทืจื‘ื™ื˜ืจืึทืจื™ืฉ ืกืœืึธื•ื“ืึทื•ื ื–, ื“ื™ ืฉืคึผื™ืœ ื•ื•ืขื˜ ื•ื™ืกืงื•ืžืขืŸ ื“ื–ืฉืขืจืงื™ ืื•ืŸ ืึทื ืกื˜ื™ื™ื‘ืึทืœ.

7.2 ื™ืžืคึผืจื•ื•ื•ื“ ืงืœื™ืขื ื˜ ืฉื˜ืึทื˜

ืžื™ืจ ื•ื•ืขืœืŸ ืžืึทื›ืŸ ืขื˜ืœืขื›ืข ื™ืžืคึผืจื•ื•ื•ืžืึทื ืฅ ืฆื• ื“ื™ ื ืึทื™ื•ื• ื™ืžืคึผืœืึทืžืขื ื˜ื™ื™ืฉืึทืŸ. ืขืจืฉื˜ืขืจ, ืžื™ืจ ื ื•ืฆืŸ ืจืขื ื“ืขืจื™ื ื’ ืคืึทืจื”ืึทืœื˜ืŸ ื“ื•ืจืš 100 ืžื™ื–. ื“ืขื ืžื™ื˜ืœ ืึทื– ื“ื™ "ืงืจืึทื ื˜" ืฉื˜ืึทื˜ ืคื•ืŸ ื“ืขื ืงืœื™ืขื ื˜ ื•ื•ืขื˜ ืฉื˜ืขื ื“ื™ืง ื–ื™ื™ืŸ 100ms ื”ื™ื ื˜ืขืจ ื“ื™ ืฉืคึผื™ืœ ืฉื˜ืึทื˜ ืื•ื™ืฃ ื“ื™ ืกืขืจื•ื•ืขืจ. ืคึฟืึทืจ ื‘ื™ื™ึทืฉืคึผื™ืœ, ืื•ื™ื‘ ื“ื™ ืกืขืจื•ื•ืขืจ ืฆื™ื™ื˜ ืื™ื– 150, ื“ืขืจ ืงืœื™ืขื ื˜ ื•ื•ืขื˜ ืžืึทื›ืŸ ื“ื™ ืฉื˜ืึทื˜ ืื™ืŸ ื•ื•ืึธืก ื“ื™ ืกืขืจื•ื•ืขืจ ืื™ื– ื’ืขื•ื•ืขืŸ ืื™ืŸ ื“ืขืจ ืฆื™ื™ื˜ 50:

ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ื“ืึธืก ื’ื™ื˜ ืื•ื ื“ื– ืึท 100ms ื‘ืึทืคืขืจ ืฆื• ื‘ืœื™ื™ึทื‘ื  ืœืขื‘ืŸ ื“ื™ ืึทื ืคึผืจื™ื“ื™ืงื˜ืึทื‘ืึทืœ ื˜ื™ื™ืžื™ื ื’ ืคื•ืŸ ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ:

ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ื“ืขืจ ืคึผืจื™ื™ึทื– ืคึฟืึทืจ ื“ืขื ื•ื•ืขื˜ ื–ื™ื™ืŸ ืฉื˜ืขื ื“ื™ืง ืึทืจื™ื™ึทื ืฉืจื™ื™ึทื‘ ืึธืคึผืฉื˜ื™ื™ ื“ื•ืจืš 100 ืžื™ื–. ื“ืึธืก ืื™ื– ืึท ืžื™ื ืขืจื•ื•ืขืจื˜ื™ืง ืงืจื‘ืŸ ืคึฟืึทืจ ื’ืœืึทื˜ ื’ืึทืžืขืคึผืœื™ื™ึท - ืจื•ื‘ึฟ ืคึผืœื™ื™ึทืขืจืก (ืกืคึผืขืฆื™ืขืœ ื’ืœื™ื™ึทื›ื’ื™ืœื˜ื™ืง ืึธื ืขืก) ื•ื•ืขื˜ ื ื™ืฉื˜ ืืคื™ืœื• ื‘ืึทืžืขืจืงืŸ ื“ืขื ืคืึทืจื”ืึทืœื˜ืŸ. ืขืก ืื™ื– ืคื™ืœ ื’ืจื™ื ื’ืขืจ ืคึฟืึทืจ ืžืขื ื˜ืฉืŸ ืฆื• ืกื˜ืจื•ื™ืขืจืŸ ื–ื™ืš ืฆื• ืึท ืงืขืกื™ื™ื“ืขืจื“ื™ืง ืœื™ื™ื˜ืึทื ืกื™ ืคื•ืŸ 100ms ื•ื•ื™ ืฆื• ืฉืคึผื™ืœืŸ ืžื™ื˜ ืึทื ืคึผืจื™ื“ื™ืงื˜ืึทื‘ืึทืœ ืœื™ื™ื˜ืึทื ืกื™.

ืžื™ืจ ืงืขื ืขืŸ ื ื•ืฆืŸ ืืŸ ืื ื“ืขืจ ื˜ืขื›ื ื™ืง ื’ืขืจื•ืคืŸ "ืงืœื™ื™ื ื˜-ื–ื™ื™ึทื˜ ืคืึธืจื•ื™ืกื–ืึธื’ืŸ", ื•ื•ืึธืก ื˜ื•ื˜ ืึท ื’ื•ื˜ ืึทืจื‘ืขื˜ ืคื•ืŸ ืจื™ื“ื•ืกื™ื ื’ ื‘ืืžืขืจืงื˜ ืœื™ื™ื˜ืึทื ืกื™, ืึธื‘ืขืจ ื•ื•ืขื˜ ื ื™ืฉื˜ ื–ื™ื™ืŸ ื“ื™ืกืงืึทืกื˜ ืื™ืŸ ื“ืขื ืคึผืึธืกื˜ืŸ.

ืืŸ ืื ื“ืขืจ ืคึฟืึทืจื‘ืขืกืขืจื•ื ื’ ื•ื•ืึธืก ืžื™ืจ ื ื•ืฆืŸ ืื™ื– ืœื™ื ืขืึทืจ ื™ื ื˜ืขืจืคึผืึธืœืึทื˜ื™ืึธืŸ. ืจืขื›ื˜ ืฆื• ืจืขื ื“ืขืจื™ื ื’ ืึธืคึผืฉื˜ื™ื™, ืžื™ืจ ื–ืขื ืขืŸ ื™ื•ื–ืฉืึทื•ื•ืึทืœื™ ืœืคึผื—ื•ืช ืื™ื™ืŸ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ืคืึธืจื•ื™ืก ืคื•ืŸ ื“ื™ ืงืจืึทื ื˜ ืฆื™ื™ื˜ ืื™ืŸ ื“ืขื ืงืœื™ืขื ื˜. ื•ื•ืขืŸ ื’ืขืจื•ืคืŸ getCurrentState(), ืžื™ืจ ืงืขื ืขืŸ ืžืงื™ื™ื ืœื™ื ืขืึทืจ ื™ื ื˜ืขืจืคึผืึธืœืึทื˜ื™ืึธืŸ ืฆื•ื•ื™ืฉืŸ ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ ื ืึธืจ ืื™ื™ื“ืขืจ ืื•ืŸ ื ืึธืš ื“ื™ ืงืจืึทื ื˜ ืฆื™ื™ื˜ ืื™ืŸ ื“ืขื ืงืœื™ืขื ื˜:

ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ื“ืึธืก ืกืึทืœื•ื•ื– ื“ื™ ืจืึทื ืงื•ืจืก ืคึผืจืึธื‘ืœืขื: ืžื™ืจ ืงืขื ืขืŸ ืื™ืฆื˜ ืžืึทื›ืŸ ื™ื™ื ืฆื™ืง ืจืึธืžืขืŸ ืื™ืŸ ืงื™ื™ืŸ ืจืึทื ืงื•ืจืก ื•ื•ืึธืก ืžื™ืจ ื“ืึทืจืคึฟืŸ!

7.3 ื™ืžืคึผืœืึทืžืขื ื˜ื™ื ื’ ืึท ื™ืžืคึผืจื•ื•ื•ื“ ืงืœื™ืขื ื˜ ืฉื˜ืึทื˜

ื‘ื™ื™ึทืฉืคึผื™ืœ ื™ืžืคึผืœืึทืžืขื ื˜ื™ื™ืฉืึทืŸ ืื™ืŸ src/client/state.js ื ื™ืฆื˜ ื‘ื™ื™ื“ืข ืจืขื ื“ืขืจื™ื ื’ ืคืึทืจื”ืึทืœื˜ืŸ ืื•ืŸ ืœื™ื ืขืึทืจ ื™ื ื˜ืขืจืคึผืึธืœืึทื˜ื™ืึธืŸ, ืึธื‘ืขืจ ื“ืึธืก ื˜ื•ื˜ ื ื™ืฉื˜ ืœืขืฆื˜ืข ืœืึทื ื’. ื–ืืœ ืก ื‘ืจืขื›ืŸ ื“ื™ ืงืึธื“ ืื™ืŸ ืฆื•ื•ื™ื™ ื˜ื™ื™ืœืŸ. ื“ืึธ ืื™ื– ื“ืขืจ ืขืจืฉื˜ืขืจ ืื™ื™ื ืขืจ:

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 ื”ื™ื ื˜ืขืจ ื“ืขื ืกืขืจื•ื•ืขืจ, ืึธื‘ืขืจ ืžื™ืจ ื•ื•ืขืœืŸ ืงื™ื™ื ืžืึธืœ ื•ื•ื™ืกืŸ ื“ื™ ืงืจืึทื ื˜ ืฆื™ื™ื˜ ืื•ื™ืฃ ื“ื™ ืกืขืจื•ื•ืขืจ, ื•ื•ื™ื™ึทืœ ืžื™ืจ ืงืขื ืขืŸ ื ื™ืฉื˜ ื•ื•ื™ืกืŸ ื•ื•ื™ ืœืึทื ื’ ืขืก ื’ืขื ื•ืžืขืŸ ืคึฟืึทืจ ืงื™ื™ืŸ ืคื•ืŸ ื“ื™ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ ืฆื• ื“ืขืจื’ืจื™ื™ื›ืŸ ืื•ื ื“ื–. ื“ืขืจ ืื™ื ื˜ืขืจื ืขืฅ ืื™ื– ืึทื ืคึผืจื™ื“ื™ืงื˜ืึทื‘ืึทืœ ืื•ืŸ ื“ื™ ื’ื™ื›ืงื™ื™ึทื˜ ืงืขืŸ ื–ื™ื™ืŸ ื–ื™ื™ืขืจ ืึทื ื“ืขืจืฉ!

ืฆื• ื‘ืึทืงื•ืžืขืŸ ืึทืจื•ื ื“ืขื ืคึผืจืึธื‘ืœืขื, ืžื™ืจ ืงืขื ืขืŸ ื ื•ืฆืŸ ืึท ื’ืœื™ื™ึทืš ืึทืคึผืจืึทืงืกืึทืžื™ื™ืฉืึทืŸ: ืžื™ืจ ืœืึธืžื™ืจ ืคืึทืจื”ื™ื˜ืŸ ืึทื– ื“ืขืจ ืขืจืฉื˜ืขืจ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ืื™ื– ืื ื’ืขืงื•ืžืขืŸ ื˜ื™ื™ืงืขืฃ. ืื•ื™ื‘ ื“ืึธืก ืื™ื– ืืžืช, ืžื™ืจ ื•ื•ืึธืœื˜ ื•ื•ื™ืกืŸ ื“ื™ ืกืขืจื•ื•ืขืจ ืฆื™ื™ื˜ ืื™ืŸ ื“ืขื ื‘ืึทื–ื•ื ื“ืขืจ ืžืึธืžืขื ื˜! ืžื™ืจ ืงืจืึธื ื“ื™ ืกืขืจื•ื•ืขืจ ืฆื™ื™ื˜ ืกื˜ืึทืžืคึผ ืื™ืŸ firstServerTimestamp ืื•ืŸ ืจืึทื˜ืขื•ื•ืขืŸ ืื•ื ื“ื–ืขืจ ื”ื™ื’ืข (ืงืœื™ืขื ื˜) ืฆื™ื™ื˜ ืกื˜ืึทืžืคึผ ืื™ืŸ ื“ืขืจ ื–ืขืœื‘ื™ืงืขืจ ืžืึธืžืขื ื˜ ืื™ืŸ gameStart.

ืื”, ื•ื•ืืจื˜ ื ืžื™ื ื•ื˜. ื–ืึธืœ ื ื™ืฉื˜ ื–ื™ื™ืŸ ืฆื™ื™ื˜ ืื•ื™ืฃ ื“ื™ ืกืขืจื•ื•ืขืจ = ืฆื™ื™ื˜ ืื•ื™ืฃ ื“ืขื ืงืœื™ืขื ื˜? ืคืืจื•ื•ืืก ื˜ืึธืŸ ืžื™ืจ ื“ื™ืคืขืจืขื ืฉื™ื™ื™ื˜ ืฆื•ื•ื™ืฉืŸ "ืกืขืจื•ื•ืขืจ ื˜ื™ืžืขืกื˜ืึทืžืคึผ" ืื•ืŸ "ืงืœื™ืขื ื˜ ื˜ื™ืžืขืกื˜ืึทืžืคึผ"? ื“ืึธืก ืื™ื– ืึท ื’ืจื•ื™ืก ืงืฉื™ื! ืขืก ืงื•ืžื˜ ืื•ื™ืก ืื– ื“ืืก ื–ืขื ืขืŸ ื ื™ืฉื˜ ื“ื™ ื–ืขืœื‘ืข ื–ืืš. Date.now() ื•ื•ืขื˜ ืฆื•ืจื™ืงืงื•ืžืขืŸ ืคืึทืจืฉื™ื“ืขื ืข ื˜ื™ืžืขืกื˜ืึทืžืคึผืก ืื™ืŸ ื“ืขื ืงืœื™ืขื ื˜ ืื•ืŸ ืกืขืจื•ื•ืขืจ ืื•ืŸ ื“ืึธืก ื“ืขืคึผืขื ื“ืก ืื•ื™ืฃ ื“ื™ ื”ื™ื’ืข ืกื™ื‘ื•ืช ืคึฟืึทืจ ื“ื™ ืžืืฉื™ื ืขืŸ. ืงื™ื™ื ืžืึธืœ ื™ื‘ืขืจื ืขืžืขืŸ ืึทื– ื˜ื™ืžืขืกื˜ืึทืžืคึผืก ื•ื•ืขื˜ ื–ื™ื™ืŸ ื“ื™ ื–ืขืœื‘ืข ืื•ื™ืฃ ืึทืœืข ืžืืฉื™ื ืขืŸ.

ืื™ืฆื˜ ืžื™ืจ ืคึฟืึทืจืฉื˜ื™ื™ืŸ ื•ื•ืึธืก ืขืก ื˜ื•ื˜ currentServerTime(): ืขืก ืงืขืจื˜ ื–ื™ืš ืกืขืจื•ื•ื™ืจืขืจ ื˜ื™ืžืขืกื˜ืึทืžืคึผ ืคื•ืŸ ื“ื™ ืงืจืึทื ื˜ ืจืขื ื“ืขืจื™ื ื’ ืฆื™ื™ื˜. ืื™ืŸ ืื ื“ืขืจืข ื•ื•ืขืจื˜ืขืจ, ื“ืึธืก ืื™ื– ื“ื™ ืงืจืึทื ื˜ ืกืขืจื•ื•ืขืจ ืฆื™ื™ื˜ (firstServerTimestamp <+ (Date.now() - gameStart)) ืžื™ื ื•ืก ืจืขื ื“ืขืจื™ื ื’ ืคืึทืจื”ืึทืœื˜ืŸ (RENDER_DELAY).

ืื™ืฆื˜ ืœืึธื–ืŸ ืก ืงื•ืง ื•ื•ื™ ืžื™ืจ ืฉืขืคึผืŸ ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ. ื•ื•ืขืŸ ืึท ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ืื™ื– ื‘ืืงื•ืžืขืŸ ืคื•ืŸ ื“ื™ ืกืขืจื•ื•ืขืจ, ืขืก ืื™ื– ื’ืขืจื•ืคืŸ processGameUpdate(), ืื•ืŸ ืžื™ืจ ืจืึทื˜ืขื•ื•ืขืŸ ื“ื™ ื ื™ื™ึทืข ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ืฆื• ืึท ืžืขื ื’ืข gameUpdates. ื“ืขืจื ืึธืš, ืฆื• ืงืึธื ื˜ืจืึธืœื™ืจืŸ ื–ื™ืงืึธืจืŸ ื‘ืึทื ื™ืฅ, ืžื™ืจ ื‘ืึทื–ื™ื™ึทื˜ื™ืงืŸ ืึทืœืข ืึทืœื˜ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ ืฆื• ื‘ืึทื–ืข ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸื•ื•ื™ื™ืœ ืžื™ืจ ื“ืืจืคืŸ ื–ื™ื™ ืžืขืจ ื ื™ืฉื˜.

ื•ื•ืึธืก ืื™ื– ืึท "ื”ืึทืจืฅ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ"? ื“ืืก ื“ืขืจ ืขืจืฉื˜ืขืจ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ืžื™ืจ ื’ืขืคึฟื™ื ืขืŸ ื“ื•ืจืš ืžืึธื•ื•ื™ื ื’ ืงืึทืคึผื•ื™ืขืจ ืคื•ืŸ ื“ื™ ืงืจืึทื ื˜ ืกืขืจื•ื•ืขืจ ืฆื™ื™ื˜. ื’ืขื“ืขื ืงื˜ ื“ืขื ื“ื™ืึทื’ืจืึทืžืข?

ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ื“ืขืจ ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ื’ืœื™ื™ึทืš ืฆื• ื“ื™ ืœื™ื ืงืก ืคื•ืŸ "ืงืœื™ืขื ื˜ ืจืขื ื“ืขืจ ืฆื™ื™ื˜" ืื™ื– ื“ื™ ื‘ืึทื–ืข ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ.

ื•ื•ืึธืก ืื™ื– ื“ื™ ื‘ืึทื–ืข ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ื’ืขื ื™ืฆื˜ ืคึฟืึทืจ? ืคืืจื•ื•ืืก ืงืขื ืขืŸ ืžื™ืจ ืคืึทืœืŸ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ ืฆื• ื‘ืึทื–ืข? ืฆื• ืคึฟืึทืจืฉื˜ื™ื™ืŸ ื“ืขื, ืœืึธื–ืŸ ืื•ื ื“ื– ืœืขืกืึธืฃ ื–ืืœ ืก ืงื•ืง ืื™ืŸ ื“ื™ ื™ืžืคึผืœืึทืžืขื ื˜ื™ื™ืฉืึทืŸ 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 ื‘ืึทืงืขื ื“ ื•ื•ืึธืก ืงืึธื ื˜ืจืึธืœืก ืื•ื ื“ื–ืขืจ ื‘ื™ื™ืฉืคึผื™ืœ ืคื•ืŸ ืึท .ื™ืึธ ืฉืคึผื™ืœ.

1. ืกืขืจื•ื•ื™ืจืขืจ ืคึผืึธื–ื™ืฆื™ืข ืคื•ื ื˜

ืฆื• ืคื™ืจืŸ ื“ื™ ื•ื•ืขื‘ ืกืขืจื•ื•ืขืจ ืžื™ืจ ื•ื•ืขืœืŸ ื ื•ืฆืŸ ืึท ืคืึธืœืงืก ื•ื•ืขื‘ ืคืจื™ื™ืžื•ื•ืขืจืง ืคึฟืึทืจ ื’ืขืจื•ืคึฟืŸ Node.js ืขืงืกืคึผืจืขืกืก. ืขืก ื•ื•ืขื˜ ื–ื™ื™ืŸ ืงืึทื ืคื™ื’ื™ืขืจื“ ื“ื•ืจืš ืื•ื ื“ื–ืขืจ ืกืขืจื•ื•ืขืจ ืคึผืึธื–ื™ืฆื™ืข ืคื•ื ื˜ ื˜ืขืงืข src/server/server.js:

server.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}`);

ื’ืขื“ืขื ืงื˜ ืึทื– ืื™ืŸ ื“ืขืจ ืขืจืฉื˜ืขืจ ื˜ื™ื™ืœ ืžื™ืจ ื“ื™ืกืงืึทืกื˜ ื•ื•ืขื‘ืคึผืึทืงืง? ื“ืึธืก ืื™ื– ื•ื•ื• ืžื™ืจ ื•ื•ืขืœืŸ ื ื•ืฆืŸ ืื•ื ื“ื–ืขืจ ื•ื•ืขื‘ืคึผืึทืงืง ืงืึทื ืคื™ื’ื™ืขืจื™ื™ืฉืึทื ื–. ืžื™ืจ ื•ื•ืขืœืŸ ืฆื•ืœื™ื™ื’ืŸ ื–ื™ื™ ืื™ืŸ ืฆื•ื•ื™ื™ ื•ื•ืขื’ืŸ:

  • ื ื™ืฅ ื•ื•ืขื‘ืคึผืึทืง-ื“ืขื•ื•-ืžื™ื˜ืœื•ื•ืึทืจืข ืฆื• ืื•ื™ื˜ืึธืžืึทื˜ื™ืฉ ืจื™ื‘ื™ืœื“ ืื•ื ื“ื–ืขืจ ืึทื ื˜ื•ื•ื™ืงืœื•ื ื’ ืคึผืึทืงืึทื“ื–ืฉืึทื–, ืึธื“ืขืจ
  • ืกื˜ืึทื˜ื™ืงืœื™ ืึทืจื™ื‘ืขืจืคื™ืจืŸ ืึท ื˜ืขืงืข dist/, ืื™ืŸ ื•ื•ืึธืก ื•ื•ืขื‘ืคึผืึทืงืง ื•ื•ืขื˜ ืฉืจื™ื™ึทื‘ืŸ ืื•ื ื“ื–ืขืจ ื˜ืขืงืขืก ื ืึธืš ื“ื™ ืคึผืจืึธื“ื•ืงืฆื™ืข ื‘ื•ื™ืขืŸ.

ืืŸ ืื ื“ืขืจ ื•ื•ื™ื›ื˜ื™ืง ืึทืจื‘ืขื˜ server.js ื‘ืืฉื˜ื™ื™ื˜ ืคื•ืŸ ื‘ืึทืฉื˜ืขื˜ื™ืงืŸ ื“ื™ ืกืขืจื•ื•ืขืจ socket.ioื•ื•ืึธืก ืคืฉื•ื˜ ืงืึทื ืขืงืฅ ืฆื• ื“ื™ ืขืงืกืคึผืจืขืกืก ืกืขืจื•ื•ืขืจ:

server.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:

server.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 ื–ื™ื™ืขืจ ื›ืึธืœืขืœ socket.io (ืื•ื™ื‘ ืื™ืจ ื–ืขื ื˜ ืฆืขืžื™ืฉื˜, ื’ื™ื™ืŸ ืฆื•ืจื™ืง ืฆื• server.js). Socket.io ื–ื™ืš ืึทืกื™ื™ื ื– ื™ืขื“ืขืจ ื›ืึธืœืขืœ ืึท ื™ื™ื ืฆื™ืง id, ืึทื–ื•ื™ ืžื™ืจ ื˜ืึธืŸ ื ื™ื˜ ื“ืึทืจืคึฟืŸ ืฆื• ื–ืึธืจื’ ื•ื•ืขื’ืŸ ืื™ื. ืื™ืš ื•ื•ืขืœ ืื™ื ืจื•ืคืŸ ืฉืคึผื™ืœืขืจ ืฉื™ื™ึทืŸ.

ืžื™ื˜ ื“ืขื ืื™ืŸ ื–ื™ื ืขืŸ, ืœืึธื–ืŸ ืื•ื ื“ื– ื•ื ื˜ืขืจื–ื•ื›ืŸ ื“ื™ ื‘ื™ื™ึทืฉืคึผื™ืœ ื•ื•ืขืจื™ืึทื‘ืึทืœื– ืื™ืŸ ื“ื™ ืงืœืึทืก Game:

  • sockets ืื™ื– ืึท ื›ื™ื™ืคืขืฅ ื•ื•ืึธืก ื‘ื™ื™ื ื“ื– ื“ื™ ืฉืคึผื™ืœืขืจ ืฉื™ื™ึทืŸ ืฆื• ื“ื™ ื›ืึธืœืขืœ ื•ื•ืึธืก ืื™ื– ืคืืจื‘ื•ื ื“ืŸ ืžื™ื˜ ื“ื™ ืฉืคึผื™ืœืขืจ. ืขืก ืึทืœืึทื•ื– ืื•ื ื“ื– ืฆื• ืึทืงืกืขืก ืกืึทืงืึทืฅ ื“ื•ืจืš ื–ื™ื™ืขืจ ืฉืคึผื™ืœืขืจ ื™ื“ืก ืื™ื‘ืขืจ ืฆื™ื™ึทื˜.
  • players ืื™ื– ืึท ื›ื™ื™ืคืขืฅ ื•ื•ืึธืก ื‘ื™ื™ื ื“ื– ื“ื™ ืฉืคึผื™ืœืขืจ ืฉื™ื™ึทืŸ ืฆื• ื“ื™ ืงืึธื“> ืฉืคึผื™ืœืขืจ ื›ื™ื™ืคืขืฅ

bullets ืื™ื– ืึท ืงื™ื™ื˜ ืคื•ืŸ ืึทื‘ื“ื–ืฉืขืงืฅ Bullet, ื ื™ืฉื˜ ื”ืึธื‘ืŸ ืึท ืกืคึผืขืฆื™ืคื™ืฉ ืกื“ืจ.
lastUpdateTime - ื“ืึธืก ืื™ื– ื“ืขืจ ืฆื™ื™ื˜ ืกื˜ืึทืžืคึผ ืคื•ืŸ ื“ื™ ืœืขืฆื˜ืข ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ. ืžื™ืจ ื•ื•ืขืœืŸ ื‘ืึทืœื“ ื–ืขืŸ ื•ื•ื™ ืขืก ืื™ื– ื’ืขื ื™ืฆื˜.
shouldSendUpdate ืื™ื– ืึท ืึทื’ื–ื™ืœื™ืขืจื™ ื‘ื™ื™ึทื˜ืขื•ื•ื“ื™ืง. ืžื™ืจ ื•ื•ืขืœืŸ ืื•ื™ืš ื‘ืึทืœื“ ื–ืขืŸ ื–ื™ื™ึทืŸ ื ื•ืฆืŸ.
ืžืขื˜ื”ืึธื“ืก addPlayer(), removePlayer() ะธ handleInput() ื ื™ื˜ ื“ืึทืจืคึฟืŸ ืฆื• ื“ืขืจืงืœืขืจืŸ, ื–ื™ื™ ื–ืขื ืขืŸ ื’ืขื ื™ืฆื˜ ืื™ืŸ server.js. ืื•ื™ื‘ ืื™ืจ ื“ืึทืจืคึฟืŸ ืึท ืจื™ืคืจืขืฉืขืจ, ื’ื™ื™ืŸ ืฆื•ืจื™ืง ืึท ื‘ื™ืกืœ ื”ืขื›ืขืจ.

ืœืขืฆื˜ืข ืฉื•ืจื” constructor() ืกื˜ืึทืจืฅ ืึทืจื•ื™ืฃ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงืŸ ืฆื™ืงืœ ืฉืคึผื™ืœืขืจื™ื™ึท (ืžื™ื˜ ืึท ืึธืคื˜ืงื™ื™ึทื˜ ืคื•ืŸ 60 ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ / s):

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 ืžืืœ / s, ืžื™ืจ ืฉื™ืงืŸ ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ 30 ืžืืœ / s. ืื–ื•ื™, ื–ื™ื™ื’ืขืจ ืึธืคื˜ืงื™ื™ึทื˜ ืกืขืจื•ื•ืขืจ ืื™ื– 30 ื–ื™ื™ื’ืขืจ ืกื™ื™ืงืึทืœื– / s (ืžื™ืจ ื’ืขืจืขื“ื˜ ื•ื•ืขื’ืŸ ื“ื™ ื–ื™ื™ื’ืขืจ ืึธืคื˜ืงื™ื™ึทื˜ ืื™ืŸ ื“ืขืจ ืขืจืฉื˜ืขืจ ื˜ื™ื™ืœ).

ืคืืจื•ื•ืืก ืฉื™ืงืŸ ืฉืคึผื™ืœ ื“ืขืจื”ื™ื™ึทื ื˜ื™ืงื•ื ื’ืขืŸ ื‘ืœื•ื™ื– ื“ื•ืจืš ืฆื™ื™ึทื˜ ? ืฆื• ืจืึทื˜ืขื•ื•ืขืŸ ืงืึทื ืึทืœ. 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:

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:

bullet.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 ื‘ืœื•ื™ื– ื“ื™ ืคืืœื’ืขื ื“ืข ื™ืงืกื˜ืขื ืฉืึทื ื–:

  • ื ื™ืฆืŸ ื“ืขื ืคึผืขืงืœ ืงื•ืจืฅ ืคึฟืึทืจ ื˜ืจืึทืค - ื“ื•ืจ id ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœ.
  • ืึทื“ื™ื ื’ ืึท ืคืขืœื“ parentID, ืึทื–ื•ื™ ืื™ืจ ืงืขื ืขืŸ ืฉืคึผื•ืจ ื“ื™ ืฉืคึผื™ืœืขืจ ื•ื•ืึธืก ื‘ืืฉืืคืŸ ื“ืขื ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœ.
  • ืึทื“ื™ื ื’ ื“ื™ ืฆื•ืจื™ืงืงื•ืžืขืŸ ื•ื•ืขืจื˜ ืฆื• update(), ื•ื•ืึธืก ืื™ื– ื’ืœื™ื™ึทืš true, ืื•ื™ื‘ ื“ื™ ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœ ืื™ื– ืึทืจื•ื™ืก ื“ื™ ืืจืขื ืข (ื’ืขื“ืขื ืงืขืŸ ืžื™ืจ ื’ืขืจืขื“ื˜ ื•ื•ืขื’ืŸ ื“ืขื ืื™ืŸ ื“ื™ ืœืขืฆื˜ืข ืึธืคึผื˜ื™ื™ืœื•ื ื’?).

ื–ืืœ ืก ืžืึทืš ืื•ื™ืฃ ืฆื• Player:

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:

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.

ื“ืึธืก ืื™ื– ื•ื•ื™ ืื•ื ื“ื–ืขืจ ื™ืžืคึผืœืึทืžืขื ื˜ื™ื™ืฉืึทืŸ ืคื•ืŸ ืฆื•ื ื•ื™ืคืฉื˜ื•ื™ืก ื“ื™ื˜ืขืงืฉืึทืŸ ืงื•ืงื˜ ื•ื•ื™:

collisions.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;
}

ื“ืขื ืคึผืฉื•ื˜ ืฆื•ื ื•ื™ืคืฉื˜ื•ื™ืก ื“ื™ื˜ืขืงืฉืึทืŸ ืื™ื– ื‘ืื–ื™ืจื˜ ืื•ื™ืฃ ื“ืขื ืคืึทืงื˜ ืึทื– ืฆื•ื•ื™ื™ ืงืจื™ื™ื–ืŸ ืฆื•ื ื•ื™ืคืฉื˜ื•ื™ืก ืื•ื™ื‘ ื“ื™ ื•ื•ื™ื™ึทื˜ืงื™ื™ื˜ ืฆื•ื•ื™ืฉืŸ ื–ื™ื™ืขืจ ืฆืขื ื˜ืขืจ ืื™ื– ื•ื•ื™ื™ื ื™ืงืขืจ ื•ื•ื™ ื“ื™ ืกืึทื›ืึทืงืœ ืคื•ืŸ ื–ื™ื™ืขืจ ืจืึทื“ื™ืขืก. ื“ืึธ ืื™ื– ืึท ืคืึทืœ ื•ื•ื• ื“ื™ ื•ื•ื™ื™ึทื˜ืงื™ื™ื˜ ืฆื•ื•ื™ืฉืŸ ื“ื™ ืกืขื ื˜ืขืจืก ืคื•ืŸ ืฆื•ื•ื™ื™ ืงืจื™ื™ื–ืŸ ืื™ื– ืคึผื•ื ืงื˜ ื’ืœื™ื™ึทืš ืฆื• ื“ื™ ืกืึทื›ืึทืงืœ ืคื•ืŸ ื–ื™ื™ืขืจ ืจืึทื“ื™ื•ืก:

ืฉืึทืคึฟืŸ ืึท ืžื•ืœื˜ื™ืคึผืœื™ื™ึทืขืจ .ื™ืึธ ื•ื•ืขื‘ ืฉืคึผื™ืœ
ื“ืึธ ืื™ืจ ื“ืึทืจืคึฟืŸ ืฆื• ื‘ืึทืฆืึธืœืŸ ื•ืคืžืขืจืงื–ืึทืžืงื™ื™ึทื˜ ืฆื• ืึท ืคึผืึธืจ ืžืขืจ ืึทืกืคึผืขืงืฅ:

  • ื“ื™ ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœ ื–ืึธืœ ื ื™ืฉื˜ ืฉืœืึธื’ืŸ ื“ื™ ืฉืคึผื™ืœืขืจ ื•ื•ืืก ื‘ืืฉืืคืŸ ืขืก. ื“ืขื ืงืขื ืขืŸ ื–ื™ื™ืŸ ืึทื˜ืฉื™ื•ื•ื“ ื“ื•ืจืš ืงืึทืžืคึผืขืจื™ื ื’ bullet.parentID ั player.id.
  • ื“ื™ ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœ ื–ืึธืœ ื ืึธืจ ืฉืœืึธื’ืŸ ืึทืžืึธืœ ืื™ืŸ ื“ื™ ืขืงืกื˜ืจืขื ืคืึทืœ ืคื•ืŸ ื”ื™ื˜ื˜ื™ื ื’ ืงื™ื™ืคืœ ืคึผืœื™ื™ึทืขืจืก ืื™ืŸ ื“ืขืจ ื–ืขืœื‘ื™ืงืขืจ ืฆื™ื™ื˜. ืžื™ืจ ื•ื•ืขืœืŸ ืกืึธืœื•ื•ืข ื“ืขื ืคึผืจืึธื‘ืœืขื ื ื™ืฆืŸ ื“ื™ ืึธืคึผืขืจืึทื˜ืึธืจ break: ืึทืžืึธืœ ืึท ืฉืคึผื™ืœืขืจ ืงืึทืœื™ื™ื“ื™ื ื’ ืžื™ื˜ ืึท ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœ ืื™ื– ื’ืขืคึฟื•ื ืขืŸ, ืžื™ืจ ื”ืึทืœื˜ืŸ ื–ื•ื›ืŸ ืื•ืŸ ืžืึทืš ืื•ื™ืฃ ืฆื• ื“ืขืจ ื•ื•ื™ื™ึทื˜ืขืจ ืคึผืจืึทื“ื–ืฉืขืงื˜ืึทืœ.

ืขืง

ืึทื– ืก ืึทืœืข! ืžื™ืจ ื”ืึธื‘ืŸ ื‘ืื“ืขืงื˜ ืึทืœืฅ ืื™ืจ ื“ืึทืจืคึฟืŸ ืฆื• ื•ื•ื™ืกืŸ ืฆื• ืฉืึทืคึฟืŸ ืึท .io ื•ื•ืขื‘ ืฉืคึผื™ืœ. ื•ื•ืืก ืื™ื– ื ืขืงืกื˜? ื‘ื•ื™ืขืŸ ื“ื™ื™ืŸ ืื™ื™ื’ืขื ืข .ื™ืึธ ืฉืคึผื™ืœ!

ื›ืœ ื‘ื™ื™ึทืฉืคึผื™ืœ ืงืึธื“ ืื™ื– ืึธืคึฟืŸ ืžืงื•ืจ ืื•ืŸ ืึทืจื™ื™ึทื ื’ืขืฉื™ืงื˜ ืื•ื™ืฃ ื’ื™ื˜ื”ื•ื‘.

ืžืงื•ืจ: www.habr.com

ืœื™ื™ื’ืŸ ืึท ื‘ืึทืžืขืจืงื•ื ื’