Photon-based lab

Table of Contents

1 Introduction

The goal of this lab is to use some of Photon's functionalities in the context of a small "game".

We suppose that you have done already the whole preparation of this lab.

This lab has been tested on Windows, Linux and MacOS.

2 Discovering Photon with its demonstration

  • C:\Software\Photon-<Windows or Linux or MacOSX>-Sdk_v4-1-16-5\Photon-Windows-Sdk.readme explains that concerning demo_basics, "the demo client is basically a 'Hello World'. So it's the best place to start if you are new to the SDK." Let's follow the advice!
  • Depending on your OS:
    • Windows
      • In Visual Studio, menu File > Open > Projet/Solution... and select C:\Software\Photon-Windows-Sdk_v4-1-16-5\Demos\demo_basics\windows\demo_windows_basics_vc16.sln: Solution opens.
      • Menu Build > Build solution. Either build goes well, or you get an error "Windows SDK kit version 10.0.10240.0 cannot be found. Install the required version of Windows SDK kit, OR change version of SDK kit in properties pages of the project; You can also right-click on the solution and select 'Retarget the solution'."
        • In our case, Right-click on the solution and select 'Retarget the solution'. Then choose "10.0 (last installed version)" as version of Windows SDK.
      • Menu Build > Build solution. You get a link error "LNK1112: computer type 'x86' is in conflict with target computer type 'ARM'"
      • Second line of menu says ARM on the right of debug. Select x64.
      • Menu Build > Build solution: Generation succeeds.
    • Linux
      • Go to directory Software/Photon-linux-Sdk_v4-1-16-5/Demos/demo_basics/linux
      • Type make
      • If compilation shows error message: ../../../Common-cpp/inc/defines.h:95:12: fatal error: sys/time.h: Aucun fichier ou dossier de ce type, type make debug_linux64 (this prevents from trying to generate 32-bits executables which lead to include error).
    • MacOS
      • With the finder, go to directory Software/Photon-MacOSX-Sdk_v4-1-16-5/Demos/demo_basics/iMac
      • Double-click on demo_iMac_basics.xcodeproj : Xcode opens.
      • Step 1 (see following screen copy): Click on "Show the project navigator" icon

        Xcode_for_photon_demo

        Figure 1: Xcode for Photon demo with arrows designating steps

      • Step2: Click on demo_iMac_basics
      • Step 3: Click on "Build settings"
      • Step 4: Click on "All"
      • Step 5: For "Base SDK", choose "macOS"
      • Step 6: For "Build Active Architecture Only", select "Yes"
      • Step 7: Click on "Run" icon
      • Step 8: The application should compile and run. After a while, the outputs are visible in output window.
  • Whatever OS you have, open file src\Photon_lib.cpp. Its line 4 contains:
static const ExitGames::Common::JString appID = L"<no-app-id>"; // set your app id here
  • Get your app id :
    • Log on Photon
    • Once connected, click on button CREATE A NEW APP
    • Type: Photon Realtime
    • Name: Whatever you want
    • Click on button Create
    • Click on line App ID:...: The whole App ID appears.
    • Copy-paste it in Photon_lib.cpp
  • Execute your application: You should get a lot of displays.
  • Stop your program.
  • If you want less traces, in file Photon_lib.cpp, in method PhotonLib::PhotonLib(), change all ExitGames::Common::DebugLevel::INFO into ExitGames::Common::DebugLevel::OFF. Rerun your program. Trace is now more readable:
connecting
2020-10-13 14:35:04,335 FATAL   Photon_lib.cpp                 PhotonLib::connectReturn()                                   174   connected to cluster default of region eu
connected to cluster default
connected
joining
2020-10-13 14:35:05,454 FATAL   Photon_lib.cpp                 PhotonLib::joinOrCreateRoomReturn()                          211
2020-10-13 14:35:05,463 FATAL   Photon_lib.cpp                 PhotonLib::joinOrCreateRoomReturn()                          220   localPlayerNr: 1
... room Basics has been entered
regularly sending dummy events now
2020-10-13 14:35:05,470 FATAL   Photon_lib.cpp                 PhotonLib::joinRoomEventAction()                             139   user179658515 joined the game

