Networked Custom Controllers

From C4 Engine Wiki

Jump to: navigation, search

Contents

Introduction

Greetings and salutations friend! I hope that you are reading this tutorial because you are interested in learning more about the mechanics of Message handling in the C4 Engine. This may seem like a daunting task, fortunately the network system integrated into the C4 Engine is some of the most well documented in all of the Engine. After this tutorial, you will know how to:

-Spawn models in a world, send spawn information to all players

-Actively send snapshot information to sync box states with all players

-When a client connects, send them snap shot information syncing current spawned boxes and states

-Destroy models when a player disconnects

Disclaimer

This tutorial assumes that you have followed and finished Basic Network Tutorial, and that you understand basic single player controller concepts, interface manipulation, and world editing.

Set Up

Before you can begin sending out controller messages and controlling the world from your masterfully written server, you need a world to play with. This section is all review from other tutorials, and can be skipped if you know how to set up a world quickly to your own tastes.

Getting Started

To get started you must create a new project in a clean install of the C4 Engine. After you have done that, you will need to create a new application object named Game.

So follow the standard mantra of setting up an Application

"EXGame.h"

#pragma once
 
#include <C4Application.h>
#include <C4Engine.h>
 
using namespace C4;
 
extern "C"
{
	C4MODULEEXPORT C4::Application *ConstructApplication(void);
}
 
namespace C4
{
 
	class Game : public Application, public Singleton<Game>
	{
	public:
		Game();
		~Game();
	};
 
	extern Game *TheGame;
}

"EXGame.cpp"

#include "EXGame.h"
 
Game *C4::TheGame = nullptr;
 
C4::Application *ConstructApplication()
{
	return (new Game);
}
 
Game::Game() : Singleton<Game>(TheGame)
{
}
 
Game::~Game()
{
}

With a little prep work

Alright so the basic application is great and all, but it doesn't do much for us in the long term. We need some way to manage and control if the client is a host or client. We also need some way to trigger the spawning of a box, and its movement. Instead of implementing actions, lets keep this simple and implement everything as part of the interface.

First we need a panel to work with, something simple works very well in this situation. I used the following layout.

Image:MainWindow.png

Once you have made your interface with those widgets, we should start linking them. In the following code, all the text buttons match their widget identifier with their displayed text. With the exception of the <<< and >>> which are named "spinLeft" and "spinRight" in mine. This is when you will need a little experience working with C4 interfaces to match up what I am doing, but it is easy enough.

"EXInterface.h"

#pragma once
 
#include <C4Interface.h>
 
using namespace C4;
 
namespace C4
{
	class MainWindow : public Window, public Singleton<MainWindow>
	{
	public:
		MainWindow();
		~MainWindow();
 
		static void Open();
 
		void Preprocess();
		void HandleWidgetEvent(Widget *widget, const WidgetEventData *eventData);
 
	private:
		PushButtonWidget	*hostGame;
		PushButtonWidget	*joinGame;
		PushButtonWidget	*quitGame;
 
		PushButtonWidget	*spawnBox;
		PushButtonWidget	*spinLeft;
		PushButtonWidget	*spinRight;
 
		EditTextWidget		*ipAddress;
	};
 
	extern MainWindow *TheMainWindow;
}

"EXInterface.cpp"

#include "EXInterface.h"
#include "EXGame.h"
 
MainWindow *C4::TheMainWindow = nullptr;
 
MainWindow::MainWindow() : Window("panel/Main"), Singleton<MainWindow>(TheMainWindow)
{
}
 
MainWindow::~MainWindow()
{
}
 
void MainWindow::Preprocess() 
{
	Window::Preprocess();
 
	hostGame = static_cast<PushButtonWidget *>(this->FindWidget("hostGame"));
	joinGame = static_cast<PushButtonWidget *>(this->FindWidget("joinGame"));
	quitGame = static_cast<PushButtonWidget *>(this->FindWidget("quitGame"));
 
	spawnBox = static_cast<PushButtonWidget *>(this->FindWidget("spawnBox"));
	spinLeft = static_cast<PushButtonWidget *>(this->FindWidget("spinLeft"));
	spinRight = static_cast<PushButtonWidget *>(this->FindWidget("spinRight"));
 
	ipAddress = static_cast<EditTextWidget *>(this->FindWidget("ipAddress"));
}
 
