Game Development Community

pickPlayerSpawnPoint() - is there a better way?

by Richard Ranft · in Torque 3D Professional · 04/22/2011 (2:47 pm) · 14 replies

I can't seem to iterate through the contents of the PlayerDropPoints group in script so what I have is this:
// pickPlayerSpawnPoint() is responsible for finding a valid spawn point for a
// player.
//-----------------------------------------------------------------------------
function pickPlayerSpawnPoint(%spawnGroups)
{
   %targetName = $Server::TargetSpawnPoint;
   if(%targetName $= "")
   {
      %targetName = "DefaultSpawnSphere";
   }
   echo("Loading - Target Spawn Point: " @ %targetName);
   // Walk through the groups until we find a valid object
   for (%i = 0; %i < getWordCount(%spawnGroups); %i++)
   {
      %group = getWord(%spawnGroups, %i);
      
      if (isObject(%group))
      {
         %spawn = %group.getRandom();
         if (isObject(%spawn))
         {
            %name = %spawn.getName();
            if(%name $= "")
            {
               echo("***Setting empty name to DefaultSpawnSphere");
               %spawn.name = "DefaultSpawnSphere";
               //return %spawn;
            }
            while(%targetName !$= %name)
            {
               %spawn = %group.getRandom();
               %name = %spawn.getName();
               echo("***Testing Spawn Point - " @%name);
            }
            echo("***Selected Spawn Point: " @ %name);
            return %spawn;
         }
         else
         {
            echo("***Spawn Point not an object!");
         }
      }
   }

   // Didn't find a spawn point by looking for the groups
   // so let's return the "default" SpawnSphere
   // First create it if it doesn't already exist
   if (!isObject(DefaultPlayerSpawnSphere))
   {
      %spawn = new SpawnSphere(DefaultPlayerSpawnSphere)
      {
         dataBlock      = "SpawnSphereMarker";
         spawnClass     = $Game::DefaultPlayerClass;
         spawnDatablock = $Game::DefaultPlayerDataBlock;
      };

      // Add it to the MissionCleanup group so that it
      // doesn't get saved to the Mission (and gets cleaned
      // up of course)
      echo("***Selected Spawn Point: " @ %spawn.getName());
      MissionCleanup.add(%spawn);
   }

   return DefaultPlayerSpawnSphere;
}

See that %spawn = %group.getRandom()? I don't like that. I have tried %group.getFirst() and %group.getNext() to no avail. It did just occur to me as I type this that perhaps %group[%index] might work though....

Anyway, please document how to handle this while you guys are at it. Thanks!

7 Jun 11 - Note - %group[%index] didn't work :(

About the author

I was a soldier, then a computer technician, an electrician, a technical writer, game programmer, and now software test/tools developer. I've been a hobbyist programmer since the age of 13.


#1
04/22/2011 (6:37 pm)
Try a loop where the spawnpoint gets a variable and if it's filled it goes to the next. I remember a TGE(A?) resource or forum post which picked spawnpoints for players, moving to the next one after one had been filled. Search is your friend (it's not really :P)

Or create an arrayObject and use that - I'm quite a big fan of arrayObject.

You could also use a "controlling object" with a variable which picks spawnpoints.

