ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ

ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ
2015 ರಲ್ಲಿ ಬಿಡುಗಡೆಯಾಯಿತು ಅಗರ್.ಓ ಹೊಸ ಪ್ರಕಾರದ ಮೂಲಪುರುಷರಾದರು ಆಟಗಳು .ioಅಂದಿನಿಂದ ಜನಪ್ರಿಯತೆಯಲ್ಲಿ ಬೆಳೆದಿದೆ. ನಾನು ವೈಯಕ್ತಿಕವಾಗಿ .io ಆಟಗಳ ಜನಪ್ರಿಯತೆಯ ಏರಿಕೆಯನ್ನು ಅನುಭವಿಸಿದ್ದೇನೆ: ಕಳೆದ ಮೂರು ವರ್ಷಗಳಲ್ಲಿ, ನಾನು ಹೊಂದಿದ್ದೇನೆ ಈ ಪ್ರಕಾರದ ಎರಡು ಆಟಗಳನ್ನು ರಚಿಸಲಾಗಿದೆ ಮತ್ತು ಮಾರಾಟ ಮಾಡಿದೆ..

ಈ ಆಟಗಳ ಕುರಿತು ನೀವು ಹಿಂದೆಂದೂ ಕೇಳಿರದಿದ್ದಲ್ಲಿ, ಇವುಗಳು ಉಚಿತ ಮಲ್ಟಿಪ್ಲೇಯರ್ ವೆಬ್ ಆಟಗಳಾಗಿದ್ದು, ಇವುಗಳನ್ನು ಆಡಲು ಸುಲಭವಾಗಿದೆ (ಯಾವುದೇ ಖಾತೆಯ ಅಗತ್ಯವಿಲ್ಲ). ಅವರು ಸಾಮಾನ್ಯವಾಗಿ ಒಂದೇ ಕಣದಲ್ಲಿ ಅನೇಕ ಎದುರಾಳಿ ಆಟಗಾರರನ್ನು ಎದುರಿಸುತ್ತಾರೆ. ಇತರ ಪ್ರಸಿದ್ಧ .io ಆಟಗಳು: Slither.io и ಡೈಪ್.ಓಒ.

ಈ ಪೋಸ್ಟ್‌ನಲ್ಲಿ, ಹೇಗೆ ಎಂದು ನಾವು ಅನ್ವೇಷಿಸುತ್ತೇವೆ ಮೊದಲಿನಿಂದ .io ಆಟವನ್ನು ರಚಿಸಿ. ಇದಕ್ಕಾಗಿ, ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ನ ಜ್ಞಾನ ಮಾತ್ರ ಸಾಕು: ನೀವು ಸಿಂಟ್ಯಾಕ್ಸ್ನಂತಹ ವಿಷಯಗಳನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳಬೇಕು ES6, ಕೀವರ್ಡ್ this и ಭರವಸೆ ನೀಡುತ್ತದೆ. ನಿಮ್ಮ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಜ್ಞಾನವು ಪರಿಪೂರ್ಣವಾಗಿಲ್ಲದಿದ್ದರೂ ಸಹ, ನೀವು ಹೆಚ್ಚಿನ ಪೋಸ್ಟ್ ಅನ್ನು ಇನ್ನೂ ಅರ್ಥಮಾಡಿಕೊಳ್ಳಬಹುದು.

.io ಆಟದ ಉದಾಹರಣೆ

ಕಲಿಕೆಯ ಸಹಾಯಕ್ಕಾಗಿ, ನಾವು ಉಲ್ಲೇಖಿಸುತ್ತೇವೆ .io ಆಟದ ಉದಾಹರಣೆ. ಅದನ್ನು ಆಡಲು ಪ್ರಯತ್ನಿಸಿ!

ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ
ಆಟವು ತುಂಬಾ ಸರಳವಾಗಿದೆ: ಇತರ ಆಟಗಾರರು ಇರುವ ಕಣದಲ್ಲಿ ನೀವು ಹಡಗನ್ನು ನಿಯಂತ್ರಿಸುತ್ತೀರಿ. ನಿಮ್ಮ ಹಡಗು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸ್ಪೋಟಕಗಳನ್ನು ಹಾರಿಸುತ್ತದೆ ಮತ್ತು ಇತರ ಆಟಗಾರರ ಸ್ಪೋಟಕಗಳನ್ನು ತಪ್ಪಿಸುವಾಗ ನೀವು ಹೊಡೆಯಲು ಪ್ರಯತ್ನಿಸುತ್ತೀರಿ.

