• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

PubNubDevelopers/Ninja-Multiplayer-Platformer: A real time multiplayer game usin ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

PubNubDevelopers/Ninja-Multiplayer-Platformer

开源软件地址:

https://github.com/PubNubDevelopers/Ninja-Multiplayer-Platformer

开源编程语言:

JavaScript 92.4%

开源软件介绍:

Ninja Multiplayer Platformer Game in Real Time

Play Now: https://pubnubdevelopers.github.io/Ninja-Multiplayer-Platformer/

npm install pubnubninja

Screenshot

A step by step tutorial of how to create this multiplayer game is available here:

https://www.pubnub.com/tutorials/javascript/multiplayer-game/

Table of Contents

Synopsis

Creating a real time multiplayer game can be a daunting task to take on alone. When I stumbled upon Mozilla’s game development workshop, I decided to take on the challenge to turn Belén Albeza's workshop into a real time game. This Ninja Platformer Multiplayer Game uses PubNub’s real time data stream network to manage network traffic between players, and also uses PubNub Blocks to manage game state between devices. PubNub made the development process incredibly simple and allowed the entire demo to be written in less than a 1000 lines of code!!! This feat is incredible for the amount of functionality this game has and is an excellent showcase of both Phaser and PubNub’s capabilities.

This Ninja Platformer Multiplayer Game is written in javascript and the levels are generated via JSON files with information on the position of the platforms and game objects.

This real time multiplayer game is a collaborative puzzle game that encourages you to work with your friends to collect the keys in clever ways. Using Phasers Arcade Physics Library, each character and object has its own physics body with its own set of physics properties. Open up a few browser windows to test out the real time functionality of the application.

Don’t forget to give it a star and a fork. It will be exciting to see what you guys can make from this example since it has so much room for expansion.

Phaser

Phaser is a fast, free, and fun open source HTML5 game framework. It uses a custom build of Pixi.js for WebGL and Canvas rendering, and supports desktop and mobile web browsers. Games can be compiled to iOS, Android and native desktop apps via 3rd party tools. You can use JavaScript or TypeScript for development. Learn More

PubNub

PubNub is a global Data Stream Network (DSN) that allows developers to build realtime web, mobile, IoT applications and real time games. PubNub API's include a Publish/Subscribe messaging service. Clients subscribe to a channel name, and any clients that are connected will receive any publish messages sent on that channel. In addition PubNub offers online presence detection that tracks the online and offline statues of users and devices in realtime. Furthermore, PubNub offers a service called PubNub Blocks which allows developers to customize the data stream in Javascript.

PubNub’s Pub/Sub and Presence is used in this demo to send information on player movements and occupancy in each level. PubNub Blocks is used as a state machine to detect if the coins of been collected by a player in each level. PubNub Blocks updates the JSON level object depending upon what actions the players take in the game. Learn More

Getting Started

In order to start the development process, you are going to need a few things:

Create a new folder anywhere you wish, for simplicity create it on your Desktop. If you have Mac OS or Linux (or have Python installed), open up your Terminal Application and type in:

python -m SimpleHTTPServer 8000

If you are using Windows download XAMPP. There are some great tutorials out there on how to setup XAMPP on your machine.

Once you have your server up and running, go to http://localhost:8000/ on your machine and navigate to your project directory. You are ready to start coding!

Now in order to get you setup with PubNub, navigate to the PubNub Website and create an account with your Google login. Once you are in the dashboard, name your application whatever you wish, and click the Create New App button. Once you create the application, click on the application to few the key information. You should see that you have two keys, a Publish Key, and a Subscribe Key. Click on the demo keyset, and it should load up a page that shows your keys in addition to Application Add-Ons. In the Application Add-Ons section, turn ON Presence and check Generate Leave on TCP FIN or RST and Global Here Now. Also turn ON PubNub Blocks. Make sure to have access manager turned off or else the sample code won't work since you need to include a secret key. Leave the page open for future reference once we start writing our code, we are going to need those PubNub keys!

Code Layout

This game is split up into four main documents, main.js , loadingState.js , playstate.js , heroScript.js , and then the server code for PubNub blocks called blockscode.js. main.js loads all of the other files internally.

Main.js

main.js is loaded from index.html. First all of the global variables are loaded into the document. I am using window to define the global variables. I set window.UniqueID by calling the PubNub.generateUUID() function. When the game loads, it first generates the unique ID of the device that way no two players share the same information.