function ClearAreaGame::preparePlayer(%game, %client)
{
   echo (%game @"\c4 -> "@ %game.class @" -> ClearAreaGame::preparePlayer");

   echo("controlNode objective is " @ controlNode.objective);

	if(isObject(controlNode))
	{
		if(controlNode.objective == 0)
		{
			%playerSpawnPoint = pickPlayerSpawnPoint($Game::DefaultPlayerSpawnGroups);
			echo("spawning objective 0");
		}

		if(controlNode.objective == 1)
			if(isObject(spawnArea1))
			{
				%playerSpawnPoint = spawnArea1;
							echo("spawning objective 1");
		}
				
		if(controlNode.objective == 2)
			if(isObject(spawnArea2))
			{
				%playerSpawnPoint = spawnArea2;
							echo("spawning objective 2");
		}

				
		if(controlNode.objective == 3)
			if(isObject(spawnArea3))
			{
				%playerSpawnPoint = spawnArea3;
							echo("spawning objective 3");
		}
				
		if(controlNode.objective == 4)
			if(isObject(spawnArea4))
			{
				%playerSpawnPoint = spawnArea4;
							echo("spawning objective 4");
		}
				
		if(controlNode.objective == 5)
			if(isObject(spawnArea5))
			{
				%playerSpawnPoint = spawnArea5;
							echo("spawning objective 5");
		}
	}
	else
	{
		   %playerSpawnPoint = pickPlayerSpawnPoint($Game::DefaultPlayerSpawnGroups);
	}
		%game.spawnPlayer(%client, %playerSpawnPoint);
	
	   // Starting equipment
   %game.loadOut(%client.player);


   // Prepare the actual player object for spawning and beginning equipment.
//   parent::preparePlayer(%game, %client);
}
#2
04/22/2011 (6:50 pm)
What Steve said, fixed spawn locations controlled by triggers/objectives, and/or you could simply increment through a set of spawn points. There are any number of ways in-game you could set which spawn point would next be used. The random choice is just a throwback to the multiplayer fps nature of Torque. The scripts are simply examples, nothing says you have to use the methods outlined in them, especially if for your game there is a better way a task can be done.
#3
04/22/2011 (8:50 pm)
Basically, I'm using $Server::TargetSpawnPoint to pass my desired spawn point between mission files. The current incarnation works, I just don't like it very much - lol! I wonder if I couldn't code the array into the actual mission file itself, so I could just look for a spawn point that I know is already in the array and run with it.

And my next task is saving the state of a mission so that I can move back and forth between them - building an RPG.

Anyway - thanks guys!

@Steve - Search and I have a long standing love/hate relationship - it loves to hate me....
#4
04/23/2011 (5:53 am)
Quote:
The current incarnation works, I just don't like it very much - lol!

lol - Any particular reason?
If I have something working I'm usually happy with it regardless of how hacky it is. <--- blatantly untrue statement

You could equally store that info in any sort of global, even as a preference, or other file that records/saves player progress.
#5
04/23/2011 (6:44 am)
Quote: Quote:
The current incarnation works, I just don't like it very much - lol!


lol - Any particular reason?
Vanity I suppose. I love pointing at my code and saying "I think this right here was a particularly elegant solution." It doesn't happen often....
#6
04/23/2011 (7:35 am)
If you could let us know a bit more about your game design and how you want spawning to work, we could probably be a bit more helpful with suggestions ;).

It sounds like you're setting up travelling between multiple .mis files like zones in an open-world game, and want to spawn at the appropriate entrance given where you're travelling from. In that situation, I'd probably specify a naming scheme for my spawn points - for example, name them EntranceSpawn_X, where X is the name of the location that the entrance is arriving from. Then, when you load a mission, you can check the last location the player was in, and find the name of the appropriate spawn point.
#7
04/23/2011 (8:11 am)
That's exactly what I'm doing, area transitions similar to Neverwinter Nights or Dragon Age et al. I store the destination at the onEnter script for a trigger in $Game::TargetSpawnPoint and try to find it in pickPlayerSpawnPoint(). The function presented above is my current working version and it does the trick, but seeing several attempts to find the desired spawn point in the console is tweaking my sense of ascetics. I use the name of the mission I'm coming from and the name of the mission I'm going to as the name of the entrance spawn point - ie CavernDesertEnter coming from the Cavern mission to the Desert mission Entrance - similar to your scheme.

Basically, getWordCount(%spawnGroups) gives us the number of groups in %spawnGroups so we can use it to iterate through them using %group = getWord(%spawnGroups, %i), but the same method doesn't work within %group to get a list of spawn points. I had a thought last night (still haven't tried it) that perhaps %group[%j] can be used to index the spawn points within %group, but I'm not optimistic.

If you've got a suggestion for upload, perhaps an FTP or something, I will put it up and you can look at the whole thing. I'm not shy - it's too early to have anything useful put together anyway. I plan to get the underlying systems working with mostly stock art along with some of my early art pipeline experiments before I really start putting it together.

