Jump to content

  • Log In with Google      Sign In   
  • Create Account

Awesome job so far everyone! Please give us your feedback on how our article efforts are going. We still need more finished articles for our May contest theme: Remake the Classics

rpiller

Member Since 26 Apr 2009
Offline Last Active May 19 2013 05:49 PM
-----

#5022501 Network message solution for client/server architecture

Posted by rpiller on 17 January 2013 - 06:48 AM

I use RakNet with BitStream's as well, and I fire "events" with messages, but I just go raw BitStream & Packet handling instead of wrapping them up in a formal message object. There are so many messages in a game that making an object for each one I just felt wasn't worth it. I'd rather just have a function be mapped to a message and then read the BitStream directly in there. The Event.h file I use is below. I do this so 1 message gets mapped to 1 function and I don't have to worry about messing with some switch control. Instead it's always just 1 mapping line of code. 

 

Events are stored like:

 

 

map<MessageID, Event2<Packet&, BitStream&>> _events;

 

 

I have 1 area that captures packets coming in from the RakNet loop to dispatch to member functions that are registered to the message:

 

 

 

for(_packet = _peer->Receive(); _packet; _peer->DeallocatePacket(_packet), _packet = _peer->Receive())
{
RakNet::MessageID msg = _packet->data[0];


// make sure we mapped to the event
if(_events.find(msg) != _events.end())
{
// create bitstream and ignore the msg
RakNet::BitStream reader(_packet->data, _packet->length, false);
reader.IgnoreBytes(sizeof(RakNet::MessageID));


// raise the event
_events[msg].Raise((*_packet), reader);
}
}

 

And I register the functions like:

 

 

_events[(MessageID)ID_NEW_INCOMING_CONNECTION].Bind(this, &Server::OnNewIncomingConnection);

 

The function using the data would be defined as:

 

 

 

void Server::OnNewIncomingConnection(Packet& p, BitStream& reader)
{
    int age;


   reader.Read(age);


   _peer->Send(..., p.address);
}

 

To me the BitStream is basically the "message" object. It just isn't wrapped in a type. The plus is it doesn't get the overhead wrapping hit that a type does, if the message changes you don't have to go 2 places for that change (the message object, then in here to use new field), & you won't have potentially hundreds of little classes laying around (although if you have multiple people on a team this might help separate duties). The only negative is you don't get a pretty type that defines all the fields that the object has. You have to do that yourself. I'm not sure of the speed hit that would occur with the message object, but it'll be "slowER" than this, but it might not matter.

 

 

 

Event.h. This has EventX objects where X is 0 - 3 which is the number of parameters to the class function.

#pragma once
#include <list>


using namespace std;




class TFunctor0
{
public:
virtual void Call()=0;
};


template <class TClass>
class TSpecificFunctor0 : public TFunctor0
{
private:
void (TClass::*fpt)();
TClass* pt2Object;         
public:
TSpecificFunctor0(TClass* _pt2Object, void(TClass::*_fpt)())
{ 
pt2Object = _pt2Object;  
fpt=_fpt; 
}


virtual void Call()
{ (*pt2Object.*fpt)(); }
};


class Event0
{
public:
list<TFunctor0*>  mCaller;


template<class Target>
void Bind(Target* t, void (Target::*fnPtr)())
{ 
mCaller.push_back(new TSpecificFunctor0<Target>(t,fnPtr));
}


void Clear()
{ mCaller.clear(); }
       
void Raise()
{
list<TFunctor0*>::reverse_iterator iter;


for (iter = mCaller.rbegin(); iter!= mCaller.rend(); iter++)
{
(*iter)->Call();
}
}
};


//===============================================================================


template<class P>
class TFunctor1
{
public:
virtual void Call(P var1)=0;
};


