Game Development Community

dev|Pro Game Development Curriculum

Tactics-Action Hybrid Game Tutorial Part10: GamePlay Test

by Steve Acaster · 05/11/2011 (2:22 pm) · 6 comments

Back to Part9: Ai Combat

Now we are ready to test the gameplay, let's ready the level for it.

As there's going to shooting we need some healthpacks for healing - not too many though, as dead Ai will drop them too. In your "TorqueTactics" Tutorial Level, go into the World Editor (F11) and create a new simGroup (Scene Tree->library->level->system->simGroup) and then name it "level_items". On the terrain, near the start of our mission where our team of Ally player's spawn, place a "healthKitPatch" (Scene Tree->library->scripted->health->healthKitPatch). Move it around halfway down the first straight of the gameplay area route, and to the right side so it won't get in the way). Next add some spare ammo nearby - in case our team members run out of bullets! (library->scripted->ammo->rocketLauncherAmmo). Make sure that both of these are in the "level_items" folder/simgroup to keep the "Scene Inspector" tidy.

yorks.deta.in/SimpleFPSTutorial/Lesson2_scripting/lesson2_1.jpg
Ignore the gun in the picture, we won't need that as there is only one weapon type in this Tutorial.

Now add more instances of "healthKitPatch", one at the far ends of each of the smaller dead-end alleyways we cut into our terrain earlier. In the "Scene Inspector", make sure that they are all placed in the "level_items" simgroup.

Now create a new simGroup and call it "BotDropPoints". Find the second rock along our path (left, and then at the first right corner). Open up the World Editor (F11) and place a new spawnSphere just to the left of the rock. (Scene Tree-> Library-> Scripted-> Misc-> SpawnSphereMarker). From the World Editor toolbar, press the "toggle button for object transforms and world transforms" (mouseover the buttons for info). The default is "world transform" and change that to "object transform". Select the "rotate selection" button below the "Object Editor" tab. Reselect the new spawnSphere if you have to and rotate it 90 degrees toward the direction that the player will come from. Now press the "Object Editor" tab's "Move Selection" button and the familiar XYZ arrows will reappear, though this time they have rotated 90 degrees, matching the "object rotation". It's doubtful that you got an exact 90 degrees rotation, so in the "Object Inspector", under the "Transform" section manually type the "rotation" to be " 0 0 1 90". Now rename this SpawnSphere "botspawn1" and place it in the "BotDropPoints" folder.

yorks.deta.in/SimpleFPSTutorial/Lesson3_ai/lesson3_1.jpg

Near the end of the first dead-end alleyway, in front of where you placed a "healthKitPatch", add another "spawnsphere" and call it "botspawn2" and add it to the appropriate simGroup folder. Now create another simGroup and call it "bot_paths". Outside of the alleyway, in the middle of the main route, create a "waypoint" (Scene Tree-> Library-> Scripted-> Misc-> WaypointMarker) and call this "bot2goal". Make sure that there is a clear line of sight between this marker and "botspawn2". Add this marker into the "bot_paths" simGroup folder.

Create yet another simGroup folder (keep Scene Inspector tidy!) and call it "level_triggers". Create a "Trigger" (Scene Tree-> Library-> Level->Level->Trigger) and a dialogue box will display. The default datablock should be "defaultTrigger". Name the trigger "trig1" and press the button marked "Create New". You should have a new trigger which when selected displays as a yellow box. Drag that over to where you placed the first healthKitPatch and the spare Ammo and position it between the objects and where your row of 3 spawnSpheres are. Set the scale of the trigger to "25 5 5" so that it fills the width of the route cut into the terrain.

Now we're going to script our enemy bots' spawn by editing the trigger's "enterCommand".We want to check whether the bot already exists, because we don't want 2 objects with the same name. If it doesn't already exist we want to spawn it, and for one bot we will give it a goal.

if(trig1.triggerOff == 1)
return;
if(!isObject(bot1))
aiplayer::tacticsSpawn(bot1, botspawn1, 0);
if(!isObject(bot2))
{
aiplayer::tacticsSpawn(bot2, botspawn2, 0);
bot2.goal=bot2goal;
trig1.triggerOff = 1;
}

We only want this trigger to fire once, regardless of how many times it is triggered, so we add a new "Dynamic Field" called" triggerOff. Initially it has the value of "0" (false), but when it is first triggered, it fires it's script and spawns our 2 enemy bots, and then set's the value to "1" (true). With it's value "true" it quits the function and will not called the scripts below the "return".

Save! And let's test it all. Restart the mission file if necessary.

The game should start with your 3 man team of "Tom", "Dick" and "Harry", with Tom under your control. Move Tom out towards the first corner. When Bot1 spots Tom, he will start shooting. Move up towards Bot1 (taking the incoming fire stoically!). When Tom is out of energy quickly press "Team" button. Now you want to do the same with Dick and Harry, try to get all 3 of your playerObjects in a staggered line where they all have a view of the entrance to the first alleyway (where Bot2 has spawned at the far end of). When your moves are completed press "End Turn" button.

yorks.deta.in/TorqueTacticsTutorial/TorqueTacticsTut2.jpg

Now it's the Enemy Team's phase. Bot1 should check whether he has a goal - he has not - then open fire at the nearest Ally Team member. Once he has fired Bot2 will take his turn at being the Enemy Team's active playerObject. Bot2 will check whether he has a goal - he has - check whether he is close enough to it - he is not - and check that he has energy to move - he has so he can. He will start running down the alley towards the action, this will take a few seconds just like a Client controlled playerObject moves, and will probably run out of energy when he emerges into view, before he has completed his way to his goal.

