So today I am preparing for the ability to create a character and I had to go back and fix part of my message handler. If you notice, I create 2 delegates for before receiving and processing the message and after, but you never see those delegates get called. To do this we need to make a few minor modifications. First, we need to change the name of HandleMessage to OnHandleMessage. Make this same change in the CharacterSelectHandler. Next we will add a new function to IMessageHandler called HandleMessage and it will look like this:
public void HandleMessage(ISFSObject data)
{
if (beforeMessageRecieved != null)
{
beforeMessageRecieved();
}
OnHandleMessage(data);
if (afterMessageRecieved != null)
{
afterMessageRecieved();
}
}
This will check if any functions have been added to beforeMessageReceived and afterMessageReceived and call them when necessary.
In the next post you will see why this is important as we set up to display the characters, a new character button and a back button that will log us off the server and put us back to the lobby.
Tuesday, May 10, 2011
Adding a Character Class, Unity3d Implementation Part 3
So after the last 2 parts we have set up our entity, our model, our handler, and we are ready to receive the message. The next thing we will be doing is creating a generic message handler and extending it with a CharacterSelectHandler.
To begin you will want to open up your character select scene and adding an empty game object. I called mine CharacterSelectGUI. I then created a new CharacterSelectGUI script which I attach to the game object. We are then going to add our disconnect functions to the script:
public void OnConnectionLost(BaseEvent evt)
{
// Display popup, when user hits ok, return to lobby.
//loginErrorMessage = "Connection lost / no connection to server";
Application.LoadLevel("Lobby");
}
void OnLogout(BaseEvent evt)
{
Application.LoadLevel("Lobby");
}
public void OnDebugMessage(BaseEvent evt)
{
string message = (string)evt.Params["message"];
if(debugMessages)
{
Debug.Log("**** DEBUG ****" + message);
}
}
To begin you will want to open up your character select scene and adding an empty game object. I called mine CharacterSelectGUI. I then created a new CharacterSelectGUI script which I attach to the game object. We are then going to add our disconnect functions to the script:
public void OnConnectionLost(BaseEvent evt)
{
// Display popup, when user hits ok, return to lobby.
//loginErrorMessage = "Connection lost / no connection to server";
Application.LoadLevel("Lobby");
}
void OnLogout(BaseEvent evt)
{
Application.LoadLevel("Lobby");
}
public void OnDebugMessage(BaseEvent evt)
{
string message = (string)evt.Params["message"];
if(debugMessages)
{
Debug.Log("**** DEBUG ****" + message);
}
}
Next we will add the Awake function and its components.
bool debugMessages = false;
CharacterSelectHandler CharacterSelect;
bool debugMessages = false;
CharacterSelectHandler CharacterSelect;
void Awake()
{
base.Awake();
if(smartFox.IsConnected)
{
CharacterSelect = new CharacterSelectHandler();
// Register callback delegate
smartFox.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost);
smartFox.AddEventListener(SFSEvent.LOGOUT, OnLogout);
smartFox.AddLogListener(LogLevel.DEBUG, OnDebugMessage);
// We are ready to get the character list
ISFSObject data = new SFSObject();
ExtensionRequest request = new ExtensionRequest("getCharacters", data);
smartFox.Send(request);
}
else
{
Application.LoadLevel("Lobby");
}
}
This will set us up so that we send the new message the server is looking for. The server should then respond with our list of characters.
In our common code section I will be creating an abstract class that will set up 2 delegates so we can handle things before and after a message is received as well as what to do when the message is actually received. Create a new class called IMessageHandler.cs in common/scripts and it will contain the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sfs2X.Entities.Data;
public abstract class IMessageHandler
{
public delegate void BeforeMessageRecieved();
public BeforeMessageRecieved beforeMessageRecieved;
public delegate void AfterMessageRecieved();
public AfterMessageRecieved afterMessageRecieved;
public abstract void HandleMessage(ISFSObject data);
}
As you can see it is fairly simple. Next we will create a CharacterSelectHandler that will listen for our event back from the server containing a list of characters:
using System;
using Sfs2X.Entities.Data;
using System.Collections.Generic;
using UnityEngine;
public class CharacterSelectHandler : IMessageHandler
{
public List characterList;
public int maxCharacters;
public CharacterSelectHandler ()
{
characterList = new List();
maxCharacters = 0;
}
public override void HandleMessage(ISFSObject data)
{
maxCharacters = data.GetInt("maxCharacters");
ISFSObject characters = data.GetSFSObject("characters");
foreach (string key in characters.GetKeys())
{
Debug.Log("Adding character: " + key);
characterList.Add(Character.LoadFromSFSObject(characters.GetSFSObject(key)));
}
Debug.Log("Max: " + maxCharacters);
Debug.Log("Characters: " + characterList.Count);
}
}
This class uses a class we haven't created yet called Character. It is what our client will use to show all of our character's data. Later we will create a RemoteCharacter class that will be for other players and NPCs as we won't be able to see all the data about those characters. Much like you can't see all the data in any other MMO.
using System;
using Sfs2X.Entities.Data;
using UnityEngine;
public class Character
{
string name;
int level;
public Character ()
{
}
public static Character LoadFromSFSObject(ISFSObject data)
{
Character newCharacter = new Character();
try
{
newCharacter.name = data.GetUtfString("name");
newCharacter.level = data.GetInt("level");
}
catch(Exception e)
{
Debug.Log(e);
}
return newCharacter;
}
}
For now I have limited it to name and level, later we will expand it to include the other important data contained in the entity [GameName]Character class. For now this will do. The last thing we need to do is to add the handler to the CharacterSelectGUI's awake function. Just after the Log Debug handler add:
// Personal message handlers
handlers.Add("characterlist", CharacterSelect.HandleMessage);
This concludes part 3. By now you should be able to log in to the server and get back a list of characters. Tomorrow we will delve into the logic to see if the user can create a character and moving to a character select screen where they can chose some simple data and create a character. Stay Tuned!
Monday, May 9, 2011
Adding a Character Class, SmartFoxServer Implementation Part 2
Now that we have the character table set up and we have the entity in place, we need to create a message to send to the client that details our characters we have on the server. In order to do this we need to create a couple things. First we will be creating a new package in the Extension called models. Now this might sound confusing but we are going to create a model that has the same name as our entity. This is because we need to extend the functionality of the entity without modifying the file. Why, you ask? Why not just modify the file? Well if we modify the [GameName]Character.java and later down the line we decide we have to add some stats to our character, when you regenerate the file, any modifications you made will be lost. Lets say you remember to do it every time? The one time you forget you will be kicking yourself. So do yourself a favor and set it up correctly now to save you that headache later.
In [GameName]Extension create a package domain.domainname.gamename.models and inside add a new class called [GameName]Character.java. Inside this class we will create a property that is our entity and a constructor that takes the entity as a parameter:
public domain.domainname.gamename.entities.[GameName]Character character;
public [GameName]Character(domain.domainname.gamename.entities.[GameName]Character character)
{
this.character = character;
}
After that we are going to add 2 helper functions. These functions will help us by making SFSObjects from our class and turning SFSObjects back into our class so that we can quickly serialize and deserialize our objects:
public void loadFromSFSObject(ISFSObject data)
{
}
public ISFSObject createSFSObject()
{
ISFSObject data = new SFSObject();
data.putUtfString("name", character.getName());
data.putInt("level", character.getLevel().intValue());
return data;
}
Now that we have our class set up. It is time to create our handler and add it to our list of handlers. The handler class is as follows:
package com.cjrgaming.aegisborn.handlers;
import domain.domainname.gamename.entities.[GameName]Character;
import domain.domainname.gamename.simulation.[GameName]Account;
import domain.domainname.gamename.simulation.World;
import domain.domainname.gamename.util.RoomHelper;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.extensions.BaseClientRequestHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.crypto.Cipher;
/**
*
* @author richach7
*/
public class CharacterListHandler extends BaseClientRequestHandler {
@Override
public void handleClientRequest(User u, ISFSObject data) {
try {
trace("got character list request");
World world = RoomHelper.getWorld(this);
[GameName]Account player = world.getPlayer(u);
trace("loading properties");
Properties serverProps = new Properties();
try
{
InputStream in = this.getClass().getResourceAsStream("/com/cjrgaming/aegisborn/ServerConfig.properties");
serverProps.load(in);
}
catch(IOException e)
{
trace(e);
}
trace("loaded properties");
ISFSObject outputObject = new SFSObject();
outputObject.putInt("maxCharacters", Integer.parseInt(serverProps.getProperty("MaxCharacters", "0")));
ISFSObject characterList = new SFSObject();
int i = 0;
trace("loading characters");
Cipher cipher = player.GetCipher();
for([GameName]Character character : player.getGuardUser().get[GameName]CharacterCollection())
{
domain.domainname.gamename.models.[GameName]Character ABCharacter = new domain.domainname.gamename.models.[GameName]Character(character);
trace("outputting character"+i);
characterList.putSFSObject("character"+i++, ABCharacter.createSFSObject());
}
outputObject.putSFSObject("characters", characterList);
send("characterlist", outputObject, u);
} catch (Exception e) {
trace(e);
}
}
}
In [GameName]Extension create a package domain.domainname.gamename.models and inside add a new class called [GameName]Character.java. Inside this class we will create a property that is our entity and a constructor that takes the entity as a parameter:
public domain.domainname.gamename.entities.[GameName]Character character;
public [GameName]Character(domain.domainname.gamename.entities.[GameName]Character character)
{
this.character = character;
}
After that we are going to add 2 helper functions. These functions will help us by making SFSObjects from our class and turning SFSObjects back into our class so that we can quickly serialize and deserialize our objects:
public void loadFromSFSObject(ISFSObject data)
{
}
public ISFSObject createSFSObject()
{
ISFSObject data = new SFSObject();
data.putUtfString("name", character.getName());
data.putInt("level", character.getLevel().intValue());
return data;
}
Now that we have our class set up. It is time to create our handler and add it to our list of handlers. The handler class is as follows:
package com.cjrgaming.aegisborn.handlers;
import domain.domainname.gamename.entities.[GameName]Character;
import domain.domainname.gamename.simulation.[GameName]Account;
import domain.domainname.gamename.simulation.World;
import domain.domainname.gamename.util.RoomHelper;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.extensions.BaseClientRequestHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.crypto.Cipher;
/**
*
* @author richach7
*/
public class CharacterListHandler extends BaseClientRequestHandler {
@Override
public void handleClientRequest(User u, ISFSObject data) {
try {
trace("got character list request");
World world = RoomHelper.getWorld(this);
[GameName]Account player = world.getPlayer(u);
trace("loading properties");
Properties serverProps = new Properties();
try
{
InputStream in = this.getClass().getResourceAsStream("/com/cjrgaming/aegisborn/ServerConfig.properties");
serverProps.load(in);
}
catch(IOException e)
{
trace(e);
}
trace("loaded properties");
ISFSObject outputObject = new SFSObject();
outputObject.putInt("maxCharacters", Integer.parseInt(serverProps.getProperty("MaxCharacters", "0")));
ISFSObject characterList = new SFSObject();
int i = 0;
trace("loading characters");
Cipher cipher = player.GetCipher();
for([GameName]Character character : player.getGuardUser().get[GameName]CharacterCollection())
{
domain.domainname.gamename.models.[GameName]Character ABCharacter = new domain.domainname.gamename.models.[GameName]Character(character);
trace("outputting character"+i);
characterList.putSFSObject("character"+i++, ABCharacter.createSFSObject());
}
outputObject.putSFSObject("characters", characterList);
send("characterlist", outputObject, u);
} catch (Exception e) {
trace(e);
}
}
}
With this in place we can add it to our list of handlers by going into [GameName]Extension and adding the following line:
addRequestHandler("getCharacters", CharacterListHandler.class);
With this in place, we are ready to add the code to Unity3d to ask the server for our list of characters. We will cover this in part 3.
As an aside, if you wish to follow along with this project as it is being worked on, I have added it to GitHub under an open source repository. Rather than charge people for all the code for this series, I've decided that if you like it, you can donate to me. Otherwise, why teach. GitHub Repository
Adding a Character Class, Symfony and SmartFoxServer Implementation Part 1
So last time we left off we had the ability to create a user through our website and we have the ability to log into both the website and the game and made the game push us over to a character select scene. The next thing we are going to do is create a basic character class, get it set up as an entity and return a list of characters to the user after they log in.
To begin with we are going to create our character class in symfony. Go to [GameName]/config/doctrine/schema.yml and here we will create a character class that will consist of a name, a race, a sex, a current class, a level, and a position in the game world. It will look like the following:
[GameName]Character:
actAs: { Timestampable: ~ }
columns:
user_id: { type: integer, notnull: true }
name: { type: string(255), unique: true}
sex: { type: string(1) }
class: { type: string(50) }
level: {type: integer, default: 1}
position_x: {type: integer, default: 0 }
position_y: {type: integer, default: 0 }
relations:
sfGuardUser:
type: one
foreignType: many
class: sfGuardUser
local: user_id
foreign: id
onDelete: cascade
foreignAlias: Characters
This will give us our basic class. From here you will want to run symfony doctrine:build --all from your command line in the xampp/[GameName] folder so that it will rebuild your database and include the new class.
From here you will want to go over to the [GameName]Entities project in netbeans and right click and hit Entity Classes from Database. When it loads up the list select [GameName]Character and add it to the right side and click finish.
You will now have a new class in your entity project. You can now use this in the Extension project to create, load, and work with your users' characters.
We now have 2 empty tables that need to be filled. one is [GameName]UserProfile which has the number of character slots the account has. The other is the new [GameName]Character table.
First we need to fill the UserProfile table. To do this, we want to go into our php project and open up app/frontend/modules/game/actions/action.class.php and replace the inside of the executeIndex function with this:
$guardUser = $this->getUser()->getGuardUser();
$characters = $guardUser->getCharacters();
$profile = $guardUser->getProfile();
if($profile == '' )
{
$profile = new AegisBornUserProfile();
$profile->setCharacterSlots(1);
$profile->setSfGuardUser($guardUser);
$profile->save();
}
To begin with we are going to create our character class in symfony. Go to [GameName]/config/doctrine/schema.yml and here we will create a character class that will consist of a name, a race, a sex, a current class, a level, and a position in the game world. It will look like the following:
[GameName]Character:
actAs: { Timestampable: ~ }
columns:
user_id: { type: integer, notnull: true }
name: { type: string(255), unique: true}
sex: { type: string(1) }
class: { type: string(50) }
level: {type: integer, default: 1}
position_x: {type: integer, default: 0 }
position_y: {type: integer, default: 0 }
relations:
sfGuardUser:
type: one
foreignType: many
class: sfGuardUser
local: user_id
foreign: id
onDelete: cascade
foreignAlias: Characters
This will give us our basic class. From here you will want to run symfony doctrine:build --all from your command line in the xampp/[GameName] folder so that it will rebuild your database and include the new class.
From here you will want to go over to the [GameName]Entities project in netbeans and right click and hit Entity Classes from Database. When it loads up the list select [GameName]Character and add it to the right side and click finish.
You will now have a new class in your entity project. You can now use this in the Extension project to create, load, and work with your users' characters.
We now have 2 empty tables that need to be filled. one is [GameName]UserProfile which has the number of character slots the account has. The other is the new [GameName]Character table.
First we need to fill the UserProfile table. To do this, we want to go into our php project and open up app/frontend/modules/game/actions/action.class.php and replace the inside of the executeIndex function with this:
$guardUser = $this->getUser()->getGuardUser();
$characters = $guardUser->getCharacters();
$profile = $guardUser->getProfile();
if($profile == '' )
{
$profile = new AegisBornUserProfile();
$profile->setCharacterSlots(1);
$profile->setSfGuardUser($guardUser);
$profile->save();
}
This block of code attempts to load the users profile and if one does not exist, creates it for them. It gives them 1 character slot and saves that to the database. Without this bit of code, we cannot send the client this information and they won't be able to create any characters. You will now want to log into the web site so that the profile gets created.
In Part 2 we will look at creating the UI in Unity3d so that we can get to a character creation screen and provide the user with the options to fill out the character.
Friday, May 6, 2011
Symfony sfForkedDoctrineApplyPlugin disable validation email
So today I did a little further research because I didn't want users to have to validate their user. This is great for testing on your own server so you don't have to run Mercury. To do this open up [GameName]/apps/frontend/config/app.yml and add the following under the sfForkedApply:
confirmation:
reset: false
apply: false
email: false
reset_logged: false
confirmation:
reset: false
apply: false
email: false
reset_logged: false
This will disable the email validation and allow you to directly create your users. If you also want to remove the recaptcha open the app.yml and make the recaptcha enabled go to false. I will be updating the zip file to have these new values.
EDIT: It has been updated and can now be downloaded. It has everything to you need to create a user with no further programming.
EDIT: It has been updated and can now be downloaded. It has everything to you need to create a user with no further programming.
Thursday, May 5, 2011
Symfony Expansion for the MMO
As I stated in my last post I was working on a download so that you can have symfony ready to go with a few simple steps.
Start by downloading the zip file GameName.zip
You will want to extract this file to where ever you have installed xampp. It will create a folder called GameName and inside you will find all the components necessary to have a working symfony install.
To customize this to your own game follow these steps:
This covers the following steps, but does not cover creating you database, db user, or installing any of the software, this is just to get symfony up and running on your system:
XAMPP, NetBeans and Symfony Install
Symfony Registration and Login
Symfony Guard and myUser
Start by downloading the zip file GameName.zip
You will want to extract this file to where ever you have installed xampp. It will create a folder called GameName and inside you will find all the components necessary to have a working symfony install.
To customize this to your own game follow these steps:
- Rename the folder from GameName to whatever you will call your game. (Remember not to use spaces as this will be part of the game URL.)
- go into GameName/web and edit index.php and frontend_dev.php and replace GameName with the name of your game. Save these files.
- go into GameName/config and edit properties.ini and ProjectConfiguration.class.php and update GameName with your game's name.
- go into GameName/config and edit databases.yml and enter in your MySQL information.
- cd
\public_html[XAMPPInstallDir] - mkdir [GameName]
- cd
\[GameName][XAMPPInstallDir] - mkdir public_html
- cd public_html
- MKLINK /J [GameName]
/public_html/[GameName][XAMPPInstallDir]
This will make a directory in your application that points to the visible part of your web server. Otherwise you end up having to copy everything manually any time you make a change.
Next we want to go ahead and copy the contents of the 'web' folder into the 'public_html' folder . Then go ahead and delete the web folder.
Open a command window and go to [XAMPPInstallDir]\[GameName] and enter the following command:
symfony configure:database "mysql:host=localhost;dbname=[DatabaseName]" [DBUser] [DBPassword]
symfony doctrine:build --all
this will display a confirmation to rebuild the databases and after you say 'y' should go into creating all the objects and tables and get you ready for what is next.
Once you have this complete you are ready to work with symfony and can skip those steps of the tutorial. Or you can go back and read through just to see what we did and why.
To create your first user go to http://localhost/GameName/user/apply
the sfForkedDoctrineApply plugin requires a locally running mail server, so if you worked with xampp just start up the Mercury app from the xampp Control Panel, it will not really send an email, but will get past that portion of the code. From there go into phpmyadmin and look up your user and get the string form the validate column. Then go to http://localhost/GameName/user/confirm/[validateString]
This will get you a user can log in with that is validated and ready to work with.
This covers the following steps, but does not cover creating you database, db user, or installing any of the software, this is just to get symfony up and running on your system:
XAMPP, NetBeans and Symfony Install
Symfony Registration and Login
Symfony Guard and myUser
Symfony Expansion and a small step back.
So I took a few months off from playing with this project. I was playing with the latest blender builds and learning to do some 3d modeling for myself. Today I got started back into this project. I'm planning on putting together some resources for a basic symfony directory with a few notable plugins to jump start the web server portion of MMO. Originally I just had Symfony and sfDoctrineGuardPlugin. Since then I have added sfForkedDoctrineApplyPlugin and its prerequisite sdFormExtraPlugin
I'm also looking at putting together a set of assets that people can purchase to give them a basic setup of all the tutorials so far.
Last time I left off talking about an item management system. I'm taking a step back from that for the moment in order to address some more pressing things. First, we can log in, great. What about getting started with seeing a player or at least a box appear to show that we are in game. That would be a better start. So I'm in the process of building these things back up and getting ready to move on to character data.
I'm also looking at putting together a set of assets that people can purchase to give them a basic setup of all the tutorials so far.
Last time I left off talking about an item management system. I'm taking a step back from that for the moment in order to address some more pressing things. First, we can log in, great. What about getting started with seeing a player or at least a box appear to show that we are in game. That would be a better start. So I'm in the process of building these things back up and getting ready to move on to character data.
Subscribe to:
Posts (Atom)