void MainWindow::HandleWidgetEvent(Widget *widget, const WidgetEventData *eventData)
{
	if(widget == hostGame)
	{
	}
	else if(widget == joinGame)
	{
	}
	else if(widget == spawnBox)
	{
	}
	else if(widget == spinLeft)
	{
	}
	else if(widget == spinRight)
	{
	}
	else if(widget == quitGame)
	{
	}
}
 
void MainWindow::Open()
{
	if(TheMainWindow)
	{
		TheInterfaceMgr->SetActiveWindow(TheMainWindow);
	}
	else
	{
		TheInterfaceMgr->AddWidget(new MainWindow);
	}
}

Open the Window

Alright, now that we have an interface and can start interacting with the game application, lets write a few basic functions for the interface to use. Add the following to your Game class declaration

"EXGame.h"

class Game : public Application, public Singleton<Game>
{
public:
	...
	void HostGame();
	void JoinGame(String<> ipAddress);
	void QuitGame();
	...
};

You'll notice that JoinGame takes one parameter, and that is a string ipAddress. Take a guess where we will be getting that information from while you code the source of these functions into the Game's definition file.

"EXGame.cpp"

void Game::HostGame()
{
	TheMessageMgr->BeginMultiplayerGame(true);
	TheEngine->Report(String<>("Initialized. Hosting on: ") + MessageMgr::AddressToString(TheNetworkMgr->GetLocalAddress(), true));
}
 
void Game::JoinGame(String<> ipAddress)
{
}
 
void Game::QuitGame()
{
	TheMessageMgr->DisconnectAll();
	TheEngine->Quit();
}

Now that you have your functions set up, lets make our second to last edit to the interface files and be done with them for a while. We just need to bind the buttons to these three functions.

Find "HandleWidgetEvent" and fill in the blanks

"EXInterface.cpp"

void MainWindow::HandleWidgetEvent(Widget *widget, const WidgetEventData *eventData)
{
	if(widget == hostGame)
	{
		TheGame->HostGame();
		this->Close();
	}
	else if(widget == joinGame)
	{
		TheGame->JoinGame(ipAddress->GetText());
		this->Close();
	}
	else if(widget == spawnBox)
	{
		this->Close();
	}
	else if(widget == spinLeft)
	{
		this->Close();
	}
	else if(widget == spinRight)
	{
		this->Close();
	}
	else if(widget == quitGame)
	{
		TheGame->QuitGame();
	}
}

Some extra code if you want to be able to open the window when it closes, you'll want to write this down into your Game class declaration and definition files.

"EXGame.h"

#include <C4Input.h>
#include "EXInterface.h"
 
class Game : public Application, public Singleton<Game>
{
public:
	...
	static void EscapeProc(void *data);
private:
	InputMgr::KeyProc	*prevEscapeProc;
	void			*prevEscapeData;
};


"EXGame.cpp"

Game::Game() : Singleton<Game>(TheGame)
{
	prevEscapeProc = TheInputMgr->GetEscapeProc();
	prevEscapeData = TheInputMgr->GetEscapeData();
	TheInputMgr->SetEscapeProc(&EscapeProc, this);
 
	TheInterfaceMgr->SetInputManagementMode(C4::kInputManagementAutomatic);
 
	MainWindow::Open();
}
 
Game::~Game()
{
	if(TheMainWindow) delete TheMainWindow;
 
	TheInputMgr->SetEscapeProc(prevEscapeProc, prevEscapeData);
}
 
void Game::EscapeProc(void *data)
{
	MainWindow::Open();
}

Alright, so your interface is properly bound to code, you have functionality to start hosting. Now its time to make a world file and check to make sure everything is running smoothly so far.

I Can See!