1. ಯೋಜನೆಯ ಸಂಕ್ಷಿಪ್ತ ಅವಲೋಕನ / ರಚನೆ

ನಾನು ಶಿಫಾರಸು ಮಾಡುತ್ತೇವೆ ಮೂಲ ಕೋಡ್ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ ಉದಾಹರಣೆ ಆಟ ಆದ್ದರಿಂದ ನೀವು ನನ್ನನ್ನು ಅನುಸರಿಸಬಹುದು.

ಉದಾಹರಣೆಯು ಈ ಕೆಳಗಿನವುಗಳನ್ನು ಬಳಸುತ್ತದೆ:

  • ಎಕ್ಸ್ಪ್ರೆಸ್ ಆಟದ ವೆಬ್ ಸರ್ವರ್ ಅನ್ನು ನಿರ್ವಹಿಸುವ ಅತ್ಯಂತ ಜನಪ್ರಿಯ Node.js ವೆಬ್ ಫ್ರೇಮ್‌ವರ್ಕ್ ಆಗಿದೆ.
  • socket.io - ಬ್ರೌಸರ್ ಮತ್ತು ಸರ್ವರ್ ನಡುವೆ ಡೇಟಾವನ್ನು ವಿನಿಮಯ ಮಾಡಿಕೊಳ್ಳಲು ವೆಬ್‌ಸಾಕೆಟ್ ಲೈಬ್ರರಿ.
  • ವೆಬ್‌ಪ್ಯಾಕ್ - ಮಾಡ್ಯೂಲ್ ಮ್ಯಾನೇಜರ್. ವೆಬ್‌ಪ್ಯಾಕ್ ಅನ್ನು ಏಕೆ ಬಳಸಬೇಕು ಎಂಬುದರ ಕುರಿತು ನೀವು ಓದಬಹುದು. ಇಲ್ಲಿ.

ಪ್ರಾಜೆಕ್ಟ್ ಡೈರೆಕ್ಟರಿ ರಚನೆಯು ಹೇಗೆ ಕಾಣುತ್ತದೆ ಎಂಬುದು ಇಲ್ಲಿದೆ:

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

ಸಾರ್ವಜನಿಕ/

ಫೋಲ್ಡರ್‌ನಲ್ಲಿ ಎಲ್ಲವೂ public/ ಸರ್ವರ್‌ನಿಂದ ಸ್ಥಿರವಾಗಿ ಸಲ್ಲಿಸಲಾಗುತ್ತದೆ. IN 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 ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ (JS) ಕ್ಲೈಂಟ್‌ನ ಪ್ರವೇಶ ಬಿಂದುವಾಗಿದೆ. ವೆಬ್‌ಪ್ಯಾಕ್ ಇಲ್ಲಿಂದ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ ಮತ್ತು ಇತರ ಆಮದು ಮಾಡಿದ ಫೈಲ್‌ಗಳಿಗಾಗಿ ಪುನರಾವರ್ತಿತವಾಗಿ ಹುಡುಕುತ್ತದೆ.
  • ನಮ್ಮ ವೆಬ್‌ಪ್ಯಾಕ್ ಬಿಲ್ಡ್‌ನ ಔಟ್‌ಪುಟ್ JS ಡೈರೆಕ್ಟರಿಯಲ್ಲಿದೆ dist/. ನಾನು ಈ ಫೈಲ್ ಅನ್ನು ನಮ್ಮ ಎಂದು ಕರೆಯುತ್ತೇನೆ js ಪ್ಯಾಕೇಜ್.
  • ನಾವು ಬಳಸುತ್ತೇವೆ ಬಾಬೆಲ್, ಮತ್ತು ನಿರ್ದಿಷ್ಟವಾಗಿ ಸಂರಚನೆ @babel/preset-env ಹಳೆಯ ಬ್ರೌಸರ್‌ಗಳಿಗಾಗಿ ನಮ್ಮ JS ಕೋಡ್ ಅನ್ನು ವರ್ಗಾಯಿಸಲು.
  • JS ಫೈಲ್‌ಗಳಿಂದ ಉಲ್ಲೇಖಿಸಲಾದ ಎಲ್ಲಾ CSS ಅನ್ನು ಹೊರತೆಗೆಯಲು ಮತ್ತು ಅವುಗಳನ್ನು ಒಂದೇ ಸ್ಥಳದಲ್ಲಿ ಸಂಯೋಜಿಸಲು ನಾವು ಪ್ಲಗಿನ್ ಅನ್ನು ಬಳಸುತ್ತಿದ್ದೇವೆ. ನಾನು ಅವನನ್ನು ನಮ್ಮ ಎಂದು ಕರೆಯುತ್ತೇನೆ 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

