View Full Version : [Solved] Leaving a room
joseph
03-31-2011, 10:32 PM
Hi,
Could not find a unity3 example of leaving a room. I am working from the JavaScript version here:
Request Leave Room: http://www.electrotank.com/docs/es5/client/csharp/
Leave Room Event: http://www.electrotank.com/docs/es5/client/csharp/
I am concerned because I know that the script is functional when I press a button to leave the room, and the delgation event and request method does not happen. Here is the code that I included in my C# Script:
public class LeaveRoomScript : MonoBehaviour {
private ElectroServer es;
void OnGUI() {
if (GUI.Button(new Rect(100,100,100,100), "LeaveRoom?")) {
Debug.Log("Button Pressed"); // <-- Happens
GameObject gameManager = GameObject.Find("_GameManager");
GameManager gm = (gameManager)gameManager.GetComponent<GameManager>();
//Make sure that our ES obj is not null
es = gm.es;
//Speak with the ES about leaving a room
es.Engine.LeaveRoomEvent += OnLeaveRoom;
}
void LeaveRoomEvent() {
Debug.Log("LeaveRoomEvent"); //<-- doesn't happen
LeaveRoomRequest lrr = new LeaveRoomRequest();
es.Engine.Send(lrr);
}
void OnLeaveRoom(LeaveRoomEvent evt) {
Debug.Log("OnLeaveRoom"); //<-- doesn't happen
Application.LoadLevel("CharacterSelection"); //To choose a different char
}
}
When I do leave the room, I don't experience any run-time errors. However, when rejoining the room that I left I will get run-time errors here, in the SimpleChat.cs example:
foreach (Users u in currentRoom.Users) {
GUI.Button (new Rect(100,100,100,100), u.UserName);
}
Could I somehow do something with the event paramater (for OnLeaveRoom) to make the iteration scheme above not error when rejoining the room that i left?
tcarr
04-01-2011, 12:07 AM
When you create a LeaveRoomRequest you have to set the roomId and zoneId of the room you want to leave on it, then send it to the ES5. I also don't see where you are invoking your LeaveRoomEvent function at all (and I would call it "LeaveRoom" or "SendLeaveRoomRequest" - it's not any kind of event).
Since clients can be in multiple rooms, your LeaveRoomRequest has to tell the ES5 which room you want to leave.
joseph
04-01-2011, 09:38 PM
Regarding the JavaScript example found here: http://www.electrotank.com/docs/es5/client/csharp/
I can't seem to pull it off in C#:
private void Start() {
gameManager = GameObject.Find("_GameManager");
gm = (GameManager)gameManager.GetComponent<GameManager>();
es = gm.es;
es.Engine.Queueing = EsEngine.QueueDispatchType.External; //Already done in main...is necessary?
es.Engine.LoginResponse += OnLogin;
es.Engine.PublicMessageEvent += OnPublicMessage;
es.Engine.JoinRoomEvent += OnJoinRoom; //Scipt invoked, so join room
es.Engine.RoomUserUpdateEvent += OnRoomUserUpdateEvent;
}
private void OnRoomUserUpdateEvent(RoomUserUpdateEvent ruuv) {
switch (ruuv.Action)
{
case RoomUserUpdateAction.AddUser: //error
updateUserList();
break;
}
}
private void updateUserList() {
List<Users> user = currentRoom.Users; //List is to C# as Array is to Javascript
}
Getting the error: Assets/Networking/SimpleChat.cs(121,44): error CS0246: The type or namespace name `RoomUserUpdateEvent' could not be found. Are you missing a using directive or an assembly reference?
I want to add new users each time they enter a room. So in the start method (above) we invoke the delegate 'es.Engine.RoomUserUpdateEvent += OnRoomUserUpdateEvent;' to add new users that enter the room.
tcarr
04-01-2011, 10:02 PM
Try UserUpdateEvent instead. Looks like we didn't get the documentation updated when the name of that event changed.
If you use Visual Studio for coding your scripts, and add the dll as a library, you can get code hinting. It's a real help when doing something complicated.
joseph
04-01-2011, 10:21 PM
How can I ensure that the below code does not error?
foreach (User u in currentRoom.Users) {
GUI.Button(new Rect(5, 100, 100, 100), u.UserName);
}
Every time I join a room, it seems that I am not in the currentRoom.Users list..How can I just add it in the JoinRoom() or JoinRoomEvent? (below):
void Start() {
es.Engine.JoinRoomEvent += OnJoinRoom;
JoinRoom();
}
private void JoinRoom()
{
//request used to create a room
CreateRoomRequest crr = new CreateRoomRequest();
crr.RoomName = "Tavern";
crr.ZoneName = "TavernZone";
// create the plugin
PluginListEntry ple = new PluginListEntry();
ple.ExtensionName = EXTENSION_NAME;
ple.PluginHandle = PLUGIN_NAME;
ple.PluginName = PLUGIN_NAME;
List<PluginListEntry> pluginList = new List<PluginListEntry>();
pluginList.Add(ple);
crr.Plugins = pluginList;
// turn off events we don't need to reduce number of socket messages
crr.ReceivingRoomAttributeUpdates = false;
crr.ReceivingRoomListUpdates = false;
crr.ReceivingRoomVariableUpdates = false;
crr.ReceivingUserListUpdates = false; // will get these from the plugin
crr.ReceivingUserVariableUpdates = false;
crr.ReceivingVideoEvents = false;
// turn on the standard chat filtering
crr.UsingLanguageFilter = true;
crr.UsingFloodingFilter = true;
//send it
es.Engine.Send(crr);
Debug.Log("CreateRoomRequest sent");
}
void OnJoinRoom (JoinRoomEvent e)
{
Debug.Log("Joined a Room!");
// Get room-by-id from Electroserver ZoneManager
currentRoom = es.ManagerHelper.ZoneManager.ZoneById( e.ZoneId ).RoomById( e.RoomId );
gameState = GameState.Chatting;
// ask plugin for the full user list
sendUserListRequest();
//SendMessage("SpawnLocalPlayer", _userName);
}
private void sendUserListRequest()
{
EsObject esob = new EsObject();
esob.setString(PluginTags.ACTION, PluginTags.USER_LIST_REQUEST);
Debug.Log("sendUserListRequest");
/*esob.setString("name", gm.userName);
esob.setString("level", gm.userLevel);
esob.setString("profession", gm.userProfession);*/
//send to the plugin
sendToPlugin(esob);
}
public void sendToPlugin(EsObject esob)
{
if (currentRoom != null && es != null)
{
//build the request
PluginRequest pr = new PluginRequest();
pr.Parameters = esob;
pr.RoomId = currentRoom.Id;
pr.ZoneId = currentRoom.ZoneId;
pr.PluginName = PLUGIN_NAME;
//send it
es.Engine.Send(pr);
}
}
}
I scroll up in the ConsoleLog in Unity and the Debug Statements have not happened.
tcarr
04-01-2011, 10:32 PM
If you are already in the room then you won't get a JoinRoomEvent. That might be the problem. I'm not expert enough in Unity to know how to get the buttons to work right. The way that I implemented the user list in my generic C# Simple Chat example just put the names into a text area. Perhaps you should comment out the "update the user list" code until you get the bugs out of leaving a room and joining it. Just use logging and possibly check the ES Admin's Status - Room and Zones to see if the user name shows in the room. After we know that you are indeed joining the room correctly, then we will worry about the user list.
joseph
04-02-2011, 07:42 PM
so i thought i'd try a different technique than the one in SimpleChat.cs:
private void initialize() {
es.Engine.GetUsersInRoomRequest += OnGetUsersInRoomResponse; //GetUsersInRoomRequest not found
GetUsersInRoomRequest guir = new GetUsersInRoomRequest();
guir.RoomId = currentRoom.Id;
guir.ZoneId = currentRoom.ZoneId;
es.Engine.Send(guir);
}
private void OnGetUsersInRoomResponse(GetUsersInRoomResponse guir) {
foreach (UserListEntry ule in guir.Users) {
GUI.Button(new Rect(100, 100, 100, 100), ule.UserName);
}
}
However, the es.Engine apparently does not have a GetUsersInRoomRequest..I'd like to see if this technqiue to display a UserListEntry would work..I don't think it will, but i'd like to give it a shot.
tcarr
04-02-2011, 10:10 PM
Question: does your room have the AvatarChat plugin attached to it? Because if it does, then you can send the plugin a request for the user list, and it will send you an array of everybody in the room, with their position. That's NetworkController.sendUserListRequest and the handler is NetworkController.handleUserListResponse.
You don't add a request callback to the engine, you add the response. Try es.Engine.GetUsersInRoomResponse += OnGetUsersInRoomResponse;
joseph
04-02-2011, 11:31 PM
The AvatarChat plugin? The only plugins that I see in the AvatarChat example are the BouncyCastle.Crypto, Electroserver5-Unity, PluginTags, and Thrift; out of the four, Plugin tags is the only I can attach to the camera. It doesn't do anything now, but after having taken your suggestion I am sure it can help.
tcarr
04-02-2011, 11:35 PM
The AvatarChat example requires the AvatarChatExtension on the server, which has an AvatarChat that is an ES5 plugin. It's not a Unity plugin. Plugin tags is only constants, doesn't need to be attached to anything. See this thread (http://www.electrotank.com/forums/showthread.php?11967-Send-local-prefab-position-and-receive-remote-with-ES5-amp-Unity&p=59833#post59833) for a similar conversation, about how you can tell if you have the AvatarChat plugin correctly working with the room you are in.
joseph
04-03-2011, 03:23 AM
It seems that it was a bad idea when mixing the SimpleChat and the AvatarChat examples for unity. I apologize for that. I thought now that I would take another look at the AvatarChat example.
Relevent to this thread about leaving the room, I found that in the SpawnController we have this:
public void UserLeaveRoom(string userName) {
Debug.Log("UserLeaveRoom Invoked.."); //added
//Just destroy the corresponding object
GameObject obj = GameObject.Find("remote_" + userName);
if (obj!=null) Destroy(obj);
Application.LoadLevel("MiddleScene"); //added and happens
}
Which is invoked in the NetworkController Script here:
private void OnGUI() {
if (GUI.Button(new Rect(100,100,100,100), "LeaveRoom")) {
Debug.Log("Button Pressed");
leaveRoom();
}
}
//I thought that I would add this because it seems like its 100% necessary to leaving a room
private void leaveRoom() {
_es.Engine.LeaveRoomEvent += OnLeaveRoom; //Add the listener
LeaveRoomRequest lrr = new LeaveRoomRequest();
lrr.RoomId = room.Id;
lrr.ZoneId = room.ZoneId;
_es.Engine.Send(lrr);
}
private void OnLeaveRoom(LeaveRoomEvent e) {
SendMessage("UserLeaveRoom", _userName); //happens
}
And when re-entering the AvatarChat scene I immediately get the error:
MissingReferenceException: The object of type 'NetworkController' has been destroyed but you are still trying to access it.Your script should either check if it is null or you should not destroy the object.
This happens when re-entering the scene (I am not reconnecting or relogging in):
private void OnJoinRoom(JoinRoomEvent evt)
{
Log("Joined a room!");
//grab and store the reference to the room you just joined
room = _es.ManagerHelper.ZoneManager.ZoneById(evt.ZoneId) .RoomById(evt.RoomId);
SendMessage("SpawnLocalPlayer", _userName); //Error on re-entry
// ask plugin for the full user list
sendUserListRequest();
}
which invokes the following:
private void SpawnLocalPlayer(string name) {
Debug.Log("SpawnLocalPlayer invoked");
int n = spawnPoints.Length;
Transform spawnPoint = spawnPoints[random.Next(n)];
localPlayerObject.transform.position = spawnPoint.transform.position;
localPlayerObject.transform.rotation = spawnPoint.transform.rotation;
localPlayerObject.SendMessage("StartSending"); // Start sending our transform to other players
localPlayerObject.SendMessage("ForceSendTransform"); // make sure we send our first position update
localPlayerObject.SendMessage("ShowName", name); // show my own name
}
I also receive errors when attempting to write a message to the chatbox. I notice a pattern, and it seems that the communication (SendMessage("MethodinOtherScript", paramater) is broken).
I see that the NetworkController is still present in the hierarchy, which has the respective scripts attached..So what is destroyed about it?
It seems that I could get the simple chat to work in the way that I attend (without all the spawning fuss) if I just knew why it is so much trouble leaving and re-entering a room.
tcarr
04-03-2011, 12:19 PM
What you have to do is find the game object that NetworkController is connected to, and add the DontDestroy script to that object, so that the NetworkController instance isn't killed.
And I apologize for getting confused - I thought that you WERE doing something based on AvatarChat, and not just doing a SimpleChat based example.
joseph
04-03-2011, 05:51 PM
What I think you mean is:
Attach the DontDestroy Script to the NetworkController GameObject in the AvatarChar Scene. If that is correct, it still will not work without heavy modification.
OR you could mean: Attach the DontDestroy Script to the Player GameObject, but then we get a lot of camera transform not found error.
tcarr
04-03-2011, 05:55 PM
:( I'm really not a Unity expert. Not sure why the NetworkController object isn't being created again when you load the scene again, or how to keep it from disappearing. Hmm... unless you are getting the NetworkController from the GameManager object and that one is null?
joseph
04-03-2011, 06:02 PM
Maybe I am not asking my question correctly: How do I invoke the following method in your AvatarChar Example:
tcarr
04-03-2011, 06:12 PM
handleUserExitEvent is invoked automatically when some OTHER user leaves the room. If it is you who are leaving the room, then you will need to remove ALL the prefabs, both remote and local, right? Or is that done automatically when you leave a scene?
I think that your code is good except for the fact that getPlayerSpawnController().UserLeaveRoom(_userName ); thinks it's a remote user who is leaving. The function code is
public void UserLeaveRoom(string userName) {
//Just destroy the corresponding object
GameObject obj = GameObject.Find("remote_" + userName);
if (obj!=null) Destroy(obj);
}
and of course your local player isn't named remote_ plus your username, it's named something like localPlayer. So in your OnLeaveRoom function, you will probably want to load a new scene, which probably destroys all the prefabs in the current scene for you. You *might* need to remove all those spawned remote users and the local player - I've never tried to do that, and I don't know enough Unity to tell if that is required or not.
joseph
04-03-2011, 07:52 PM
So I reloaded the AvatarChat example, and ALL I added was this to the NetworkController.cs script:
private void OnGUI() {
if (GUI.Button(new Rect(100, 100, 100, 100), "Leave Room!")) {
leaveRoom();
}
}
private void leaveRoom() {
_es.Engine.LeaveRoomEvent += OnLeaveRoom;
LeaveRoomRequest lrr = new LeaveRoomRequest();
lrr.ZoneId = room.ZoneId;
lrr.RoomId = room.Id;
_es.Engine.Send(lrr);
}
private void OnLeaveRoom(LeaveRoomEvent evt) {
SendMessage("UserLeaveRoom", _userName);
}
and this to the PlayerSpawnController.cs script:
public void UserLeaveRoom(string userName) {
//Just destroy the corresponding object
GameObject obj = GameObject.Find("remote_" + userName);
if (obj!=null) Destroy(obj);
Application.LoadLevel("MiddleScene"); // just this
}
And then the middle scene:
void OnGUI() {
if (GUI.Button(new Rect(100,100,100,100), "Click Me")) {
//SetUserVariables();
Application.LoadLevel("AvatarChat");
}
GUI.Label(new Rect(100, 116, 100, 20), "Char Name: ");
NameInput = GUI.TextField(new Rect(200, 116, 200, 20), NameInput, 25);
}
The example doesn't come with leaving the room. The downloader needs to implement a Button to leave a room and take the necessary actions when that button is pressed. Just doing the above code is not enough.
tcarr
04-03-2011, 08:03 PM
With the code that you have changed, this will cause the local user to load the new scene if some OTHER user leaves the room.
You are asking for the AvatarChat example to be changed to having three scenes. I'm not going to do that - the thing is complicated enough as it is. We don't implement custom examples for free, unless they are things that will make excellent examples. We try to keep our examples reasonably simple so that users don't get confused in the extra details.
My workload ebbs and flows. When I'm not busy with other tasks, I have time to spend several days working an example. Right now I have a high urgency task on my lap that is resulting me having to use overtime to keep up with the forum posts at all, so there isn't any time for me to even continue working on the Android game example that I was in the midst of about ten days ago.
joseph
04-03-2011, 08:16 PM
I am willing pay a small amount for this simple concept to function properly. If not, thats ok! I am persistant and will continue until its right! Again, thanks thus far! :-)
tcarr
04-03-2011, 08:51 PM
I don't think that we have anybody available currently to figure this one out. After my workload lets up again, I'll have a lot more patience with struggling with Unity. It's not easy trying to learn new client side languages; I'm a server side developer, but I'm tasked with doing simple code examples in multiple languages because I'll have to field forum questions in those multiple languages. I haven't even started trying iOS yet.
joseph
04-04-2011, 12:16 AM
tcarr, I have another attempt:
Description: displayed user_name is present (was erroring before) and given an initial re-entry run-time error. And also with the sending messages
Current State: No errors!! For example, previously, i entered the game initially and received a run-time error because the Network controller has been destoryed (and the your code was tryiing to acess it). I would receive similar errors when attempting to send a chat message.
Soultion for the now current state: I attached your DontDestroy script to the NetworkController and when i re-entered the room, i got no errors! This was strange because I already thought i took that step as per your suggestion.
Now, I am wondering why these debug.log statements **do not happen** when renetering the room:
// This method to be called when remote chat message is received
void AddChatMessage(String message) {
Debug.Log("Add Chat Message"); //added to see if working when re-entry *it doesn't*
messages.Add(message);
scrollPosition.y = 10000000000; // To scroll down the messages window
}
// Send the chat message to all other users
private void SendChatMessage(String message) {
Debug.Log("Send Chat Message"); //added to see if working when re-entry *it doesn't*
getNetworkController().SendPublicMessage(message);
}
EDIT: Yeah, I totally see what you mean now, tcarr. Here is how it went:
I fired up an instance of the modified version of your web player, logged in and got into the game just fine. I sent a message just for the heck of it and saw that it worked. So far so good I told myself. Then, I fired up a secondary instance of the modified web player, logged in, got into the game just fine, saw the first instance, sent a message to simulate a consersation, clicked back to the first instance and saw that the second instance (the one that I was previously in) had entered the room and sent a message.
Problem: When I exited the first instance, and came back and then clicked back to see the second instance, it was gone, and in fact was in the 'middle scene' when i clicked back to that instance.
Here is modified version of your web player: http://dl.dropbox.com/u/21159096/ES5Example/WebPlayer.html
and it takes unusually long (at least for this first timer) to leave and rejoin the game scene room..I think it might be time now to fire up the admin tool..
tcarr
04-04-2011, 12:54 AM
Glad that you are making progress. Perhaps I'm tired, but is this "completely fixed now" or is there still a bug that you want help with?
joseph
04-04-2011, 01:03 AM
Have you tried the web player? I am wondering if you are noticing/experiencing the same issues? Ya, I totally still need help. I am wondering what could be causing: not being able to send a message upon re-entry to the game room?
tcarr
04-04-2011, 01:12 AM
No I haven't tried your web player. It's Sunday, and I've already put in way too many hours today on work instead of relaxing, after working too many hours last week. If you want debugging help you are going to have to explain exactly what the problem is, and have patience until I find time to look at it.
joseph
04-04-2011, 09:06 PM
Game State Description: two or more remote users and the local user are able to log into the game room scene and are able to chat with each other.
Problem Description: when one of the two remote users leaves the game room scene any other remote user (and even the local user) will be forced out of their current room (in this case the game room scene).
I suspect the below code is relevant to my problem:
//In the NetworkController Script we draw a button that enables leaving a room:
private void OnGUI() {
if (GUI.Button(new Rect(100, 200, 100, 100), "Leave Room!")) {
leaveRoom();
}
}
private void leaveRoom() {
_es.Engine.LeaveRoomEvent += OnLeaveRoom;
LeaveRoomRequest lrr = new LeaveRoomRequest();
lrr.ZoneId = room.ZoneId;
lrr.RoomId = room.Id;
_es.Engine.Send(lrr);
}
private void OnLeaveRoom(LeaveRoomEvent evt) {
SendMessage("UserLeaveRoom", _userName);
}
and then the OnLeaveRoom(LeaveRoomEvent evt) method sends a message (invokes) the following in the PlayerSpawnController script:
public void UserLeaveRoom(string userName) {
//Just destroy the corresponding object
GameObject obj = GameObject.Find("remote_" + userName);
if (obj!=null) Destroy(obj);
Application.LoadLevel("Tavern");
}
Also, the DontDestroy script is attached to the NetworkController game object in the game scenes hierarchy.
It survives whenever a new scene is loaded and most likely tells other connected remote users (and even the local user) that a new scene is loaded.
I suspect that this the reason why I think the problem in the problem description occurs.
tcarr
04-04-2011, 09:28 PM
What this function:
public void UserLeaveRoom(string userName) {
//Just destroy the corresponding object
GameObject obj = GameObject.Find("remote_" + userName);
if (obj!=null) Destroy(obj);
Application.LoadLevel("Tavern");
}
is for is updating the clients for those who are still in the room, by removing the remote user who just left. Because you added Application.LoadLevel("Tavern"); any time that ANY user other than the one who just left the room leaves the room, all users are forced to load the Tavern level.
What you need to do is remove that line from that function. Put it in your OnLeaveRoom function instead of the SendMessage one. The ES5 plugin will take care of telling the other users that you left, it's built in. You just need to make sure that your own client loads the next scene when you leave the current room. I would also suggest that you set your room variable to null if you aren't joining a room in the new scene.
I don't know if there will be a memory leak due to those remote user game objects left behind in the other scene. I just don't know enough about Unity. If you think that there would be, then before loading the new scene, get a list of all users in the room who are not yourself, then call SendMessage("UserLeaveRoom", otherUser); on each of them (where otherUser is one of the remote users). This will remove the prefab that your client made for that user.
joseph
04-04-2011, 10:15 PM
Thanks for the response, and I hope you are doing well. I will try and this suggestion as best and I can and report back with what will hopefully be a solution.
joseph
04-05-2011, 08:37 AM
I took all your suggestions and I solved leaving the game room scene and re-entering the game room scene without forcing other users out! Thanks so much for that! However, it seems to be the first step of many. I have spent quite a bit of trial and error and found that if I *do not* include your "DontDestroy" Script on the NetworkController we get some positives and negatives:
negative:
- when connected to the Electrosever with 2 remote users the first user who entered will not be able to see the first. This happens with or without being logged in locally (This didn't happen with the "DontDestroy" script attached to the NetworkController GameObject).
postives:
- fixed *not* being able to leave the game room scene consistantly
- GUI.Button is not 'overlapping'
- Not 'polutuing' the game scene hierarchy with duplicate NetworkController GameObject's (I think the duplicates had empty prefab slots)
I also fixed something programmatically in NetworkController:
if (playerSpawnController != null)
SendMessage("SpawnLocalPlayer", _userName);
I would receive a run-time error if the above wasn't testing for if playerSpawnController is null, which would prevent the user from leaving consistantly...However, I receive a run-time error when attempting to send a chat message after leaving and returning to the game room scene, i tried a condition similar to the above, here in your ChatController script:
// Send the chat message to all other users
private void SendChatMessage(String message) {
if (room != null && _es != null && playerSpawnController != null) {
getNetworkController().SendPublicMessage(message);
}
}
The problems that I am trying to fix are as follows:
1) Sending a Chat message after leaving and returning to the game room scene (This happened regardless)
2) Having the second remote player visable for the firstly connected remote player (This happened when I took DontDestroy off the NetworkController GameObject)
tcarr
04-05-2011, 01:05 PM
Making good progress! Sorry that I can't be more help. You are tackling something more complicated than I have tried to do in Unity myself.
joseph
04-05-2011, 07:04 PM
I think that I ALMOST have it solved!! Could I please have one more hint as to why the second connected remote user is unable to see the first connected remote user? For example, connect with one remote user and join the game room scene, then do this for another instance (a second remote user), then somehow the second is only able to see the first..
This happens with or without running the local player..
http://dl.dropbox.com/u/21159096/ES5Example/WebPlayer.html
I appreciate all the help thus far.. :)
tcarr
04-05-2011, 07:16 PM
Every client uses localPlayer to represent itself. When the client first joins the room, it asks the plugin for a list of all the users that are in the room, and spawns remotes for those that aren't itself. When another user joins the room, the plugin sends a message about this fact. NetworkController's handleUserEnterEvent function processes this message. Are you getting a chat message line saying the new user " entered the room." ?
If your application is ignoring the TARGET_X and TARGET_Y that might be the problem. This is the only time that those values are used by the Unity application, and it's because I was too lazy to change them to generate a default Unity position. The function calls getPlayerSpawnController().UserEnterRoom(esob);
After the user is in the room, it is supposed to send position update messages periodically. If your ES5 is set to debug level logging, you might be able to see those messages in the server log (I don't recall if I commented them out or not). The client does NOT spawn a remote if a position update comes in for a user and he can't find the game object, because that user might have just left the room.
One workaround you might try is if a position update message arrives from a user who is not your own username and there is not remote + username game object, is to invoke NetworkController.sendUserListRequest which will get a fresh user list. Make sure that your SpawnRemotePlayers function checks to make sure that there isn't already a game object for this player, and this should fix any missing players. Not an elegant solution, more of a duct tape one.
joseph
04-05-2011, 10:19 PM
Solved it! However it was not a 'clean' getaway. Whenever any local or remote user enters the game room scene I will expreince the following errors:
NullReferenceException: Object reference not set to an instance of an object
NullReferenceException: Object reference not set to an instance of an object
which is the cause of:
void OnGUI() {
int height = 100;
foreach (User u in room.Users) {
GUI.Label(new Rect(100, height, 100, 100), u.UserName, s);
height +=15;
}
}
In the NetworkController script. It only happens twice on any type of re-entry. For example: two run-time errors (above) happen pretty consistantly when leaving and re-entering the game room scene and also entering the game room scene for the first time on either the local or remote user.
Also, I can only run two instances of the Unity Web Player. If I try a third the web player will not load and ie will fail. This also happens with any other web site (like google). Is this a problem with Unity or is it network related? I have probable cause that it is related to networking because I can run my game (offline) with no problem on multiple tabs in the ie (internet explorer).
One thing that I would like to add is that I really enjoy writing electro server code and picking up these skills :)
Thanks very much for all the help.
joseph
04-06-2011, 09:41 AM
It seems that I have another problem. I think that the localPlayer is somehow spawning duplicates of it self.
Description:
Joined the game room scene with localPlayer. Joined the game room scene as remote user. Both are visable to each other and are able to chat. I leave the game room scene with the localPlayer, rejoin the game room scene and i can move about; everything is fine.
However if I leave the scene with the remote user, and rejoin the game room scene, and flip back to the localPlayer, I notice duplicates (of the localPlayer) on the remote users end. I am sure that this process is revered if I did it vise versa. I find that this is more important that the run-time errors caused in the iteration scheme above.
tcarr
04-06-2011, 01:18 PM
If you implemented "just ask for the user list a second time" then odds are you are adding two prefabs for the same user. I'd look at that code first.
Try hitting the link to the electrotank.com AvatarChat example, and see if that works for you with more than two tabs running on your desktop. I am pretty sure that I did have 4 users in the room at the same time, all run from my desktop, however that could have been when I had it wired up so that some of the extra users were AS3 clients in the same room. If you verify that it works fine for my version of AvatarChat, then we will at least have the bug narrowed down to either some of the new code or your network.
Note: are you using UDP for the position updates? If so, that might be at least part of the problem. It's tricky running multiple clients with UDP - have to make sure that the local player port is unique.
joseph
04-06-2011, 07:23 PM
In regards to:
Try hitting the link to the electrotank.com AvatarChat example, and see if that works for you with more than two tabs running on your desktop
Thought that this might take care of it today:
void OnLeaveRoom(LeaveRoomEvent e) {
foreach (User u in room.Users) {
GameObject obj = GameObject.Find("remote_" + u.UserName);
if (obj != null && u.UserName != userName) //obj.name? //gm.usrName?
{
Destroy(obj);
}
}
}
However, it doesn't help. There are *three* duplicates foeach remote user after leaving with the local user...http://dl.dropbox.com/u/21159096/ES5%20Example/WebPlayer.html
When re-entering the game room scene *after having left* duplicates will be spawned, maybe i could incldue something similar to the above in SpawnLocalPlayer or SpawnRemotePlayer:
//Get the remote user list and spawn all remote players that have already joinded before us
public void SpawnRemotePlayers(EsObject[] list, string myname) {
Debug.Log("SpawnRemotePlayers");
Debug.Log("my name = " + myname);
foreach (EsObject obj in list)
{
String thisPlayer = getPlayerName(obj);
//attempt to destroy any duplicates coming back after leaving?
GameObject obj = GameObject.Find("remote_" + myname);
if (obj != null) Destroy(obj);
Debug.Log("processing " + thisPlayer);
Debug.Log("obj = " + obj.ToString());
if (!myname.Equals(thisPlayer))
{
Debug.Log("need to spawn remote");
SpawnRemotePlayer(obj);
}
}
}
I also notice that every time I leave the game room scene and re-enter and then type a message, the message will double, tripple, or even quadruple based on how many times i have re-entered. for example: if I left and re-entered four times on either a remote or local player and then enter a message, a total of four duplicate messags will be written.
joseph
04-06-2011, 11:03 PM
So I thougt that the following code would work:
private void OnLeaveRoom(LeaveRoomEvent evt) {
Debug.Log("OnLeaveRoom");
foreach (User u in room.Users)
{
Debug.Log("Names in List: " + u.UserName); // <-- doesn't happen
//if (_userName != u.UserName) {
Debug.Log("LEAVING ROOM"); //<-- doesn't happen either
SendMessage("UserLeaveRoom", u.UserName); // so this won't execute
//handleUserExitEvent(esob);
//}
}
Debug.Log("iteration scheme failed..find local Player"); //Happens
GameObject obj = GameObject.Find("localPlayer");
if (playerSpawnController == null)
{
GameObject gObj = GameObject.Find("NetworkController");
playerSpawnController = (PlayerSpawnController)gObj.GetComponent<PlayerSpawnController>();
}
if (obj.name == "localPlayer") {
Destroy(obj);
}
else
{
playerSpawnController.SendMessage("UserLeaveRoom", obj.name);
}
Application.LoadLevel("Tavern");
}
The above did not solve duplicates appearing upon tab changes (instance changes, i suppose), and the reason that i decided to implement that is because the foreach (User in in room.Users) condition was actually not happening, I didn't notice that.
What is a little strange is that when I go to the localPlayer (after having seen duplicate instances appear on one of the remote players tabs), is that their are the correct number of remote users in the game room scene..
I think that if i Find the any user connected to the game room scene then the duplicate player problem will be solved!
tcarr
04-06-2011, 11:36 PM
Aha - if you have already left the room, then room.Users will be empty, because you aren't allowed to see the names of who is in a room if you aren't in that same room (not through ZoneManager anyway). Try removing the remote player game objects when the button is clicked to send the leave room request?
joseph
04-07-2011, 12:32 AM
I did away with the iteration scheme foreach (User u in room.Users), then noticed that it was not happening when i clicked the button to leave the room! So I thought that what I coded prior to your reply was similar to this:
private void leaveRoom() {
_es.Engine.LeaveRoomEvent += OnLeaveRoom;
LeaveRoomRequest lrr = new LeaveRoomRequest();
lrr.ZoneId = room.ZoneId;
lrr.RoomId = room.Id;
GameObject obj = GameObject.Find("localPlayer");
GameObject rem = GameObject.Find("remote_" + _userName);
if (playerSpawnController == null)
{
GameObject gObj = GameObject.Find("NetworkController");
playerSpawnController = (PlayerSpawnController)gObj.GetComponent<PlayerSpawnController>();
}
if (obj != null) {
Debug.Log("Destroying localPlayer");
Destroy(obj);
}
if (rem != null) {
Debug.Log("Sending Message to Destroy Remote Player");
playerSpawnController.SendMessage("UserLeaveRoom", obj.name);
}
_es.Engine.Send(lrr);
}
I am still, however, *spawning duplicate remote users when i leave and re-enter the game room scene* - That is the definite problem, thus I believe the relavent code is this:
//Get the remote user list and spawn all remote players that have already joinded before us
public void SpawnRemotePlayers(EsObject[] list, string myname) {
Debug.Log("SpawnRemotePlayers");
Debug.Log("my name = " + myname);
foreach (EsObject obj in list)
{
String thisPlayer = getPlayerName(obj);
Debug.Log("processing " + thisPlayer);
Debug.Log("obj = " + obj.ToString());
if (!myname.Equals(thisPlayer))
{
Debug.Log("need to spawn remote");
SpawnRemotePlayer(obj);
}
}
}
I can't seem to find where the above code is invoked. Is their something that I can do to fix spawning duplicate remote users iin the method above? It seems that would be where i would need to do it, or find out where and how it is invoked from. I could do something like, for example "if 'this' userName exists don't spawn" ?
tcarr
04-07-2011, 01:03 AM
Your leaveRoom is never removing the remote users. For example, if the current client is named Joseph, and there are remote users in the room named Teresa and Sam, your code will try to destroy remote_Joseph, which doesn't exist, and will leave remote_Teresa and remote_Sam still spawned.
Before sending your LeaveRoomRequest, get the room's list of users and iterate through that, the way that you were trying to do before. Since you are in the room, this should work. Another way is to simply keep a list of the remote users that you have spawned, then destroy them when you leave the room.
Question: when you leave the room, do you load a new scene? I don't know enough about Unity to know what happens to a scene when you load a new one, but I would hope that this would clear out all the objects, then when you want to join the room again you load the scene with the room. If you end up with two copies of NetworkController that way, then that could explain having two copies of each of the remote users, because both copies of NetworkController will try to spawn the remote players, at the same time.
joseph
04-07-2011, 06:41 AM
To answer your question: yes, when i leave the room i load a new scene. Everything has been removed from the hierarchy when the scene changes. I do not end up with copies of the NetworkController.
In regards to:
"Before sending your LeaveRoomRequest, get the room's list of users and iterate through that ..."
Thought that the below would take care of it.. Could it be the condition? What else could i not be doing before wanting to leave a room? I still experience the same problem: it seems that when just flipping back and forth between tabs is when I will see copy of the remote user that I am not in control of. This is strange!
private void OnGUI() {
if (GUI.Button(new Rect(100, 200, 100, 100), "Leave Room!")) {
InitUserList(); //GetUsersInRoomRequest is made and iterates foreach user
InitLeaveRoom(); //LeaveRoomRequest is made and changes scene
}
}
private void InitUserList()
{
Debug.Log("InitUserList");
_es.Engine.GetUsersInRoomResponse += OnGetUsersInRoomResponse;
GetUsersInRoomRequest guir = new GetUsersInRoomRequest();
guir.RoomId = room.Id;
guir.ZoneId = room.ZoneId;
_es.Engine.Send(guir);
}
private void OnGetUsersInRoomResponse(GetUsersInRoomResponse e)
{
Debug.Log("OnGetUsersInRoomResponse");
if (playerSpawnController == null)
{
GameObject gObj = GameObject.Find("NetworkController");
playerSpawnController = (PlayerSpawnController)gObj.GetComponent<PlayerSpawnController>();
}
foreach (UserListEntry ule in e.Users)
{
Debug.Log("outside: " + ule.UserName);
if (!_userName.Equals(ule.UserName)) //Not sure if the condition is correct
{
Debug.Log("inside: " + ule.UserName);
playerSpawnController.SendMessage("UserLeaveRoom", ule.UserName);
}
}
//GameObject Obj = GameObject.Find("localPlayer");
//if (Obj != null) Destroy(Obj);
}
private void InitLeaveRoom()
{
Debug.Log("InitLeaveRoom");
_es.Engine.LeaveRoomEvent += OnLeaveRoom;
LeaveRoomRequest lrr = new LeaveRoomRequest();
lrr.ZoneId = room.ZoneId;
lrr.RoomId = room.Id;
_es.Engine.Send(lrr);
}
private void OnLeaveRoom(LeaveRoomEvent evt) {
Debug.Log("OnLeaveRoom");
if (playerSpawnController == null)
{
GameObject gObj = GameObject.Find("NetworkController");
playerSpawnController = (PlayerSpawnController)gObj.GetComponent<PlayerSpawnController>();
}
Application.LoadLevel("Tavern");
}
No errors whatsoever, and does not solve the problem. I'm hesistant to move on given the problem of seeing duplicates, 'copies' of the player. It might just be that condition.
tcarr
04-07-2011, 12:32 PM
I don't understand why you are using GetUsersInRoomRequest which requires a round trip to the ES5. Using ZoneManager doesn't. However, I would think that would work. Can you please add logging lines to NetworkController.handleUserListResponse and PlayerSpawnController.SpawnRemotePlayer? I want to see how many times handleUserListResponse is triggered when you join the room the second time. Have the SpawnRemotePlayer specify the name of the remote user that you are spawning.
If you are running this from the web player and so you don't get logs, have the logging lines output to the chat window instead. Sometimes that is easier anyway, because you see it as it is happening.
joseph
04-07-2011, 07:58 PM
Here is the debug.log:
Leaving the room the first time:
-InitUserList
-InitLeaveRoom
-OnGetUsersInRoomResponse
-outside: user_3281
-OnLeaveRoom
2nd entry into game room scene:
-Chat Controller started..
-NetworkController starting..
-listeners addded
-Create Room Request sent
-Joined a room!
-SpawnLocalPlayer invoked
-StartSending invoked
-NetworkTransform is initializing controller
-sendUserListReuqest
-sendToPlugin
-sendToPlugin
-sendToPlugin
-SpawnRemotePlayeres
-myname = user_3281
-processing user_3281
-obj = {EsObject:n:String = user_3281
MyOwnPosition
2nd time leaving room:
InitUserLiost
InitLeaveRoom
OnGetUseresInRoomResponse
outside: user_3281
OnLeaveRoom
3rd time re-entering room:
-ChatControllerStarted
-NetworkController starting
-listeners aded
-CreateRoomRequest sent
-Joined a room!
-SpawnLocalPlayer invoked
-StartSending invoked
-NetworkTransform is initializing controller
-sendUserListRequest
-sendToPlugin
-sendToPlugin
-sendToPlugin
-SpawnRemotePlayers
-myname = user_3281
-processing user_3281
-obj = {EsObject:n:String = user_3281
-obj = {EsObject:n:String = user_3281
-My Own Position
3rd time leaving room is same as second.
tcarr
04-07-2011, 08:01 PM
Is user_3281 the one that was doing the logging? I need to see where the remote users are being spawned, so I have to know who is the local one and who is the remote one.
tcarr
04-07-2011, 08:03 PM
Ooooohh it does look as if the plugin might be sending you one username twice. Right?
-SpawnRemotePlayers
-myname = user_3281
-processing user_3281
-obj = {EsObject:n:String = user_3281
-obj = {EsObject:n:String = user_3281
-My Own Position
If the plugin thinks that there are two instances of user_3281 in the room then you will get 2 spawning. Hmm.... let me check the plugin code.
tcarr
04-07-2011, 08:14 PM
I think we need to add a logging line to NetworkController.handleUserListResponse. Please log esob.toString() so that we can see whether the plugin is sending the same username twice or not. I don't see how my plugin could do that. We also need a logging line in NetworkController.handleUserEnterEvent so that we know which user just entered the room, because it's possible that the "-obj = {EsObject:n:String = user_3281" might be coming from first the handleUserListResponse and then the handleUserEnterEvent or vice versa, at about the same time.
In PlayerSpawnController.SpawnRemotePlayer, are you doing any checking to make sure that there isn't already a game object with the name you are planning to use? That might be the best fix. As in something like:
GameObject obj = GameObject.Find("remote_" + userName);
if (obj!=null) return;
joseph
04-07-2011, 08:18 PM
This is your code for SPawnRemotePlayer:
public void SpawnRemotePlayers(EsObject[] list, string myname) {
Debug.Log("SpawnRemotePlayers");
Debug.Log("my name = " + myname);
foreach (EsObject obj in list)
{
String thisPlayer = getPlayerName(obj);
Debug.Log("processing " + thisPlayer);
Debug.Log("obj = " + obj.ToString());
if (!myname.Equals(thisPlayer))
{
Debug.Log("need to spawn remote");
SpawnRemotePlayer(obj);
}
}
}
which is invoked here in NetworkController:
private void handleUserListResponse(EsObject esob)
{
//Debug.Log("handleUserListResponse");
EsObject[] players = esob.getEsObjectArray(PluginTags.USER_STATE);
getPlayerSpawnController().SpawnRemotePlayers(play ers, _userName);
//InitUserList();
}
I just did the log with the localPlayer.
tcarr
04-07-2011, 08:20 PM
Yes, and if somehow that method gets invoked twice for the same user, you will get two copies showing in the room. So add a test to make sure that the user that you are going to spawn hasn't already been spawned into the room.
joseph
04-07-2011, 08:34 PM
Here is the new version of it, no errors:
private void handleUserListResponse(EsObject esob)
{
Debug.Log("handleUserListResponse");
EsObject[] players = esob.getEsObjectArray(PluginTags.USER_STATE);
foreach (EsObject obj in players)
{
String myname = PlayerSpawnController.getPlayerName(obj);
if (!myname.Equals(_userName)) {
getPlayerSpawnController().SpawnRemotePlayers(play ers, _userName);
}
}
}
I had to make your getPlayerName function in PlayerSpawnController public and static obviously, to get the name(s).
Didn't solve it! :(
tcarr
04-07-2011, 08:39 PM
I'm really confused at what this code is trying to do. I thought that the problem was that you were seeing two copies of at least some of the other users in the room, right? If that is the case, then checking to make sure that this is a remote user and not the local user won't help. You need to check to make sure that the remote user hasn't already been spawned.
joseph
04-07-2011, 08:44 PM
I am only testing it with two remote users: http://dl.dropbox.com/u/21159096/ES5%20Example/WebPlayer.html
When two remote users are in the game room scene and one of them exits, a copy of that one who exited will be visable on the remote user who did not exit (after having re-entered). I am trying (in any way possible) to prevent spawning copies of remote users after having left..
If I am understanding your reply, the code that I posted will not fix my problem? Just wondering if I am on the right track.
tcarr
04-07-2011, 08:48 PM
Ok, if the other user in the room leaves, I still see him? Then you aren't removing him correctly. Look at NetworkController.handleUserExitEvent - are you seeing the chat line about the user leaving the room? If so, then look at PlayerSpawnController.UserLeaveRoom to make sure that it is removing the remote user.
joseph
04-07-2011, 09:00 PM
Leaving the the game room scene is okay, its coming back (re-entering) that is the problem. When leaving and re-entering the room, the remote user that *did not* leave and re-enter will see a copy of the remote user who did. So I need someway to destroy any possible copies when i come back:
private void handleUserEnterEvent(EsObject esob)
{
Debug.Log("handleUserEnterEvent");
string name = esob.getString(PluginTags.USER_NAME);
GameObject rem = GameObject.Find("remote_" + name);
EsObject[] players = esob.getEsObjectArray(PluginTags.USER_STATE); //key not found
foreach (EsObject obj in players)
{
String myname = PlayerSpawnController.getPlayerName(obj);
if (myname.Equals(name))
{
Destroy(rem);
}
}
if (rem == null && !name.Equals(_userName))
{
// need to spawn player, pick arbitrary spot and move with first position update
esob.setInteger(PluginTags.TARGET_X, 110);
esob.setInteger(PluginTags.TARGET_Y, 150);
getPlayerSpawnController().UserEnterRoom(esob);
SendMessage("AddChatMessage", name + " entered the room.");
}
}
Or am I way off base? Because I am getting the run-time error (just once): KeyNotFoundException: The given key was not present in the dictionary.
I suppose I'll try a different foreach condition, supposedly a List<string> remoteUserNames = new List<string>(); foreach remote user name that joined the game room scene, and destroy if there are two of the same...I suppose I could try that and figure out where to execute that code..
joseph
04-07-2011, 11:05 PM
I'm going to try and start over, copying what I need (such as multiple scenes) and see if I did something stupid early on..
joseph
04-08-2011, 07:04 AM
When testing, the most consistant problem that I see is regarding the following debug line:
-obj = {EsObject n = String = user_n
It happens *twice* in the method: PlayerSpawnControler.SpawnRemotePlayers(EsObject[] list, string myname);
public void SpawnRemotePlayers(EsObject[] list, string myname) {
Debug.Log("SpawnRemotePlayers");
Debug.Log("my name = " + myname);
foreach (EsObject obj in list)
{
String thisPlayer = getPlayerName(obj);
Debug.Log("processing " + thisPlayer); //<-- happens twice
Debug.Log("obj = " + obj.ToString()); //<-- happens twice
if (!myname.Equals(thisPlayer))
{
Debug.Log("need to spawn remote");
SpawnRemotePlayer(obj);
}
}
Why would this happen twice if I only entered once? Thus, I believe we are iterating more than we should be and spawning 'copies' of remote prefabs. What I have also considered is the following code found in PlayerSpawnController.SpawnRemotePlayer(EsObject obj);
private void SpawnRemotePlayer(EsObject obj)
{
Debug.Log("SpawnRemotePlayer");
// Just spawn remote player at a very remote point
Debug.Log("getting position");
Vector3 pos = NetworkTransform.getPlayerPosition(obj);
Debug.Log("getting rotation");
Quaternion rot = NetworkTransform.getPlayerRotation(obj);
//Give remote player a name like "remote_<id>" to easily find him then
Debug.Log("getting player name");
string name = getPlayerName(obj);
Debug.Log("name is " + name);
foreach (string s in NetworkController.remoteUserList) //iterate for all added users, started adding in NetworkController.Start();
{
String thisPlayer = getPlayerName(obj);
if (s.Equals(thisPlayer))
{
return; //Return if we have a duplicate user
}
}
UnityEngine.Object remotePlayer = Instantiate(remotePlayerPrefab, pos, rot);
remotePlayer.name = "remote_" + name;
//Start receiving trasnform synchronization messages
(remotePlayer as Component).SendMessage("StartReceiving");
// show the remote player's name
(remotePlayer as Component).SendMessage("ShowName", name);
}
This all makes sense, because the copy of the player does not follow its orignal. This must be it!
And the list is added here in NetworkController:
void Start() {
public static List<string> remoteUserList = new List<string>( );
//...
remoteUserList.Add(gm.userName); //start building list of all users who joined the game room scene
}
I have attempted many techniques to try and prevent a copy of a remote user being spawned on the remote users screen who *did not exit*. I tested 'adding' the gm.userName's varaible to a List<string> names = new List<string>(); after the room was joined (in NetworkController.cs) and then testing to see if that name exists more than once in virtually every relevent method in the PlayerSpawnController.cs Script. I've come up dry!
I will still try and attempt a solution for this.
tcarr
04-08-2011, 02:05 PM
SpawnRemotePlayer is supposed to just spawn ONE remote player. It is called in two use cases, first when the plugin sends the user list (so NetworkController invokes PlayerSpawnController.SpawnRemotePlayers with an s, which then iterates through the array and spawns each remote user) and second when the plugin sends a user enter message. What your code above does is every time the plugin sends a user enter message, you seem to be adding all the users all over again.
I strongly suggest that you use the version of SpawnRemotePlayer from my example, with the addition of the following lines right after "string name = getPlayerName(obj);":
GameObject gobj = GameObject.Find("remote_" + name);
if (gobj!=null) return;
This will prevent a second game object being spawned for the same user.
joseph
04-08-2011, 06:27 PM
I was so close! You're a genius tcarr! I think that it worked...thanks much!
http://dl.dropbox.com/u/21159096/ES5%20Example/WebPlayer.html
Another thing that I also notice is that when i re-enter the game room scene adding chat messages will 'increment'. For example, if I leave and re-enter, and type a message, two chat messages will be written. If i leave and re-enter a third time, three chat messages will be written, so on and so forth..
tcarr
04-08-2011, 06:34 PM
That sounds as if you are adding the listener for chat messages every time you enter the room or else that you have multiple instances of ChatController. I don't know how Unity works well enough, but is it possible that NetworkController.Start is running each time you load the scene? If so, then you will need to remove the callbacks that it adds, when you leave the room, or perhaps there is some way to check whether the callback has already been added.
joseph
04-08-2011, 06:39 PM
Yes, start is used for initialization for the scene(s) that it is attached in. The debug lines:
Joined a room!
Joined a room!
Joined a room!
Joined a room!
are written to the chat box four times because it was the forth time that I re-entered the game room scene. In regards to:
If so, then you will need to remove the callbacks that it adds, when you leave the room, or perhaps there is some way to check whether the callback has already been added.
What exactly are the call backs? If call backs are not the delgated methods such as es.Engine.JoinRoomEvent += OnJoinRoom; then i dont know and will do much research today to find out :)
tcarr
04-08-2011, 06:43 PM
That's likely the root cause of seeing multiple remote users in the room then - because you were getting multiple copies of each plugin message too. If you look in NetworkController.Start you will see these lines:
_es.Engine.JoinRoomEvent += OnJoinRoom;
_es.Engine.PublicMessageEvent += OnPublicMessage;
_es.Engine.ConnectionClosedEvent += OnConnectionClosedEvent;
_es.Engine.GenericErrorResponse += onGenericErrorResponse;
_es.Engine.PluginMessageEvent += onPluginMessageEvent;
These are what I was referring to as callbacks. I think if we just surround those lines with "if (!started)" that would do the trick. See if that works for you.
joseph
04-08-2011, 06:58 PM
Right, I edited my post almost at the time you were posting that :) (I see now that i have a much more extensive understanding since when i first started).
I tested surrounding the call backs with "if (!started)" but to no avail. :(
I will give this some thought..
tcarr
04-08-2011, 07:04 PM
If the IF block doesn't do it, then when you leave the room, (either right before or in the onLeaveRoom event) add these lines:
_es.Engine.JoinRoomEvent -= OnJoinRoom;
_es.Engine.PublicMessageEvent -= OnPublicMessage;
_es.Engine.ConnectionClosedEvent -= OnConnectionClosedEvent;
_es.Engine.GenericErrorResponse -= onGenericErrorResponse;
_es.Engine.PluginMessageEvent -= onPluginMessageEvent;
This will cause headaches if your other scene needs to hear plugin messages without being in a room, but it ought to do the trick.
joseph
04-08-2011, 07:17 PM
Could you describe headaches? What will I be limited to after leaving the game room scene? I need to join a new room (similar to simpleChat) because I dont need anything regarding animations or spawning, just chatting -- Because it worked! :)
joseph
04-08-2011, 07:22 PM
I am trying to iterate for each user that we know about who joined the game room scene, so could I still use my public static List<string> remoteUserList = new List<string>(); to iterate for each user that the one who is leaving the room *knew* about? For example:
public class ExitGame : MonoBehaviour { //Loads after leaviing the game room scene.
void OnGUI() {
int height = 100;
foreach (string s in NetworkController.remoteUserList)
{
GUI.Label(new Rect(100, height, 100, 100), s, guiStyle);
height += 15;
}
if (Input.GetKeyDown(KeyCode.Escape)) {
Application.LoadLevel("Tavern");
NetworkController.remoteUser.Remove(gm.userName); //pop from our list
}
}
}
And we push on the stack here:
public class NetworkController : MonoBehaviour {
void Start() {
public static List<string> remoteUserList = new List<string>( );
//...
remoteUserList.Add(gm.userName); //start building list of all users who joined the game room scene
}
}
Thus, in the Tavern scene I will need to do a simple chat. I actually implemented that during the first few stages of this thread.
tcarr
04-08-2011, 07:24 PM
When you join the other room, you won't hear the join room event or chat messages, if you remove those callbacks. You certainly don't want to be hearing the JoinRoomEvent twice however. Will the other room have a script similar to NetworkController? If so, then in that room's network controller you add the callbacks you will need, to the methods in that class. Trying to use the same NetworkController class for both rooms would be tricky, but possible.
I'd think that you could still access your list of usernames, but I haven't tried it myself. Since it's a list that you are maintaining, not something built into ES5, it will depend on your own code.
joseph
04-08-2011, 07:27 PM
I edited my post above. And you are correct:
Will the other room have a script similar to NetworkController? I did that prior to confusing the simple chat with the avatar chat, e.g. I was trying to combine them in my first week of analyzing your server, and noticed that it worked (to a certain extent). I'll begin reimplementing that now.
Thanks.
joseph
04-09-2011, 10:03 PM
Is their a way that I can dislay out-of-room users logged in to a prior scene? For example, I want to display the remote users that we were in game with for the user who exits the room. I could find how to transfer the contents of a room.Users list into another that I could reference. What i ended up doing is using the GetUserCountResponse call back here:
//Code in the game room scene, e.g. the users that we want to display for whomever exits
public class NetworkController : MonoBehaviour {
//....
private int mycount = 0;
private int _userCount = 0;
private void OnGUI()
{
if (GUI.Button(new Rect(100,100,100,100), "Leave Room!"))
{
InitUserCount();
InitLeaveRoom();
}
int height = 100;
if (DisplayUsers) //set to true in after sendUserListRequest() is invoked.
{
foreach (User u in room.Users)
{
if (mycount < _userCount)
{
ExitGame.usersInRoom.Add(u.UserName);
mycount = mycount + 1;
}
GUI.Label(new Rect(100, height, 200, 100), u.UserName, s);
height += 15;
}
}
}
private void InitUserCount()
{
_es.Engine.GetUserCountResponse += OnGetUserCountResponse;
GetUserCountRequest gucr = new GetUserCountRequest();
_es.Engine.Send(gucr);
}
private void OnGetUserCountResponse(GetUserCountResponse e)
{
mycount = 0;
_userCount = e.Count;
Debug.Log("UserCountIs: " + _userCount);
}
//....
}
then we try and display the users that we added above in the scene that is loaded below:
public class ExitGame : MonoBehaviour
{
public static List<string> usersInRoom = new List<string>();
void OnGUI()
{
int height = 100;
foreach (string s in usersInRoom)
{
GUI.Label(new Rect(100, height, 200, 100), s, guiStyle);
height +=15;
}
if (Input.GetKeyDown(KeyCode.Escape))
{
Application.LoadLevel("Tavern");
}
}
}
This is most likely not an elegant solution. But is only what I could come up with for now.
tcarr
04-09-2011, 10:16 PM
GetUserCountResponse gives the total number of users currently logged into the server. If you are in a room in same zone as another room, you can use ZoneManager to get the number of users in any room in the same zone. If you need the actual names of the users in a room, send a GetUsersInRoomRequest specifying the roomId and zoneId of the room, and listen for GetUsersInRoomResponse. According to the documentation, that's supposed to give you the usernames of all the users currently in the specified room.
A faster way to get the list as it was at the moment when you leave the room is before you send a LeaveRoomRequest, iterate through room.Users and fill an array or ArrayList that you add to the GameManager object class. That's a "don't destroy" object, so you will have access to it in another scene. It won't update though.
A sneaky way to maintain a constantly updating list of all users in a selected set of scenes is to have each user join one "world room" and store the roomId and zoneId for that world room, and then not leave it while playing in that set of scenes. Since a user can be in multiple rooms, after joining the world room you load one of the normal room scenes. This would not have the users marked as to which one is in which of the scenes but it would give you a constantly updating list of names of users who are playing the set of scenes (using ZoneManager to get the world room, then iterating through the room's list of users).
joseph
04-10-2011, 12:23 AM
Solve that! Thanks for being so clear!
I am trying to understand the networking side of instantiating a prefab for only the remote user who is instantiating it. I also need it to be visable on any other remote users field of view. Thus, I coded the following in a script that is attached to the remotePlayerPrefab:
public class InstPrefabs : MonoBehaviour {
public GameObject instPrefab;
private bool _instPrefabs = false;
public static List<string> usersInRoom = new List<string>(); //users added in NetworkController.OnJoinRoom(JoinRoomEvent evt)
// Use this for initialization
private void Start ()
{
GameObject gameManager = GameObject.Find("_GameManager");
GameManager gm = (GameManager)gameManager.GetComponent<GameManager>();
foreach (string s in usersInRoom)
{
if ( s.Equals(gm.userName)) _instPrefabs = true;
if (!s.Equals(gm.userName)) _instPrefabs = false;
}
}
// Update is called once per frame
private void Update ()
{
if (Input.GetKeyDown(KeyCode.LeftControl) && _instPrefabs)
{
Instantiate(instPrefab, transform.position, transform.rotation);
}
}
}
I am getting some strange results. For example, given two remote users in the game room scene, the prefab instantiated by the remote user will appear in the other remote users position, and then when flipping back to the *that* remote user i can't see the instantiated prefab (the particles). So we flip back to the one who actually instantiated it and the prefab effect is happening.
I suppose the code that I wrote does not take in some important considerations.
tcarr
04-10-2011, 12:42 AM
I really don't understand what you are trying to do. If you need to have a second prefab for a given user, that is visible to all users in the room, then that's going to require either changes to the AvatarChat extension, or else some really tricky workarounds with the AvatarChat plugin messages (which still wouldn't cover the case when a user joins the room later, most likely). If you are comfortable making changes to Java code, I'll outline what would need to be done.
joseph
04-10-2011, 01:05 AM
Prior to this posting, I think it was my code is what is confusing the problem. Here is the edit:
public class InstPrefabs : MonoBehaviour
{
public GameObject instPrefab;
private Vector3 position;
private EsObject data;
private void Start() {
GameObject gameManager = GameObject.Find("_GameManager");
GameManager gm = (GameManager) gameManager.GetComponent<GameManager>();
position = gm.position;
//*tried this as well*
//data = NetworkController.pscData; //*Assigned to obj in PlayerSpawnController.SpawnRemotePlayer(EsObject obj);*
}
private void Update ()
{
if (Input.GetKeyDown(KeyCode.LeftControl)) // *instantiate Particles for "this player" *
{
Debug.Log("Networked Position: " + position + " Unity Position: " + transform.position); // *position = (0,0,0) ?*
Instantiate(instPrefab, transform.position, transform.rotation);//*instantiate a particle effect*
//*Also tried this:*
//Instantiate(instPrefab, NetworkTransform.getPlayerPosition(data), NetworkTransform.getPlayerRotation(data));
}
}
}
Given the scenario of two remote users being in the same room, when I press the LeftControl key on the keyboard the prefab happens at *both* remote users' positions. I just need it to happen for the one user that pressed the left control key.
Question:
Would I need to obtain the Networked Position and not just the Unity Position?
Attempted solutions:
-I added Vector3 and Quaternion variables to the GameManager class and attempted to transfer the data which is set in the NetworkTransformReceiver.InterpolateTransform();
-I statically declared a EsObject type 'psc.Data' to transfer the contents of the obj paramater and reference that as getRemotePlayer(EsObject data) for the 2nd paramater of UnityEngine.Instantiate(prefab, vector3, quaternion);.
// This method is called in every Fixed Update in recseiving mode. And it does transform interpolation to the latest state.
void InterpolateTransform() {
// If interpolationg
if (interpolationPoint < maxInterpolationPoints) {
interpolationPoint++;
float t = interpolationPoint*interpolationDelta;
if (t>1) t=1;
transform.position = Vector3.Lerp(interpolateFrom.position, interpolateTo.position, t);
transform.rotation = Quaternion.Slerp(interpolateFrom.rotation, interpolateTo.rotation, t);
gm.position = transform.position; //*update the position of 'thisPlayer'*
gm.rotation = transform.rotation; //*update the rotation of 'thisPlayer'*
}
//....
Does this make sense for my problem?
To reiterate:
Given the scenario of two remote users being in the same room, when I press the LeftControl key on the keyboard the prefab happens at the other remote users' position. I need it to happen for the one user who pressed the left control key.
tcarr
04-10-2011, 01:44 AM
I still don't understand what you are trying to do. Are you trying to do some kind of effect that is only seen by the user who pressed the key? If so, then this is purely a Unity question, not ES5. You can get the location of the user who pressed the key by looking for the localPlayer game object. If all users in the room have to be able to see this effect, and it's just a transient effect, then you can have the key trigger a plugin request of this type (using Java or AS3 syntax but it will be close enough):
esob.setString(PluginTags.ACTION, PluginTags.BROADCAST_REQUEST);
esob.setBoolean(PluginTags.CREATE_EFFECT, true); // add a new constant to PluginTags for this
esob.setFloat(PluginTags.POSITION_X, x); // X position of where you want the effect
esob.setFloat(PluginTags.POSITION_Y, y); // Y position ditto
esob.setFloat(PluginTags.POSITION_Z, z); // Z ditto
Send that as a plugin request to the the AvatarChat plugin. In NetworkController.onPluginMessageEvent, add an IF branch for the case where action =
PluginTags.BROADCAST_REQUEST, and then spawn the prefab there if PluginTags.CREATE_EFFECT is true. Then you can either have a timer that removes it at a preset time later, or you can send another plugin request with PluginTags.CREATE_EFFECT set to false, to remove the prefab.
If this second prefab is going to have to move, then it's going to be complicated if you don't edit the AvatarChat extension (Java) code.
Oh, if the effect follows the player that created it, then it won't be too terribly difficult to do on the client. Let me know if that's what you need.
joseph
04-10-2011, 09:41 AM
Thanks for the reply!
I implemented that quite nicely, and it worked! However,When I flip back to the other instance, it is not on the player who intantiated the effect (particles); its on the instance that I flipped back to.
tcarr
04-10-2011, 01:52 PM
You are going to have to explain more clearly what it is that you are trying to do, and what didn't work.
joseph
04-10-2011, 07:26 PM
This is exactly it:
If all users in the room have to be able to see this effect, and it's just a transient effect, then you can have the key trigger a plugin request of this type (using Java or AS3 syntax but it will be close enough):
I made the request to the plugin and tested for if the getBoolean(PluginTags.CREATE_EFFECT)) was true. It worked, the particles only happened for the player who pressed the button, but when flipping back to any other remote player, they will be engulfed in the particles!
It seems similar to the spawning problem that I had. I suppose I could test for if the getBoolean(PluginTags.CREATE_EFFECT)) is true on the other remote users end. I can include some code if I am being unclear.
tcarr
04-10-2011, 07:36 PM
If you are using the X, Y, and Z from the plugin event to create the effect, then it should only spawn in the location specified by the user who triggered this. Is that what you want? That is, if player A presses the key, this fixes the single location of the effect, so that player A's client spawns it in that spot, and all other clients will see it in the same spot. I'm not asking if this is what is happening, but if this is what you want to happen, because I still don't know exactly what effect you need.
joseph
04-10-2011, 07:50 PM
In regards to:
If you are using the X, Y, and Z from the plugin event to create the effect, then it should only spawn in the location specified by the user who triggered this
Yes, that is what i need to happen, here is what i have coded.
joseph
04-10-2011, 07:59 PM
I set a new vector3 in the script that contains the prefab declaration, and it still didn't work out properly: http://dl.dropbox.com/u/21159096/ES5%20Example/WebPlayer.html
tcarr
04-10-2011, 08:01 PM
I'm sorry, but I just don't know enough about Unity to help I think. That is, I can't read the Unity code and grasp what it is trying to do. The way I would do it would be similar to the way that a remote prefab is spawned.
joseph
04-10-2011, 08:16 PM
Your understanding of what I am trying to do is correct:
If you are using the X, Y, and Z from the plugin event to create the effect, then it should only spawn in the location specified by the user who triggered this. Is that what you want? That is, if player A presses the key, this fixes the single location of the effect, so that player A's client spawns it in that spot, and all other clients will see it in the same spot. I'm not asking if this is what is happening, but if this is what you want to happen, because I still don't know exactly what effect you need.
The below is included in your NetworkController script which prepares to trigger the event:
I am not sure of the problem. I believe to be taking the necessary networking steps in order to get that effect to happen correctly.
joseph
04-10-2011, 08:58 PM
I think that I solved it, I needed to set the _x, _y and _z component values to a static vector in NetworkController, and reference that vector3 in InstPrefabs script. Thanks! :)
--Edit:
Nope, the particles are *not* visable on any other remote users end.i Notice that it can be seen and does happen on the position of the NetworkController game object. I could supposedly attach as a child to the remotePlayerPrefab or localPlayer parent..
Edit2: With a few modifications, attaching the NetworkController to the remotePlayerPrefab and the localPlayer(For testing) did work! I can see the particle effect happening in one instance when I triggered it, and I saw it happening on the other instance that did not trigger it; and vice versa..
I have to fix a couple more things, but I think that the result is good. I will post back when I work out the remaiing problems..It seems that problems have a hierarchy! :P
Its almost their! It is a little weird, and i have to force some of it to happen...soo close! :)
http://dl.dropbox.com/u/21159096/ES5%20Example/WebPlayer.html
tcarr
04-11-2011, 12:14 PM
Glad to hear that you are making progress! You are tackling something far more complicated than anything I've tried in Unity.
joseph
04-11-2011, 08:05 PM
Hi tcarr!! :)
Thanks for the response! I'm having some inconsistant problems with instantiating. Then, whats worse, is that when I leave the room and come back I won't be visable to the other remotePlayer!
Problems with the instantaition include 1 consistant run-time error regarding:
KeyNotFoundException: The given key was not present in the dictionary.
System.Collections.Generic.Dictionary`2[System.String,Electrotank.Electroserver5.Api.EsObj ectDataHolder].get_Item (System.String key)
It seems that when this happens, the particles that I attempt to instantiate for the 'thisPlayer' appear at the NetworkControllers position; unfortunatly, not at the remote players position. This happens *sometimes* but frequently enough for it to be noticable. When I test it, it seems that I have to move around to get it to happen at the remote players position.
Here is the code leading up to the instantiation. The remote player prefab is dropped into the 'Player' slot in the NetworkController script so that I can set its updated position in the PluginRequest:
Its just too inconsistant for me to move on..The particles are being instantiated *sometimes* at the NetworkControllers posiition, and when I leave and re-enter (only after attempting to instantiate) the other player will not be in the scene (although my list shows otherwise which is strange).
tcarr
04-11-2011, 08:09 PM
KeyNotFoundException is probably "you are trying to get a value from the EsObject and that variable isn't in the EsObject". Try logging the plugin message EsObject before you attempt to instantiate the prefab, so that you can see what is missing. For example, if the plugin message that has the create boolean set to false doesn't have values set for X, Y, and Z, but you are trying to read those values anyway, you will get an exception.
joseph
04-11-2011, 08:16 PM
And I presume that this error is causing the instantiation to occur at the NetworkControllers position (0,0,0)?
joseph
04-11-2011, 08:31 PM
With the execption of the run-time errors, the concept happens beautifully when running as the localPlayer only. Which meant that I dragged the localPlayer prefab into the public GameObject Player; slot, in the NetworkController Script.. All I do to test it as a remote player, is replace the localPlayer prefab with the remotePlayerPrefab, then test my results online..I don't know what is causing the inconsistancy..
When I attempt to see if the key 'variableExists' is this:
private void OnPluginMessageEvent(PluginMessageEvent evt)
{
if (evt.Parameters.variableExists(PluginTags.POSITION _X))
{
Debug.Log("Does not exist!");
return;
}
}
chengen
04-11-2011, 08:38 PM
Does this mean there is no built in LeaveRoomRequest in ES5? What about KickUserFromRoom kind of thing on the server side?
joseph
04-11-2011, 09:17 PM
I don't know what you mean by built in. Following the documentation here: http://www.electrotank.com/docs/es5/client/csharp/ and click the API branch.
..Leaving a room is quite easy. Instantiating prefabs and having it work properly is not. Because now for some reason (I don't knwo what I did), Unity is pasuing itself after I instantiate a prefab..Problems indeed have a scary heirarchy..
chengen
04-11-2011, 09:25 PM
I see. Sorry about the interruption to the thread.
joseph
04-11-2011, 10:18 PM
So apparently, now unity is doing some kind of weird crash because keys either already exist, or are not present:
This is dissapointing because I think the only technique that can enable me from instantiating prefabs is by making a new plugin request. The above plugin request occurs when I attempt to instantiate a prefab by pressing a key on the keyboard.
tcarr
04-11-2011, 11:38 PM
Does this mean there is no built in LeaveRoomRequest in ES5? What about KickUserFromRoom kind of thing on the server side?
Yes there is a LeaveRoomRequest in the ES5 client api. It works well. Joseph is trying to do a complicated Unity application, and Unity clients have what I consider to be weird behavior. Scripts are tied to game objects in a scene, so that if you leave a scene, it will destroy the instance of the script that you made when you loaded the scene, unless you actively prevent that from happening, but then there are problems when you join the scene again. Grrrr....
tcarr
04-11-2011, 11:42 PM
Joseph, I'd feel more comfortable if you create an EsObject variable and then set the keys into it, than if you just use pr.Parameters directly. I don't trust pr.Parameters.setString...
You just created a new EsObject for the parameters, so you will need to set the ACTION variable. It won't already be there, if you just created a new EsObject, and if the ACTION is missing, the plugin is going to send you an error message instead of broadcasting the message to the room. After you finish filling your EsObject variable, then set it into the PluginRequest's Parameters.
joseph
04-12-2011, 12:15 AM
You just created a new EsObject for the parameters
Yes..EEK! I noticed that :) Been trying to find a work around, like creating a handleUserInstantiateEvent(EsObject esob); if you will. I know that it would get invoked based on a string (action) comparison e.g. if (action == "inst"); get positon and rotation and inst..But what is the step before we compare the action string to the event?
tcarr
04-12-2011, 12:20 AM
I strongly suggest that you look at the way that the example AvatarChat's NetworkController.onPluginMessageEvent works. First it looks for the ACTION variable. It then decides what to do with the event based on the value of the ACTION variable. Since we are only using the broadcast action for the special effect, that's when we would deal with it.
joseph
04-12-2011, 12:36 AM
where could I obtain the EsObject object 'esob' to use esob.setString("instantiate"); that way i could test for it in NetworkController.onPluginMessageEvent. I can't really obtain the object in FixedUpdate..I need to get the input e.g. LeftControl before i set the action..
tcarr
04-12-2011, 01:13 AM
I really don't understand your question here. When the plugin sends a plugin event, NetworkController.onPluginMessageEvent will get it. The AvatarChat example version of that function is:
private void onPluginMessageEvent(PluginMessageEvent e)
{
if (e.PluginName != PLUGIN_NAME)
{
// we aren't interested, don't know how to process it
return;
}
EsObject esob = e.Parameters;
//trace the EsObject payload; comment this out after debugging finishes!
//Log("Plugin event: " + esob.ToString());
//get the action which determines what we do next
string action = esob.getString(PluginTags.ACTION);
if (action == PluginTags.POSITION_UPDATE_EVENT)
{
handlePositionUpdateEvent(esob);
}
else if (action == PluginTags.AVATAR_STATE_EVENT)
{
SendAnimationMessageToRemotePlayerObject(esob);
}
else if (action == PluginTags.USER_LIST_RESPONSE)
{
handleUserListResponse(esob);
}
else if (action == PluginTags.USER_ENTER_EVENT)
{
handleUserEnterEvent(esob);
}
else if (action == PluginTags.USER_EXIT_EVENT)
{
handleUserExitEvent(esob);
}
else
{
Log("Action not handled: " + action);
}
}
So just add another branch to the IF structure, such as inserting
else if (action == PluginTags.BROADCAST_EVENT)
{
handleUserInstantiateEvent(esob);
You just have to make sure that when you send the PluginRequest, that you have added a line similar to:
esob.setString(PluginTags.ACTION, PluginTags.BROADCAST_REQUEST);
before you send the request to the server. If you don't set that action, the message won't get broadcast to the room anyway because the plugin relies on the ACTION tag to tell it what to do with a request.
Edit: and it will help with debugging if you uncomment the line that logs esob.toString(). This will tell us the exact format of the incoming plugin event, so that if there's a bug, we have a chance of fixing it.
joseph
04-12-2011, 02:06 AM
What I think that is confusing me is that I need to use esob.setString(PluginTags.ACTION, PluginTags.BROADCAST_REQUEST); after I trigger the event e.g. by pressing the LeftControl key:
void FixedUpdate() {
if (Input.GetKeyDown(KeyCode.LeftControl)) // happens when user presses the left control key
{
//we don't have access to a EsObject here in fixed update
//how can I use: esob.setString(PluginTags.ACTION, PluginTags.BROADCAST_REQUEST); here?
}
}
It seems that I would need to set the action key after pressing the 'hot key' to invoke the correct if statement in NetworkController.onPluginMessageEvent.
tcarr
04-12-2011, 02:37 AM
Ok, what you need is how you make the PluginRequest to send to the server when the key is pressed. What I would do is invoke a method from inside FixedUpdate. I'd look at the way that NetworkTransformSender.cs has a FixedUpdate function that calls the DoSend function on an instance of NetworkTransform. Whatever class has your key listener could do something similar, keeping an instance of NetworkTransform in the class, and add a different function to NetworkTransform that would create the right kind of plugin request.
joseph
04-12-2011, 06:16 AM
In regards to:
You just have to make sure that when you send the PluginRequest, that you have added a line similar to:
esob.setString(PluginTags.ACTION, PluginTags.BROADCAST_REQUEST);
before you send the request to the server. If you don't set that action, the message won't get broadcast to the room anyway because the plugin relies on the ACTION tag to tell it what to do with a request.
Alright so starting from the beginning: we declare a private boolean variable in NetworkController and set it to false. Then set it to true when we press the leftControl key:
//....
void FixedUpdate() { //method need to process input
if (started) {
/*
* Dispatch events from the Electroserver instance internal event queue.
* Being in fixed update ensures it occurs in a timely fashion independent
* of frame rate.
*/
_es.Engine.Dispatch();
}
if (Input.GetKeyDown(KeyCode.LeftControl))
{
inst = true;
Debug.Log("Prepare to Instantiate Particles At the Player's position");
}
}
//....
Then we wrap getting the positon and rotation in an if block that only executes if inst is true (e.g. when the user pressed the LeftControl key):
private bool inst = false;
public static Vector3 position;
public static Quaternion rotation;
private void handleUserInstantiateEvent(EsObject esob)
{
if (inst)
{
position = new Vector3(esob.getFloat(PluginTags.POSITION_X), //I still get key not found
esob.getFloat(PluginTags.POSITION_Y),
esob.getFloat(PluginTags.POSITION_Z));
rotation = new Quaternion(esob.getFloat(PluginTags.ROTATION_X),
esob.getFloat(PluginTags.ROTATION_Y),
esob.getFloat(PluginTags.ROTATION_Z),
esob.getFloat(PluginTags.ROTATION_W));
}
}
And now, NetworkController.onPluginiMessageEvent needs to know about this method:
private void onPluginMessageEvent(PluginMessageEvent e)
{
if (e.PluginName != PLUGIN_NAME)
{
Debug.Log("Aren't Intersted..Don't know how to process it");
return;
}
EsObject esob = e.Parameters;
Debug.Log("Plugin event: " + esob.ToString());
//get the action which determines what we do next
string action = esob.getString(PluginTags.ACTION);
Log("Action: " + action);
if (action == PluginTags.POSITION_UPDATE_EVENT)
{
handlePositionUpdateEvent(esob);
}
//......
else if (action == PluginMessage.BROADCAST_EVENT)
{
Log("Broadcasted event..");
handleUserInstantiateEvent(esob);
}
else
{
Log("Action not handled: " + action);
}
}
Thus, in NetworkController.sendToPlugin(EsObject esob); we have:
/**
* Sends formatted EsObjects to the plugin
*/
public void sendToPlugin(EsObject esob)
{
if (room != null && _es != null)
{
//set another action for the onPluginMessageEvent to know about
esob.setString(PluginTags.ACTION, PluginTags.BROADCAST_REQUEST); //Key already Present error! :(
//build the request
PluginRequest pr = new PluginRequest();
pr.Parameters = esob;
pr.RoomId = room.Id;
pr.ZoneId = room.ZoneId;
pr.PluginName = PLUGIN_NAME;
//send it
_es.Engine.Send(pr);
}
}
Is this process correct step-by-step? I think that the NetworkController.onPluginMessageEvent will know about the broadcasted event regardless of whether i press the 'hot key' or not. But I can't test it really because I am getting an error in the sendToPlugin, I cannot set the key that I need to (e.g. the BROADCAST_REQUEST).
ArgumentException: An element with the same key already exists in the dictionary.
System.Collections.Generic.Dictionary`2[System.String,Electrotank.Electroserver5.Api.EsObj ectDataHolder].Add (System.String key, Electrotank.Electroserver5.Api.EsObjectDataHolder value)
tcarr
04-12-2011, 01:04 PM
I need to see the code for when you invoke sendToPlugin, because that is where you are building the EsObject that gets sent. It's probably going to be cleanest if you don't change the esob from inside sendToPlugin. Instead, build a new esob wherever it is that you notice that you need to send a plugin request, and then call sendToPlugin passing in that esob.
You can create a new EsObject from ANY class. Just add the right using line. It's going to be one of these two - I'd have to look up which one.
using Electrotank.Electroserver5.Api;
using Electrotank.Electroserver5.Core;
Each time you send a plugin request you should create a fresh EsObject. I assume that you are seeing the key already exists error because you are trying to take an EsObject that already has an ACTION key and change the value of it. I would think that you should be able to do that anyway if the esob was created in the same class, but perhaps strange things happen in C# when you pass objects between classes.
The chain of events should go in this order:
1. Key is pressed
2. EsObject is created with the information packed into it
3. sendToPlugin is invoked, passing in the correctly constructed esob
4. plugin processes the request (changing the action from a request to an event) and broadcasts it to the room
5. onPluginRequestEvent is triggered on all clients in the room, which should notice that this was a BROADCAST_EVENT and process it
joseph
04-12-2011, 11:23 PM
Solved it! Which is great, i could not have done it without your help! I do have another question, regarding changing the contents of the gm.userName variable after connecting and logging in. It seems that when I attempt to do this, I will spawn a duplicate player..The code below attempts to overwrite the contents of the gm.userName that was used to login:
public class Selection : MonoBehaviour {
private ElectroServer _es;
private void Start()
{
GameObject gameManager = GameObject.Find("_GameManager");
GameManager gm = (GameManager) gameManager.GetComponent<GameManager>();
_es = gm.es;
}
private void OnGUI() {
GUI.Label(new Rect(100, 116, 100, 20), "Char Name: ");
NameInput = GUI.TextField(new Rect(200, 116, 200, 20), NameInput, 25); //is conceptual for changing characters
if (GUI.Button(new Rect(500, 100, 100, 100), "Chat"))
{
if (!String.IsNullOrEmpty(NameInput)) gm.userName = NameInput; //Assign the users "CharacterChoice"
Application.LoadLevel("Chat"); //Load the scene regardless for now *temporary*
}
}
}
Essentially, before I join the game room scene, I need to overwrite the contents of the gm.userName variable. I cannot just simply specify that when/before logging in. I know that I cannot login twice. What happens when I implemented the above code is that I spawned a copy of the player. One tagged as a random user name the other as the name that i chose.
I tried to pin prick through the network controller and player spawn controller to see if I could get it to work but came up dry. I will continue to work at this.
tcarr
04-13-2011, 12:00 AM
The AvatarChat example was implemented based on the label of the user being the username, which will not change. If you want an "Avatar Name" to show, then one way to do this is to add a UserVariable to the user with the avatar name. Don't change the gm.userName variable, because there are multiple places that use that to determine whether a plugin message is about this local user or about one of the remote users, so if you change it, then you won't be able to recognize any of the messages as being about yourself.
Each user adds an optional UserVariable that is the avatar name (character name).
Add an ES5 listener (callback) for the user variable events
maintain a dictionary that can look up the character name, given the username
modify the class that adds the username to the avatar so that it consults the dictionary instead and uses the character name (with a default of the username)
See the UserVariables code example. Yes, it's in AS3, but between that and the code docs you should be able to figure it out.
tcarr
04-13-2011, 12:03 AM
Oh yes, in NetworkController.JoinRoom, you will need to comment out this line:
crr.ReceivingUserVariableUpdates = false;
You may run into difficulty if there are lots of users joining the room. There's a better way to do this but it involves modifications to the plugin, and I gather that you aren't comfortable with Java.
joseph
04-13-2011, 07:54 AM
Thanks for the reply tcarr! :)
Regarding the userVariables, I have two other individual classes attached to their respective scenes after logging in: Selection and Chat. Selection as you might imagine is for providing input in a text field as this is similar to choosing between multiple characters. We provide input, then after clicking a button, the scene changes to the Chat which invokes the method Selection.InitUserVariable() (similar to Chat except we are getting and not setting). I have defined that to make the UserVariablesUpdateRequest directly following where we 'set' the 'uuvr.Name' to our input 'NameInput'. The callback is invoked in Selection.Start().
So that Chat Scene loads immediately after this happens which is where we want to iterate through out collection of 'user names'. But I couldn't quite come up with it, for some reason, 'e.Variable.Name' in Chat.OnUserVariableUpdateEvent() is a collection of chars (subStrings), why would that be if I assigned a string to it when i set it intially?
I am taking input with me between scene transitons..I think that it would work if I could iterate a string through some list in a UserVariableUpdateEvent object..Could find one, I'll keep looking.
tcarr
04-13-2011, 12:37 PM
Where you do set the value of the user variable? You need both a name and a value set in it. The value is an EsObject.
joseph
04-13-2011, 06:56 PM
When I try and set the value to a fresh EsObject it will give me a run-time error. Do I have to be in a room to set these variables?
tcarr
04-13-2011, 06:58 PM
No, you just have to be using the right Electroserver namespace, and setting non-null values. Try logging each line of the method, so that you can see how far it gets before the run time error is thrown.
joseph
04-13-2011, 08:48 PM
Strange that the callback is not invoked for some reason..
tcarr
04-13-2011, 09:03 PM
Where are you creating your user variables in the first place? I need to see the code snippet that does that. For AS3 it would look something like this.
var uuvr:UpdateUserVariableRequest = new UpdateUserVariableRequest();
uuvr.name = "myDescription";
uuvr.value = new EsObject();
uuvr.value.setString("hairColor", "brown");
uuvr.value.setInteger("age", 35);
//send to the server
_es.engine.send(uuvr);
Then you listen for UserVariableUpdateEvent. You should be able to see the user variables of anybody in the room that you are in without sending an ES5 request asking for it, using the UserManager.userByName("hisusername"), which returns a User object. User.userVariables should be an array of UserVariable objects, each of which has a name and a value (EsObject).
It's also possible to add a user variable to the LoginRequest, if you know the value at that time.
joseph
04-13-2011, 09:44 PM
Right. Sorry for not posting where I am setting the uuvr object:
[code]
The contents of the locally scoped userVar read as:
UserVar is: Electrotank.Electroserver5.Api.UserVariable
Sorry for not posting that first. I wasn't clear as that to be the distinct problem.
tcarr
04-13-2011, 10:39 PM
You are setting the user variable to an EMPTY EsObject? That might be the problem. Try creating a variable that is an EsObject, then using setString and setInteger on that EsObject, and logging the esob.toString(), then setting uuvr.Value to that esob.
Also, in OnUserVariableUpdateEvent instead of using Debug.Log("UserVar is: " + userVar); try Debug.Log("UserVar is: " + userVar.toString()); . I'm pretty sure that UserVariableByName returns an EsObject, but in either case you can't log userVar by itself and expect to get anything useful.
Alternately, you should be able in OnUserVariableUpdateEvent to get the EsObject from the user variable by using e.Variable.Value which you can then do toString() on to log.
edit: what you need to do is have the name of the user variable be "name" and then in the EsObject you setString("name", avatarName). That way if a user has multiple user variables you won't have to guess which one is his avatar name.
Or just give up on user variables, and use Broadcast to tell everybody in the room what your avatar name is, then keep a data structure with the names.
joseph
04-13-2011, 10:47 PM
The contents of the locally scoped userVar, even when using .ToString() on it when logging it is: Electrotank.Electroserver5.Api.UserVariable. I also set the user varaible to a non-empty EsObject:
I might just continue to the other scene and iterate to see if multiple users are added to a list via GetUserVariableRequest..
tcarr
04-13-2011, 10:56 PM
Ok, I'm going to create a ticket to determine whether user variables work correctly from Unity C# api.
Why don't you just do something similar to the "tell everybody that you pressed a key" broadcast message, that gives the value of your avatar name? Or you could even just add the avatar name to the position update object when that gets sent, although that's overkill.
joseph
04-13-2011, 11:00 PM
Yes, I am sure their is a work around if user variables do not work corectly. I actually think it would be much easier if a create a "World Room", as per your suggestion here:
A sneaky way to maintain a constantly updating list of all users in a selected set of scenes is to have each user join one "world room" and store the roomId and zoneId for that world room, and then not leave it while playing in that set of scenes. Since a user can be in multiple rooms, after joining the world room you load one of the normal room scenes. This would not have the users marked as to which one is in which of the scenes but it would give you a constantly updating list of names of users who are playing the set of scenes (using ZoneManager to get the world room, then iterating through the room's list of users).
I was wondering how I would go to set that up? For example, after connecting and logging in, the scene after that is when i would 'start' the world room. I did experience problems with leaving and re-entering the room without registering the callback: _es.Engine.JoinRoomEvent -= OnJoinRoomEvent;
tcarr
04-13-2011, 11:19 PM
You could do the room join in the same scene that creates NetworkController, and just join the world room first, then in the join room event handler if your world room variable is null, you know you just joined that room, so you store it and send a join room request for joining the chat room.
joseph
04-13-2011, 11:45 PM
Does joining a 'world room' mean that you join 1 room and never leave it?
tcarr
04-14-2011, 12:14 AM
yes, join one room, and never leave it. then after you finish joining that room, join the one that uses avatar chat, so that you are in TWO rooms at the same time, even if you leave the avatar chat room and then come back again several times - you will still be in the same world room.
joseph
04-14-2011, 01:00 AM
It seems (and makes the most sense to me) to leave the room when I change scenes *backwards* to select another character. However, not *forwards* to proceed to the AvatarChat. When I am in the AvatarChat game room scene, I commented out the JoinRoom and JoinRoomEvent callback. I also registered the callback GetRoomsInZoneResponse but it is acting strangely, for example, I am getting a "RegistryTransactionEncounteredError" in NetworkController when preparing to send a public message..Here is what I Modifed from the "Tavern" (a chat scene) to the "AvatarChat":
Trying to get this to 'just work'. I will need to set or add a specific user name to the collection of room.Users in the SelectionScene.
tcarr
04-14-2011, 01:40 AM
I'm lost. I just don't understand what the heck you are doing when you just give me your source code. Add logging lines to your code in the section that you think is throwing the error, so that you can pinpoint the lines of code that are causing the error. If you try to send a public message to a room that you aren't in, that will cause an error, for example. I'd have to see the exact error message to be able to help you.
I'm not a coworker by the way. I'm here to help you over the humps with ES5, not to help you implement your project. I will answer specific questions about ES5, and try to help you fix specific bugs, but you have to give me enough information that I can see what the problem is quickly.
joseph
04-14-2011, 02:00 AM
Right, I apologize for posting prematurely.
I created a room prior to the AvatarChat. This room a type of "SimpleChat" and its purpose is to display the users that are essentially 'chatting' or preparing to join a match e.g. the AvatarChat..The best version of this is apparently making the scene 'joinable' and 'leaveable'. That way, when I enter the game room scene (the AvatarChat) nothing will go terribly wrong..However, when I leave and attempt to re-enter it apparantly "kicks" me from the room. THis never happend before adding a new room prior to the game room scene (the AvatarChat). The error is regarding the playerSpawnController:
The debug log shows that I attempted to leave the room..when re-entering. The way that I have it set up is that users can only leave the room by clicking a button. This is how I know that something made it to where I got kicked automatically when re-joining.
I solved it but it requires another step (removing the user who entered the game and starting/getting an in-game user list). Here are the steps prior to supposedly "hiding" the loginRequest.UserName with members in the Room.Users list: being able to join a new room, leave a new room, needing to fix a run-time error between the playerSpawnController, changiing the plugin name and the plugin extension name, and finding which true/false setting the ccr boolean types to.
So to attempt removing from user who entered the game and starting/getting an in-game user list I suppose I could start registering some call backs :) I hope I am not setting myself up for dissapointment if I cannot modify the contents of a user the Room.Users list (because of the recently made ticket regarding UserVariables).
tcarr
04-14-2011, 12:57 PM
The world room doesn't need any plugin attached to it, because PublicMessageRequest doesn't use a plugin. That might help right? You only need the AvatarChat plugin for a room where you need to broadcast position updates, etc.
Odds are there's no bug with the user variables. I'll let you know.
Question: would it be possible to ask the user to specify his avatar name before he logs into ES5? Of course if you want to allow the user to change his avatar name in the middle of a game you would still need to be able to send user variable update requests.
tcarr
04-14-2011, 02:53 PM
Assuming you just need to set the avatar name before you log on to ES5, it's a piece of cake. I haven't tested changing later yet. I made the following changes to SimpleChat (Unity C# version) and it works perfectly.
private string avatarName = "";
in OnConnect:
if (avatarName.Length < 1)
{
avatarName = userName;
}
EsObject avob = new EsObject();
avob.setString("av", avatarName);
Dictionary<string, EsObject> uvs = loginRequest.UserVariables;
if (uvs == null) {
Debug.Log("uvs == null");
uvs = new Dictionary<string, EsObject>();
}
uvs.Add("av", avob);
loginRequest.UserVariables = uvs;
in LoginUI:
GUILayout.BeginArea (new Rect (Screen.width / 2 - 150, Screen.height / 2 - 100, 300, 300), "", "box");
GUILayout.Label("AvatarName");
avatarName = GUILayout.TextField(avatarName, 24);
in ChatUI:
foreach (User u in currentRoom.Users) {
UserVariable uv = u.UserVariableByName("av");
string avName = uv.Value.getString("av");
GUILayout.Box(avName);
}
This allows the user to specify an avatar name right there on the login UI, then in the chat UI the user list shows the avatar names instead of the usernames. I'll test changing the avatar name next.
tcarr
04-14-2011, 03:15 PM
Changing user variables works perfectly too. Here's how to do it.
When you know the string you want for the avatar name:
UpdateUserVariableRequest uuvr = new UpdateUserVariableRequest();
uuvr.Name = "av";
EsObject value = new EsObject();
value.setString("av", newAvatar);
uuvr.Value = value;
es.Engine.Send(uuvr);
With the other changes, this worked automatically for my SimpleChat example, however you may need to trigger a label change explicitly. Here's how to listen for the events and process them. First, where you add the rest of the listeners, add
es.Engine.UserVariableUpdateEvent += OnUserVariableUpdateEvent;
Then add the handler:
void OnUserVariableUpdateEvent(UserVariableUpdateEvent e)
{
string newAvatar = e.Variable.Value.getString("av");
string forUser = e.UserName;
Debug.Log("New avatar for user " + forUser + " is " + newAvatar);
}
Please note that you will need ReceivingUserVariableUpdates to be true for the room where you need this information, so check your CreateRoomRequest and make sure that you aren't setting ReceivingUserVariableUpdates to false. The default is true.
joseph
04-14-2011, 08:33 PM
A lot of good information in the replies, thanks a lot!
I seem to only get as far as 'setting' the name in the UserVariables, I can't seem to 'get' the userVariables that I set a scene prior and iterate through that list via a callback. For example, in the 'selection' scene where I 'end' the scene with some input:
The error when declaring in the foreach condition is:
Cannot convert type `System.Collections.Generic.KeyValuePair<string,Electrotank.Electroserver5.Api.EsObject>' to `string'
What could is compatable with e.UserVariables?
tcarr
04-14-2011, 08:54 PM
e.UserVariables is a collection of UserVariables, not a collection of strings. You might want to try doing foreach(UserVariable uv in e.UserVariables) and see if that works.
See if this tutorial (http://www.electrotank.com/docs/es5/manual/index.html?avatar_names.htm) that I just uploaded helps.
joseph
04-14-2011, 09:32 PM
Still can't quite pull it together. I think that if I set a name key after making the UpdateUserVariablesRequest to a value that the user chose, I might be able to iterate similarly to: foreach(UserVariable uv in /*some event object*/), but I get a run-time error:
And then I might be able to iterate through a list that has a compatable UserVariable type..In the next scene.
tcarr
04-14-2011, 09:36 PM
You have to use the username for User, which would be e.UserName I think. If you use "joseph" for the username in looking up the User variable, then you are going to get null, so of course it crashes.
joseph
04-14-2011, 10:02 PM
It crashs again when I try and iterate in another scene: the string "name" is the key that I used when setting the name to begin with..
tcarr
04-14-2011, 10:35 PM
Odds are you have a null pointer somewhere. Add some logging lines to your loop. For example, is uv null? Is uv.Value null? What does uv.Value.getString("name") return? What is the value of s in that GUI.Label line? Oh and check that room isn't null, and room.Users isn't null. If you aren't currently in the room, then you won't be able to get the list of Users at all.
These are basic debugging techniques which you should already be familiar with.
joseph
04-14-2011, 11:41 PM
Solved it! :)
Thanks so very much tcarr you have been amazing!
tcarr
04-15-2011, 12:24 AM
Whew! Here's hoping the rest is smooth sailing.
joseph
04-15-2011, 01:18 AM
Sure hope so. It works in terms of how my existing project is set up; which is awesome. For now, I am working on displaying the correct user name when sending a chat message. I suppose I would need to iterate and determine which name to display. I will keep woking on that.
tcarr
04-15-2011, 01:52 AM
No, the evt should give the username of the user who sent the chat message. Just use that instead of _userName for getting the User u. You are close!
joseph
04-20-2011, 06:39 AM
I am happy to report that it was a successful implementation/integration into my exisiting project! Thanks again for your help! I do have another question regarding needing to assign a Room variable Id to one that is null:
By assigning the roomId, what I am hoping to acheive is the concept of instantiating prefabs for multiple instances, but instead, declare, invoke and register everything that makes it possible in seperate script that is *not* attached to the NetworkController game object. This would be ideal as it is the way that I already have it set up. If i could assign/find the room id I might be able to know if what I am trying to do is possible in a different script that is attached to the player.
Thanks agian for your help.
tcarr
04-20-2011, 01:39 PM
First comment: griz.ZoneId = "GameZone"; was a typo in the documentation. You have to specify the zone in the request, but a zoneID is an integer. ZoneName is a string.
Second comment: if you want to know the roomIds of all the rooms that you are currently in, use ZoneManager to get the list of rooms in the zone that you are in, iterate through the list, and pull the ones that have the isJoined property of true. Since the rooms are created dynamically there's no way to know what the roomId will be if the room doesn't exist at the moment.
If your various rooms are all part of a larger game, you may want to have a plugin keep track of the rooms for the given game. If the rooms are part of a virtual world (so that users can enter the rooms whenever they wish) then you may want to use persistent rooms which will still exist even if there are no users in them.
joseph
04-20-2011, 08:22 PM
I need to get my hands on the room.Id and room.ZoneId, so i can send that information when making a plugin request. If I just register the call back delegate: JoinRoomEvent += OnJoinRoom; to get the room.Id and room.ZoneId that way, would I be joining the same room that I am currently in?
scripting component a -- joins room on start
scripting component b -- joins room on start
so they both are in the same room..
I am trying not to get an error like: User already in room! And this is through the ZoneManager as per your suggestion.
tcarr
04-20-2011, 08:39 PM
If you have es.Engine.JoinRoomEvent += OnJoinRoom; in multiple active scripts, then a single JoinRoomEvent will trigger the function in each of those scripts.
What you can do about the "user already in room!" is check before sending the JoinRoomRequest to see if you are already in the room that you want to join. You can tell which rooms you are in by using es.ManagerHelper.ZoneManager.ZoneByName("nameOfZoneHere").Rooms to get the list of rooms in the zone that you are in, and iterate through that list of Room objects, checking for the IsJoined property being true. If IsJoined, then you are already in that room, so don't send a JoinRoomRequest.
Your client will be quite confused if you are in more than one room at a time that has a plugin attached. If you are only going to be in one room at a time with a plugin attached, then you can just store the current Room variable in the GameManager object class, and then have your script grab the roomId and zoneId when you need it.
If you do need to be in more than one room at a time that has a plugin attached, then have some kind of data structure in the GameManager object class that can store the Room objects with a key that lets you know what scene or script each room goes with.
edit: If it's exactly two rooms, then it's pretty easy. if (worldRoom == null) { // we just entered the worldRoom, so store it in the worldRoom variable} else { // store the room in the currentRoom variable instead }
joseph
04-20-2011, 09:07 PM
In regards to:
then you can just store the current Room variable in the GameManager object class, and then have your script grab the roomId and zoneId when you need it. That sounds like the best idea.
joseph
04-20-2011, 10:44 PM
I was able to store the room variable into a game manager variable. Thanks for the advice i'll see what I can overall to acheive my goal. It sseems that I would also need to invoke the onPluginMessageEvent delegate and send a new plugin request to set player.transform.position data to the plugin, when making a request for it, and trigger the onPluginMessageEvent their, in that aseondary script
tcarr
04-20-2011, 10:47 PM
try gm.currentRoom = room
joseph
04-20-2011, 10:48 PM
Yup, i did. I seem to have posted a little prematurely.
joseph
04-20-2011, 11:10 PM
That worked! However, I am instantiating an effect for both remote users when only one remote user triggers the instantiation. I think this is because the "InstPrefabs" script is attached to both the remote and local player..I'll try removing that script from the remotePlayerPrefab and onkly including it on the localPlayer (as I did with my gui scripts for my existing project).
How is the position triggered in the onPluginMessageEvent? Should I need to do this? It does not work at all when moving and the needing to instantiate (no instantiations are seen).
tcarr
04-20-2011, 11:53 PM
The way that the AvatarChat example works, instantiation is triggered by NetworkController.onPluginMessageEvent getting a plugin event from the AvatarChat plugin that has action == PluginTags.USER_ENTER_EVENT. This invokes handleUserEnterEvent, and if there isn't any GameObject.Find("remote_" + name); it creates one. So perhaps you just need to add a check that the name != local username. The local user is already instantiated.
The position of an avatar is set when NetworkController.onPluginMessageEvent gets a plugin event from the AvatarChat plugin that has action == PluginTags.POSITION_UPDATE_EVENT. This invokes handlePositionUpdateEvent, which looks for GameObject obj = GameObject.Find("remote_" + name);
and if it finds one, it tells that game object obj.SendMessage("ReceiveTransform", esob); See NetworkTransformReceiver for more details - mostly it is invoking methods that are in the NetworkTransform class.
joseph
04-21-2011, 12:02 AM
Is this a correct statement: as long as the sceondary script is the *only* script doing the instantaitions, I can worry about setting the positions that a i want (such as a camera or the player) in the NetworkController script?
When I do handleUserInstantaiteEvent I am "getting" the positon and rotations that I set in the NetworkController.cs script. I assume I can use any game object, such as the camera, and then the secondary script (where I am invoking the handleUserInstantiatEvent), I am "getting" what I set in NetworkController Script.
tcarr
04-21-2011, 12:11 AM
I'm sorry but I'm lost on what you are trying to say. I suggest that you simply try the idea and see if it works. I am supposed to provide support for ES5. You are asking for information that isn't really ES5; it's either Unity specific or your own project specific. Giving advice on how to use ES5 is within my job description, but the level of support that you have been asking for isn't.
joseph
04-21-2011, 04:37 AM
I found the definite problem: other remote users *need* to be able to see an instantiation that is travelling at a velocity > 0.
In regards to:
If this second prefab is going to have to move, then it's going to be complicated if you don't edit the AvatarChat extension (Java) code. Oh, if the effect follows the player that created it, then it won't be too terribly difficult to do on the client. Let me know if that's what you need.
Where is the AvatarChat extension (Java) code so that I may start my editing?
And I suppose when OnControllerColliderHit method is invoked (a popular unity collision detection scheme) via another event in NetworkController.cs I would need the effect to follow the player (but that is a step or two ahead).
tcarr
04-21-2011, 04:51 AM
Look in the server/java folder of the example. The source code is in the /src folder. See Using NetBeans (http://www.electrotank.com/docs/es5/manual/using_netbeans.htm) for instructions on how to set up a NetBeans project for one of the examples. If you prefer Eclipse or IntelliJ, the instructions will be similar to those for NetBeans.
You can easily add more actions to the plugin, and have the plugin broadcast messages sent using those actions to the room, with or without processing them first. You might want to look at the plugin for LobbySystem or RealtimeTankGame just to get an idea of the type of processing a plugin might do.
joseph
04-22-2011, 01:19 AM
Are there any Unity examples of this? By "this" I mean other remote users being able to see an instantiation with a velocity > 0. It is strange why this does not happen because all remote users are instantiations and are moving and are seen moving by other remote users..
tcarr
04-22-2011, 01:59 AM
We do not currently have any other Unity examples of that, unless the RTG game example does. I'm not familiar with that one's code. It's the only other Unity example we have that isn't just a simple code example. I'm sure there's a way to do it, but it took me weeks to learn enough Unity to get AvatarChat implemented in the first place.
If there's a way for the transform to set a velocity (extremely likely), then you could most likely add the velocity vector as three more floats (vx, vy, vz) in a position update message, and then just make sure that the velocity is updated when the position update message comes in.
tcarr
04-22-2011, 02:13 AM
Question: when a SINGLE player game instantiates a game object with a velocity > 0, how is it done? What information is needed? If you know how to do that, you should be able to figure out how to add the extra needed information into the EsObject giving the initial position of the game object.
joseph
04-22-2011, 02:27 AM
So what I think the first step would be is invoking teh NetworkTransform script and passing in the object being instantiated. Is this along the lines of where I would start? (from top to bottom of this post).
joseph
04-22-2011, 03:16 AM
In regards to:
Question: when a SINGLE player game instantiates a game object with a velocity > 0, how is it done? What information is needed? If you know how to do that, you should be able to figure out how to add the extra needed information into the EsObject giving the initial position of the game object
Could I possibly monitor the instantiations position and do a handleInstantationPositionUpdateEvent() in the NetworkController script?
tcarr
04-22-2011, 12:35 PM
The cleanest solution is to add another type of plugin message (a new action). That would be done on both the client and the plugin of course. It should be pretty obvious how to do it, just follow the same design pattern as the other actions.
If you don't want to add another action, just add more variables to the position update messages, with possibly a boolean that says whether this message includes info about particles. Then you can test for the new variables when you process the message.
edit: just saw the other new post, on the previous page. That looks like a good start, but you would need to modify the extension or it will ignore the new action messages because it won't know how to handle them.
joseph
04-22-2011, 08:04 PM
Is it plausible to create a fresh EsObject 'esob' and set the 'new event' and velocity (vx, vy, vz) parameters in a different script (when first getting the room from GameManager) then triggering it with input and sending the esob data to the plugin, and 'run over it' in the NetworkController script (by adding the event which handles it)?
tcarr
04-22-2011, 08:10 PM
I really don't understand what you are asking. Just try it and see what happens.
joseph
04-22-2011, 11:04 PM
I got it happening! However, the *first* instantiation does not appear to travel on the other remote users end. Every other time after that, it does and will be seen traveling for both remote users.
I am just wondering why it does not work *the first time* for any order of the remote users entering the game room scene.
joseph
04-23-2011, 12:04 AM
It works perfectly when both remote users leave and then re-enter the room! However, I am primarily concerned with why it does not happen correctly upon initial run-time? I do appreciate your help tcarr! :)
tcarr
04-23-2011, 12:55 AM
Check the code trail from the plugin event for user enter, and for getting the full user list. If it works when they re-enter the room, then the user enter event is probably being handled correct, but possibly the spawning from the full user list isn't. If you have information coming from a new action that users will need in order to spawn the remote users correctly, then you will need to have the plugin add that info to the PlayerInfo object, the way that the position update message is added.
joseph
04-23-2011, 01:39 AM
Hi tcarr!
I figured out how to acheive instantating two *different* effects based on different inputs. For example, remote_user_a presses either left control or the right control keys which instantiates either blue_particles or red_particles and the other remote user (remote_user_b) is able to do the same and see it fluctuate when switching back to its instance and vice versa (this did not happen before because when flipping back between instances, e.g. there was a mix match between particles). Very proud of acheiving this as it is very conceptual to my existing project.
However, I am still left with the problem of not being able to see the *first* instantations velocity (upon initial run-time) that is > 0. Again, leaving and re-entering some how makes it work as it should on initial run-time.
As always, thanks very much for all your help! :)
joseph
04-25-2011, 07:41 PM
when I join a world room, i need to be able to remove the 'thisUser' from a list that displays all users when the 'thisUser' leaves the 'worldRoom'. I tried:
void OnGUI() {
GUI.Label(new Rect(100, 75, 100, 100), "Users:", s); //headiing for the users in our current scene
int height = 100;
if (room != null && _es != null)
{
foreach (User u in room.Users) //Display all users in our current scene *works*
{
UserVariable uv = u.UserVariableByName("name");
string names = uv.Value.getString("name");
GUI.Label(new Rect(100, height, 100, 100), names, s);
height += 15;
}
}
if (GUI.Button(new Rect(500,100,100,100), "Game"))
{
foreach (User u in room.Users) //iterate through our currentList and remove 'thisUser'
{
UserVariable uv = u.UserVariableByName("name");
if (_userName.Equals(uv.Value.getString("name"))) //_userName is from gm.userName;
{
uv.Value.setString("name", null);
}
}
Application.LoadLevel("AvatarChat"); // loads a new scene, thus the 'thisUser' name should not be displayed
}
if (GUI.Button(new Rect(500,200,100,100), "Selection"))
{
foreach (User u in room.Users) //iterate through our currentList and remove 'thisUser'
{
UserVariable uv = u.UserVariableByName("name");
if (_userName.Equals(uv.Value.getString("name")))
{
uv.Value.setString("name", null);
}
}
Application.LoadLevel("Selection"); // loads a new scene, thus the 'thisUser' name should not be displayed
}
and that did not work..I am wondering if I need to include the onPluginMessageEvent and utilize the UserExitRoomEvent and UserEnterRoomEvents in scenes that we display users in.
tcarr
04-25-2011, 07:45 PM
When you created the world room, did you turn off any listeners? There should be one that listens for users entering and joining the room. If you didn't turn it off, then you just need to add a callback for that event, and in your handler check whether it's the world room that the user is leaving or some other room.
joseph
04-25-2011, 08:36 PM
What call backs listen for users entering and joining rooms? If it is the onPluginMessageEvent, which invokes events like entering and leaving a room based on actions, then that is going to be difficult..
tcarr
04-25-2011, 08:38 PM
Check the SimpleChat example. That one listens for joining and leaving rooms without using any plugin.
Edit: in the CreateRoomRequest, it's receivingUserListUpdates that has to not be false for world room. Then you listen for UserUpdateEvent.
joseph
04-25-2011, 08:58 PM
The SimpleChat example does not really tell me anything new. I understand how to leave and join rooms. The problem that I experienced when frequently needing to join and leave rooms is that my characters animation cycle 'pops' and is quite noticable (this is in an out-of-game room scene). I commented a bunch of my code and it still popped.
So I need to join a world room, and never leave it, but somehow still be able to remove the 'thisUser' when they change scenes.
joseph
04-25-2011, 09:12 PM
Would it seem plausible to include in the Game Manager class multiple Room variables? One for each room that we are interseted in? Then I could assign room_1 to scene_1 and room_2 to scene_2 so on and so forth..Then I would be able to leave respective rooms and not be included in the list...
Edit: including multiple Room variables for the GameManager class does not really seem to work out..I'll keep trying at that though.
tcarr
04-25-2011, 10:02 PM
The problem is that if you are not currently in a room, you are not allowed to see the list of users in that room, not through ZoneManager or a Room object.
I keep forgetting that the Unity version of SimpleChat doesn't have a user list. Grrr... In the generic C# version, look at Controller.cs.
es.Engine.UserUpdateEvent += OnUserListUpdate;
....
/**
* Fired when the client receives a UserUpdateEvent occurs
*/
private void OnUserListUpdate(UserUpdateEvent evt)
{
//render user list
view.ShowUserList();
}
In the handler, you can either just get the current list of users from the Room variable, or you can look at the event to see what the UserUpdateAction is which will tell you whether somebody left the room or joined it, and the event will give the username of who left or joined. Note: this only works if you are IN the room, and if when you created the room you didn't set receivingUserListUpdates to false, and it's what you would use if you don't have a plugin attached to the room (and sometimes even if you do).
joseph
04-25-2011, 11:19 PM
Thanks, I actually solved it! It involed setting some bools as to which scene is being loaded:
void OnGUI ()
{
ChatUI();
if (GUI.Button(new Rect(Screen.width-200, 300, 128, 15), "Character Selection", s)) { //a scene that shows animation for created chars
selectionScene = true;
gameScene = false;
InitLeaveRoom(); // initialize the leave room call back, make necessary requests and load new scene
}
if (GUI.Button(new Rect(500,100,100,100), "Game"))
{
selectionScene = false;
gameScene = true;
InitLeaveRoom(); // initialize the leave room call back, make necessary requests and load new scene
}
}
Not an elegant solution, but it does the trick. I suppose this could be a job more well-suited for PollGameLevelLoading? Found in Main.cs of the AvatarChat.I have been noticing over the past few days that my implementation performs a little slow. I would possibly need to join a world room which could perhaps speed things up.
joseph
04-26-2011, 01:29 AM
Hi tcarr,
when I display all the users in a room, and then I leave, the name is removed from the room.Users list *before* the next scene loads. Is there a way to not let that happen, in such a way that i don't see my name dissappear before the next scene loads?
Is that what the PollGameLevelLoading IEnumerator method is for? I am just wondering if that is what I should continue to use?
tcarr
04-26-2011, 01:55 AM
If you are changing the user list using OnUserListUpdate, then just check to see if the user who just left is yourself. If so, return instead of updating the list.
I don't know anything about PollGameLevelLoading IEnumerator. That's Unity specific stuff. If it's used in AvatarChat (doesn't look familiar) it's because I took it from another example.
edit: another idea is to load the new scene before you leave the current scene's room, but that could lead to confusion about what you do with the plugin events, etc.
joseph
04-27-2011, 12:10 AM
hi tcarr,
Apparently, when I instantiate a projectile while strafing with my primary game play character the projectiles are 'late'. As in not immediately at the players postion. This is because I am performing instantiations in the handleUserInstantiateEvent invoked in onPluginMessageEvent(PluginMessageEvent evt); so its not being called in update! Aha!
I know that instantiating inside the handleUserInstantiateEvent is the reason for late instantiations because i performed the instantiation directly like this in NetworkController:
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftControl)
{
GameObject go (GameObject) Instantiate(prefab, Player.transform.position, Player.transform.rotation); //instantiate at local players position
go.rigidbody.velocity = transform.forward * 100; //give the prefab velocity
}
}
which prevents instantaitions that are to far to the right of the player (when strafing left) and vice versa. I am asking if I could just set some bool to true when I get the position and rotation from the esob key for the player, then instantaite in update?
Thanks again for all your help.
tcarr
04-27-2011, 12:19 AM
I don't really grasp what you are trying to describe here. The reason for latency is that the shooter's client has to send the plugin request (time to reach the ES5), the plugin has to broadcast it to the room (time from the ES5 to the client), then the clients process the plugin events in the FixedUpdate if I remember correctly, so that's another delay. Some of the plugin events are sent queued instead of immediately, which also can add a delay.
You can certainly have the handler for that particular plugin event set a boolean and store the EsObject from the event, then in Update can look at the boolean. I would think that this would add yet another delay.
You have to make a decision on whether the local player's bullets instantiate immediately or wait until the plugin broadcasts the event (so that all players see them at approximately the same time).
joseph
04-27-2011, 12:57 AM
In regards to:
You have to make a decision on whether the local player's bullets instantiate immediately or wait until the plugin broadcasts the event (so that all players see them at approximately the same time)
I would make the decision on instantiating immediately. So would I start by brodcasting the event in update, then the next lines of code would be to invoke the handleUserInstantiateEvent and setUserInstantiateEvent?
That is what I did in my previous post and the instantiations are not seen at all. The setUserInstantiateEvent sets the keys like position and rotation, velocity and gets what prefab it is, and sends it to the plugin in the form of a esob. The next line, handleUserInstantiateEvent instantiates the projectile at the keys we set (because it has the returned esob)...and the result is that no users are able to see the instantiation.
Other users are able to see the instantiations happen when invoking handleUserInstantiateEvent in the onPluginMessageEvent and its slow when strafing. Which is why I tried to use setUserInstantiate and handleUserInstantiate in update.
tcarr
04-27-2011, 12:59 AM
It's possible for your local player to see his own bullets immediately, and remote players to only see them from the plugin event. If you want to make sure that the plugin sends the event immediately, just make sure that the plugin uses sendAndLogImmediately or sendAndLogUDP instead of sendAndLogQueued. You will still get a delay of at least several dozen milliseconds due to the internet roundtrip time, and your Unity client is set to process the plugin messages in batches instead of constantly.
You can try changing NetworkController so that it uses Update instead of FixedUpdate for the _es.Engine.Dispatch();, but that will have other side effects.
The AvatarChat example is just not set up as a FPS game. Doing multiplayer FPS is tricky if you want the illusion that all players are seeing the same things without delays. I know that Jobe discusses this in his book (http://www.amazon.com/ActionScript-Multiplayer-Games-Virtual-Worlds/dp/0321643364/), in chapters 7 and 9. It's an ActionScript book but the techniques are still valid.
joseph
04-27-2011, 01:08 AM
Thanks tcarr,
I am still a novice, how do I set the plugin so that it uses sendAndLogImmediately or sendAndLogUDP? Would those be members of the esob parameter in sendToPlugin?
tcarr
04-27-2011, 01:13 AM
In the Java plugin code, what are using to process this particular event? If you are using one of the action types from AvatarChat instead of a new one, then which one? Some of them you can cause to be sent without queuing by adding a boolean to the plugin request's EsObject:
esob.setBoolean(PluginConstants.IMMEDIATELY, true);
You won't want to do a send immediately on plugin requests for position updates, since those are sent every frame.
joseph
04-27-2011, 01:37 AM
Thanks tcarr,
I noticed that when using your example in comparison to what I had originally, did in fact made the instantiations faster..Its not 100% and I suppose that is just latency? I suppose that even with the bare minumum of passes, it is still not as fast as invoking the instantiation directly
tcarr
04-27-2011, 01:38 PM
There are a lot of things that can cause a perceived slowness in response times. Is your ES5 running from a real server or is it just locally on your desktop? If the slowness is sporadic then it could be the Java garbage collection kicking in, which can be solved by adjusting the command line parameters (use ES Admin), but if the ES5 is running on a desktop then it could be that the operating system is doing other things, particularly if that same desktop is also one of the clients.
Another possible cause of slow response is if there are too many plugin messages coming in a short period of time. The client has to process each of those plugin messages, and if there are so many that they can't be processed before the next FixedUpdate period delivers another batch, it can snowball into a big mess.
joseph
04-27-2011, 09:28 PM
Thanks tcarr,
I would like to test the slowness against colliding projectiles with other remote users. I suppose I would need to set another event?
On the localPlayerPrefab (the sender), I have a script which includes the popular unity collision detection scheme: MonoBehaviour.OnColliderControllerHit(ColliderCont roller hit);
void OnControllerColliderHit (ControllerColliderHit hit)
{
if (hit.gameObject.tag.Equals("remote_user + gm.userName")) //or Player since remote user is tagged as player
{
//distribute damage accordingly, send chat message between the two (or more players)
}
}
I already wrote a script before hand (which I most likely will use as a referece) for when projectiles meet collision, effects will happen, and particles and/or .max imports will destroy (dissappear).
Now this is where it becomes a little confusing to me. Since the code about is written in a different script (e.g., not the NetworkController.cs Script) I will most likely need to register the onPluginMessageEvent call back, and methods such as: setUser*doSomething*Event (which invokes sendToPlugin), handleUser*doSomething*Event, and technqiues used to distinguish remote users from each other.
Is this where I would need to start? Again, I would like to work on testing the slowness by implementing 'hitting' the other player.
joseph
04-27-2011, 10:55 PM
I implemented code from the NetworkController into a secondary script that performs the desired function/event:
public class PlayerReceiveDamage : MonoBehaviour
{
private ElectroServer _es;
private GameObject gameManager;
private GameManager gm;
private Room room;
void Start()
{
gameManager = GameObject.Find("_GameManager");
gm = (GameManager)gameManager.GetComponent<GameManager>();
_es = gm.es;
//_es.Engine.PluginMessageEvent += onPluginMessageEvent; //no point, send to plugin failed.
}
void FixedUpdate() {
_es.Engine.Dispatch();
}
void setMyEvent()
{
Debug.Log("setMyEvent");
EsObject esob = new EsObject();
esob.setString(PluginTags.ACTION, "myEvent");
esob.setBoolean(PluginTags.IMMEDIATELY, true);
sendToPlugin(esob);
}
void sendToPlugin(EsObject esob)
{
Debug.Log("sendToPlugin");
if (room != null && _es != null)
{
//build the request
PluginRequest pr = new PluginRequest();
pr.Parameters = esob;
pr.RoomId = room.Id;
pr.ZoneId = room.ZoneId;
pr.PluginName = "AvatarChat";
Debug.Log("send it");
//send it
_es.Engine.Send(pr);
}
}
/*void onPluginMessageEvent(PluginMessageEvent evt)
{
EsObject esob = evt.Parameters;
string action = esob.getString(PluginTags.ACTION);
if (action == "myEvent")
{
//handleUserEvent(esob);
Debug.Log("Action: " + action);
}
}*/
//CharacterController can tell the event that we got hit
void OnCharacterControllerHit (CharacterController hit)
{
if (hit.gameObject.tag.Equals("Player")) setMyEvent(); //or the remote player names e.g., the loginRequest.UserName;
}
}
But that does not work.
So I have to appearently stick to my instantiation technique. For example, ScriptA statically communicates with the NetworkController which sets the event and handles it based on certain parameters that we set in ScriptA.
I am just conerned/confused about the collision, because the NetworkController is not attached to the player and the above script is attached to the localPlayerPrefab
The problem is that the NetworkController.Log(string Message) reports with "Action not handled: err"
tcarr
04-27-2011, 11:11 PM
You created a new action for this plugin request, but the plugin isn't expecting a "myEvent" action, so it is responding with an error message (the "err" action). If you don't want to use one of the actions that the plugin already knows about, you will need to modify the extension.
joseph
04-27-2011, 11:30 PM
i made the "myEvent" a plugin tag and the NetworkController.Log(string message) still returns, "Action not hanlded: err". The NetworkController.onPluginMessageEvent(PluginMessa geEvent e); is checking for the event, "myEvent". So now I have two scripts (two onPluginMessageEvent's) checking for the action, "myEvent" and it still gives the error "Action not hanlded: err"
The code in my secondary script is as follows:
private ElectroServer _es;
private GameObject gameManager;
private GameManager gm;
private Room room;
void Start()
{
gameManager = GameObject.Find("_GameManager");
gm = (GameManager)gameManager.GetComponent<GameManager>();
_es = gm.es;
_es.Engine.PluginMessageEvent += onPluginMessageEvent;
}
void FixedUpdate() {
_es.Engine.Dispatch();
}
void setMyEvent()
{
Debug.Log("setMyEvent");
EsObject esob = new EsObject();
esob.setString(PluginTags.ACTION, PluginTags.BROADCAST_EVENT);
esob.setBoolean(PluginTags.IMMEDIATELY, true);
sendToPlugin(esob);
}
void sendToPlugin(EsObject esob)
{
Debug.Log("sendToPlugin");
if (room != null && _es != null)
{
//build the request
PluginRequest pr = new PluginRequest();
pr.Parameters = esob;
pr.RoomId = room.Id;
pr.ZoneId = room.ZoneId;
pr.PluginName = "AvatarChat";
Debug.Log("send it");
//send it
_es.Engine.Send(pr);
}
}
void onPluginMessageEvent(PluginMessageEvent evt)
{
EsObject esob = evt.Parameters;
string action = esob.getString(PluginTags.ACTION);
if (action == PluginTags.BROADCAST_EVENT)
{
//handleMyEvent(esob);
Debug.Log("Action: " + action);
}
else if (action == PluginTags.BROADCAST_EVENT)
{
//handleMyEvent(esob);
Debug.Log("Action: " + action);
}
}
// Update is called once per frame
void Update ()
{
if (Input.GetKeyDown(KeyCode.LeftControl)) setMyEvent();
}
}
I kick off the event by pressing the left control key, which invokes setMyEvent() which sets esob paramaters so that onPluginMessageEvent can intercept the event and handle it, but this is not before the information that we set is sent to the plugin via sendToPlugin(esob);
This is why it is confusing that it doesn't work. I appear to be setting the event correctly, just in another script. I was intending that the NetworkController.onPluginMessageEvent(...) pick up the event that I set in the secondary script (above) and do the event there...(didn't work)...so then I hoped for the event to be picked up (triggered) in the script above via onPluginMessageEvent, but that didnt work either.
it really seems that the best way of doing this is by statically communicating with the NetworkController to get specific parematers specific for the event that I am trying to acheive.
tcarr
04-27-2011, 11:55 PM
What's the Java side handling code? It's the Java plugin that is complaining and sending the error message.
joseph
04-28-2011, 12:12 AM
I am sorry but I don't understand. is there more specifics about the Java side handling code? Where can I find it? The AvatarChat was written entirely in C#. Do you mean either the BouncyCastle.Crypto, ElectroServer5-Unity or the Thrift files found in the plugins folder (sub folder of Networking) in the AvatarChat example?
tcarr
04-28-2011, 12:26 AM
When you send a plugin request, it goes to the server, where it it is processed by AvatarChatPlugin.java. You either have to use the actions that plugin knows how to process, or else you have to modify the plugin so that it knows what to do with the new action. If you send a plugin request with an action that the plugin doesn't recognize, it will send an "err" action which is an error message. I thought that you had already set up NetBeans so that you could see the server side code for this example, but perhaps I have you confused with somebody else. The java code is found in the same zip as the c# code, only in the server/java/src folder instead of the client/unity folder.
joseph
04-28-2011, 01:18 AM
I could not find the path: server/java/src.
For the ElectroServer5_1 download to windows7, there is a path: apis/server/java then dist or docs..in dist there is an ElectroServer5 Executable Jar File. I would download Netbeans and open that file? The following link supports my this information: http://www.electrotank.com/docs/es5/manual/index.html?avatar_names.htm
I think that i do need to downoad Netbeans and open the ES5 Exe. jar file with it..Does that sound correct?
tcarr
04-28-2011, 01:31 AM
Find the zip file that you downloaded to get the AvatarChat example. In the zip, you will see AvatarChat/server/java/src, just as you would see AvatarChat/client/unity. You don't open the ES5 exe with NetBeans. See Using NetBeans (http://www.electrotank.com/docs/es5/manual/using_netbeans.htm).
If you have never programmed in Java before, then trust me, and just using ONLY the actions that were already available through AvatarChat, such as the BROADCAST_REQUEST which relays any request to the room as a BROADCAST_EVENT. And frankly, you won't get the type of game that you envision unless you are good enough to grasp how to customize an ES5 extension. A multiplayer real time shooting game is *complicated* if you want it to be done so that it appears to be real time. Get your hands on that book of Jobe's that I mentioned, for tips on how to hide the latency.
joseph
04-28-2011, 01:57 AM
I would still please like to make an attempt. I am a little confused on the thrid step:
- Use File - New Project, or click //did this
- Click Java, and choose Java Free-form Project. Click Next. //did this
- Browse to find the java folder for the example. NetBeans needs the folder that has build.xml in it.
What is confusing me is that I don't see a relationship between netbeans and the AvatarChat example. Do I need to create a new xml file within my project or my projects directory? This would be for the AvatarChat.
tcarr
04-28-2011, 02:07 AM
NetBeans is an IDE for Java. The extension plugin is implemented in Java, and the source code files are .java files. There should be a build.xml file in AvatarChat/server/java, where the Extension.xml file also is. You can program using Eclipse or IntelliJ or even notepad and then compile from the command line if you like - but if you haven't coded in Java before, stick to NetBeans because I'll be able to answer your questions better.
joseph
04-28-2011, 06:49 AM
Thanks tcarr,
I was able to create the nbproject and open the java AvatarChatPlugin that is within the server/java path in my projects directory (because i integrated the two). I think everything else went OK as well.
I am able to open and edit the AvatarChatPlugin.java and the PluginConstants.java files within the NetBeans IDE and made my changes to the AvatarCharPlugin.request(String playerName, EsObjectRO requestParameters); and adding:
public void request(String playerName, EsObjectRO requestParameters)
{
else if (action.equals(PluginConstants.AVATAR_STATE_REQUES T)) {
handleAvatarStateRequestRequest(playerName, messageIn);
} else if (action.equals(PluginConstants.AVATAR_INST_REQUEST )) { //<---A new Constant defined and intialized to "air";
handleAvatarInstantiateRequest(playerName, messageIn); //<-- New event handle..defined below
}
else if (action.isEmpty()) {
sendErrorMessage(playerName, PluginConstants.MISSING_ACTION);
}
Then setting necessary PluginTags.Constants and EventRequests in the AvatarChat plugin, all I really did was 'mimic' the broadcast request method:
AvatarChatPlugin:
private void handleBroadcastUDPRequest(String playerName, EsObject messageIn)
{
EsObject messageOut = new EsObject();
messageOut.addAll(messageIn);
messageOut.setString(PluginConstants.ACTION, PluginConstants.BROADCAST_UDP_EVENT);
// Add the username so that everybody knows who sent the message
messageOut.setString(PluginConstants.USER_NAME, playerName);
sendAndLogUDP("handleBroadcastUDPRequest", messageOut);
}
private void handleAvatarInstantiateRequest(String playerName, EsObject messageIn)
{
EsObject messageOut = new EsObject();
messageOut.addAll(messageIn);
messageOut.setString(PluginConstants.ACTION, PluginConstants.AVATAR_INST_REQUEST);
// Add the username so that everybody knows who sent the message
messageOut.setString(PluginConstants.USER_NAME, playerName);
sendAndLogUDP("handleAvatarInstantiateRequest", messageOut);
}
then on the *client* side of things in the unity project where the newly created nbproject exists we have the following in script, not the NetworkController.cs script:
public class InstPrefabs : MonoBehaviour
{
void setInstRequestParams()
{
Debug.Log("setInstRequestParams");
EsObject esob = new EsObject();
esob.setString(PluginTags.ACTION, PluginTags.AVATAR_INST_REQUEST);
esob.setBoolean(PluginTags.IMMEDIATELY, true);
Debug.Log("send it!") //<-- happens
sendToPlugin(esob);
}
//...
now that we sent the request to the plugin, we now should be checking in NetworkController.onPluginMessageEvent(PluginMessa geEvent e); if it all worked. If that now that the added PluginConstants in the java file and new events in the AvatarChatPlugin exist and correspond with the client side, we can set events from different scripts:
private void onPluginMessageEvent(PluginMessageEvent e)
{
if (e.PluginName != PLUGIN_NAME)
{
Debug.Log("Aren't Intersted..Don't know how to process it");
return;
}
else if (action == PluginTags.AVATAR_INST_REQUEST)
{
handleUserInstantiateEvent(esob);
}
else Log("Action not handled: " + action);
}
and NetworkController.Log(string message); is still whining at me :(
tcarr
04-28-2011, 01:00 PM
You mimicked the broadcast UDP method. Do you have the client set up to use UDP? Because I didn't do that with AvatarChat.
What is the error message that you are seeing? And is there any whining in the ElectroServer5.log file?
After you made the change to the .java file, did you compile a new jar file and drop it into your extension, then restart ES5?
joseph
04-28-2011, 08:12 PM
thanks tcarr,
In regards to:
Do you have the client set up to use UDP? Because I didn't do that with AvatarChat.
UDP is handled on the client side in the NetworkController:
// TODO: set up login screen to specify UDP port, then connect using UDP if useUDP is true
private bool useUDP = true; //initially false with downloaded project
//...
/**
* Sends a position update message to the plugin.
*/
public void sendPositionUpdate(EsObject esob)
{
esob.setBoolean(PluginTags.USE_UDP, useUDP);
sendToPlugin(esob);
}
I think that Main.cs would be the login screen:
server.AddAvailableConnection(new AvailableConnection(serverURL, Convert.ToInt32(serverPort), AvailableConnection.TransportType.BinaryTCP));
I am not sure if the above is what I would need to change..And in regards to:
After you made the change to the .java file, did you compile a new jar file and drop it into your extension, then restart ES5?
I could not figure out how to compile a jar file and drop it into my extension because when I compiled it I was only able to create a new xml file..how would i create a jar file?
tcarr
04-28-2011, 08:22 PM
Just changing the boolean to true will not enable your client to use UDP. See the UDPExample example for how UDP is done with Unity.
Jobe has 4 video tutorials (http://www.electrotank.com/docs/es5/manual/video_tutorials.htm) dealing with extensions. I suggest that you watch all 4, then reread the Using NetBeans tutorial. If you still can't figure out how to get a jar file, then go to the NetBeans website and read the tutorials there. That's basic Java.
joseph
04-28-2011, 10:42 PM
I ran into some trouble, i get an error with the EmoChatFilter.java: import org.apache.commons.io.IOUtils; I don't think I started correctly.
My first step was to find, in the zip, the AvatarChat/server/java/src since that is the example that I am using. I tried to build it, but before that I noticed an immediate ! for the EmoChatFilter.java
tcarr
04-28-2011, 11:05 PM
You have to add all the jars in the lib folder to your compile path.
joseph
04-28-2011, 11:26 PM
I added all the jar files in the ES5 lib folder to my new nbproject.. I still get an error in the EmoChatFilter.java
I re-downloaded the avatar chat zip and copyed a fresh emo chat filter and still got an error. :(
tcarr
04-28-2011, 11:29 PM
You need to add the jars that are in the example's server/lib folder.
joseph
04-28-2011, 11:35 PM
I am copying the server folder from the AvatarChat into my projects directory. I notice that in the build.xml file, the projects name is "AvatarChat" which is different from my current projects name..could that be the problem?
edit: problem with the project name is fixed.
tcarr
04-28-2011, 11:39 PM
You said that you added the jars from the ES5 lib folder. You need to add the ES5 jar, yes, but you also need to add all the jars that are in the example's server/java/lib folder to your compile path.
joseph
04-28-2011, 11:53 PM
Alright I fixed the errors, implemented the PluginConstant, the event and the event body in the ServerSide Java plugin code via NetBeans..I made sure that I am using the same PluginTag that I set in the Java Plugin code, and I still got the error for the new event: "Action not handled: err"...Thus, I must be forgetting something.. :(
edit: do i need to include the UDP functions in the Main.cs of the UDP Example?
tcarr
04-29-2011, 12:10 AM
After deploying the new version of the extension, did you restart your ES5?
joseph
04-29-2011, 12:21 AM
not until just now..and still the NetworkController.Log(string message); logs: Action Not handled: err
tcarr
04-29-2011, 12:30 AM
This is far too complicated for a first plugin, for somebody who isn't familiar with NetBeans. If you don't want to just give up on modifying the plugin, back off and go through the Hello World Plugin tutorial in the manual. Make sure that you understand all the steps and that it works with the AS3 client provided. I can't keep holding your hand; read the tutorials, and learn by looking at the easier examples. You are in way over your head on this. Unity multiplayer is difficult for experts, with real time shooters even harder.
joseph
04-29-2011, 08:12 PM
Hi tcarr,
i have a couple quick questions:
When I created the nbproject with my edits for the new event i notice that an Extension.xml file was not created..I also notice that Jobe took the one from the UDPExample and pasted it into the ES5_0_1/Extensions.
Does this mean that I can take the AvatarChat example Extension.xml file and paste that one in with the nbproject.jar i just made? Because I did and tried it, but the name of the Extension.xml and the nbproject are different. I am not sure if they need to correspond. The Extension.xml for the avatarchat is in the ES5/Extensions folder and my nbproject file jar is in the ES5/Extensions/lib
Also, I notice that Jobe uses the safe mode when he starts electro server. I have been doing the stand alone...
When I try to do the stand alone I get errors and its unable to start...here is a snippet from the log file:
2011-Apr-29 12:52:44:358 [main] ERROR com.electrotank.electroserver5.servers.registry.Re gistryServer - Error occurred during init: Unable to locate an Extension.xml file in 'temp\b79ed5f218e13f8f7ede191def9eac2f\'
java.lang.RuntimeException: Unable to locate an Extension.xml file in 'temp\b79ed5f218e13f8f7ede191def9eac2f\'
at com.electrotank.electroserver5.extensions.config.E xtensionConfigurationService.buildExtensionConfigu ration(ExtensionConfigurationService.java:82)
tcarr
04-29-2011, 08:32 PM
If you create your NetBeans project right there in the same folder as the build.xml file from the zip, where the Extension.xml is, then if you use the "deploy" or "test" target from inside NetBeans, it should create a "drop it in" extension folder for you in dist/ext/. That's assuming you are using the build.xml from the example and not making a standard NetBeans project. When you created the NetBeans project, did you use "Java Free-form Project"? If so then you should be able to right click on the project name, then click Test, and it will make the extension folder for you. If you don't have a build.properties file in the folder with build.xml, then the extension will be dropped in a weird named subfolder that should be really obvious.
Don't worry about the name of the project vs the name of the extension. As long as your client is connecting to YOUR ES5 (not electrotank.com's) and is using the extension name and plugin name that show in your ES Admin, it will work. If your client is connecting to electrotank.com, then you will never be able to make any changes to the plugin that the client uses.
Jobe likes safe mode because he can see the console without pulling up ES Admin. When I'm testing and debugging something that is likely to have errors on the console, I use safe mode too, but the normal way is nice because I can't accidentally close the window and kill my ES5. Either way works.
joseph
04-29-2011, 09:13 PM
In regards to:
As long as your client is connecting to YOUR ES5 (not electrotank.com's)
Then that may be the problem because my project is connecting to the electrotank.com.
I can't really get into the es5 web admin. I try to login and I get an error: Please recheck your credentials ..., i went to the Configuration.xml file and the information corressponds with what is displayed in the web admin. Everything corresponds to the display in the manual..
The server is running.
tcarr
04-29-2011, 09:33 PM
Have you tried the tips in ES Admin won't connect (http://www.electrotank.com/docs/es5/manual/es_admin_wont_connect.htm)?
joseph
04-29-2011, 09:50 PM
Thanks tcarr,
I think it might be the first item in the list:
- Check server/logs/ElectroServer5.log to see if it says "Started successfully" or has error messages. If you do not see "Started successfully" or error messages, check Console.log for error messages.
if this is for when running the "safemode" and not the "stand alone" then this may be the problem. Could these error messages be preventing me from logging in to the admin?
tcarr
04-29-2011, 09:55 PM
The console will display all errors and you will see the console if you are running in safemode. If you are running in stand alone, then you will still see the errors but will have to look in server/logs/ElectroServer5.log and server/logs/Console.log, or else use the ES Admin to look at those files.
If the ES5 did not start successfully, then it is quite possible that you can't connect using the ES Admin. Most of the time errors will not prevent the ES Admin from starting. It depends on the error.
joseph
04-29-2011, 11:34 PM
Thanks tcarr,
i was able to login and it looks great! :) I was presented with the following notification (highlighted in yellow): Unable to locate an Extension.xml file in 'temp\b79ed5f218e13f8f7ede191def9eac2f\'
I followed the technique 1. When browsing for the Extension.xml file under the file type ElectroServer Extension of where i copied the extension (ES5_1/server/extensions), no files were displayed.
Technqiue 2 was a little different. I tried selecting both the lib and Extension.xml and created a zip on my desktop. I logged in to the es5 admin and found and uploaded the Extension.zip (with the lib and Extension.xml files) on my desktop, and restarted the es5 through the admin (not by ctrl+alt+delete and killing the java process). I logged in again, and the extension was not there.
tcarr
04-29-2011, 11:37 PM
Did you click the drop down box on the Extensions tab? Because you won't see any extensions listed until you click the drop down box, and it drops down.
glad you finally got into the Es Admin!
When you copy an extension to the server/extensions folder, if you copy it the right way you WILL see the folder there that you copied. Don't put it into the ES5's server/lib folder (a common mistake). You have to make a folder that has a lib folder inside it with your extensions jars including the new one, and has the Extension.xml file in the same location.
If you use NetBeans correctly, it will make this folder for you, with all the pieces in the right relative location, then you can just copy the folder into server/extensions.
joseph
04-29-2011, 11:39 PM
yes, sorry for not specifying that.
tcarr
04-29-2011, 11:44 PM
I suggest that you practice deploying the official AvatarChat extension first, or one of the other examples. Make sure that you have the procedure down pat. And possibly reread those tutorials.
joseph
04-30-2011, 02:27 AM
Alrighty, the AvatarChatExtension appears! :)
I restart the server restarting through the Es5 Admin and also by ctrl+alt+delete and removing the java process, and the AvatarChatExtension is not initially in the drop down window. Should it be? Also should i need to add any xml variables? I notice that Jobe did not, so i'll meet that bridge when i come to it.
tcarr
04-30-2011, 02:41 AM
Ah I had forgotten about an ES Admin bug that we squashed after 5.1 was released. If you add an extension (or anything else that would get added to a drop down box), the silly ES Admin fails to show the new item in the drop down until after you have closed the ES Admin and opened it again.
No you don't need to add any XML variables using the ES Admin. It's better to put them in Extension.xml anyway, if your extension needs any.
joseph
04-30-2011, 03:25 AM
When i run the safemode i do see that the extension is being read. However, there are errors with it apparently:
Error occured calling extension code - Extension: AvatarChatExtension - Name: AvatarChat - Handle: AvatarChat
java.lang.RuntimeException: This API call can only be invoked in a room-level plugin.
at com.electrotank.electrosever5.extensions.api.Plugi nApiImpl.startQueue(PluginApiImpl.java:200)
and there is a lot more. Could this be referring to the 'mimicked' broadcast request/event for the new event/request?
The safemode did not start successfully.
tcarr
04-30-2011, 04:09 AM
Don't make AvatarChat a server level component. It's just a plain room plugin. So go back to the ES Admin's extensions screen, and remove the plugin. Keep the extension, just remove the plugin as a server level component.
joseph
04-30-2011, 04:38 AM
-- OK. The AvatarChatExtension is visable in the drop down box and no server-level component is created and the AvatarChat component is still present even when i 'can' it and then restart the server to log back in.
I performed the process over again and restarted the ES5 Safe Mode and notice that in the console the ElectroServer has started successfully. Yay!
I go into unity and execute my new event and still receive the Action Not handled err. Thus, the reason why I am getting the error is definitly the code implementation?
The java plugin code, i think is, simply adding new plugin constants and checking for a new 'request' action in the AvatarChatPlugin.request(String playerName, EsObjectRO requestParameters); Then invoking the plugin similar to how handleBroadCastRequest is done:
private void handleAvatarInstantiateRequest(String playerName, EsObject messageIn) {
EsObject messageOut = new EsObject();
messageOut.addAll(messageIn);
messageOut.setString(PluginConstants.ACTION, PluginConstants.AVATAR_INST_EVENT); //request made in request method.
// Add the username so that everybody knows who sent the message
messageOut.setString(PluginConstants.USER_NAME, playerName);
if (messageOut.variableExists(PluginConstants.IMMEDIA TELY)) {
sendAndLogImmediately("handleAvatarInstantiateEvent", messageOut);
} else {
// If there is a queue label on the message, use it.
String queueLabel = messageIn.getString(PluginConstants.QUEUE_LABEL, "");
sendAndLogQueued("handleAvatarInstantiateEvent", messageOut, queueLabel);
}
}
and then, the scripting on the client side may be similar to:
public class NetworkController : MonoBehaviour
{
private void Update() {
if (Input.GetKeyDown(KeyCode.LeftControl))
{
EsObject esob = new EsObject();
esob.setString(PluginTags.ACTION, PluginTags.AVATAR_INST_REQUEST);
esob.setBoolean(PluginTags.IMMEDIATELY, true);
sendToPlugin(esob);
}
}
and the check for if the PluginTags.AVATAR_INST_EVENT is triggered in the onPluginMessageEvent (it is included)..and the code drops directly to the last else statement in that method..
If it works the way that i think it works, something minor is just forgotten..
tcarr
04-30-2011, 12:58 PM
Show me the code for the plugin's request method. That's where the "err" message is being triggered.
joseph
04-30-2011, 07:26 PM
/**
* Called when a plugin message arrives.
*
*/
private void onPluginMessageEvent(PluginMessageEvent e)
{
if (e.PluginName != PLUGIN_NAME)
{
Debug.Log("Aren't Intersted..Don't know how to process it");
return;
}
EsObject esob = e.Parameters;
//trace the EsObject payload; comment this out after debugging finishes!
Debug.Log("Plugin event: " + esob.ToString());
//get the action which determines what we do next
string action = esob.getString(PluginTags.ACTION);
Log("Action: " + action);
if (action == PluginTags.POSITION_UPDATE_EVENT)
{
handlePositionUpdateEvent(esob);
}
else if (action == PluginTags.AVATAR_STATE_EVENT)
{
SendAnimationMessageToRemotePlayerObject(esob);
}
else if (action == PluginTags.USER_LIST_RESPONSE)
{
handleUserListResponse(esob);
}
else if (action == PluginTags.USER_ENTER_EVENT)
{
handleUserEnterEvent(esob);
}
else if (action == PluginTags.USER_EXIT_EVENT)
{
handleUserExitEvent(esob);
}
else if (action == PluginTags.BROADCAST_EVENT) //<-- this can be triggered because the event and request came with the plugin
{
handleUserInstantiateEvent(esob); // <--just defined this locally to NetworkController
}
else if (action == PluginTags.AVATAR_INST_EVENT) // <-- has the same things as broadcast request/event
{
Debug.Log("NetworkController");
handleAvatarInstEvent(esob);
}
else
{
Log("Action not handled: " + action);
}
}
private void handleAvatarInstEvent(EsObject esob);
{
Debug.Log("here");
}
tcarr
04-30-2011, 07:38 PM
No, in the Java Plugin code, there is a request method. That's where the action that the client sent is noticed, and the plugin determines what to do about the request. That's what is sending you the err message. If you didn't add a branch to the IF statement there, then that's why the plugin is sending you an err.
joseph
04-30-2011, 07:45 PM
sorry for omitting that code, it is included in the Java Plugin Code:
@Override
public void request(String playerName, EsObjectRO requestParameters) {
EsObject messageIn = new EsObject();
messageIn.addAll(requestParameters);
getApi().getLogger().debug(playerName + " requests: " + messageIn.toString());
String action = messageIn.getString(PluginConstants.ACTION, "");
if (action.equals(PluginConstants.USER_LIST_REQUEST)) {
handleUserListRequest(playerName);
} else if (action.equals(PluginConstants.BROADCAST_REQUEST)) {
handleBroadcastRequest(playerName, messageIn);
} else if (action.equals(PluginConstants.BROADCAST_UDP_REQUE ST)) {
handleBroadcastUDPRequest(playerName, messageIn);
} else if (action.equals(PluginConstants.POSITION_UPDATE_REQ UEST)) {
handlePositionUpdateRequest(playerName, messageIn);
} else if (action.equals(PluginConstants.AVATAR_STATE_REQUES T)) {
handleAvatarStateRequestRequest(playerName, messageIn);
} else if (action.equals(PluginConstants.AVATAR_INST_REQUEST )) { //<--- here is where we make the request
handleAvatarInstantiateRequest(playerName, messageIn); //<--- here is where we handle the request
} else if (action.isEmpty()) {
sendErrorMessage(playerName, PluginConstants.MISSING_ACTION);
} else {
sendErrorMessage(playerName, PluginConstants.INVALID_ACTION);
}
}
and this is the method that *should* get invoked
private void handleAvatarInstantiateRequest(String playerName, EsObject messageIn) {
EsObject messageOut = new EsObject();
messageOut.addAll(messageIn);
messageOut.setString(PluginConstants.ACTION, PluginConstants.AVATAR_INST_EVENT); //request made in request method.
// Add the username so that everybody knows who sent the message
messageOut.setString(PluginConstants.USER_NAME, playerName);
if (messageOut.variableExists(PluginConstants.IMMEDIA TELY)) {
sendAndLogImmediately("handleAvatarInstantiateEvent", messageOut);
} else {
// If there is a queue label on the message, use it.
String queueLabel = messageIn.getString(PluginConstants.QUEUE_LABEL, "");
sendAndLogQueued("handleAvatarInstantiateEvent", messageOut, queueLabel);
}
}
then on the scripting side of things we do this:
public class NetworkController : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftControl)) // Grab input from user which will trigger "air" event
{
EsObject esob = new EsObject();
esob.setString(PluginTags.ACTION, PluginTags.AVATAR_INST_REQUEST); //trigger the "air" avatar inst request event
esob.setBoolean(PluginTags.IMMEDIATELY, true); //the variable exists so the java plugin code should hit the request method above
sendToPlugin(esob); //send to plugin
}
}
and as you know, the plugin request code has the AVATAR_INST_EVENT included in the if else if else block. I would just like to have another event that I can use, like broadcast event..
tcarr
04-30-2011, 10:01 PM
Your request method has "getApi().getLogger().debug(playerName + " requests: " + messageIn.toString());" in it. Are you seeing the message come in? It should appear in the ElectroServer5.log file. You should also see another line immediately after it that mentions handleAvatarInstantiateEvent or else one that mentions sendErrorMessage. See if you can find those lines or at least that section of the log file. It will tell us what is actually reaching your plugin and what is going out from it in response.
joseph
04-30-2011, 10:06 PM
In the ElectroServer5.log file, i open it as a text file:
2011-Apr-30 12:53:45:976 [main] INFO DisplayLogger -
_____ _ _ ____
| ____| | ___ ___| |_ _ __ ___ ___ ___ _ ____ _____ _ __ | ___|
| _| | |/ _ \/ __| __| '__/ _ \/ __|/ _ \ '__\ \ / / _ \ '__| |___ \
| |___| | __/ (__| |_| | | (_) \__ \ __/ | \ V / __/ | ___) |
|_____|_|\___|\___|\__|_| \___/|___/\___|_| \_/ \___|_| |____/
Starting ElectroServer 5.1.0
Go to http://www.electrotank.com/ for the latest version.
################################################## #########
Computer Information
Operating System: Windows 7
Operating System Architecture: amd64
Processors Available: 8
Memory Available: 908Mb
################################################## #########
Virtual Machine Information
Virtual Machine Vendor: Sun Microsystems Inc.
Virtual Machine Version: 1.6.0_21
2011-Apr-30 12:53:53:495 [main] INFO com.electrotank.electroserver5.servers.gateway.Gat ewayServer - GatewayServer.doStart
2011-Apr-30 12:53:53:620 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened BinaryTCP listener on /0.0.0.0:9899
2011-Apr-30 12:53:53:636 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened RTMP listener on /0.0.0.0:1935
2011-Apr-30 12:53:53:651 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened BinaryHTTP listener on /0.0.0.0:8989
2011-Apr-30 12:53:53:667 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened TextTCP listener on /0.0.0.0:8990
2011-Apr-30 12:53:53:698 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened BinaryUDP listener on /0.0.0.0:10000
2011-Apr-30 12:53:53:698 [main] INFO DisplayLogger -
-=-=-=-=-=-=-=-
ElectroServer has started successfully
2011-Apr-30 12:54:57:601 [main] INFO DisplayLogger -
_____ _ _ ____
| ____| | ___ ___| |_ _ __ ___ ___ ___ _ ____ _____ _ __ | ___|
| _| | |/ _ \/ __| __| '__/ _ \/ __|/ _ \ '__\ \ / / _ \ '__| |___ \
| |___| | __/ (__| |_| | | (_) \__ \ __/ | \ V / __/ | ___) |
|_____|_|\___|\___|\__|_| \___/|___/\___|_| \_/ \___|_| |____/
Starting ElectroServer 5.1.0
Go to http://www.electrotank.com/ for the latest version.
################################################## #########
Computer Information
Operating System: Windows 7
Operating System Architecture: amd64
Processors Available: 8
Memory Available: 908Mb
################################################## #########
Virtual Machine Information
Virtual Machine Vendor: Sun Microsystems Inc.
Virtual Machine Version: 1.6.0_21
2011-Apr-30 12:55:04:236 [main] INFO com.electrotank.electroserver5.servers.gateway.Gat ewayServer - GatewayServer.doStart
2011-Apr-30 12:55:04:330 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened BinaryTCP listener on /0.0.0.0:9899
2011-Apr-30 12:55:04:345 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened RTMP listener on /0.0.0.0:1935
2011-Apr-30 12:55:04:361 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened BinaryHTTP listener on /0.0.0.0:8989
2011-Apr-30 12:55:04:377 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened TextTCP listener on /0.0.0.0:8990
2011-Apr-30 12:55:04:392 [main] INFO com.electrotank.electroserver5.protocol.IOProvider - Opened BinaryUDP listener on /0.0.0.0:10000
2011-Apr-30 12:55:04:392 [main] INFO DisplayLogger -
-=-=-=-=-=-=-=-
ElectroServer has started successfully
tcarr
04-30-2011, 10:10 PM
That's not showing that your client is sending any plugin messages to your ES5 at all. Is your client connecting to your network? The default log4j.properties configuration should display all the getApi().getLogger lines at the debug level.
joseph
04-30-2011, 10:17 PM
I found the log4j.properties in the instalation directory under server/config and i don't know how to open it.
My client is connecting to the electrotank.com host. Do I need to fire up net beans and some how debug when I run the game?
edit: i clicked on the log4j.properties and chose to use notepad as its "Open with:" property. The text file is as follows:
log4j.rootLogger=warn, file, console
log4j.logger.DisplayLogger=info
log4j.logger.com.electrotank.electroserver5.server s.gateway.GatewayServer=info
log4j.logger.com.electrotank.electroserver5.protoc ol.IOProvider=info
log4j.logger.com.electrotank.electroserver5.server s.gateway.DisconnectedClientIoHandler=DEBUG
log4j.logger.com.electrotank.electroserver5.entiti es.PeopleImpl=DEBUG
#log4j.logger.com.electrotank.electroserver5.entit ies.DisconnectedGatewayClient=DEBUG
#log4j.logger.com.electrotank.electroserver5.serve rs.registry.GatewayIoHandler.outOfOrder=debug
log4j.logger.com.gamebook=debug
log4j.logger.Extensions=debug
log4j.logger.com.electrotank.electroserver5.exampl es=debug
log4j.appender.file=org.apache.log4j.DailyRollingF ileAppender
log4j.appender.file.DatePattern='.'yyyyMMdd
log4j.appender.file.File=logs/ElectroServer5.log
log4j.appender.file.layout=org.apache.log4j.Patter nLayout
log4j.appender.file.layout.ConversionPattern=%d{yy yy-MMM-dd HH:mm:ss:SSS} [%t] %-5p %c %x - %m%n
log4j.appender.console=org.apache.log4j.ConsoleApp ender
log4j.appender.console.layout=org.apache.log4j.Pat ternLayout
log4j.appender.console.layout.ConversionPattern=%d {ABSOLUTE} [%t] %-5p %c %x - %m%n
#log4j.logger.com.electrotank.electroserver5.serve rs.gateway.transactions=DEBUG
#log4j.logger.com.electrotank.electroserver5.serve rs.registry=DEBUG
#log4j.logger.com.ibatis=DEBUG
#log4j.logger.java.sql.PreparedStatement=DEBUG
#log4j.logger.java.sql.Connection=DEBUG
#log4j.logger.java.sql.ResultSet=DEBUG
#log4j.logger.java.sql.Statement=DEBUG
#log4j.logger.com.electrotank.electroserver5.entit ies.DefaultGatewayClient=DEBUG
#log4j.logger.com.electrotank.electroserver5.serve rs.registry.config=DEBUG
log4j.appender.sysout=org.apache.log4j.DailyRollin gFileAppender
log4j.appender.sysout.DatePattern='.'yyyyMMdd
log4j.appender.sysout.File=logs/Console.log
log4j.appender.sysout.layout=org.apache.log4j.Patt ernLayout
log4j.appender.sysout.layout.ConversionPattern=%m% n
log4j.logger.com.electrotank.electroserver5.Slf4jP rintStream=debug,sysout
log4j.additivity.com.electrotank.electroserver5.Sl f4jPrintStream=false
tcarr
04-30-2011, 10:38 PM
If your client is connecting to YOUR ES5 and not to electrotank.com, then when the client sends any messages to the plugin they should appear in the log. That log that you showed me only showed that the ES5 had started successfully. It did not show any of the normal logging lines from the AvatarChat plugin at all.
Check your client. Make sure that it is no longer connecting to electrotank.com, but to your local ES5.
joseph
04-30-2011, 10:48 PM
Thanks tcarr,
at first, that appeared to solve it, however i do not seem to be joining the game room scene..the NetworkController.Log(string message) is apparently not telling me that I have joined a room. I created the room, but did not join it.
This is strange because when connecting to electrotank.com as the host, the above code worked fine. Do I need to set the room's up in the web admin? I have to keep restarting the electroserver because if i do not, i will get a "PluginNotFound" log error.
I am not seeing the debug statements in the onPluginMessageEvent for the AVATAR_INST_EVENT condition.. :(
tcarr
04-30-2011, 11:07 PM
If you are seeing logging lines about plugin requests at all now, then you have made some progress at least.
No you do not need to set up the room in the web admin. You do need to look at the ElectroServer5.log file for the time right around when the user leaves the room and the client sees the error, to see if there's an error in the log. It is also quite likely that your plugin code (which is not the standard AvatarChatPlugin code) is doing something different than electrotank.com does, because it does run the standard AvatarChatPlugin code.
Please note that you are now well past the point where you should be able to do simple debugging yourself. I'm supposed to help with ES5 questions, not be on your development team. You may want to consider finding another developer who is familiar with Java and hopefully also familiar with web servers and the like to team with you. Games these days usually take a full team, not just one developer, and eventually you will need a sysadmin/webadmin team member.
joseph
05-01-2011, 12:22 AM
Thanks tcarr,
From what I gather, I cannot invoke new events when connecting to the electrotank.com because i'll need to modify the plugin. So then one needs to modify the plugin but then needs to connect to 127.0.0.1 but then cannot invoke new events any way because it changes the extension?
There is no initial error. When i do strike the error (wanting to leave the game room scene), it does not show up in the console or the es5 log. The room request is made but the room is never joined. This happend when connecting to the "127.0.0.1" host. Could it be that I need to connect using UDP instead of TCP?
tcarr
05-01-2011, 01:50 AM
Don't use UDP - that's more complicated than TCP. You need to back up, and do the intro tutorials for plugins, such as the Hello World Plugin tutorial. Make sure that it works on your own ES5, that logging lines show, etc. Next, install the "just drop it in" extension for AvatarChat into your own ES5, and make sure that it behaves for you the way that connecting to electrotank.com does, but that you see the logging lines. Then compile your version of the extension, and replace just the AvatarChat jar file in the AvatarChat extension's lib folder and restart your ES5 and see if you get any error.
This is BASIC freshman level debugging. Make sure that you understand something simple before you start trying to do something really complicated. You are attempting a project that normally takes a team of professionals, before you have learned the ropes on ES5.
joseph
05-01-2011, 10:51 PM
Thanks tcarr,
[solved it]
Steps:
- Find the installation directory of ES5
- Find its Extensions Folder
- Create a new Folder in the extensions folder with whatever naming convention you choose
- Copy and paste the contents of the AvatarChat's server/java path into the new folder you just created (w/o the src folder)
-Take the NetBeans jar file (found in the dist folder) and paste it into the lib
This all assumes one is famialir with how to perform a clean and build on the avatar chat plugin source, setting the events/request on both client and server, and configuring the es5 web admin..safe mode only (recommended) :P
tcarr
05-02-2011, 12:17 AM
surely you aren't pasting the entire server/java folder? You definitely don't need the src folder for one thing.
However, if you have it WORKING, that's FANTASTIC!!! And you figured it out yourself too!
joseph
05-02-2011, 04:16 AM
Thanks tcarr!
It sure is a lot better than referencing everything statically..whew, that really was a mess!
Also..one thing that seems to be pure client side is that only my .max import is in the center (dead center) of the the player when strafing left or right....particles on the other hand (done inside unity) are not..so w/e on unity for that!
Thanks again tcarr :)
joseph
05-05-2011, 07:30 PM
Hi tcarr,
I was wondering about implementing a UDP connection on top of the current TCP connection?
So far i included the UDP connection in Main.cs of the AvatarChat example:
Server server = new Server("default");
server.AddAvailableConnection(new AvailableConnection(serverURL, Convert.ToInt32(serverPort), AvailableConnection.TransportType.BinaryTCP));
server.AddAvailableConnection(new AvailableConnection(hostName, Convert.ToInt32(udpPort), AvailableConnection.TransportType.BinaryUDP, udpHostName, Convert.ToInt32(udpEndPoint)));
AvailableConnection availConn = server.AvailableConnections[0];
_es.Engine.AddServer(server);
_es.Engine.Connect(server, availConn);
I notice in the debugging lines in the console that i am still using BinaryTCP. I think that it will be that way until I send the information to the plugin. Thus, in the NetworkController.cs of the AvatarChat example we have:
public class NetworkController : MonoBehaviour {
//....
// TODO: set up login screen to specify UDP port, then connect using UDP if useUDP is true
private bool useUDP = true;
/**
* Sends a position update message to the plugin.
*/
public void sendPositionUpdate(EsObject esob)
{
esob.setBoolean(PluginTags.USE_UDP, useUDP);
//esob.setNumber(PluginTags.TIME_STAMP, Time.time * 1000);
sendToPlugin(esob);
}
//...
}
tcarr
05-05-2011, 07:45 PM
If you look at UDPExample you will see the proper form for connecting using both TCP and UDP. You will probably also need to use ES Admin to add a custom policy file (http://www.electrotank.com/docs/es5/manual/custom_policy_file.htm), depending on the type of build Unity is targetting. The ones that need to use PreFetch will require that your custom policy file on the TCP port give permission for the UDP port to be used as well as the TCP port.
Next, using AvatarChat example, you just add esob.setBoolean(PluginTags.USE_UDP, useUDP). The messages will not have a different action on them. Your code snippet above has you setting a boolean for USE_UDP and then setting a string for it, on the same request, which will mean that the plugin will assume that USE_UDP is false because it will error when trying to read the boolean.
To see whether the message arrives using UDP or TCP, use a packet sniffer such as WireShark.
Please note that UDP messages are NOT reliable. This means that some of them will be dropped. For position update messages, that's usually not a problem because if one gets dropped the next position update message will fix any lost info.
joseph
05-05-2011, 08:27 PM
Thanks tcarr,
I think i understand about the unrealiability and also the basic concept of the udp connection. I also appreciate the tutorials and the manual as it is where i gather most of my information.
I'd like to stress that I am wondering if using a UDP in conjunction with TCP will potentially fix the problem of the user being a little behind of their instantiations when strafing.
I am using the esob.setBoolean(PluginTags.IMMEDIATELY, true); in my user defined instantiation event..if the udp cannot solve the slow strafe+instantiation problem, then could I potentially set the same boolean, e.g., setBoolean(PluginTags.IMMEDIATELY, true) for when the position is updated?
Albeit, i reset the NetworkController.sendPositionUpdate(EsObject esob); back to its initial state:
public void sendPositionUpdate(EsObject esob)
{
Debug.Log("sendPositionUpdate");
esob.setBoolean(PluginTags.USE_UDP, useUDP); //useUDP = true
sendToPlugin(esob);
}
However, i cannot find where it is invoked. I also downloaded WireShark and am reviewing its documentation to learn how to check for possible UDP transfer sockets. Also I am using a standalone build for my unity project, not the web player, from what you suggested i don't need to create a polcity file then?
tcarr
05-05-2011, 08:36 PM
UDP messages usually arrive sooner than TCP ones, so hopefully this will help. Yes, if you don't set the UDP boolean, you can set the IMMEDIATELY boolean instead, so that the position update messages goes out at once.
When the plugin gets a position update message, it calls handlePositionUpdateRequest. If you look at that method you will notice that it reads the value of USE_UDP, and if that value is true it calls sendAndLogUDP instead of sendAndLogImmediately or sendAndLogQueued.
joseph
05-05-2011, 09:47 PM
Thanks tcarr,
I commented out the conditions in both the plugin position request method and the instantiate request method. This is so that they will both use sendAndLogImmediately, regardless of any condition. Then i tried commenting out the conditions in both plugin position request method and the instantiate request method and invoking sendAndLogUDP...Everything is true in the debug console....Unfortunately, I still do not notice a difference.. :(
I am running two remote instances connecting to 127.0.0.1 as the host (with an included UDP connection) and just strafing and instantiating. I would like it to perceive other remote users instantantiating at the same rate that I am. Can this be helped? What is the web page for Jobe's book about hiding latency (if it would help turn on some more lights)?
tcarr
05-05-2011, 10:03 PM
The type of question you are asking here is the kind that we charge per hour for consulting rather than free advice, because it's specific to your application and not general advice. It's possible that Jobe's book might have some advice that is relevant. You can order it from Amazon using this link (http://www.amazon.com/ActionScript-Multiplayer-Games-Virtual-Worlds/dp/0321643364/) and I'm sure that it's available from other booksellers as well. Chapter 7 has a section on "latency and clock synchronization", as well as sections on frame-based vs time-based movement and responsive controls. Chapter 9 uses this info in the Realtime Tank Game example which is an AS3 shooter with server side collision detection.
tcarr
05-05-2011, 10:18 PM
One more comment: you are not going to get the same behavior from a real server where all the clients are on different boxes. It sounds as if you are running multiple instances of the client on the same box as the ES5. That's going to be SLOW. Set up your ES5 for remote access, and see what happens when you have just one client on each of several computers, none of them being the ES5's computer. This will be more easily done in a computer lab of some kind, so that you can keep an eye on the various clients at the same time.
Also, Unity UDP doesn't like using 127.0.0.1 if I remember correctly. I know that I need to use my external IP address when I test the UDPExample example.
joseph
05-06-2011, 10:22 PM
Hi tcarr,
I have a question regarding a NetGear router. I have been reading and re-reading web pages and documents on how to add a new service by port forwarding. Can't quite pull it off just yet and wondering if there are any tutorials on this site that I may have overlooked.
Edit: I could add the ports 9899 and 10000 for TCP and UDP. However, the ES5 Admin port: 8080 will not add as it is a port that is apparently in use.
tcarr
05-06-2011, 11:22 PM
You can easily change port 8080 to some other port, such as 8081 or whatever is available. Just find your ES5 installation folder/server/config/Configuration.xml and edit the port (then restart ES5 and you will need to configure ES Admin to use the new port). For that matter, if you don't need to use the ES Admin remotely, you can leave Configuration.xml using 127.0.0.1 and port 8080.
Have you been following Enabling Remote Access to ElectroServer (http://www.electrotank.com/docs/es5/manual/enabling_remote_access_to_elec.htm)? That's the best tutorial we have for what you are trying to do. The various routers have vastly different user interfaces, so you would need to find a tutorial on your specific router if it isn't obvious how to configure it.
joseph
05-07-2011, 08:50 PM
Hi tcarr,
i was wondering if I could get the users in a room when I am not in it? I tried saving a copy of the room into a Room Variable in the Game Manager class, so that when one remote user enters the 'certain room' the other that is not in it will be able to see the user that is.
private void OnJoinRoom(JoinRoomEvent evt)
{
Debug.Log("Joined a room");
room = _es.ManagerHelper.ZoneManager.ZoneById(evt.ZoneId) .RoomById(evt.RoomId);
//if this is null then no one is in the room
if (gm.gameRoom != null) InitUsersInGameRoom();
}
private void InitUsersInGameRoom()
{
Debug.Log("InitUsersInGameRoom");
Room room = gm.gameRoom;
_es.Engine.GetUsersInRoomResponse += OnGetUsersInRoomResponse;
GetUsersInRoomRequest guir = new GetUsersInRoomRequest();
guir.ZoneId = room.ZoneId;
guir.RoomId = room.Id;
_es.Engine.Send(guir);
}
private void OnGetUsersInRoomResponse(GetUsersInRoomResponse e)
{
foreach (UserListEntry ule in e.Users)
{
AppendToChat(ule.UserName, "test");
Debug.Log("names: " + ule.UserName);
}
Debug.Log("List Empty"); //<-- doesn't happen
}
tcarr
05-07-2011, 09:15 PM
Saving a copy of a room you are not in will not work, not if you want the current users in the room. If you need the names of users in a room there are various ways to do that.
Use GetUsersInRoomRequest and listen for the response, each time you need the list
For multiple rooms at once, have a plugin do it. Each time the user lists are needed, client sends a PluginRequest to a plugin. Plugin then does getApi().getUsersInRoom for each of the rooms needed, then sends a plugin message with this info.
For GameManager rooms, the game's plugin can store the names of the users in the room in the gameDetails EsObject. Clients can then do a FindGamesRequest and iterate through looking at the gameDetails to get the names of the users. This is trickier to do that you would think; ask for advice if you choose this option.
joseph
05-08-2011, 06:34 AM
Hi tcarr,
regarding: "enabling remote access to the server", i am wondering if I may verify that I have it correct and that what I am experiencing is a router problem on my end:
- During the ES5 Admin, the listeners are listening for my local ip then i clicked update and restarted ES5 in Safemode
- Went back to the ES5 Admin to verify that the listeners have been added; true
- Changed the installation/server/config/Configuration.xml files IP to match that in the ES5 Admin and changed the host from 8081 to 8080 as it was already in use; I was told this by my routers UI.
- After logging in to my routers UI, I added new ports
-P1 START PORT = 9899 , END PORT = 9899, PROTOCOL = BOTH, IP = 192.168.0.10 //<-- which is what ES5 Admin is listening to
-P2 START PORT = 10000, END PORT = 10000, PROTOCL = BOTH , IP = 192.168.0.10
-P3 START PORT = 8081, END PORT = 8081, PROTOCAL = BOTH, IP = 192.168.0.10 // There port here matches the ES5 Admin port and Configuration.xml port as well as the IP
- Refreshed the netgears UI verifed the information, restarted the window instance and verifed the information
If everything appears to look okay, it is a minor problem with my apparent port forwarding during my netgears UI as I cannot seem to login on the live example either.
tcarr
05-08-2011, 06:51 AM
Check your firewalls.
tcarr
05-08-2011, 07:13 AM
One more possibility. Are you testing only locally? Some routers do not allow inside the network clients to connect using the external ip, the way most routers do. Outside the network clients should connect using the public ip, but you may need to have your inside the network clients use 192.168.0.10. For that matter if all your clients are on the same LAN you can just use the internal ip and the router won't need to do any port forwarding. You would still need to check your firewalls.
joseph
05-10-2011, 04:07 AM
Thanks tcarr,
I was able to setup remote access to the server! I do, however, need to forward other clients that are outside of my area.
I have a question about an instantiation that would need to follow the player that instantiated it. I acheived it locally, but haven't really gotten anwhere remotely..inevidably i end up here in NetworkController:
public void handleAvatarInstantiateEvent(EsObject esob)
{
GameObject instParticles = GameObject.Find("particles"); //exists in heirarchy.
instParticles.transform.position = NetworkTransform.getPlayerPosition(esob); //need to get the updating position
instParticles.transform.rotation = NetworkTransform.getPlayerRotation(esob); //and rotation
ParticleEmitter pEmitter = instParticles.GetComponent<ParticleEmitter>();
pEmitter.emit = true;
}
I tried the above after I set position keys in the unity void Update() method. This actually created what seemed to be a huge amount of traffic and the movement for the player was very, very slow.
I think the best solution to instantiate something that follows the player is to use a similar technique as NetworkController.handlePositionUpdateEvent(EsObje ct esob);
private void handlePositionUpdateEvent(EsObject esob)
{
//Debug.Log("handlePositionUpdateEvent");
string name = esob.getString(PluginTags.USER_NAME);
if (name.Equals(_userName))
{
// my own position
// unless the plugin is likely to enforce a position on me, just ignore this
}
else {
// remote player position
GameObject obj = GameObject.Find("remote_" + name);
if (obj == null)
{
Debug.Log("remote_" + name + " not found");
return;
}
else
{
// move the remote player to match
Debug.Log("asking remote player to move");
obj.SendMessage("ReceiveTransform", esob);
}
}
}
I am not really to sure how I would use it to get something to follow the reletive player.
tcarr
05-10-2011, 11:37 AM
Do the speech bubbles for remote users follow them correctly? If so, you could try just having a boolean in the position update message (whether to show the effect or not) then do something similar to the code that places the speech bubbles. I know that the "below the feet" names of the players do follow the remote users around, so that's another thing to try.
joseph
05-10-2011, 11:30 PM
Excellent idea!
I lost the effect of it though.
I have been trying to show other remote user names that are logged in, but that doesn't happen. This is because I am not using a MainCamera tagged as MainCamera in the scene. I am toggling between two different cameras, First Person and Third Person. I kinda lost the effect of other names being visable for when other remote users are logged in to the game room scene. So that's a bummer and I am trying to fix that.
The two toggable camera's are sub objects of the local players hierarchy. They are not included as sub objects of the remote players hierarchy. Both local and remote player have the Name Bubble script attached.
I went ahead and tagged the camera that I needed (the Third Person Camera) as the MainCamera to not get an error in the Name Bubble script, but that didn't work. It seems like a Camera, tagged as MainCamera needs to be in the hierarchy alone and seems to only work in conjunction with the Controller scripts that are initially attached to the local player in the initial avatar char example. So i gotta find some work around for that! :)
tcarr
05-11-2011, 12:01 AM
When I was developing the example, I had to fiddle with the height of the name tags quite a bit, because if they were too low they would spawn below the ground and couldn't be seen. Perhaps that's where yours disappeared to? Try editing NameBubble to change private float NAME_POSITION = AT_FEET; to be ON_BACK instead and see if that helps.
Powered by vBulletin® Version 4.1.6 Copyright © 2013 vBulletin Solutions, Inc. All rights reserved.