So I created something simple for the world file, its nothing but a skybox, a small box in the center (you can use a plane if you want), and a light source. Don't worry about a spawn locator, or anything like that. That will be handled by the "random" spawn location code.

Now I named this world file "mult" and put it in a folder called "world" under my root directory "Example". You can place yours anywhere you want, just keep note where it is. We are going to hard code the world file load.

The next thing we need in order to get set up and started is to be able to see anything in the world. We will achieve this in our load function, don't worry about subclassing world and making your own camera. We aren't doing any camera tricks in this tutorial.

Modify your Game's declaration file with the following.

"EXGame.h"

#include <C4World.h>
 
class Game : public Application, public Singleton<Game>
{
public:
	…
	EngineResult LoadWorld(const char *name);
	void UnloadWorld();};

We will need to fill in the code in these two functions, and call them from the appropriate places.

"EXGame.cpp"

Game::~Game()
{
	this->UnloadWorld();}
 
EngineResult Game::LoadWorld(const char *name)
{
	WorldResult result = TheWorldMgr->LoadWorld(name);
 
	if(result == C4::kWorldOkay)
	{
		World *world = TheWorldMgr->GetWorld();
		if(world)
		{
			FrustumCamera *camera = new FrustumCamera(2.0F, 1.0F);
 
			camera->SetNodePosition(Point3D(20.0F, 20.0F, 10.0F));
			camera->LookAtPoint(Point3D(0.0F, 0.0F, 0.0F));
			camera->Invalidate();
 
			world->SetCamera(camera);
		}
 
		TheEngine->Report("Game has begun!");
	}
 
	return result;
}
 
void Game::UnloadWorld()
{
	TheWorldMgr->UnloadWorld();
	TheMessageMgr->EndGame();
}
 
void Game::HostGame()
{
	…
	TheGame->LoadWorld("world/mult");
}

Now compile your project and hit that "host" button. We can see!!!

Filling the World

Now that the world has been generated we need something to fill it with. This section will show you how to spawn a box through a multiplayer Spawn Message.

Boxes, Boxes, and More Boxes

So you can see in the world, thats fun, but its not all that great for a game! We need to be able to create stuff in the world. The easiest thing for us to spawn would be to spawn a model - this can easily be interchanged with any model you want, but I am going to use a 1x1x1 wooden box for this tutorial.

I am a fan of boxes, they are easy to make, easy to view, and easy to test. To test our box, there is a simple controller class that has already been written for single player purposes. I reworked it a little bit, and named it "BoxController".

"EXController.h"

#pragma once
 
#include <C4Controller.h>
 
using namespace C4;
 
namespace C4
{
	enum
	{
		kModelWoodBox = 'wbox',
	};
 
	enum
	{
		kControllerBox = 'cbox'
	};
 
	class BoxController : public Controller
	{
	public:
		BoxController();
		BoxController(float rate);
		~BoxController();
 
		float GetSpinRate() const
		{
			return (spinRate);
		}
 
		void SetSpinRate(float rate)
		{
			spinRate = rate;
		}
 
		float GetCurrentAngle() const
		{
			return (currentAngle);
		}
 
		void SetCurrentAngle(float angle)
		{
			currentAngle = angle;
		}
 
		void Preprocess(void);
		void Move(void);
	private:
		float	spinRate;
		float	currentAngle;
 
		BoxController(const BoxController& boxController);
		Controller *Replicate(void) const;
 
		void Destroy();
	};
}

"EXController.cpp"

#include "EXController.h"
#include <C4Node.h>
#include <C4World.h>
 
BoxController::BoxController() : Controller(C4::kControllerBox)
{
	spinRate = 0.0F;
	currentAngle = 0.0F;
}
 
BoxController::~BoxController()
{
}
 
BoxController::BoxController(const BoxController& boxController) : Controller(boxController)
{
	spinRate = boxController.spinRate;
}
 
Controller *BoxController::Replicate(void) const
{
    return (new BoxController(*this));
}
 
void BoxController::Preprocess(void)
{
    Controller::Preprocess();
}
 