Now when the Client's team members see Bot2 is moving, they might have chance to use their "autoShoot" function, and open fire spontaneously, using the same rules which Enemy Team Ai used to shoot at the Client's moving playerObjects. When Bot2 has run out of energy or completed his move, he will fire his single shot at the nearest hostile Ally and then the turns will increase and restart with the Ally Team back in control of the Client.

If your 3 team members are all in a nice staggered line and can see Bot2, just "End Turn". Bot1 will shoot once, then had control to Bot2. Bot2 will probably have to finish his move, and the Ally Team members will have an opportunity to "autoShoot" as he does. Bot2, will then complete his move (if he has not been killed by the autShooting of the Allies' passiveThink function), fire his shot, and then end the Enemy Phase and start a new Player Turn.

Go ahead and shoot the 2 Enemy Team members until they are killed. They'll each drop a healthKitPatch, a rocketLauncherAmmo, and a rocketLauncher. When they are dead, get your most wounded team members to run into the healthKitPatches and heal. When you're done, quit.

If you checked the console you would see spam about triggers not being able to find an onParent::sometingOrOther anytime a playerObject interacted with them. Let's clean that up. In scripts/server/triggers.cs comment out the "onParent::" callback in each of the 3 functions. Save.

When Ai die they throw out equipment, whilst Ammo and HealthKitPacks are great, no-one needs a second rocketLauncher. In scripts/server/player.cs edit "onDisabled()":

function Armor::onDisabled(%this, %obj, %state)
{
   // Release the main weapon trigger
   %obj.setImageTrigger(0, false);

   // Toss current mounted weapon and ammo if any
   %item = %obj.getMountedImage($WeaponSlot).item;
   if (isObject(%item))
   {
      %amount = %obj.getInventory(%item.image.ammo);
      if(%amount)
         %obj.throw(%item.image.ammo, %amount);
    //  %obj.throw(%item, 1);// <--- yorks don't drop the gun
   }

   // Toss out a health patch
   %obj.tossPatch();

//...

Also, whilst it's nice to be able to identify Ally Team members, we don't need to know what the Enemy Team are called. In scripts/server/aiplayer.cs:

function AIPlayer::tacticsSpawn(%name, %spawnPoint, %team)
{

//...

   MissionCleanup.add(%player);
   
 // %player.setShapeName(%name);// <---- yorks out here
   %player.team = %team;
  
//...

	if(isObject($AllyList) && %team == 1)
	{
		  %player.setShapeName(%name);// <--- yorks moved here!
	
		$AllyList.add(%player, %playerType);

//...

You may have also noticed that during the Enemy Ai's activeThink Phase, they are willing to try and shoot through walls - so let's fix that.

function AIPlayer::Targeting(%this)
{

//...

				if(%tempdist < 100)//100 is max range for Client so same for Ai
					if(%tempdist < %dist || %dist == 0)
					{
						if(%this.targetClearView(%bot) == true)//yorks new start<---
						{
							%dist = %tempdist;
							%index = %i;
						}//<--- yorks new end
					}
//...

}

Also in aiplayer.cs, we can make the Client's camera move to show the Ally Team member who is being attacked during the Enemy Phase.

function AIPlayer::activeThink(%this)
{
//...

		%target = %this.AttackTarget(%obj);
		
		if(%target != 0)
		{
			if(%target != localClientConnection.player)//<--yorks new start
			{
				localClientConnection.player = %target;
				commandToServer('tacticsCam');
			}// <--- yorks end new
		
			%this.setAimObject(%target, "0 0 1.5");//aim a little up
			%this.schedule(200, "autoShoot");//to help aiming delay
//...

And whilst we are messing around with the camera, let's change the way it displays the playerObject whenever we select/cycle to a new team member. Currently the default view is always from the global Y-axis, it'd be much cooler if it was orientated behind the playerObject, showing what they could see. In scripts/server/commands.cs replace the whole of our custom "tacticsCam" function:

function serverCmdtacticsCam(%client)
{
//	%pi = 3.14159;
	//mDegToRad(20) = 0.349066
	%posneg = getword(%client.player.getTransform(), 5);
	%deg = getword(%client.player.getTransform(), 6);

	if(%posneg == -1)
		%deg = %deg - %deg - %deg;
		
	%client.camera.setOrbitObject(%client.player, "0.349066 0 " @ %deg, 0, 1.5, 1.5);
	%client.camera.camDist = 1.5;
}

That's probably not the greatest example of mathematics but hey ...

Feel free to replay our gamePlay test and hopefully, it looked something like this:



Part Eleven: Ai Paths

#1
05/11/2011 (4:56 pm)
Thumbs up for this whole Resource series! Filled my mind with ideas of re-visiting some of those tactical/rpg ideas I once had -- used to love old turn-based strategy and rpg games :)

#2
05/13/2011 (3:28 pm)
you are truly a Wiz Steve, love your resources :-)

P.S. are you from Yorkshire (YorkshireRifles)?

D.R. Pemberton
www.deadlyassets.com
#3
05/13/2011 (4:20 pm)
Aye, I'm East Riding of the Shire.

Up the Tigers!
#4
05/14/2011 (6:52 am)
Nice one

<- West Yorkshire here, so not far

Nice to see someone local(ish) :-D
#5
11/01/2011 (6:59 pm)
That is an awesome tutorial. Thanks.
#6
04/08/2012 (11:46 pm)
Oftentimes, expensive tiffany and co jewelry jewelry get learned merely women of all ages that contain dads. Charm bracelets seemed to be often Cheap beats times with the toys that this little girl earns on birth records or sometimes top selling baptism. occasions True Religion Outlet because a young stumbling crazy about charm additionally, the thinking in it, they begin picking the True Religion Outlet appropriate that. Once the author grows to perhaps passes across True Religion sale the actual youth, your girl repeatedly has an big selection of them dearest valuables.