player 1 user179658515 has joined the game
ingame
sent event Nr. 0
received event Nr. 0
ingame
sent event Nr. 1
received event Nr. 0
ingame
sent event Nr. 2
received event Nr. 1
ingame
sent event Nr. 3
received event Nr. 2
ingame
sent event Nr. 4
received event Nr. 3
ingame
  • Your program connects to Photon, joins room Basics (see variable gameName defined in Photo_Lib.cpp), then sends a message to itself, receives it, sends another message, …, 100 times. Then, it leaves the room, and starts again.
  • It is essential to understand that Photon informs your programm of state evolutions thanks to callbacks, callbacks that Photon is able to call only when it is given explicitely the authorization to do so. This authorization is given by calling method service() in PhotonLib::update().
    • To ckeck this, comment line mLoadBalancingClient.service(); in your program.
    • Build your program and run it: Your program cannot go beyond connection phase.

3 V0 of a multiplayer game with Photon

By inspiring ourselves from the demonstration application previously studied, we have written some code for a multiplayer "game" that you will need to enrich to make it operational:

  • Decompress Multi_Photon_V0.zip in the directory you want.
  • In file Multi_Photon_V0\CMakeLists.txt, modify line 9 set (PHOTON_DIR "C:/Software/Photon-Windows-Sdk_v4-1-16-5") so that it contains absolute path to the directory where Photon is. Note: Even if you are using Windows envirnement (where "\" character separates directories), use "/" to separate directories.
  • Apply cmake procedure of this document and stop after step "Menu Build > Build all to check that build is correct". Note: Do not try to execute the application. It crashes because of a vector subscript out of range problem caused by variables not initialized by Photon.

To get a multiplayer version of our "game", let's follow the explanations given in page Realtime documentation: It contains C# and C++ examples (This page can be found by going to https://www.photonengine.com/en-US/Photon, click on SDKs, click on Realtime / Client / C++, then on puis Realtime Windows, and Documentation). Let's follow step by step the explanations of this page.

3.1 Section Connect - Connect To Photon Cloud of page Realtime documentation

  • In Game.cpp
    • Give your value of app id to appId.
    • In Game::Game(), call mNetworkLogic.connect();
  • Enrich implementation of NetworkLogic::connect() with:
    • Sample code in SampleNetworkLogic::connect() of Realtime documentation.
    • A call to waitForListener() to wait that Ă‘etworkLogic automaton goes from state NetworkLogic::State::INITIALIZED to state NetworkLogic::State::CONNECTED

3.2 Section Call Service of page Realtime documentation

  • As suggested in this section of page Realtime documentation, in Game.cpp, in game loop, we call mNetworkLogic.service().
  • Then, we copy code proposed in SampleNetworkLogic::run() of their C++ sample into NetworkLogic::service() method of our code.

3.3 Section Disconnect of page Realtime documentation

Enrich NetworkLogic::disconnect() (there is nothing to do in Game.cpp which does not call disconnect().

3.4 Sections Matchmaking and Persist Games of page Realtime documentation

In our "game", we do not use these Photon functionnalities. Nevertheless, read these sections to see what Photon offers.

Instead of these functions, we reuse philosophy of Photon demonstration application:

  • In Game::Game() implementation, call mNetworkLogic.joinOrCreateRoom(gameName);
  • Enrich implementation of NetworkLogic::joinOrCreateRoom() with the following code:
std::wcout << "Trying to joinOrCreate room: " << roomName << std::endl;
mLoadBalancingClient.opJoinOrCreateRoom(roomName);
waitForListener(NetworkLogic::State::CONNECTED, NetworkLogic::State::JOINED);
  • Add a call to waitForListener() to wait for the automaton to go from state NetworkLogic::State::CONNECTED to state NetworkLogic::State::JOINED

3.5 Section Gameplay - Sending events of page Realtime documentation

  • In implementation of Game::handlePlayerInput(), call mNetworkLogic.sendPlayerChange(key); Note: We make this call (in other words, we send a message on the network to tell a key was activated by this player) only if the keypress is local to this machine. So, we do not make this call if the keypress is a simulation due to the receiving of a message from the network.
  • Enrich implementation of NetworkLogic::sendPlayerChange() with following code:
nByte eventCode = PlayerChange; // use distinct event codes to distinguish between different types of events (for example 'move', 'shoot', etc.)
ExitGames::Common::Hashtable evData; // organize your payload data in any way you like as long as it is supported by Photons serialization
evData.put(static_cast<nByte>(0), static_cast<int>(key));
mLoadBalancingClient.opRaiseEvent(true, evData, eventCode); // true, because it is not acceptable to lose player actions
++mSendCount;

This code is directly inspired form example given in page Realtime documentation. We just casted key with static_cast<int>

Photon chose HashTable to serialize/deserialize messages. This choice may seem suprising, as it is not optimal from a network usage point of view. Nevertheless, it is interesting to handle the case of a client running a certain version of the software which sends a message to a client running another version of the software. Between the two version of the software, message format may have changed. With a HashTable, a client can only look at the message fields which are of interest for it.

As suggested by documentation, go to page Serialization in Photon to learn more.

3.6 Section Gameplay - Receiving events of page Realtime documentation

Modify NetworkLogic::customEventAction() to process an eventCode with value PlayerChange :

  • Compute key value transmitted inb the message.
  • call mGame->handlePlayerInput with playerNr - 1 (operation - 1 is due to the fact that Photon starts player number with 1, while our player array starts with 0), the value of key and the boolean true to tell mGame->handlePlayerInput that we invoke it after receiving a network message.

3.7 Section Custom or Authoritative Server Logic of page Realtime documentation

With Photon, it is possible to have a dedicated server which can, for instance, an authorative server (holding the reference of the game state). For our "game", we consider that working peer-to-peer is enough: We do not use thisnotion of server.

3.8 Testing your multiplayer "game"

Build your application. Then, lauch two instances of your game on your machine. Under Visual Studio:

  1. Lauch first instance with Menu Debug > Run without debugging
  2. Lauch second instance in the same way or in debug mode.

Check that both instances see the car moves.

3.9 Performances of the "game"

In Game::run(), call updateStatistics(elapsedTime);. Moreover, in Game::updateStatistics(), look at the statistics related to Photon that wa added. See how they are implemented in NetworkLogic.cpp.

Run two instances to see the performances.

3.10 Correction

4 V1 of a multiplayer game with Photon

Version previously developped has an anomaly:

  1. Start a first instance of the game.
  2. Make its associated car move.
  3. Start a second instance of the game.
  4. You can observe that, on the second instance, the car of the first player is not correctly positionned compared to the first instance.

Let's correct this anomaly:

  • In NetworkLogic::MessageType, add a new message type InitialState
  • In Game.hpp, create a method std::vector<std::unique_ptr<Entity>> &getEntities(void);
  • In Game.cpp, implement this method.
  • In NetworkLogic.cpp, modify NetworkLogic::joinRoomEventAction so that if call to mLoadBalancingClient.getLocalPlayer().getNumber() returns 1 (you are the first player to have joined the group) and playerNr is different from 1 (there is another player who joined the group), for each element of getEntities(), broadcast an InitialState message containing:
    • playerNr
    • Rank of entity in getEntities()
    • x and y of getPosition()
    • Result of getRotation()
    • Result of getSpeedFactor()
    • Result of getWheelsTurn()
  • In NetworkLogic.cpp, modify NetworkLogic::customEventAction to handle the case when receiving InitialState.
    • Take into account the message only if playerNr equals mLoadBalancingClient.getLocalPlayer().getNumber()
    • Update concerned entity. Note: You will need to add methods Entity::setSpeedFactor() and Entity::setWheelsTurn()

Check that this anomaly is corrected.

Correction: Multi_Photon_V1_correction.zip

5 V2 de jeu multijoueur avec Photon

V1 version has an anomaly:

  1. Start a first instance of the game.
  2. Make its car move.
  3. Start a second instance of the game.
  4. At the level of the first instance, fo a lot of direction changes, going at full speed. Do several bounces on the edge of the screen.
  5. After a while, stop the car of the first player. You should see a difference of position for this cas between the two instances.

Explain the origin of the porblem.

To correct this problem, when sending PlayerChange, we modify the code to send not only the key pressed, but also the position of the car when the keypress was taken into account.

Check that this anomaly is corrected.

Correction : Multi_Photon_V2_correction.zip

6 To the infinity and beyond! (To many other versions of the "game")

Guess what! V2 version contains another anomaly!

  1. If you make lots of changes on the car of a player, you will see slight shakes on the instance of the other player.
  2. It is as if the cas was "teleported".
  3. This brings several problems:
    • If there ware obstacles in our game, the car could go through for a player and not for the other one.
    • Moreover, if a car could shoot, bullets could reach the target car for one player and not for the other one.

Correcting these problems is beyond this lab. There are two streams of solutions:

Moreover, as our game will be a planetary success, we will have to deal with millions of players simultaneously connected in the same room Thus, our instance will need to send state changes only to players geographically close in the virtual world. This is another problem which is beyond this lab. Nevertheless, we will study it from a theoretical point of view.

Date: 11 may 2021

Author: Michel SIMATIC

Created: 2021-10-04 lun. 09:24

Validate