ಮತ್ತು ವೆಬ್ ಬ್ರೌಸರ್‌ಗೆ ಹೋಗಿ ಲೋಕಲ್ ಹೋಸ್ಟ್: 3000. ಕೋಡ್ ಬದಲಾದಂತೆ ಅಭಿವೃದ್ಧಿ ಸರ್ವರ್ ಸ್ವಯಂಚಾಲಿತವಾಗಿ JS ಮತ್ತು CSS ಪ್ಯಾಕೇಜ್‌ಗಳನ್ನು ಮರುನಿರ್ಮಾಣ ಮಾಡುತ್ತದೆ - ಎಲ್ಲಾ ಬದಲಾವಣೆಗಳನ್ನು ನೋಡಲು ಪುಟವನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡಿ!

3. ಕ್ಲೈಂಟ್ ಎಂಟ್ರಿ ಪಾಯಿಂಟ್‌ಗಳು

ಆಟದ ಕೋಡ್‌ಗೆ ಇಳಿಯೋಣ. ಮೊದಲು ನಮಗೆ ಒಂದು ಪುಟ ಬೇಕು index.html, ಸೈಟ್ಗೆ ಭೇಟಿ ನೀಡಿದಾಗ, ಬ್ರೌಸರ್ ಅದನ್ನು ಮೊದಲು ಲೋಡ್ ಮಾಡುತ್ತದೆ. ನಮ್ಮ ಪುಟವು ತುಂಬಾ ಸರಳವಾಗಿರುತ್ತದೆ:

ಸೂಚ್ಯಂಕ

ಉದಾಹರಣೆ .io ಆಟ  ಪ್ಲೇ ಮಾಡಿ

ಸ್ಪಷ್ಟತೆಗಾಗಿ ಈ ಕೋಡ್ ಉದಾಹರಣೆಯನ್ನು ಸ್ವಲ್ಪಮಟ್ಟಿಗೆ ಸರಳೀಕರಿಸಲಾಗಿದೆ ಮತ್ತು ನಾನು ಇತರ ಪೋಸ್ಟ್ ಉದಾಹರಣೆಗಳೊಂದಿಗೆ ಅದೇ ರೀತಿ ಮಾಡುತ್ತೇನೆ. ಪೂರ್ಣ ಕೋಡ್ ಅನ್ನು ಯಾವಾಗಲೂ ಇಲ್ಲಿ ವೀಕ್ಷಿಸಬಹುದು github.