template <class TClass, class param1>
class TSpecificFunctor1 : public TFunctor1<param1>
{
private:
void (TClass::*fpt)(param1);
TClass* pt2Object;         
public:
TSpecificFunctor1(TClass* _pt2Object, void(TClass::*_fpt)(param1))
{ pt2Object = _pt2Object;  fpt=_fpt; }


virtual void Call(param1 var1)
{ (*pt2Object.*fpt)(var1); }
};


template<class T1>
class Event1
{
public:
list<TFunctor1<T1>* >  mCaller;


template<class Target>
void Bind(Target* t, void (Target::*fnPtr)(T1))
{ mCaller.push_back(new TSpecificFunctor1<Target, T1>(t,fnPtr)); }
       
void Raise(T1 V1)
{
list<TFunctor1<T1>*>::reverse_iterator iter;




for (iter = mCaller.rbegin(); iter!= mCaller.rend(); iter++)
{
(*iter)->Call(V1);
}
}


void Clear()
{
mCaller.clear();
}
};


//===============================================================================


template<class P, class Q>
class TFunctor2
{
public:
virtual void Call(P var1, Q var2)=0;
};


template <class TClass, class param1, class param2>
class TSpecificFunctor2 : public TFunctor2<param1, param2>
{
private:
void (TClass::*fpt)(param1, param2);
TClass* pt2Object;         
public:
TSpecificFunctor2(TClass* _pt2Object, void(TClass::*_fpt)(param1, param2))
{ pt2Object = _pt2Object;  fpt=_fpt; }


virtual void Call(param1 var1, param2 var2)
{ (*pt2Object.*fpt)(var1, var2); }
};


template<class T1, class T2>
class Event2
{
public:
list<TFunctor2<T1, T2>* >  mCaller;


template<class Target>
Event2(Target* t, void (Target::*fnPtr)(T1, T2))
{ mCaller.push_back(new TSpecificFunctor2<Target, T1, T2>(t,fnPtr)); }


Event2(){}


template<class Target>
void Bind(Target* t, void (Target::*fnPtr)(T1, T2))
{ mCaller.push_back(new TSpecificFunctor2<Target, T1, T2>(t,fnPtr)); }
       
void Raise(T1 V1, T2 V2)
{
list<TFunctor2<T1, T2>*>::reverse_iterator iter;




for (iter = mCaller.rbegin(); iter!= mCaller.rend(); iter++)
{
(*iter)->Call(V1, V2);
}
}


void Clear()
{
mCaller.clear();
}
};




//===============================================================================


template<class P, class Q, class Y>
class TFunctor3
{
public:
virtual void Call(P var1, Q var2, Y var3)=0;
};


template <class TClass, class param1, class param2, class param3>
class TSpecificFunctor3 : public TFunctor3<param1, param2, param3>
{
private:
void (TClass::*fpt)(param1, param2, param3);
TClass* pt2Object;         
public:
TSpecificFunctor3(TClass* _pt2Object, void(TClass::*_fpt)(param1, param2, param3))
{ pt2Object = _pt2Object;  fpt=_fpt; }


virtual void Call(param1 var1, param2 var2, param3 var3)
{ (*pt2Object.*fpt)(var1, var2, var3); }
};


template<class T1, class T2, class T3>
class Event3
{
public:
list<TFunctor3<T1, T2, T3>* >  mCaller;


template<class Target>
void Bind(Target* t, void (Target::*fnPtr)(T1, T2, T3))
{ mCaller.push_back(new TSpecificFunctor3<Target, T1, T2, T3>(t,fnPtr)); }
       
void Raise(T1 V1, T2 V2, T3 V3)
{
list<TFunctor3<T1, T2, T3>*>::reverse_iterator iter;




for (iter = mCaller.rbegin(); iter!= mCaller.rend(); iter++)
{
(*iter)->Call(V1, V2, V3);
}
}


void Clear()
{
mCaller.clear();
}
};



#5019585 Removing NPC economy.

Posted by rpiller on 09 January 2013 - 01:37 PM

Buying and selling goods takes time, is monotonous, and requires having goods worth buying and selling. It's why player economies in games are all focused around the "better items" and not rat fur #6billionand4

 

