Photon-based lab
Table of Contents
- 1. Introduction
- 2. Discovering Photon with its demonstration
- 3. V0 of a multiplayer game with Photon
- 3.1. Section Connect - Connect To Photon Cloud of page Realtime documentation
- 3.2. Section Call Service of page Realtime documentation
- 3.3. Section Disconnect of page Realtime documentation
- 3.4. Sections Matchmaking and Persist Games of page Realtime documentation
- 3.5. Section Gameplay - Sending events of page Realtime documentation
- 3.6. Section Gameplay - Receiving events of page Realtime documentation
- 3.7. Section Custom or Authoritative Server Logic of page Realtime documentation
- 3.8. Testing your multiplayer "game"
- 3.9. Performances of the "game"
- 3.10. Correction
- 4. V1 of a multiplayer game with Photon
- 5. V2 de jeu multijoueur avec Photon
- 6. To the infinity and beyond! (To many other versions of the "game")
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 selectC:\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 ofdebug
. Selectx64
. - Menu
Build
>Build solution
: Generation succeeds.
- In Visual Studio, menu
- 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
, typemake debug_linux64
(this prevents from trying to generate 32-bits executables which lead to include error).
- Go to directory
- 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
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.
- With the finder, go to directory
- Windows
- 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 RealtimeName
: Whatever you want- Click on button Create
- Click on line
App ID:...
: The wholeApp 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 methodPhotonLib::PhotonLib()
, change allExitGames::Common::DebugLevel::INFO
intoExitGames::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 variablegameName
defined inPhoto_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()
inPhotonLib::update()
.- To ckeck this, comment line
mLoadBalancingClient.service();
in your program. - Build your program and run it: Your program cannot go beyond connection phase.
- To ckeck this, comment line
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 9set (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 "MenuBuild
>Build all
to check that build is correct". Note: Do not try to execute the application. It crashes because of avector 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()
, callmNetworkLogic.connect();
- Give your value of app id to
- 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 stateNetworkLogic::State::INITIALIZED
to stateNetworkLogic::State::CONNECTED
- Sample code in
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 callmNetworkLogic.service()
. - Then, we copy code proposed in
SampleNetworkLogic::run()
of their C++ sample intoNetworkLogic::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, callmNetworkLogic.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 stateNetworkLogic::State::CONNECTED
to stateNetworkLogic::State::JOINED
3.5 Section Gameplay - Sending events of page Realtime documentation
- In implementation of
Game::handlePlayerInput()
, callmNetworkLogic.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
withplayerNr - 1
(operation- 1
is due to the fact that Photon starts player number with 1, while our player array starts with 0), the value ofkey
and the booleantrue
to tellmGame->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:
- Lauch first instance with Menu
Debug
>Run without debugging
- 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
Correction: Multi_Photon_V0_correction.zip
4 V1 of a multiplayer game with Photon
Version previously developped has an anomaly:
- Start a first instance of the game.
- Make its associated car move.
- Start a second instance of the game.
- 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 typeInitialState
- In
Game.hpp
, create a methodstd::vector<std::unique_ptr<Entity>> &getEntities(void);
- In
Game.cpp
, implement this method. - In
NetworkLogic.cpp
, modifyNetworkLogic::joinRoomEventAction
so that if call tomLoadBalancingClient.getLocalPlayer().getNumber()
returns 1 (you are the first player to have joined the group) andplayerNr
is different from 1 (there is another player who joined the group), for each element ofgetEntities()
, broadcast anInitialState
message containing:playerNr
- Rank of entity in
getEntities()
x
andy
ofgetPosition()
- Result of
getRotation()
- Result of
getSpeedFactor()
- Result of
getWheelsTurn()
- In
NetworkLogic.cpp
, modifyNetworkLogic::customEventAction
to handle the case when receivingInitialState
.- Take into account the message only if
playerNr
equalsmLoadBalancingClient.getLocalPlayer().getNumber()
- Update concerned entity. Note: You will need to add methods
Entity::setSpeedFactor()
andEntity::setWheelsTurn()
- Take into account the message only if
Check that this anomaly is corrected.
Correction: Multi_Photon_V1_correction.zip
5 V2 de jeu multijoueur avec Photon
V1 version has an anomaly:
- Start a first instance of the game.
- Make its car move.
- Start a second instance of the game.
- 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.
- 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!
- If you make lots of changes on the car of a player, you will see slight shakes on the instance of the other player.
- It is as if the cas was "teleported".
- 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:
- Implement ideas form article Believable Dead Reckoning for Networked Games
- Have an authorative server, holding the game reference.
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.