ನಾವು ಹೊಂದಿದ್ದೇವೆ:

  • HTML5 ಕ್ಯಾನ್ವಾಸ್ ಅಂಶ (<canvas>) ನಾವು ಆಟವನ್ನು ನಿರೂಪಿಸಲು ಬಳಸುತ್ತೇವೆ.
  • <link> ನಮ್ಮ CSS ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಸೇರಿಸಲು.
  • <script> ನಮ್ಮ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಸೇರಿಸಲು.
  • ಬಳಕೆದಾರಹೆಸರಿನೊಂದಿಗೆ ಮುಖ್ಯ ಮೆನು <input> ಮತ್ತು ಪ್ಲೇ ಬಟನ್ (<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 ಆಮದು (ಆದ್ದರಿಂದ Webpack ಅವುಗಳನ್ನು ನಮ್ಮ 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. ಕ್ಲೈಂಟ್ ರೆಂಡರಿಂಗ್

ಪರದೆಯ ಮೇಲೆ ಚಿತ್ರವನ್ನು ಪ್ರದರ್ಶಿಸುವ ಸಮಯ!

…ಆದರೆ ನಾವು ಅದನ್ನು ಮಾಡುವ ಮೊದಲು, ಇದಕ್ಕಾಗಿ ಅಗತ್ಯವಿರುವ ಎಲ್ಲಾ ಚಿತ್ರಗಳನ್ನು (ಸಂಪನ್ಮೂಲಗಳು) ನಾವು ಡೌನ್‌ಲೋಡ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ. ಸಂಪನ್ಮೂಲ ವ್ಯವಸ್ಥಾಪಕವನ್ನು ಬರೆಯೋಣ:

ಆಸ್ತಿಗಳು.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() ಇತ್ತೀಚೆಗೆ ಸ್ವೀಕರಿಸಿದ ಆಟದ ನವೀಕರಣದ ಡೇಟಾವನ್ನು ಮಾತ್ರ ನೇರವಾಗಿ ಹಿಂತಿರುಗಿಸಬಹುದು.

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 ಬಾರಿ, ನಂತರ ಈ ಅರ್ಧದಷ್ಟು ಕರೆಗಳು ಒಂದೇ ವಿಷಯವನ್ನು ಪುನಃ ಬರೆಯುತ್ತವೆ, ಮೂಲಭೂತವಾಗಿ ಏನನ್ನೂ ಮಾಡುವುದಿಲ್ಲ. ನಿಷ್ಕಪಟ ಅನುಷ್ಠಾನದ ಮತ್ತೊಂದು ಸಮಸ್ಯೆ ಅದು ವಿಳಂಬಕ್ಕೆ ಗುರಿಯಾಗುತ್ತದೆ. ಆದರ್ಶ ಇಂಟರ್ನೆಟ್ ವೇಗದೊಂದಿಗೆ, ಕ್ಲೈಂಟ್ ಪ್ರತಿ 33ms (ಸೆಕೆಂಡಿಗೆ 30) ನಿಖರವಾಗಿ ಆಟದ ನವೀಕರಣವನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ:

ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ
ದುರದೃಷ್ಟವಶಾತ್, ಯಾವುದೂ ಪರಿಪೂರ್ಣವಾಗಿಲ್ಲ. ಹೆಚ್ಚು ವಾಸ್ತವಿಕ ಚಿತ್ರ ಹೀಗಿರುತ್ತದೆ:
ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ
ಲೇಟೆನ್ಸಿಗೆ ಬಂದಾಗ ನಿಷ್ಕಪಟ ಅನುಷ್ಠಾನವು ಪ್ರಾಯೋಗಿಕವಾಗಿ ಕೆಟ್ಟ ಪ್ರಕರಣವಾಗಿದೆ. 50ms ವಿಳಂಬದೊಂದಿಗೆ ಆಟದ ನವೀಕರಣವನ್ನು ಸ್ವೀಕರಿಸಿದರೆ, ನಂತರ ಗ್ರಾಹಕರ ಮಳಿಗೆಗಳು ಹೆಚ್ಚುವರಿ 50ms ಏಕೆಂದರೆ ಇದು ಹಿಂದಿನ ಅಪ್‌ಡೇಟ್‌ನಿಂದ ಆಟದ ಸ್ಥಿತಿಯನ್ನು ಇನ್ನೂ ರೆಂಡರ್ ಮಾಡುತ್ತಿದೆ. ಆಟಗಾರನಿಗೆ ಇದು ಎಷ್ಟು ಅನಾನುಕೂಲವಾಗಿದೆ ಎಂಬುದನ್ನು ನೀವು ಊಹಿಸಬಹುದು: ಅನಿಯಂತ್ರಿತ ಬ್ರೇಕಿಂಗ್ ಆಟವನ್ನು ಜರ್ಕಿ ಮತ್ತು ಅಸ್ಥಿರವಾಗಿ ಭಾವಿಸುತ್ತದೆ.

7.2 ಸುಧಾರಿತ ಕ್ಲೈಂಟ್ ಸ್ಥಿತಿ

ನಿಷ್ಕಪಟ ಅನುಷ್ಠಾನಕ್ಕೆ ನಾವು ಕೆಲವು ಸುಧಾರಣೆಗಳನ್ನು ಮಾಡುತ್ತೇವೆ. ಮೊದಲಿಗೆ, ನಾವು ಬಳಸುತ್ತೇವೆ ರೆಂಡರಿಂಗ್ ವಿಳಂಬ 100 ms ಗೆ ಇದರರ್ಥ ಕ್ಲೈಂಟ್‌ನ "ಪ್ರಸ್ತುತ" ಸ್ಥಿತಿಯು ಸರ್ವರ್‌ನಲ್ಲಿನ ಆಟದ ಸ್ಥಿತಿಗಿಂತ ಯಾವಾಗಲೂ 100ms ಗಳಷ್ಟು ಹಿಂದುಳಿಯುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, ಸರ್ವರ್‌ನಲ್ಲಿ ಸಮಯ ಇದ್ದರೆ 150, ನಂತರ ಕ್ಲೈಂಟ್ ಆ ಸಮಯದಲ್ಲಿ ಸರ್ವರ್ ಇದ್ದ ಸ್ಥಿತಿಯನ್ನು ನಿರೂಪಿಸುತ್ತದೆ 50:

ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ
ಅನಿರೀಕ್ಷಿತ ಆಟದ ನವೀಕರಣ ಸಮಯವನ್ನು ಬದುಕಲು ಇದು ನಮಗೆ 100ms ಬಫರ್ ನೀಡುತ್ತದೆ:

ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ
ಇದರ ಪ್ರತಿಫಲ ಶಾಶ್ವತವಾಗಿರುತ್ತದೆ ಇನ್ಪುಟ್ ಮಂದಗತಿ 100 ms ಗೆ ಇದು ಸುಗಮ ಆಟಕ್ಕಾಗಿ ಒಂದು ಸಣ್ಣ ತ್ಯಾಗವಾಗಿದೆ - ಹೆಚ್ಚಿನ ಆಟಗಾರರು (ವಿಶೇಷವಾಗಿ ಕ್ಯಾಶುಯಲ್ ಆಟಗಾರರು) ಈ ವಿಳಂಬವನ್ನು ಗಮನಿಸುವುದಿಲ್ಲ. ಅನಿರೀಕ್ಷಿತ ಸುಪ್ತತೆಯೊಂದಿಗೆ ಆಡುವುದಕ್ಕಿಂತ ಜನರು ಸ್ಥಿರವಾದ 100ms ಲೇಟೆನ್ಸಿಗೆ ಹೊಂದಿಕೊಳ್ಳುವುದು ತುಂಬಾ ಸುಲಭ.

ಎಂಬ ಇನ್ನೊಂದು ತಂತ್ರವನ್ನು ಸಹ ನಾವು ಬಳಸಬಹುದು ಕ್ಲೈಂಟ್-ಸೈಡ್ ಭವಿಷ್ಯ, ಇದು ಗ್ರಹಿಸಿದ ಸುಪ್ತತೆಯನ್ನು ಕಡಿಮೆ ಮಾಡುವ ಉತ್ತಮ ಕೆಲಸವನ್ನು ಮಾಡುತ್ತದೆ, ಆದರೆ ಈ ಪೋಸ್ಟ್‌ನಲ್ಲಿ ಒಳಗೊಂಡಿರುವುದಿಲ್ಲ.

ನಾವು ಬಳಸುತ್ತಿರುವ ಮತ್ತೊಂದು ಸುಧಾರಣೆಯಾಗಿದೆ ರೇಖೀಯ ಪ್ರಕ್ಷೇಪಣ. ರೆಂಡರಿಂಗ್ ಮಂದಗತಿಯ ಕಾರಣ, ನಾವು ಸಾಮಾನ್ಯವಾಗಿ ಕ್ಲೈಂಟ್‌ನಲ್ಲಿ ಪ್ರಸ್ತುತ ಸಮಯಕ್ಕಿಂತ ಕನಿಷ್ಠ ಒಂದು ಅಪ್‌ಡೇಟ್‌ನಲ್ಲಿದ್ದೇವೆ. ಕರೆದಾಗ getCurrentState(), ನಾವು ಕಾರ್ಯಗತಗೊಳಿಸಬಹುದು ರೇಖೀಯ ಪ್ರಕ್ಷೇಪಣ ಕ್ಲೈಂಟ್‌ನಲ್ಲಿ ಪ್ರಸ್ತುತ ಸಮಯದ ಮೊದಲು ಮತ್ತು ನಂತರ ಆಟದ ನವೀಕರಣಗಳ ನಡುವೆ:

ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ
ಇದು ಫ್ರೇಮ್ ದರ ಸಮಸ್ಯೆಯನ್ನು ಪರಿಹರಿಸುತ್ತದೆ: ನಾವು ಈಗ ನಮಗೆ ಬೇಕಾದ ಯಾವುದೇ ಫ್ರೇಮ್ ದರದಲ್ಲಿ ಅನನ್ಯ ಫ್ರೇಮ್‌ಗಳನ್ನು ಸಲ್ಲಿಸಬಹುದು!

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. ನಂತರ, ಮೆಮೊರಿ ಬಳಕೆಯನ್ನು ಪರಿಶೀಲಿಸಲು, ನಾವು ಮೊದಲು ಎಲ್ಲಾ ಹಳೆಯ ನವೀಕರಣಗಳನ್ನು ತೆಗೆದುಹಾಕುತ್ತೇವೆ ಮೂಲ ನವೀಕರಣಏಕೆಂದರೆ ನಮಗೆ ಇನ್ನು ಮುಂದೆ ಅವು ಅಗತ್ಯವಿಲ್ಲ.

"ಮೂಲ ನವೀಕರಣ" ಎಂದರೇನು? ಈ ಸರ್ವರ್‌ನ ಪ್ರಸ್ತುತ ಸಮಯದಿಂದ ಹಿಂದಕ್ಕೆ ಚಲಿಸುವ ಮೂಲಕ ನಾವು ಕಂಡುಕೊಳ್ಳುವ ಮೊದಲ ನವೀಕರಣ. ಈ ರೇಖಾಚಿತ್ರವನ್ನು ನೆನಪಿದೆಯೇ?

ಮಲ್ಟಿಪ್ಲೇಯರ್ .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 ಮೇಲೆ github.

ಭಾಗ 2. ಬ್ಯಾಕೆಂಡ್ ಸರ್ವರ್

ಈ ಭಾಗದಲ್ಲಿ, ನಮ್ಮ ನಿಯಂತ್ರಿಸುವ Node.js ಬ್ಯಾಕೆಂಡ್ ಅನ್ನು ನಾವು ನೋಡೋಣ .io ಆಟದ ಉದಾಹರಣೆ.

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

ಮೊದಲ ಭಾಗದಲ್ಲಿ ನಾವು ವೆಬ್‌ಪ್ಯಾಕ್ ಅನ್ನು ಚರ್ಚಿಸಿದ್ದೇವೆ ಎಂದು ನೆನಪಿದೆಯೇ? ಇಲ್ಲಿ ನಾವು ನಮ್ಮ ವೆಬ್‌ಪ್ಯಾಕ್ ಕಾನ್ಫಿಗರೇಶನ್‌ಗಳನ್ನು ಬಳಸುತ್ತೇವೆ. ನಾವು ಅವುಗಳನ್ನು ಎರಡು ರೀತಿಯಲ್ಲಿ ಬಳಸುತ್ತೇವೆ:

  • ಬಳಸಲು webpack-dev-middleware ನಮ್ಮ ಅಭಿವೃದ್ಧಿ ಪ್ಯಾಕೇಜ್‌ಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಮರುನಿರ್ಮಾಣ ಮಾಡಲು, ಅಥವಾ
  • ಸ್ಥಿರವಾಗಿ ವರ್ಗಾವಣೆ ಫೋಲ್ಡರ್ 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ಆದ್ದರಿಂದ ನಾವು ಅದರ ಬಗ್ಗೆ ಚಿಂತಿಸಬೇಕಾಗಿಲ್ಲ. ನಾನು ಅವನನ್ನು ಕರೆಯುತ್ತೇನೆ ಆಟಗಾರರ 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:

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

ಈ ಸರಳ ಘರ್ಷಣೆ ಪತ್ತೆಹಚ್ಚುವಿಕೆ ಎಂಬ ಅಂಶವನ್ನು ಆಧರಿಸಿದೆ ಅವುಗಳ ಕೇಂದ್ರಗಳ ನಡುವಿನ ಅಂತರವು ಅವುಗಳ ತ್ರಿಜ್ಯದ ಮೊತ್ತಕ್ಕಿಂತ ಕಡಿಮೆಯಿದ್ದರೆ ಎರಡು ವೃತ್ತಗಳು ಘರ್ಷಣೆಗೊಳ್ಳುತ್ತವೆ. ಎರಡು ವೃತ್ತಗಳ ಕೇಂದ್ರಗಳ ನಡುವಿನ ಅಂತರವು ಅವುಗಳ ತ್ರಿಜ್ಯಗಳ ಮೊತ್ತಕ್ಕೆ ನಿಖರವಾಗಿ ಸಮಾನವಾಗಿರುವ ಸಂದರ್ಭ ಇಲ್ಲಿದೆ:

ಮಲ್ಟಿಪ್ಲೇಯರ್ .io ವೆಬ್ ಗೇಮ್ ಅನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ
ಇಲ್ಲಿ ಪರಿಗಣಿಸಲು ಇನ್ನೂ ಒಂದೆರಡು ಅಂಶಗಳಿವೆ:

  • ಉತ್ಕ್ಷೇಪಕವು ಅದನ್ನು ರಚಿಸಿದ ಆಟಗಾರನನ್ನು ಹೊಡೆಯಬಾರದು. ಹೋಲಿಸುವ ಮೂಲಕ ಇದನ್ನು ಸಾಧಿಸಬಹುದು bullet.parentID с player.id.
  • ಒಂದೇ ಸಮಯದಲ್ಲಿ ಅನೇಕ ಆಟಗಾರರು ಘರ್ಷಣೆ ಮಾಡುವ ಸೀಮಿತ ಸಂದರ್ಭದಲ್ಲಿ ಉತ್ಕ್ಷೇಪಕವು ಒಮ್ಮೆ ಮಾತ್ರ ಹೊಡೆಯಬೇಕು. ಆಪರೇಟರ್ ಬಳಸಿ ನಾವು ಈ ಸಮಸ್ಯೆಯನ್ನು ಪರಿಹರಿಸುತ್ತೇವೆ break: ಉತ್ಕ್ಷೇಪಕದೊಂದಿಗೆ ಡಿಕ್ಕಿ ಹೊಡೆಯುವ ಆಟಗಾರನು ಕಂಡುಬಂದ ತಕ್ಷಣ, ನಾವು ಹುಡುಕಾಟವನ್ನು ನಿಲ್ಲಿಸುತ್ತೇವೆ ಮತ್ತು ಮುಂದಿನ ಉತ್ಕ್ಷೇಪಕಕ್ಕೆ ಹೋಗುತ್ತೇವೆ.

ಅಂತ್ಯ

ಅಷ್ಟೇ! .io ವೆಬ್ ಆಟವನ್ನು ರಚಿಸಲು ನೀವು ತಿಳಿದುಕೊಳ್ಳಬೇಕಾದ ಎಲ್ಲವನ್ನೂ ನಾವು ಒಳಗೊಂಡಿದೆ. ಮುಂದೇನು? ನಿಮ್ಮ ಸ್ವಂತ .io ಆಟವನ್ನು ನಿರ್ಮಿಸಿ!

ಎಲ್ಲಾ ಮಾದರಿ ಕೋಡ್ ಮುಕ್ತ ಮೂಲವಾಗಿದೆ ಮತ್ತು ಪೋಸ್ಟ್ ಮಾಡಲಾಗಿದೆ github.

ಮೂಲ: www.habr.com

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