Chapter X
Local Procedures - When and Why to Use Them
We had to get to this chapter at some point. By the time we are half way through it you will wish we never started it, but it is a very important chapter. I would recommending taking this chapter slowly and absorbing the information or it might get too confusing.
Now that I have your spirits raised and your attitudes adjusted, let's get it on.
I can hear the question now, "What exactly is a local procedure?" Let's see what are handy 3DGS manual says.
These instructions start a client-side function for the given entity.
Proc_local starts the given function on all clients.
Proc_client starts the given function only on the client which has created the given entity or it's creator entity.
Local or client functions can be used for detecting local mouse events, for starting local sound or particle effects, or for local entity animation.
Amazing, that is actual a pretty good description. In this tutorial we will cover using local animation, which means, each client will do each entity's animation on his computer so the server doesn't have to be sending all of the animation frame data to all the clients. In this way we can drastically cut down on network traffic. So that answers When and Why to use them. We use them for actions like particles or animation which takes too much of our network resources to achieve if they are transmitted from the server to the clients. So the real goal is, the less traffic we are sending over the network eventually results in the more players we can have in the game or zone.
There are actually many uses for both proc_local and proc_client but we will only be covering proc_local in this tutorial.
In this chapter, we will use proc_local to do the animation of the entities. So instead of the server sending animation updates, what we will do in this chapter is have the clients handle the animation locally, only sending when a entity's animation_state has changed. Thus cutting down network traffic significantly.
As of right now, we have the animation in our code being sent from the server to all the clients.
Ok, how would we start a local procedure? Huh? What? Slow down big daddy! I have no idea! Ok, stupid question. Well, we do know we have proc_local(entity,function), that would probably be a good place to start. We probably so also consider that we are going to be doing animation locally, so we will not be having to send the animation data from server, so we will need to shut that off. Which brings us to the Nosend commands. The Nosend commands tells the server not to send some kind of data for this entity to the clients. We are looking not to send animation data, searching deligently through the manual we finally come up with :
entity.nosend_frame
Prevents sending of frame and next_frame.
Range:
on - prevents sending of frame and next_frame.
off - frame and next_frame will be sent to the clients (default)
I suppose that is enough to get us going. So first let's shut off the sending of animation frames to the clients using nosend_frame. Add this line of script to function move_player.
function move_player
{
my.ENABLE_DISCONNECT = ON; // player can disconnect from session
my.EVENT = player_events; // player events function
my.nosend_frame = ON; // don't send animation
If we were to run the program now, none of the client's entities would be animated. They would remain at the first frame because the server is no longer sending animation information after the player's entity is created. Actually, let's do a test run, so you can see exactly what is happening. Save in SED. Create a executive file in WED using Publish or Resource, copy it to secondary computer. Well, you know the routine. Now when the client joins the game, none of his entities will be animated, because the server is no longer sending the animation frame data now. Very, very interesting.
Now that we see what nosend_frame is doing, let's go ahead and add our possible local procedures. There will be 3 possible local procedure that might be called. Which local procedure is called is dependent on this entity's profession. Let's add our nuclear scientist local procedure call first in function move_player in the if (my.profession == PROF_NUCLEAR_SCIENTIST) statement.
my.weapon = WEAPON_NUCLEAR_ROD; // set weapon type
proc_local(my,local_activities_nuclear); // local animation and other activities
}
Go to the if statement below that and add our biological scientist local procedure call.
my.weapon = WEAPON_PHOTOSYNTHESIS_EMULATOR; // set weapon
proc_local(my,local_activities_bio); // local animation and other activities
}
Under that in the next if statement add our operations officer local procedure call.
my.weapon = WEAPON_COMPRESSION_RIFLE; // set weapon type
proc_local(my,local_activities_officer); // local animation and other activities
}
Now that we have the local procedure calls in the right places, let's add our local procedures for each of the 3 possible professions. So above function move_player add these 3 functions.
//--------------------------------------------------------------------
// LOCAL_ACTIVITIES_NUCLEAR - performs local ativities for Nuke Scientist
//--------------------------------------------------------------------
function local_activities_nuclear()
{
my.weapon = WEAPON_NUCLEAR_ROD; // set weapon type
animate(); // do my animation locally
}
//--------------------------------------------------------------------
// LOCAL_ACTIVITIES_BIO - performs local ativities for Bio Scientist
//--------------------------------------------------------------------
function local_activities_bio()
{
my.weapon = WEAPON_PHOTOSYNTHESIS_EMULATOR; // set weapon type
animate(); // do my animation locally
}
//--------------------------------------------------------------------
// LOCAL_ACTIVITIES_NUCLEAR - performs local ativities for Op Officer
//--------------------------------------------------------------------
function local_activities_officer()
{
my.weapon = WEAPON_COMPRESSION_RIFLE; // set weapon type
animate(); // do my animation locally
}
//--------------------------------------------------------------------
// MOVE_PLAYER - Move the player
//--------------------------------------------------------------------
function move_player()
Now you might be asking yourself why are we creation three seperate local procedure for each profession when they are doing the exact same thing? We are doing this because I looked into my crystal ball and I know of an ugly problem that will pop up later that has to do with our weapons. Actually, you might have noticed, I added the local weapon types in already. If you were just writing the script for the first time you probably would have just made one local procedure for all the professions (just like I did the first time) until later on you ran into this future problem. We will eventually get to that problem so I am not going to explain it now.
Note: You can only have one local procedure running on a client per entity at a time, so if you have a lot of activities that need to be done, place them all in one function. Also, you might be thinking we just added three local procedures. This is true, but only one of them will be called and be running for each entity depending on the player's profession.
Go ahead and do a test run and see what happens now.
You will notice the entities are once again animated on the client, but the animation is always stand no matter what the entities are doing. Why is this? The reason is we haven't sent any animation_state data from the server or host to the clients yet. So that is what we must do now.
We could just send the animation_state every frame to the clients, but if you think about it that is a bad idea. It would defeat the whole purpose of making our animation local to the clients. We must remeber why we are making the animation local in the first place which is to lessen the amount of network traffic. So if our FPS is 60, we would be sending a horrible 60 animation updates per second. I hope you can see the problem with this.
So how do we solve this problem? Anyone? Anyone?
This problem should remind you of something we did earlier. Remember in Chapter VII we experience this exact same problem, except if was input forces instead of animation states. But since we were able to solve the problem of transferring the forces in a efficient manner, we can copy the technique we learned there and apply it to the transferring of our animation states. The main difference will be, unlike the forces tranfer where we were sending the data from the clients to the server only when necessary, with our animation states we will be sending the data from the server to the clients only when necessary.
How we solved our forces problem was to save our previous forces into force_x_old and force_y_old and then we checked if they changed with the if(player.force_x != force_x_old || player.force_y != force_y_old) statement. Only if a force had changed did we send it to the server. So let's set up something like that for our animation states.
Amazingly enough we have already added a skill to handle this for our animation blending. The skill we will use will be prev_animation_state, all we have to do is figure out how to use it on the server. What the heck, let's just add line of code into function move_player.
while(1)
{
my.prev_anim_state = my.animation_state; // save animation state
// intially set animation to walk, this will be changed below if necessary
my.animation_state = ANIMATION_STATE_WALK;
Yes! You got to love simplicity. Actually there may have been a bit of pre-planning on my part. Anyway, now we have just have to place the animation_state transfer code liek we had the force transfer code set up. So let's add that script into function move_player now too.
my.animation_state = ANIMATION_STATE_STAND;
}
}
}
// if animation state changed, send new animation state to all clients
// so they can do animation locally
if(my.animation_state != my.prev_anim_state)
{
send_skill(my.animation_state,SEND_ALL);
}
Now, do you remember the "The Client is the Server Stupid!" problem. I told you we would revisit this problem some time. I mention this now because there is a line of code you would expect to take out of the move action if it wasn't for this problem. It is the call to animate() from the server.
animate(); // animate entity
You might think that since all the animation is being done locally we wouldn't have to call animate() from action move since it is being done in the proc_local() function, but since the Host is not only a Client but the Server too, the Server doesn't call the Server so if this call to animate() wasn't left here, the hosts' entities would not be animated. So even though the host is not sending any frame data to clients it still must run animation for itself. I just love that problem. It took me a while to figure that one out when I first started.
Ok, now do a test run and see what happens.
It's a miracle! All the animation works now and once again, not unlike our sending of forces, over time the amount of animation_state data transfered per second approaches zero.
I just now realized, after re-writing this tutorial I have made this chapter much easier by spreading some of the difficult issues out over earlier chapters. So, it wasn't all that difficult after all, which should much everyone very grateful or at least very happy.