Rendering Remote player agents

Apr 26, 2012 at 12:30 AM

 

How do I render remote Player Agents in a Lidgren Lan session?

Session gives me access to IList<IdentifiedPlayer> RemotePlayers.

Do I need to call Session.CreatePlayerAgent and attach SceneObjects to them for each remote player in the list - so they can be rendered locally?

Are these remote players,  "Player agents" for my local session or "NonPlayer agents" (since I dont control them)

I looked at Host doing SynchronizeSceneEntitiesOnClients, but its confusing, all its doing is sending Unique IDs to clients and then I dont really get whats happening at the receiving end - syncing IDs i guess, but that means the SceneEntities need to be present on clients. Similarly for Command Syncing its just sending Ids.

Also AI NonPlayerAgents - Should the Host create them, and once thast done how do I replicate them to clients? On client side where do I find them so I can render them?

Once they are created I understand that their position etc can be done/replicated through Server Commands.

 

Apr 27, 2012 at 9:18 PM

Update:

 

Ok I rendered them myself and it works, i added Player agents via session. I have a simple Local+Server command that moves the player. What I see is that when I move player 1, on the remote session, player 2 is moving. probably some sort of ID sync issue?

Coordinator
May 6, 2012 at 9:36 AM

Hi emi_oasis,

Sorry to reply to you so late but I've been busy lately.

One of the core things to understand when using IGF's Logic system is that you must code your game as if the code was always run in one single machine. The framework then adds the magic by distributing the commands that have to be executed on clients or the server.

So, when you create an agent through one of the SessionManager.CurrentSession.CreateXXXAgent() methods, behind the scenes, it'll synchronize all game elements associated to this agent in all clients.

For your specific issue, make sure to set a UniqueId to the SceneObject controlled by the agent before applying the agent to its components. That should solve your issue.

May 6, 2012 at 1:25 PM

Hmm, so I got it wrong then?

Here is what I did:

I create local player scene object and associate a Player Agent with it - I set a name for the sceneobject. Then I check the CurrentSession.RemotePlayers, lets say it has one entry so I go ahead and create another PlayerAgent+SceneObject with the remote playername. Then in the PlayerMoveBehavior I send the playername (sceneobject name) as well in ClientCommand. In ApplyServerResult Command I find the playername from Scene and apply the move force.

Similarly for AI agents, all clients create NonPlayerAgents+SceneObjects (with a uniquename for each AI sceneobject). Then as per AI franework server command computes ai agent moves them on all clients.

However from what you are saying it seems I am doing it wrong?. According to what you said, I should just create one PlayerAgent+SceneObject and that should do the job. However I have looked at the IGF code at length, I have looked at SyncSceneEntity and SyncCommand code and I do not see anywhere the framework code making call to Session.CreatePlayerAgent or CreateNonPlayerAgent OR creating a SceneEntity/SceneObject for remote players. Its just sending an id and the id sync code I dont really understand but it definitely is not doing a NEW SceneEntity and adding it to Scene.

So my question is where is the framework code (Session, SessionManager) creating PlayerAgents or SceneObjects for remote players. Infact the framework code cannot do that since it doesnt know which sceneentity class to instantiate. (since I have derived sceneobject for my player ships)

Also initially I thought logic framework knows which command "instance" to execute on remote end. since I have two player agents on both sides one for local one for remote. each instance has its own command instances. but when I move a player I have to send player name as parameter as well and in Applyserverresult I have to find the sceneobject by name to move. if I dont do the find by name and instead just apply the force to the Agent.ParentObject.CollisionMove, it moves the local player instead of remote player.

So maybe I have made fundmental mistakes that has been causing these issues. Can you clarify a little. in context of one player agent per client as well as 2 AI agents on server. 

I also sent you some fixes in the Lidgren session code.. or at least I think they were fixes.

 

I have assigned unique id to sceneobjects but will make sure to do that again and remove my code that appears to be wrong as per your reply. Lets see.

 

Another thing I had to do was do a position sync behavior for AI agents. the AI starts off the same but diverges after a bit, I dont know if its due to floating point non-deterministic nature or what but I had to sync AI positions at a predefined interval lets say twice a second. am I doing this wrong ?

 

May 6, 2012 at 1:32 PM

And I know you hv been busy :) Congrats on the new job. This is imran by the way :)

Coordinator
May 6, 2012 at 4:38 PM

You are getting it wrong effectivelly ;)

What you need to do is simply create your SceneObject and Agents without looking either you are the host or not.

For instance let's say you have 2 players, in your PlayingGameState.Initialize() override method, you just code this:

public override void Initialize()
{
	_player1 = new MyCustomPlayerSceneObject{ UniqueId = "1234"};
	_player2 = new MyCustomPlayerSceneObject{ UniqueId = "5678"};

	var player1Agent = SessionManager.CurrentSession.CreatePlayerAgent();
	_player1.Add(_player1);

	var player2Agent = SessionManager.CurrentSession.CreatePlayerAgent();
	_player2.Add(_player2);

	GameState.SunBurn.ObjectManager.Submit(player1Agent);
	GameState.SunBurn.ObjectManager.Submit(player2Agent);
}

This means that this code will be executed for all clients since it resides on the GameState.Initialize() method.
Obviously, you'd add your own Behaviors to the PlayerAgent instances.

Now, the next time the SessionManager.CurrentSession.Update() gets called, it'll synchronize all SceneEntities, and Commands created.
All commands will therefore get executed wherever they have been told to be executed.