If it took 10k rat fur to make the better item, then they would be all about rat fur. That doesn't devalue rat fur though, it keeps it's value depending on how many rats are in the world and how long it takes to "harvest" the fur and what it's used to make. It's all about supply and demand. The problem most MMO's have is they make the demand for certain resources obsolete the higher level you go. If they didn't do that and combine that with pulling currency out of thin air (the more you kill the more money you make from out of nowhere) and things get out of hand fast on these items.

 

If you need a light feather to craft the Epic Sword of Kickass and the higher level you are the more money you make from killing things, then the light feather will cost lots more because people know you need it to craft that sweet sword AND that you have the money to spend, so the price is much higher (and out of the reach of beginners), than if the money situation wasn't so easy to come by. If it was close to a zero sum game with rare injections of currency then money wouldn't be so meaningless and prices wouldn't go all crazy. Enter the real world. Money gets passed around and stays in circulation, with "rare" injections from the government. 




#5018246 Easy C++ BCrypt library

Posted by rpiller on 06 January 2013 - 11:39 AM

I'm using raknet and it has like TLS security, but isn't exactly TLS. I'm not going to change from using raknet, so I figure this will help some in the case raknet can be broken. Odds are my game won't be popular enough for anyone to care but adding this is est to take about a couple hours if that, so not long at all.

Also doing this do I really need TLS level of security? If bcrypt is good enough for if the db gets hacked shouldn't it be good enough for sending the bcrypted pw over the wire? It would take just as long to brute force if someone got it. If I implement some sort of expired pw system it wouldn't be hacked before the pw needs changing.

I'm not obsessed, I'm paranoid about storing passwords as its a big responsibility and so just looking at every angle.

Thanks for the help guys.


#5017479 Good habits for game development

Posted by rpiller on 04 January 2013 - 01:40 PM

@jbadams

 

I stand by the small section of my comment you decided to quote. IF one wants to go the global route, use a singleton because it offers more benefits than a pure global. You may not think those benefits are worth anything but I do. Benefits in refactoring at a later date to actually be a normal object, better organization & structure, encapsulation, etc. A singleton is clearly better than a global because it's wrapped up in a neat little class and you get the benefits that classes get. Because that allows for more flexibility and organization than a loose global variable. Yes, they have the same scope, but one adds more benefits than the other, does it not?

 

I don't get belittling the benefits of what a class offers and clearly " just because it's wrapped up in a fancy class" is belittling what a class offers in it's tone.




#5017110 Good habits for game development

Posted by rpiller on 03 January 2013 - 08:33 AM

I think an object oriented language is the way to go for organization and maintenance (not speed though but it's fast enough for 99% of the code).

 

- Encapsulate as much as you can. I end up re-factoring my code all the time. To think you'll get it right the first time every time, or that you won't change your mind is silly, and the more you have things encapsulated the easier it'll be to re-factor things around without breaking the things that rely on the code. It also allows for experimenting with new ideas without breaking existing code. Even if you think something is small, encapsulate it because chances are you'll come back and make it more complex eventually and doing this can really make your life easier later.

 

- Make self containing code as much as possible. The more your code relies on each other the harder it'll be to make changes without breaking a bunch of stuff. This again allows for easier changes that don't break as much code if any. I like using event programming for this so that my objects fire events and other objects can subscribe to these events if they want. This way my objects aren't tightly coupled together (ie the HUD object doesn't need to directly know about the Player object), and don't rely on polling systems which waste time.

 

- Always make sure your code compiles each day that you are done with it. Don't leave compile errors overnight. Logical errors are OK to leave, but be sure to note them.

 

- I'll contradict Servant's comment and say, never use goto, global's, or macro's. In my 15 years of programming I've never used goto outside of first learning it. It's simply not needed. Globals took longer for me to realize their danger but eventually I have seen the harm they cause and haven't used one myself (libraries you use might) for probably the last 8 years. Stick to a singleton if you need something like a global. Macro's start out making your code easier and then you start abusing them and they actually end up making your code harder to read & maintain. 

 