void BoxController::Move(void)
{
	Node *node = this->GetTargetNode();
	if(node)
	{
		Zone *zone = node->GetOwningZone();
 
		Transform4D trans = node->GetNodeTransform();
 
		currentAngle += ((spinRate / 1000.0F) * TheTimeMgr->GetFloatDeltaTime());
		trans.SetRotationAboutZ(currentAngle);
		trans.SetTranslation(node->GetNodePosition());
 
		node->SetNodeTransform(zone->GetInverseWorldTransform() * trans);
		node->Invalidate();
	}
}
 
void BoxController::Destroy()
{
	Node *node = this->GetTargetNode();
	if(node)
	{
		delete node;
	}
}

Before we can begin using the controller there is a need for us to have a model attached to it. I used a simple 1x1x1 box, that looks like this:

Now we need to be able to use it, so lets register it with the engine, and do all the prep work that will allow us to spawn the box.

"EXGame.h"

#include "EXController.h"
 
class Game : public Application, public Singleton<Game>
{
public:private:
	…
	ModelRegistration	boxModelReg;
};

"EXGame.cpp"

Game::Game() : Singleton<Game>(TheGame),
		boxModelReg(C4::kModelWoodBox, nullptr, "model/wood_box", C4::kModelPrecache, C4::kControllerBox)
{}

Starting up some networking

Up to this point, we've been dealing with purely single player concepts, these things should be nothing new to you. If they are, then I hope you learned something new in setting up a clean project. The next couple of steps are going to be very familiar if you have finished Basic_Network_Tutorial. We are going to set up a couple of simple messages to send to the server and back to the client letting each other know that a spawn is requested, and a how a spawn should be set up.

Before we can set up the client to receive messages, we need to know what messages are going to be sent. So here are 2 sample messages we will use during this tutorial, one is a Request for a spawn and the other contains Spawn information. The request is sent from client to server, and the spawn message is sent from server to client.

"EXMultiplayer.h"

#pragma once
 
#include <C4Messages.h>
 
using namespace C4;
 
namespace C4
{
	const unsigned long kGameProtocol = 0x00000012;
	const unsigned short kGamePort = 3003;
 
	enum MESSAGES
	{
		kMessageServerInfo = kMessageBaseCount,
		kMessageSpawn,
		kMessageRequest
	};
 
	class RequestMessage : public Message
	{
	public:
		RequestMessage();
		~RequestMessage();
 
		void Compress(Compressor &data) const;
		bool Decompress(Decompressor &data);
	};
 
	class SpawnMessage : public Message
	{
	public:
		SpawnMessage();
		SpawnMessage(PlayerKey playerkey, long controllerIndex, Point3D location);
		~SpawnMessage();
 
		void Compress(Compressor &data) const;
		bool Decompress(Decompressor &data);
 
		Point3D GetLocation() const
		{
			return loc;
		}
 
		PlayerKey GetPlayerKey() const
		{
			return key;
		}
 
		long GetControllerIndex() const
		{
			return contIndex;
		}
	private:
		Point3D loc;
		long contIndex;
		PlayerKey key;
	};
}

"EXMultiplayer.cpp"

#include "EXMultiplayer.h"
 
RequestMessage::RequestMessage() : Message(C4::kMessageRequest)
{
}
 
RequestMessage::~RequestMessage() 
{
}
 
void RequestMessage::Compress(Compressor &data) const
{
}
 
bool RequestMessage::Decompress(Decompressor &data)
{
	return true;
}
 
SpawnMessage::SpawnMessage() : Message(C4::kMessageSpawn)
{
	this->contIndex = 0;
	this->key = -1;
}
 
SpawnMessage::SpawnMessage(PlayerKey playerkey, long controllerIndex, Point3D location) : Message(C4::kMessageSpawn)
{
	this->contIndex = controllerIndex;
	this->key = playerkey;
	this->loc = location;
}
 
SpawnMessage::~SpawnMessage()
{
}
 