Honestly, I'd rather build something like Morrowind or Oblivion, but using Torque's portals/zones to just create the caves and buildings all in one giant map (more like Ultima 9) thereby avoiding transitions altogether, but I'm too lazy to fiddle with the terrain system. But, now that I think about it, someone was saying that they've gotten multiple terrain blocks working in a single mission file.... Have to start playing with that again, too. I can only imagine the load times would get really annoying though.
#8
06/07/2011 (11:39 am)
Were you ever able to get this working properly? And if so, how did you do it? I'm trying to set up something similar, where my missions are like large zones, in an effort to make a more expansive world without having incredibly long load times.

Thanks ahead of time for any help you could toss my way!
#9
06/07/2011 (1:04 pm)
Ok, I modified the pickPlayerSpawnPoint() function in core/scripts/server/spawn.cs as follows:

//-----------------------------------------------------------------------------
// pickPlayerSpawnPoint() is responsible for finding a valid spawn point for a
// player.
//-----------------------------------------------------------------------------
function pickPlayerSpawnPoint(%spawnGroups)
{
   %targetName = $Server::TargetSpawnPoint;
   if(%targetName $= "")
   {
      %targetName = "DefaultSpawnSphere";
   }
   echo("Loading - Target Spawn Point: " @ %targetName);
   // Walk through the groups until we find a valid object
   for (%i = 0; %i < getWordCount(%spawnGroups); %i++)
   {
      %group = getWord(%spawnGroups, %i);
      
      if (isObject(%group))
      {
         %spawn = %group.getRandom();
         if (isObject(%spawn))
         {
            %name = %spawn.getName();
            if(%name $= "")
            {
               echo("***Setting empty name to DefaultSpawnSphere");
               %spawn.name = "DefaultSpawnSphere";
               //return %spawn;
            }
            while(%targetName !$= %name)
            {
               %spawn = %group.getRandom();
               %name = %spawn.getName();
               echo("***Testing Spawn Point - " @%name);
            }
            echo("***Selected Spawn Point: " @ %name);
            return %spawn;
         }
         else
         {
            echo("***Spawn Point not an object!");
         }
      }
   }

   // Didn't find a spawn point by looking for the groups
   // so let's return the "default" SpawnSphere
   // First create it if it doesn't already exist
   if (!isObject(DefaultPlayerSpawnSphere))
   {
      %spawn = new SpawnSphere(DefaultPlayerSpawnSphere)
      {
         dataBlock      = "SpawnSphereMarker";
         spawnClass     = $Game::DefaultPlayerClass;
         spawnDatablock = $Game::DefaultPlayerDataBlock;
      };

      // Add it to the MissionCleanup group so that it
      // doesn't get saved to the Mission (and gets cleaned
      // up of course)
      echo("***Selected Spawn Point: " @ %spawn.getName());
      MissionCleanup.add(%spawn);
   }

   return DefaultPlayerSpawnSphere;
}

Then I added the following functions to scripts/server/game.cs :
//-----------------------------------------------------------------------------
// Mission transition code
// This is intended to be called from a trigger's onEnter() method.
//-----------------------------------------------------------------------------

function transition(%targetMission, %targetSpawnPoint)
{
   $Server::TargetMission = %targetMission;
   $Server::TargetSpawnPoint = %targetSpawnPoint;
   
   echo("Start Transition - Target Mission: " @ $Server::TargetMission);
   echo("Start Transition - Target Spawn Point: " @ $Server::TargetSpawnPoint);
   
   if (!$Game::Cycling)
      $Game::Cycling = true;
   
   endGame();
   
   $Game::Schedule = schedule($Game::EndGamePause * 50, 0, "loadTransition");
}

function loadTransition(%targetSpawn)
{   
   $Game::Cycling = false;
   %search = $Server::TargetMission;
   %file = findFirstFile(%search);
   loadMission(%file, false);   
}

I also added the following globals in scripts/server/init.cs after $Server::TestCheats = false; in the initServer() function :
$Server::TargetMission = "";
   $Server::TargetSpawnPoint = "";

You then make a trigger and call transition() from the onEnter() event with the appropriate values. You can tweak the schedule delay, but anything much shorter than what I've used tends to hang the system. I was able to create several missions and transition smoothly between them to named spawn points. The thing to watch for is that the script requires all spawn points to be named and will name any unnamed points DefaultSpawnSphere - otherwise the pickPlayerSpawnPoint() method will hang.