window.syncOtherPlayerFrameDelay = 0; //30 frames allows for 500ms of network jitter, to prevent late frames
window.currentChannelName; // Global variable for the current channel that your player character is on
window.currentFireChannelName; // Global variable that checks the current stage you are on to send the correct information to the PubNub Block
window.globalCurrentLevel = 0; // Global variable for the current level (index starts at 0)
window.UniqueID = window.PubNub.generateUUID(); // Generate a unique id for the player. Generated by the PubNub Network
window.globalLevelState = null; // Sets the globalLevelState to null if you aren't connected to the network. Once connected, the level will generate to the info that was on the block.
window.globalWasHeroMoving = true;
window.text1 = 'Level 1 Occupancy: 0'; // Global text objects for occupancy count
window.text2 = 'Level 2 Occupancy: 0';
window.text3 = 'Level 3 Occupancy: 0';
let textResponse1;
let textResponse2;
let textResponse3;
window.updateOccupancyCounter = false; // Occupancy Counter variable to check if the timer has already been called in that scene
window.keyMessages = [];

Then we load the external Javascript files:

// Load External Javascript files
const loadHeroScript = document.createElement('script');
loadHeroScript.src = './js/heroScript.js';
document.head.appendChild(loadHeroScript);

const loadLoadingState = document.createElement('script');
loadLoadingState.src = './js/loadingState.js';
document.head.appendChild(loadLoadingState);

const loadPlaystate = document.createElement('script');
loadPlaystate.src = './js/playState.js';
document.head.appendChild(loadPlaystate);

After the other javascript files have been loaded, we call the function (promise) window.addEventListener. The function creates the phaser window, adds the various game states, then calls the window.createMyPubNub function and passes 0 which tells the game to load the level 0 json file (or pull it from the PubNub blocks server if it exists).

window.addEventListener('load', () => {
  const game = new window.Phaser.Game(960, 600, window.Phaser.AUTO, 'game');
  game.state.disableVisibilityChange = true; // This allows two windows to be open at the same time and allow both windows to run the update function
  game.state.add('play', window.PlayState);
  game.state.add('loading', window.LoadingState);
  window.createMyPubNub(0); // Connect to the pubnub network and run level code 0
  window.StartLoading = function () {
    game.state.start('loading'); // Run the loading function once you successfully connect to the pubnub network
  };
});

Now we create the global variables for what level the device is on and also generate the channel names according to the current level the user is loaded into. Next, you need to go into your PubNub admin console and get your Pub/Sub keys and replace them with the demo keys here. After the PubNub API is initialized, we subscribe to messages sent on whatever level we are currently on. The first part of the function window.createMyPubNub is coded as follows:

window.createMyPubNub = function (currentLevel) {
  window.globalCurrentLevel = currentLevel; // Get the current level and set it to the global level
  window.currentFireChannelName = 'realtimephaserFire2';
  window.currentChannelName = `realtimephaser${currentLevel}`; // Create the channel name + the current level. This way each level is on its own channel.
  let checkIfJoined = false; // If player has joined the channel

  // Setup your PubNub Keys
  window.pubnub = new window.PubNub({
    publishKey: 'demo',
    subscribeKey: 'demo',
    uuid: window.UniqueID,
  });

  // Subscribe to the two PubNub Channels
  window.pubnub.subscribe({
    channels: [window.currentChannelName, window.currentFireChannelName],
    withPresence: true,
  });

There is plenty of more code in the window.listener function which is called when a PubNub message is recieved on the channel you are subscribed too. However for now, lets skip that code and add a listener for when a user leaves the window. Also we use the navigator.sendBeacon to call the PubNub unsubscribe function even if the globalUnsubscribe() function isn't able to run.

// If person leaves or refreshes the window, run the unsubscribe function
  window.addEventListener('beforeunload', () => {
    navigator.sendBeacon(`https://pubsub.pubnub.com/v2/presence/sub_key/mySubKey/channel/ch1/leave?uuid=${window.UniqueID}`); // pub
    window.globalUnsubscribe();
  });

We then create the window.globalUnsubscribe function which also has the code to start listening to the listener PubNub function.

// Unsubscribe people from PubNub network
  window.globalUnsubscribe = function () {
    try {
      window.pubnub.unsubscribe({
        channels: [window.currentChannelName, window.currentFireChannelName],
        withPresence: true
      });
      window.pubnub.removeListener(window.listener);
    } catch (err) {
      // console.log(err);
    }
  };
  window.pubnub.addListener(window.listener);
};

