Game Development Community

Teams Implementation

by Xavier "eXoDuS" Amado · 03/07/2002 (3:30 pm) · 140 comments

Well, in this tutorial I'll help you implement teams in your game, I've seen some bad implementations of this, I'm not telling that mine is the best but it's not bad for sure :)

Part 1: Adding basic team support
Ok the first and most important part of making teams is setting each player a team variable which stores which team he is in. At first i thought doing this by adding the variable to the player class (lots of ppl thought the same thing) but I found out that the player class is deleted when you die and is then re-created. So I looked for something relating to the player that isn't deleted, well you might have guessed it, the GameConnection class that is.
So what i did was add a joinTeam function to my GameConnection. To do this open up Game.cs located in fps/server/scripts/game.cs and scroll to the end of the file, there add this function:

function GameConnection::joinTeam(%this, %teamid)
{
   //We only have 2 teams so if team is greater than 2 or less than 0 its invalid.
   if (%teamid > 2 || %teamid < 0) 
      return false;
   //If we already are on that team return. 
   if (%teamid == %this.team.teamId)
      return false;

   %this.leaveTeam();

   if (%teamid == 1)
      %this.team = $Team1; 
   if (%teamid == 2)
      %this.team = $Team2;

   MessageAll('MsgClientJoinTeam', '\c2%1 joined the %2 side',
      %this.name,
      %this.team.name,
      %this.team.teamId,
      %this,
      %this.sendGuid,
      %this.score,
      %this.isAiControlled(),
      %this.isAdmin,
      %this.isSuperAdmin);

   %this.spawnPlayer();}

Ok I'll explain all this stuff, the first two lines checks to see if the team number is valid. I know this is a bad way of checking that but it's easy to understand and code for new people reading this, a better way would be to have a global define setting the number of teams and using that instead, sorry here. The two following lines check to see if you already are in that team, if you already are is in that team it returns false. You can use this feature to check the status of joinTeam function, if it returns false you could spawn a gui saying there was an error or something like that. The following line calls the function leaveTeam (of the GameConnection class too) which takes the player out of the team he is in, deletes the player object and resets the player's score. In the following if block we are creating the team variable inside the GameConnection class and setting it to the corresponding Team ScriptObject (I'll explain script objects later), again, this is a bad way of doing this too, but it's easy. The next block of code sends a message to all clients saying that the player joined team X and it also passes some variables we will need to catch on later.
Finally the last line spawns the new player.
Ok now let's complete this and define the leaveTeam function. After the joinTeam function you just added add this code:

function GameConnection::leaveTeam(%client, %teamnumber)
{
   %this.score = 0;
   
   messageAll('MsgClientLeaveTeam', '\c2%1 left the %2 team.',
      %client.name,
      %client.team.name,
      %client);
   
   if (%client.player)
      %client.player.delete();
}

Now lets define the ScriptObject for each team. ScriptObjects are like structures of data, you can have many variables inside a ScriptObject, for example name and numplayers. So if you create a ScriptObject called Team1 with those 2 members, you can access them using a syntax similar to: $Team1.name and $Team1.numPlayers.
So on the top of the game.cs file, choose a nice camping site and place your team scriptobjects there with this code:

$Team1 = new ScriptObject()
{
   teamId = 1;
   name = Red;
   score = 0;
   numPlayers = 0;
};

$Team2 = new ScriptObject()
{
   teamId = 2;
   name = Blue;
   score = 0;
   numPlayers = 0;
};

Well here the first line resets the score, the following block sends a leave team message to all clients and the last line deletes the player object if it exists.
Ok, now we need to actually call the joinTeam function from somewhere. We will do this from a gui which lets you choose between teams. Ok so now create the gui as you wish or you can use mine which is as follows:

//--- OBJECT WRITE BEGIN ---
new GameTSCtrl(TeamSelectDlg) {
   profile = "GuiDefaultProfile";
   horizSizing = "right";
   vertSizing = "bottom";
   position = "0 0";
   extent = "640 480";
   minExtent = "8 8";
   visible = "1";
   helpTag = "0";

   new GuiWindowCtrl() {
      profile = "GuiWindowProfile";
      horizSizing = "center";
      vertSizing = "center";
      position = "159 155";
      extent = "321 170";
      minExtent = "8 8";
      visible = "1";
      helpTag = "0";
      maxLength = "255";
      resizeWidth = "1";
      resizeHeight = "1";
      canMove = "1";
      canClose = "1";
      canMinimize = "1";
      canMaximize = "1";
      minSize = "50 50";
      closeCommand = "Canvas.popDialog(TeamSelectDlg);";

      new GuiTextCtrl() {
         profile = "GuiBigTextProfile";
         horizSizing = "center";
         vertSizing = "relative";
         position = "32 30";
         extent = "257 40";
         minExtent = "8 8";
         visible = "1";
         helpTag = "0";
         text = "Choose your team";
         maxLength = "255";
      };
      new GuiButtonCtrl(RedTeam) {
         profile = "GuiButtonProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "10 130";
         extent = "140 30";
         minExtent = "8 8";
         visible = "1";
         command = "jointeam(1);";
         helpTag = "0";
         text = "Red Team";
         groupNum = "-1";
         buttonType = "PushButton";
      };
      new GuiButtonCtrl(BlueTeam) {
         profile = "GuiButtonProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "170 130";
         extent = "140 30";
         minExtent = "8 8";
         visible = "1";
         command = "jointeam(2);";
         helpTag = "0";
         text = "Blue Team";
         groupNum = "-1";
         buttonType = "PushButton";
      };
   };
};
//--- OBJECT WRITE END ---