Now, say you want to create a new SceneObject in your game such as a Bullet, you'll want it to be created inside an Command that will get executed on all Clients when the server received a Fire Command.

public class PlayerFireBehavior : Behavior
{
	private bool _hasFiredBulletLocally;

	public PlayerFireBehavior()
	{
		AddLocalCommand(IsButtonFirePressed, PlayerFireBullet, ServerFireBullet, UpdateFireBulletOnAllClients, typeof(Matrix), DataTransferOptions.Reliable);
	}

	private bool IsButtonFirePressed()
	{
		return ((PlayerAgent)Agent).Input.Buttons.A.IsPressed;
	}

	private void PlayerFireBullet(Command command)
	{
		// First we create a Matrix containing the start translation, direction of the bullet:
		var bulletMatrix = Matrix.Identity;
		bulletMatrix.Translation = Agent.ParentObject.World.Translation;
		bulletMatrix.Forward = Agent.ParentObject.World.Forward;

		// Create a local bullet instance and make it visible to avoid firing not having visual effect on the client which actually fired the bullet.
		var bullet = new Bullet{ World = bulletMatrix, UniqueId = "bulletX" };
		// We then add a specialized BulletAgent which will contain Collision Behaviors as well as any other behaviors you'd need
		bullet.Add(SessionManager.CurrentSession.CreateNonPlayerAgent<BulletAgent>());
		// We locally add the bullet to the ObjectManager so that it renders for the local client if you need to.
		Application.SunBurn.ObjectManager.Submit(bullet);
		// We'll look if the bullet was fired locally later
		// Since this method is only executed on the client which fired the bullet, it won't be true on all other clients 
		_hasFiredBulletLocally = true;

		// we loaclly apply a force to the bullet so that it starts moving next frame
		bullet.CollisionMove.ApplyObjectForce(Vector3.UnitZ);

		// and we tell the framework to share the bulletMatrix with the next Command in line
		return bulletMatrix;
	}

	private void ServerFireBullet(Command command, object networkValue)
	{
		// We could make a few logic tests on the server to verify for instance that the player has enough ammo. For simplicity, we'll just pretend everything went fine
		// Therefore, we simply return the networkValue (actually the bullet matrix) so that it gets associated with the next command in the pipeline: the command applied on all clients
		return networkValue;
	}

	private void UpdateFireBulletOnAllClients(Command command, object networkValue)
	{
		// first we look if the current Behavior started within this PlayerAgent scope.
		if(_hasFiredBulletLocally)
		{
			// if so, we don't need to do anything since everything was setup before.
			// In a real case, you'd like to smoothly lerp the forces and position of the bullet with the server known bullet position for a few frames.
			_hasFiredBulletLocally = false;
			return;
		}
		else
		{
			// otherwise, we just create the bullet as we did in the client which actually fired the bullet.

			var bulletMatrix = Matrix.Identity;
			bulletMatrix.Translation = Agent.ParentObject.World.Translation;
			bulletMatrix.Forward = Agent.ParentObject.World.Forward;

			var bullet = new Bullet{ World = bulletMatrix, UniqueId = "bulletX" };
			
			bullet.Add(SessionManager.CurrentSession.CreateNonPlayerAgent<BulletAgent>());
			
			Application.SunBurn.ObjectManager.Submit(bullet);
			
			bullet.CollisionMove.ApplyObjectForce(Vector3.UnitZ);
		}
	}
}

This way, you'll see the Bullet and its Agent and Behaviors being created when they are expected to be and synchronized with the server and all other clients ;)

I hope it explains the process a bit more.

I know it needs to break a bit with the usual concepts but it gets really powerful once you understand it as this would work with any Network implementation afterwards ;)

May 6, 2012 at 5:32 PM

Sure this helps, I will try it as you have described.

One more observation about the input part. Sometimes the button press is missed by the behavior even if its working at full 60 hz update. maybe thats because in ur commenst u say the button down state is set for just one frame. I need to differentiate between each button press since i want to fire one bullet for each mouse button release, i dont want to do rapid fire if the button is kept pressed.

Also I am trying to modify the input code a little because for WP7 i have a few issues with it e.g.

I need a few fixed (visual)  buttons in the top right of the screen e.g. changing ammunition. but I also need user tap on the full screen as input to fire in the direction where the user taps. this is an invisible button, and i know i can provide a touchmap for the full screen. but then I also need the virtual pad in the bottom left of the screen to move the ship and so its hard to seperate the these overlapping inputs. Similarly I have use for the screen swipe as well, and for that I am using a non-static invisible thumbstick, which will give me a force vector. But I went ahead and exposed position properties as well, since I need the drag positions as well. I know you are sticking to the XBOX controller model and its kinda hard to fit that in the XBOX model as there is no swipe or drag in XBOX.

Also on menu screens, the space key press will execute two screens in a row .. so i then used ur technique where you have a timer/interpolator to hide one screen and show another that basically gives the "pause" between screens so the key press is not executed on two screens within a millisecond

I am very impressed with teh overall frameowrk, but as u said it takes time to get used to.

AI steering behaviors i still am unable to make it behave properly, I have tried playing around with teh maxforce, min velocity, max velocity etc but i can not get it right.. especially "Obstacle avoidance + Pursuit" or "Obstacle Avoidance+Wander". its bumps into obstacles and tries to get into it, even though my debug output tells e it dd detect the obstacle.

Also my levels have a enclosed boundary wall and I havent been able to do a wall avoidance thing.