void SpawnMessage::Compress(Compressor &data) const
{
	data << loc;
	data << contIndex;
	data << key;
}
 
bool SpawnMessage::Decompress(Decompressor &data)
{
	data >> loc;
	data >> contIndex;
	data >> key;
	return (true);
}

Hopefully you will notice that the spawn message contains 3 parameters in it's message. The server has to notify each user of the controller index and location where the box will spawn. I have it also send the player key, because I eventually will be storing the controller in a custom player object, for easier access later on.

We have the 2 basic messages we need, now lets jump back over to the Game's definition file and add some handling for the messages.

"EXGame.h"

#include "EXMultiplayer.h"
 
class Game : public Application, public Singleton<Game>
{
public:
	...
	Message *ConstructMessage(MessageType type, Decompressor &data) const;
	void ReceiveMessage(Player *sender, const NetworkAddress &address, const Message *message);
	void HandlePlayerEvent(PlayerEvent event, Player *player, const void *param);
	void HandleConnectionEvent(ConnectionEvent event, const NetworkAddress& address, const void *param);
	...
};

"EXGame.cpp"

void Game::ReceiveMessage(Player *sender, const NetworkAddress &address, const Message *message)
{
	switch(message->GetMessageType())
	{
		case C4::kMessageSpawn: break;
		case C4::kMessageRequest: 
		{
			break;
		}
	}
}
 
Message *Game::ConstructMessage(MessageType type, Decompressor &data) const
{
	switch(type)
	{
		case C4::kMessageSpawn: return (new SpawnMessage);
		case C4::kMessageRequest: return (new RequestMessage);
	}
 
	return nullptr;
}
 
void Game::HandlePlayerEvent(PlayerEvent event, Player *player, const void *param)
{
	switch(event)
	{
		case C4::kPlayerInitialized:
		{
			TheEngine->Report("Player initialized.");
 
			break;
		}
 
		case C4::kPlayerDisconnected:
		{
			break;
		}
	}
 
	Application::HandlePlayerEvent(event, player, param);
}
 
void Game::HandleConnectionEvent(ConnectionEvent event, const NetworkAddress& address, const void *param)
{
	switch(event)
	{
		case C4::kConnectionClientOpened:
		{
			Engine::Report("Client Connected.");
			break;
		}
		case C4::kConnectionClientClosed:
		case C4::kConnectionClientTimedOut:
		{
			Engine::Report("Client Connection Closed.");
			break;
		}
		case C4::kConnectionServerAccepted:
		{
			Engine::Report("We are connected.");
			TheGame->LoadWorld("world/mult");
			break;
		}
		case C4::kConnectionServerClosed:
		case C4::kConnectionServerTimedOut:
		{
			Engine::Report("Server Connection Closed.");
			UnloadWorld();
			break;
		}
	}
 
	Application::HandleConnectionEvent(event, address, param);
}

Your First Messages

Alright, now that we have the network set up properly we can start sending messages and get that box onto the screen. Fun! The first message we'll want to send in the RequestMessage, notifying the server that the client is ready to spawn a box. So go into your MainWindow's interface class and add the following code to HandleWidgetEvent.

"EXInterface.cpp"

void MainWindow::HandleWidgetEvent(Widget *widget, const WidgetEventData *eventData)
{else if(widget == spawnBox)
	{
		TheMessageMgr->SendMessageAll(RequestMessage());
		this->Close();
	}}

Yes, we are sending the message to all. That just means that if you are a server, the request message gets send to everyone, and if you are a client it only gets sent to the server. We can, if we wanted to, explicitly say TheMessageMgr->SendMessage(C4::kPlayerServer, RequestMessage());.

The server will now receive requests from the players, you can go ahead and test this by adding an Engine::Report("Receiving message."); in your Game::ReceiveMessage member, under the Request case.

Continuing on, we are now going to interpret the Request and make sure that only the server interprets it, that clients will ignore it. You don't have to ensure this if you decided to go with the explicit SendMessage(kPlayerServer, RequestMessage()) code.

"EXGame.cpp"