Ok, save that code in a new file with the name TeamSelectDlg.gui (under the directory fps/client/ui/TeamSelectDlg.gui)
Please notice that the buttons call a jointeam function passing a value, but this is not the joinTeam function we defined in game.cs, instead this function is defined in the corresponding TeamSelectDlg.cs file (under fps/client/scripts/TeamSelectDlg.cs).
And this function is like this:

function jointeam(%teamid)
{
   commandtoserver('JoinTeam', %teamid);
   Canvas.popDialog(TeamSelectDlg); //Hide the dialog once selection was made
}

Now open up commands.cs (fps/server/scripts/commands.cs) and add this function to the end of the file:

function serverCmdJoinTeam(%client, %teamid)
{
  %client.joinTeam(%teamid);
}

Since the GameConnection::joinTeam() function is server side and the gui is client side, we need to call this function which actually calls the server side command JoinTeam which finally calls the server side function joinTeam.
Well, before our gui works we need to add the two files to the execute list of the client side init.cs file (fps/client/init.cs)
You should see a list of exec("xxx.gui"); lines. There you will need to add two new executes:

exec("./scripts/TeamSelectDlg.cs");
exec("./ui/TeamSelectDlg.gui");

Finally for this part, we need to bind a key to the dialog. So open up default.bind.cs and add this line to the end:

moveMap.bindCmd(keyboard, "m", "Canvas.pushDialog(TeamSelectDlg);", "");

Add the same line to config.cs just in case, im not sure if it's needed tho.
Now we will want our player to start as an Observer by default, not in any team, so open up game.cs again and find the function GameConnection::onClientEnterGame()
and replace it with this one:

function GameConnection::onClientEnterGame(%this)
{
   commandToClient(%this, 'SyncClock', $Sim::Time - $Game::StartTime);

   // Create a new camera object.
   %this.camera = new Camera() {
      dataBlock = Observer;
   };
   MissionCleanup.add( %this.camera );
   %this.camera.scopeToClient(%this);

   // Setup game parameters, the onConnect method currently starts
   // everyone with a 0 score.
   %this.score = 0;
   %this.team = 0;  //default team is 0 (observer)

   //Spawn a camera by default on a observer point
   %this.camera.setTransform(pickObserverPoint(%this));
   %this.camera.setVelocity("0 0 0");
   %this.setControlObject(%this.camera);
}

Ok, two things changed in this function, first there's a line which sets the player team to 0, this is because i called observer team, 0.
Then the function call to spawn a player was replaced with the observer spawn. This are 3 lines, the first one sets the camera transform matrix to the matrix returned by the pickObserverPoint() function (we will define this one later on). The second line sets the camera velocity to zero on the 3 axis's and the last line sets the controlObject to the camera (so you can move around with the camera).
Ok, now if you scroll down a bit in this file you'll find the pickSpawnPoint() function, what i did is copy this same function and modify it a bit, so after the pickSpawnPoint function ends add this new function:

function pickObserverPoint(%client)
{
   %groupName = "MissionGroup/ObserverDropPoints";
   %group = nameToID(%groupName);
   
   if (%group != -1) {
      %count = %group.getCount();
      if (%count != 0) {
         %index = getRandom(%count-1);
         %spawn = %group.getObject(%index);
         return %spawn.getTransform();
      }
      else
         error("No observer spawn points found in " @ %groupName);
   }
   else
      error("Missing observer spawn points group " @ %groupName);

   // Could be no spawn points, in which case we'll stick the
   // player at the center of the world.
   return "0 0 300 1 0 0 0";
}

Notice that this new function looks for a group called ObserverDropPoints instead of PlayerDropPoints. Ok so what's left is adding this new group for spawning the cameras. Open up the mission file you want to test this on (actually you should add this to every mis file of your game) and add the following:

new SimGroup(ObserverDropPoints) {
      new SpawnSphere() {
         position = "109.514 -180.815 202.815";
         rotation = "0 0 1 130.062";
         scale = "0.940827 1.97505 1";
         dataBlock = "SpawnSphereMarker";
         radius = "10";
         sphereWeight = "1";
         indoorWeight = "1";
         outdoorWeight = "1";
         locked = "false";
         lockCount = "0";
         homingCount = "0";
      };
   };

This example has only 1 spawnsphere object, but you can add as many as you want, it will randomly choose one. Notice that the spawnsphere's must be beetween the SimGroup opening brace ( { ) and the end closing brace ( }; ). You can just copy the code I pasted above in your mission file and then move it with the in-game editor to the position you want it to be. I wont cover this on this tutorial tho, sorry.
Ok that's all concerning part 1 of this tutorial, you can try it now. You should enter the game being an observer, when you press the "m" key a popup will appear asking you what team you want to join to and then you should respawn but now being in that team. You wont notice any changes yet tho.

Part 2: Adding Team Spawn Points

Ok, what's a team support without being able to spawn team members at different locations? Well, that's what we are going to do now, and it's pretty simple. If you open your mission file you'll see that there's a SimGroup called PlayerDropPoints, and inside it you may have one or more spawnspheres which are chosen at random by the function pickSpawnPoint in the game.cs file (fps/server/scripts/game.cs). Well let's modify the mission file, rename the PlayerDropPoints SimGroup so that it's called something team related, for example I'm using for this example RedTeamDropPoints. Now copy the whole SimGroup "RedTeamDropPoints" and paste it after the existing one, now rename the second copy to BlueTeamDropPoints. You should end with something like this:

new SimGroup(RedTeamDropPoints) {
      new SpawnSphere() {
         position = "80.0366 -215.868 183.615";
         rotation = "0 0 1 130.062";
         scale = "0.940827 1.97505 1";
         dataBlock = "SpawnSphereMarker";
         radius = "10";
         sphereWeight = "1";
         indoorWeight = "1";
         outdoorWeight = "1";
         locked = "false";
         lockCount = "0";
         homingCount = "0";
      };
};

new SimGroup(BlueTeamDropPoints) {
      new SpawnSphere() {
         position = "80.0366 -215.868 183.615";
         rotation = "0 0 1 130.062";
         scale = "0.940827 1.97505 1";
         dataBlock = "SpawnSphereMarker";
         radius = "10";
         sphereWeight = "1";
         indoorWeight = "1";
         outdoorWeight = "1";
         locked = "false";
         lockCount = "0";
         homingCount = "0";
      };
};

In the example code I'm only using one spawnsphere per SimGroup, you can add as many as you want. You might also want to change the position of the spawnspheres, do this changing the coordinates in the code above or by entering the game using the in-game editor and moving the SpawnSpheres around.
Let's implement the code that will read through the SpawnPoints. Open up game.cs and in the GameConnection::spawnPlayer() function add %this between the parentheses of the first line, the whole function should then look like this:

function GameConnection::spawnPlayer(%this)
{
   // Combination create player and drop him somewhere
   %spawnPoint = pickSpawnPoint(%this);  //This line changed
   %this.createPlayer(%spawnPoint);
}

Well what's that for? I made that change cause you'll have to access the GameConnection in the pickSpawnPoint function to get the team the player is in, so I just passed the %this variable which points to GameConnection, to the pickSpawnPoint function. Another way of doing this could be to include the pickSpawnPoint function in the GameConnection class, but I prefered to pass it as argument.
Well now scroll down in game.cs and look for the pickSpawnPoint function, should be almost at the end of the file. Now change that function to look like this (just replace the old one with this one):

function pickSpawnPoint(%client)
{
   %groupName = "MissionGroup/" @ %client.team.name @ "TeamDropPoints";
   %group = nameToID(%groupName);

   if (%group != -1) {
      %count = %group.getCount();
      if (%count != 0) {
         %index = getRandom(%count-1);
         %spawn = %group.getObject(%index);
         return %spawn.getTransform();
      }
      else
         error("No " @ %client.team.name @ " team spawn points found in " @ %groupName);
   }
   else
      error("Missing " @ %client.team.name @ " team spawn points group " @ %groupName);

   // Could be no spawn points, in which case we'll stick the
   // player at the center of the world.
   return "0 0 300 1 0 0 0";
}

The most important change in this function is the first line, instead of looking for PlayerDropPoints group it will now look for RedTeamDropPoints or BlueTeamDropPoints, depending on the client's team. I also changed the two error lines to report what team is missing spawn points instead of just saying there are no spawn points.
Well that's all, easy part huh? Enjoy your new team spawn points.

