So this next portion will be pertaining to the Data transfer object, or DTO. The reason we are creating such an object is to make it easier to save our character's data. Many of you may have run into trouble when you tried to add data to the AegisBornCharacter class and have the server complain that not everything was declared as virtual public/protected. The reason is because Hibernate creates a class based on our class and if things are marked public it expects to be able to use them to store/retrieve from the database. The easiest way around this is to create a DTO and add 2 functions to the class to create a DTO and get all its data from the DTO. So first lets introduce this DTO object.
First, if you pull down my project you will notice that I created a couple folders under Models, Persistence and under it is Map. I moved the map classes under map and i moved SfGuardUser and AegisBornUserProfile under Persistence. Next I created the new DTO object:
AegisBornCharacterDTO
namespace AegisBorn.Models.Persistence
{
public class AegisBornCharacterDTO
{
public virtual int Id { get; set; }
public virtual String Name { get; set; }
public virtual int Level { get; set; }
public virtual String Class { get; set; }
public virtual String Sex { get; set; }
public virtual float X { get; set; }
public virtual float Y { get; set; }
public virtual float Z { get; set; }
public virtual String BaseStats { get; set; }
public virtual SfGuardUser UserId { get; set; }
}
}
The first thing you will notice is that all the variables are public virtual, just like hibernate wants them. Next you will notice that BaseStats is a string. The reason was covered in the last few tutorials, we will be storing the Base Stats as XML in order to keep them generic and dynamic.
The next thing to introduce is how we get our base stats onto our character, but first i have to show the new classes created to help with inheritance:
IAegisBornObject.cs
namespace AegisBorn.Models.Base
{
public interface IAegisBornObject
{
int Id { get; set; }
string Name { get; set; }
}
}
This is our base object, everything will always have these functions and properties. Next we have our object that implements this:
AegisBornObject.cs
namespace AegisBorn.Models.Base
{
public abstract class AegisBornObject : IAegisBornObject
{
public int Id { get; set; }
public string Name { get; set; }
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
}
}
This defines our basic object which has a position and the implementation for IAegisBornObject. Next we have our interface for all our character classes:
IAegisBornCharacter.cs
namespace AegisBorn.Models.Base.Actor
{
public interface IAegisBornCharacter
{
AegisBornCharacter Target { get; set; }
List<AegisBornCharacter> AttackerList { get; set; }
CharacterStats Stats { get; set; }
}
}
This tells other classes that we will always have a target, a list of attackers (people/mobs attacking us), and our new stats class. Next we have our real implemented character class:
AegisBornCharacter.cs
namespace AegisBorn.Models.Base.Actor
{
public abstract class AegisBornCharacter : AegisBornObject, IAegisBornCharacter
{
public SfGuardUser UserId { get; set; }
public string Class { get; set; }
public string Sex { get; set; }
protected AegisBornCharacter()
{
Stats = new CharacterStats(this);
}
public Hashtable GetHashtable()
{
var character = new AegisBornCommon.Models.AegisBornCharacter
{
Id = Id,
Name = Name,
Sex = Sex,
Class = Class,
Level = Stats.Level,
X = X,
Y = Y,
Z = Z
};
return character.GetHashtable();
}
public AegisBornCharacter Target { get; set; }
public List AttackerList { get; set; }
public CharacterStats Stats{ get; set; }
}
}
Characters, when created, build a default CharacterStats. Like before we also use it to create our hashtable of values that we send to the client. You will notice we do not create the XML stats here. That is because ONLY player characters will be storing their stats as xml. Later when we add NPCs, we will load stats but never write back to the database, so rather than have accidents we will be putting this code into AegisBornPlayer. So lets move on to the next two classes, AegisBornPlayable and AegisBornPlayer. The reason we have a middle class is because Pets will also fall into the "Playable" class set as you direct their actions through the player.
AegisBornPlayable.cs
namespace AegisBorn.Models.Base.Actor
{
///
/// Any set of classes that is "playable", these are players and pets.
///
public abstract class AegisBornPlayable : AegisBornCharacter
{
private AegisBornCharacter _lockedTarget;
}
}
As you can see, right now it is very simple, just a locked target. This will allow us to keep track of 2 targets like most modern MMOs where the locked target is the one you are attacking and casting spells on and the other target is what you are looking at, such as another NPC, player, or another object. Lastly we have our AegisBornPlayer class, which finally makes use of the DTO object:
AegisBornCharacter.cs
namespace AegisBorn.Models.Base.Actor
{
///
/// This is a real player on a real client, here we can send packets back to the user when things happen.
///
public class AegisBornPlayer : AegisBornPlayable
{
public Peer Peer { get; set; }
public void Logout()
{
// kick the player out of the game.
}
public void CreateFromDTO( AegisBornCharacterDTO aegisBornCharacterDto)
{
Id = aegisBornCharacterDto.Id;
Name = aegisBornCharacterDto.Name;
Stats.Level = aegisBornCharacterDto.Level;
Class = aegisBornCharacterDto.Class;
Sex = aegisBornCharacterDto.Sex;
X = aegisBornCharacterDto.X;
Y = aegisBornCharacterDto.Y;
Z = aegisBornCharacterDto.Z;
Stats.BaseStatsXML = aegisBornCharacterDto.BaseStats;
UserId = aegisBornCharacterDto.UserId;
}
public AegisBornCharacterDTO CreateDTO()
{
AegisBornCharacterDTO aegisBornCharacterDto = new AegisBornCharacterDTO
{
Id = Id,
Name = Name,
Level = Stats.Level,
Class = Class,
Sex = Sex,
X = X,
Y = Y,
Z = Z,
// Change this to be a string that will be serialized/deserialized
BaseStats = Stats.BaseStatsXML,
UserId = UserId,
};
return aegisBornCharacterDto;
}
}
}
The player class also contains a peer. This will be used later to send messages back to our player ranging from taking damage to receiving a tell. With this in place we are finished with the majority of our stats code. You can see we create an AegisBornCharacterDTO and the BaseStats uses our XML serialization and do the reverse when we create an AegisBornPlayer. Next time we will add the function to increment base stats. I'm trying to think of how to do it using a calculator so that you can add based on a calculation rather than hard coding it and it will return a number for you to remove from another object such as stat points. Those are more up to you the developer, but we will put them in. Then in the code where you level up your character, you can decide how much they should get, etc. Lastly we will look at passing this data to the client where we can view it in the character status screen on the client once we are in game.
Until then, enjoy!
Comments
midomido
Mon, 08/22/2011 - 04:18
Permalink
Help !
Hey Dragagon , i really need your help in the first steps there is somethings i dont understand , would you please give me your email that you use to chat please ?
xcalpro
Mon, 08/22/2011 - 06:17
Permalink
Stats and StatsUtil defined twice
When I try to build your current version, I get error saying that Stats and StatsUtil are already define. I see that they are defined in both StatsUtil.cs and BaseStats.cs
dragagon
Mon, 08/22/2011 - 07:44
Permalink
Delete the StatsUtil file.
Delete the StatsUtil file. everything is in the BaseStats file.
Laki
Mon, 08/29/2011 - 16:12
Permalink
Thank you!
Thank you very much for your help and teaching.
Laki
Thu, 09/01/2011 - 08:54
Permalink
Waiting next part.
Would it be "Interest Management" (Regions, Square Tile Algorithm)?
dragagon
Thu, 09/01/2011 - 09:05
Permalink
The next part was going to be
The next part was going to be getting the world and regions set up. However, Exit Games has released RC1 of Photon 3.0 and there are enough changes that I feel it would be more appropriate to begin from ground zero and build up multiple interconnected servers using the new Server2Server piece of the API to build individual servers which will handle a piece of the world as a whole and pass information between each other. This is going to rely heavily on the XML Serialization in order to pass the entire AI state of mobs or the player so that the player doesn't notice anything. It'll be interesting to see how it works out.
Janidu
Thu, 10/27/2011 - 08:28
Permalink
Do you know how you save
Do you know how you save Unity3d game world in to a database and retrieving it? I mean how do you store, code, images, meshes, animations, audio,text etc to the database. Do they has to be streamed in with a suitable binary format and saved or were only a reference related to the object is saved?
dragagon
Thu, 10/27/2011 - 13:29
Permalink
If I were to store data such
If I were to store data such as that in a database I would consider using a binary serializer (such as inheriting from Serializable) and store the data in a BLOB field in the database. You would want to make sure the object is in a common project so that both the server and client know how to serialize and deserialize the object.
I would consider a slightly different option, such as only storing the terrain and let unity3d stream the rest of the resources to the client if you are doing something like a web client. Then you would only need to specify which object it is and where it is located for the rest of the client to work correctly.