- The biggest thing I think is to make your functions small! They should be visible on about 1 "screen". Any bigger than that and it's most likely doing too much and should be split. It will also be harder to maintain than a small specific function. This is important, as it's all to common to see new programmers putting so many things into giant functions. Don't do it! This is worth repeating. Practice doing this until it's second nature. This is a big one.




#4994433 [Solved]Tower which enemy to shoot at?

Posted by rpiller on 27 October 2012 - 09:27 AM

Not sure what you did to solve this, but what I would do is have the ShootAt function only called at the time of shooting and the details of what it should shoot at stored in a bullet class. Then just loop over all bullets and call an update function where each one is heading towards its stored off target.


#4942964 Behavior Trees

Posted by rpiller on 24 May 2012 - 11:57 AM

Is there any generally accepted method for this?

I'm very new with Behavior Trees, but I was playing around with the idea of these tasks & actions being functions inside the Gameplay state class which would hold all your game data anyway. Each node would be an event which points to a function you create in your Gameplay state class instead of being their own classes in themselves. When learning about these I find it very odd that actions or tasks are their entirely own class because it seems really all it needs to be is a function. Then all the nodes in your tree just point to the function to run instead of creating an entire object. You'd still need a tree hierarchy setup but they could only store variables of their children, and basically a pointer to the function to run isntead of definding their own Update() methods. I'll try to get this in practice soon, but it just seemed odd that tasks and actions where their entire class. Seems by doing that you have to break/bend some "guidelines" with programming to get them to see the data they need.

I'm a big OOP guy so I can see breaking them into classes helps separate the creation of these easier then if they were all in the same class, but it still just seems odd the tasks/actions are entire classes themselves.


#4917128 TCP - Keeping clients synchronized

Posted by rpiller on 27 February 2012 - 02:01 PM

This timer will start at 60 seconds and countdown to 0 and at 0 some game state change will occur, so it is important that all clients get to 0 at a similar time.



Start the timer on the server
Send start timer command to clients to do locally
When the server timer hits send your state change to the clients
The clients will be lagged behind by ms most likely but by the sounds that won't be a big deal

You see this often in games like World of Warcraft when crafting or gathering materials that run a progress bar. With high lag the progress bar is complete but the window doesn't come up yet. I can only imagine it's because my local timer for my progress bar completed (no lag locally) but the command from the server to open the loot window and show me my loot is taking longer to get to me. It doesn't screw things up just looks funny in high lag spikes. Then again most things do look funny in high lag spikes. The lowest lag person will always have an advantage in any game because they'll see data first. Most of the time this will be in MS so it won't be much of an advantage is slow paced games like your game sounds. If it was like 3 seconds it could mean a big difference however, but if someone has a 3 second lag that's pretty significant and shouldn't be playing online games anyway. :)


#4875148 Where do I do pathfinding?

Posted by rpiller on 21 October 2011 - 01:52 PM

I'm confused because it sounds like we are saying the same thing. Have the server do validations/sanity checks.


#4875103 Where do I do pathfinding?

Posted by rpiller on 21 October 2011 - 12:14 PM

This would depend on your game. If it's a board game like chess you want the server validate or else the client could hack it to bypass the client side rule checks. Let's say they make their king move 6 spaces in a zigzag pattern because they were able to bypass the client side rules check. Locally they can do that all they want, but the server would validate that move and realize it's not valid and not update the server board or the opponents client board. So that cheating client is only cheating themselves because now they are out of sync with the server and other client.

Ideally the server would send back to the cheating client a 'no no' msg and move the piece back but chances are if they hacked their client they'll intercept that 'no no' msg and not let it get to the application.

You will want the server to do some kind of validation. It could just be sanity checks like making sure the King in chess only moves 1 tile.


PARTNERS