I'll be posting a ScoreBoard later, it should have been here as Part #3 but got delayed, and delayed, etc so i decided to release what i had! have fun!
Thanks to Phil for "Beta" testing the tutorial :)
Page «Previous 1 2 3 4 5 6 7 Last »
#1
03/07/2002 (3:02 pm)
You have at least two errors in this code, both are in the file "TeamSelectDlg.gui". When you set the new GameTSCtrl(SelectTeamDlg) you changed the name. It should have been GameTSCtrl(TeamSelectDlg). Also when you set the closeCommand = "Canvas.popDialog(SelectTeamDlg);";, it should have been closeCommand = "Canvas.popDialog(TeamSelectDlg);"; Other than that everything is working for me fine with a little tweaking for my own needs.
#2
03/07/2002 (6:03 pm)
Also I found that GameConnection::joinTeam(%this, %teamnumber)needs to be GameConnection::joinTeam(%this, %teamid) I wasn't able to find anything that made use of "%teamnumber"
#3
03/09/2002 (6:13 pm)
ok fixed those 3 errors, thanks guys! for some reason i had them wrong in the tutorial .doc but they were ok in my code :)
Also, just in case you were asking the MessageToClient statements which pass some team info are used for the playerList gui, ie the scoreboard, which i will post someday too in a tutorial form :)
#4
03/09/2002 (10:07 pm)
Nice, my main mission i have been working on was designed around team play, now i got team play :) next stop: getting my bots to work with this ( a few if(!this.isaicontrolled()){..} :)

Thx ExoDuS, is the scoreboard a "team score" like the single player score, or something graphically in game, coz that would be cool too, but a team score dlg would suffice (And also be easy)
ryan
#5
03/09/2002 (10:07 pm)
stupid inet connection, double posts are bad
ryan
#6
03/10/2002 (8:38 am)
ryan, i dont understand what you mean with something graphically in-game?
Basically the scoreboard is like the playerList.gui on torque rigth now, you press F2 and displays player name and score. My scoreboard has 3 lists, Team1, Team2 and Observer.. and also the score... it isnt finished yet and i havent worked on it for some time now.. i should finish it soon tho :\
#7
03/10/2002 (12:39 pm)
heh i ment something like a bitmap one some object in the game (like the scores printed on the side of a building or something) but i figured it was probably a score gui. The one you have sounds pretty sweet. cant wait!
ryan
#8
03/27/2002 (6:19 pm)
How would you load a different player model depending on the team with this code?

Thanks for the tutorial.
#9
04/08/2002 (12:07 pm)
I am getting an error says "waiting for server" and hangs. Any ideas? I am a torque noob still.

Randy...
#10
04/08/2002 (12:07 pm)
I am getting an error says "waiting for server" and hangs. Any ideas? I am a torque noob still.

Randy...
#11
04/14/2002 (1:40 am)
how can I set weapons for teams? like have it so red team starts with a glock and blue team starts with a Colt .45?

And can you get different models for the teams?

Has anyone tried classes?
#12
08/30/2002 (1:31 am)
Nice tutorial. Works right out of the box. =) Thank you.
#13
09/17/2002 (11:32 pm)
Awesome. Many thanks.
#14
12/12/2002 (6:40 pm)
did you ever do that scoreboard?
#15
03/03/2003 (10:56 pm)
sorry but may i know how do u change the numplayers variable in the scriptobject $team1 and $team2.
i tried %this.numplayers = 1 in the jointeam in game.cs but it does not work.
#16
03/09/2003 (1:59 pm)
Thanks for the tutorial, works great!
#17
03/10/2003 (8:49 am)
[Edit: Posted comment into wrong resource..]

Btw, great teams mod. :)
#18
03/29/2003 (11:54 am)
I dont spawn is observer..
and i get error in console when i try choose team (nothing happens)


(0): Unable to find function joinTeam

:(
#19
05/02/2003 (11:35 am)
to spawn any where in the sphere replace
return %spawn.getTransform();
in the PickSpawnPoint() with
//do random placment
          %sphereCenter = %spawn.getTransform();
          %cXoord = getRandom(%spawn.radius)  ;
          %cYoord = getRandom(%spawn.radius)  ;
          %cZoord = getRandom( %spawn.radius) ;
          %newPoint = %cXoord @ " " @  %cYoord @ " " @  %cZoord;

         %randomLocation = MatrixMulPoint(%sphereCenter, %newPoint);

         return %randomLocation;
#20
05/02/2003 (12:23 pm)
also for if each spawn area is incremented numnerically you can do this
%sphereCount = 0;
   while(nameToID(%groupName @ %sphereCount)!= -1)
   {
        %sphereCount++;
   }
   %groupEnd = getRandom(%sphereCount)  ;
   
   %randomGoupName =  %groupName @ %groupEnd;

   %group = nameToID(%randomGoupName);
to get it to use random areas
Page «Previous 1 2 3 4 5 6 7 Last »