Hope it helps!
#10
06/07/2011 (1:08 pm)
My complaint wasn't that it didn't work, just that it's kludgy. Having to randomly pick spawn points from the group until you find the right one is just ugly. We should be able to iterate over the indices in the group as if it were an array or list. It works, but it's not pretty.
#11
06/07/2011 (2:50 pm)
How about using forEach?
%set = playerDropPoints;//or whatever
%node = %obj.getclassname(spawnSphere);
foreach( %node in %set )
{
   //science goes here
}

... it is kinda the same though ...
#12
06/07/2011 (3:17 pm)
Ah - I'm glad you read. I'm too lazy most days....

I'll give that a shot. It's more elegant than the way I'm doing it now, for sure.

Thanks, Steve!
#13
06/08/2011 (2:32 pm)
I'm not sure how less 'kludgy' this is, but I'm no longer using the getRandom() function. This gets the number of spawn points found under each spawn group, iterates through them, and tests against each one for a match. Once a match is found, the player is spawned there.

//-----------------------------------------------------------------------------
// pickPlayerSpawnPoint() is responsible for finding a valid spawn point for a
// player.
//-----------------------------------------------------------------------------
function pickPlayerSpawnPoint(%spawnGroups)
{
   %targetName = $Server::TargetSpawnPoint;
   if(%targetName $= "")
   {
      %targetName = "DefaultSpawnSphere";
   }
   // Walk through the groups until we find a valid object
   for (%i = 0; %i < getWordCount(%spawnGroups); %i++)  
   {  
      %group = getWord(%spawnGroups, %i);  
        
      if (isObject(%group))  
      {  
         // Gets number of spawn points under current spawn group
         %spawnCount = %group.getCount();
         
         // If there are no valid spawn points, let us know
         if (%spawnCount $= 0)
         {
            echo("****c4 -> No valid spawn points found in this group!");
         }
         else
         {
            // If spawn points are found, iterate through them
            for (%x = 0; %x < %spawnCount; %x++)
            {
               %spawn = %group.getObject(%x);
               %name = %spawn.getName();
               
               // Test the current spawn point for a match
               echo(%spawn @ "c4 -> Testing spawn point " @ %name);
               if (%targetName $= %name)
               {
                  // Once a match is found, spawn player at matching location
                  echo(%spawn @ "c4 -> SPAWNING AT LOCATION: " @ %name);
                  return %spawn;
               }
            }
         }
      }
   }

   // Didn't find a spawn point by looking for the groups
   // so let's return the "default" SpawnSphere
   // First create it if it doesn't already exist
   if (!isObject(DefaultPlayerSpawnSphere))
   {
      %spawn = new SpawnSphere(DefaultPlayerSpawnSphere)
      {
         dataBlock      = "SpawnSphereMarker";
         spawnClass     = $Game::DefaultPlayerClass;
         spawnDatablock = $Game::DefaultPlayerDataBlock;
      };

      // Add it to the MissionCleanup group so that it
      // doesn't get saved to the Mission (and gets cleaned
      // up of course)
      echo("***Selected Spawn Point: " @ %spawn.getName());
      MissionCleanup.add(%spawn);
   }

   return DefaultPlayerSpawnSphere;
}

I also altered your transition function a little, and changed the endGameGui.gui into a "loading..." screen with a background bitmap image that only lasts until the next mission is loaded:

function transition(%targetMission, %targetSpawnPoint)
{
   $Server::TargetMission = %targetMission;
   $Server::TargetSpawnPoint = %targetSpawnPoint;

   echo("****\c4 -> CLIENT ACCESS REQUEST: " @ $Server::TargetMission @ " - " @ $Server::TargetSpawnPoint);

   if (!$Game::Cycling)
      $Game::Cycling = true;

   endMission();

   $Game::Cycling = false;
   %file = "levels/" @ $Server::TargetMission @ ".mis";
   loadMission(%file, false);
}
#14
06/08/2011 (4:57 pm)
Nice - I think that's a little cleaner, and I had thought about doing something with the loading between missions but had gotten sidetracked. Awesome!