void Game::ReceiveMessage(Player *sender, const NetworkAddress &address, const Message *message)
{
	switch(message->GetMessageType())
	{
		case C4::kMessageSpawn: break;
		case C4::kMessageRequest: 
		{
			if(TheMessageMgr->Server())
			{
				Point3D loc;
				loc.x = -10.0F + Math::Random(20.0F);
				loc.y = -10.0F + Math::Random(20.0F);
				loc.z = 1.0F;
 
				long contIndex = TheWorldMgr->GetWorld()->NewControllerIndex();
				TheMessageMgr->SendMessageAll(SpawnMessage(sender->GetPlayerKey(), contIndex, loc));
			}
			break;
		}
	}
}

This will take the sender (the player making the request), get their playerKey and send it along with a brand new controller index and location for everyone to use as spawn data. Each player will take the controller index at this point and build a new controller with it. Then they will attach that controller to the appropriate player object, which we will override with a new player object soon. After that, it will create the actual box, assign the controller to the box and place it in the world for everyone to see.

The controller index here is key, it is what allows us to send specific messages to a single controller across multiple machines.

Now that the Spawn Message is being sent, we have to receive it on our client machines. Go to where you have your SpawnMessage defined. We are going to add a member to it, and handle the spawn information.

"EXMultiplayer.h"

class SpawnMessage : public Message
{
public:bool HandleMessage(Player *sender) const;};

"EXMultiplayer.cpp"

#include "EXGame.h"
 
bool SpawnMessage::HandleMessage(Player *sender) const
{
	Player *player = TheMessageMgr->GetPlayer(this->GetPlayerKey());
	TheGame->SpawnBox(player, this->GetLocation(), this->GetControllerIndex());
	return (true);
}

Now before you can compile this code you need to add the function for SpawnBox into your Game construct. So head on over, and add this following public member. Make sure you didn't forget to include "EXGame.h" at the top of your EXMultiplayer.cpp definitions file, or else HandleMessage won't be able to access the TheGame construct.

"EXGame.h"

class Game : public Application, public Singleton<Game>
{
public:
	...
	void SpawnBox(Player *player, Point3D location, long controllerIndex);};

"EXGame.cpp"

void Game::SpawnBox(Player *player, Point3D location, long controllerIndex)
{
	World *world = TheWorldMgr->GetWorld();
	if(world)
	{
	}
}

Now you should be able to compile and run. Test your RequestMessage out, place a few Engine::Report messages in ReceiveMessage, SpawnBox, HandleMessage.


Spawning the Box

Before we can get to the new topic of controller messages, we need to be able to easily find each controller and bind them to a player. We do this by overriding the Player class. Go into your Multiplayer declaration file and add the following construct.

"EXMultiplayer.h"

#include "EXController.h"
 
class GamePlayer : public Player
{
public:
	GamePlayer(PlayerKey key);
	~GamePlayer();
 
	BoxController *GetController() const
	{
		return cont;
	}
		void SetController(BoxController *controller)
	{
		cont = controller;
	}
private:
	BoxController *cont;
};

"EXMultiplayer.cpp"

GamePlayer::GamePlayer(PlayerKey key) : Player(key)
{
	cont = nullptr;
}
 
GamePlayer::~GamePlayer()
{
}

Real easy. Now we need to set up the player constructor so that this derived object is used, this works exactly the same way as you have done in the past with the World object. So go to your application's definition add a function to construct a player object, and define it. Then set the player constructing function with the message manager.

"EXGame.h"

class Game : public Application, public Singleton<Game>
{
public:static Player *ConstructPlayer(PlayerKey key, void *data);}

"EXGame.cpp"

Game::Game() : Singleton<Game>(TheGame),
		boxModelReg(C4::kModelWoodBox, nullptr, "model/wood_box", C4::kModelPrecache, C4::kControllerBox)
{
	…
	TheMessageMgr->SetPlayerConstructor(&ConstructPlayer, this);}
 
Player *Game::ConstructPlayer(PlayerKey key, void *data)
{
	return (new GamePlayer(key));
}

Now that all players will able to store their controller object, as well as all other's we need to set code up that will actually do the binding. I did this binding in the SpawnBox function. For future reference, not every client needs to keep track of every player's controller. Only the server may need to, there are other implementations too.

Go to the SpawnBox function an add the following code.

"EXGame.cpp"

void Game::SpawnBox(Player *player, Point3D location, long controllerIndex)
{
	World *world = TheWorldMgr->GetWorld();
	if(world)
	{
		GamePlayer *gPlayer = static_cast<GamePlayer *>(player);
		BoxController *cont = gPlayer->GetController();
		if(!cont)
		{
			cont = new BoxController();
			gPlayer->SetController(cont);
			cont->SetControllerIndex(controllerIndex);
 
			Model *box = Model::Get(C4::kModelWoodBox);
			box->SetController(cont);
 
			Zone *zone = world->FindZone(location);
			box->SetNodePosition(zone->GetInverseWorldTransform() * location);
			zone->AddNewSubnode(box);
		}
	}
}

You can now hit compile, run, and hit "RequestSpawn". You should see a box appear on the screen in a random location based on the random location spawn code. Yay!

Controller Messaging

This section contains information about sending controller messages across the network.

Something New

Are you ready to deal with controller messages? I sure am, this long winded tutorial has only led us through what we already know. It is finally time that we start coding the controller messages! So how do controller messages work? The Message Manager sends controller messages out to all clients connected, with a controller index attached to it. When the client receives this message, it sends it automatically to the controller in question to handle. This way, controller specific code can remain in the controller, even in multiplayer code.

To begin, we will need a couple of new message types, one is a SpinMessage, sent by a single client when he/she wants their box to spin. The other is a state message, which is sent by the server to all clients containing the rate of rotation and the current angle. All clients will receive this state message and will sync their local boxes with these messages.

So head on over to your multiplayer message enumeration and add a couple of new message types, and declare the new SpinMessage for the game to use.

"EXMultiplayer.h"

enum MESSAGES
{
	kMessageServerInfo = kMessageBaseCount,
	kMessageSpawn,
	kMessageRequest,
	kMessageSpin,
	kMessageBoxState
};
 
class SpinMessage : public Message
{
public:
	SpinMessage();
	SpinMessage(float rate);
	~SpinMessage();
 
	void Compress(Compressor &data) const;
	bool Decompress(Decompressor &data);
 
	float GetRate() const
	{
		return rate;
	}
private:
	float rate;
};


"EXMultiplayer.cpp"

SpinMessage::SpinMessage() : Message(C4::kMessageSpin)
{
	rate = 0.0F;
}
 
SpinMessage::SpinMessage(float r) : Message(C4::kMessageSpin)
{
	rate = r;
}
 
SpinMessage::~SpinMessage()
{
}
 
void SpinMessage::Compress(Compressor &data) const
{
	data << rate;
}
 
bool SpinMessage::Decompress(Decompressor &data)
{
	data >> rate;
	return (true);
}

This spin message is nothing new, it only contains the rate at which we want to modify the spin rate of the controller. For your code, you can make this the absolute spin rate, or a change in rate like I did.

Don't forget to add the types to your application's ConstructMessage.

"EXGame.cpp"

 

Final Notes

The Full Source

Here is a link to the full source if you don't like reading tutorials, it is complete and fully functional. Run it across two machines, not two instances on the same machine.


Networked_Custom_Controllers:Full_Source

How Do You Get to Carnegie Hall?

Practice, practice, practice.

You should now be able to use controller message effectively and safely. Things you may want to try out:

Beginner

-Use actions instead of the interface to spawn and rotate the objects

-Add more state handling to allow the user to control the box's position

Intermediate

-Allow the user to click on anyone's box to "hit" it and cause it to spin the direction his "hit" is facing. Almost as if you shot it with something.

Advanced

-Change out the standard controller with a rigid body controller, using more state handling to manage the rigid body's position and speed.

--Codenewt 18:06, 19 June 2011 (UTC)

Personal tools