Next we are going to create the window.sendKeyMessage function. This function publishes a message containing all of the player movement information and the framecount information. This function is global and is called from other .js files such as playState.js. The window.fireCoins function sends a message using PubNub's fire message API which only publishes to the Block server. The information sent contains information about the current level, the time at which you join the level, and if you have any cache information about the level. We send this information on a seperate channel.

  window.sendKeyMessage = (keyMessage) => {
      try {
        if (window.globalMyHero) {
          window.pubnub.publish({
            message: {
              uuid: window.UniqueID,
              keyMessage,
              position: window.globalMyHero.body.position,
              frameCounter: window.frameCounter
            },
            channel: window.currentChannelName,
            sendByPost: false, // true to send via posts
          });
        }
      } catch (err) {
        console.log(err);
      }
  };

window.fireCoins = () => {
  const message = {
    uuid: window.UniqueID,
    coinCache: window.globalLevelState.coinCache,
    currentLevel: window.globalCurrentLevel,
    time: window.globalLastTime
  };
  window.pubnub.fire(
    {
      message,
      channel: window.currentFireChannelName,
      sendByPost: false, // true to send via posts
    });
};

After this code has been executed, the window.StartLoading function will be called which will load the document loadingState.js.

loadingState.js

All the loadingState.js does is load assets into the game. This is all using the phaser API's. After all the assets have been loaded into the game, we call this.game.state.start to launch the playState.js file.

window.LoadingState = { // Create an object with all of the loading information inside of it
  init() {
    // keep crispy-looking pixels
    this.game.renderer.renderSession.roundPixels = true; // Make the phaser sprites look smoother
  },

  preload() {
    this.game.stage.disableVisibilityChange = true;

    // Load JSON levels
    this.game.load.json('level:0', 'data/level00.json');
    this.game.load.json('level:1', 'data/level01.json');
    this.game.load.json('level:2', 'data/level02.json');


    this.game.load.image('font:numbers', 'images/numbers.png');
    this.game.load.image('icon:coin', 'images/coin_icon.png');
    this.game.load.image('background', 'images/bg.png');
    this.game.load.image('invisible-wall', 'images/invisible_wall.png');
    this.game.load.image('ground', 'images/ground.png');
    this.game.load.image('grass:8x1', 'images/grass_8x1.png');
    this.game.load.image('grass:6x1', 'images/grass_6x1.png');
    this.game.load.image('grass:4x1', 'images/grass_4x1.png');
    this.game.load.image('grass:2x1', 'images/grass_2x1.png');
    this.game.load.image('grass:1x1', 'images/grass_1x1.png');
    this.game.load.image('key', 'images/key.png');

    this.game.load.spritesheet('decoration', 'images/decor.png', 42, 42);
    this.game.load.spritesheet('herodude', 'images/hero.png', 36, 42);
    this.game.load.spritesheet('hero', 'images/gameSmall.png', 36, 42);
    this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22);
    this.game.load.spritesheet('door', 'images/door.png', 42, 66);
    this.game.load.spritesheet('icon:key', 'images/key_icon.png', 34, 30);

    this.game.load.audio('sfx:jump', 'audio/jump.wav');
    this.game.load.audio('sfx:coin', 'audio/coin.wav');
    this.game.load.audio('sfx:key', 'audio/key.wav');
    this.game.load.audio('sfx:stomp', 'audio/stomp.wav');
    this.game.load.audio('sfx:door', 'audio/door.wav');
    this.game.load.audio('bgm', ['audio/bgm.mp3', 'audio/bgm.ogg']);
  },

  create() {
    this.game.state.start('play', true, false, { level: window.globalCurrentLevel }); // Start Game
  }
};

playState.js

playState.js is responsible for most of gameplay logic and processes the information on player movements. First we need to create some var's and create a function to log the current game state of the coins. In this function, the coins will be spliced from the json array everytime a hero collides with a coin object. That information is then fired to the PubNub Blocks server for processing

const keyStates = {};
let keyCollected = false;
window.frameCounter = 0;

function logCurrentStateCoin(game, coin) {
  // Log Current Game State of Collected Coins
  for (const value of window.globalLevelState.coinCache.coins) {
    if (coin.x === value.x) {
      window.globalLevelState.coinCache.coins.splice(window.globalLevelState.coinCache.coins.indexOf(value), 1);
      // console.log(value)
    }
  }
  window.fireCoins();
  // console.log(window.globalLevelState.coinCache.coins)
}

The handleKeyMessages() function handles


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap