Game Development Community

dev|Pro Game Development Curriculum

Top-Down 8-Directional Shooter: Das Swarmy: Part 1

by Bryan Sawler · 02/26/2011 (2:34 pm) · 11 comments

Introducing Das Swarmy: Part 1
A simple sample of a top-down swarming enemies game in Torque 3D.

I got the idea to do this over a delicious and nutritious meal of French Toast Sticks with my son at Burger King this morning. I've been wanting to contribute to the community a bit and decided to whip up this little game demo. I'm kicking this off at 12:15pm.


Step 1: Make a new project, starting from the Full template. Since I'm doing this on my macbook pro, I have to take the steps documented in <forum link> to get the project to actually build on my laptop. And now I BUILD...and wait...and refresh soda...and wait some more

721 source files later...I'm wonder why since this will be all script I bothered making a debug build. Current time is 12:20pm.


Step 2: We need to make our scene. I'm going to VERY quickly throw a scene together, with a few important points. First, a picture:
mutekicorp.com/images/dasswarmy1.pngNow that the scene is all laid out, we begin making things happen. Current time is 12:40pm (yes I spent way too much time tinkering with how big of a room etc). Key points is a Generators SimGroup, with 2 PlayerSpawnPoints. These are where the swarming enemies will be spawning from. The walls are the simple "cube.dae" shape scaled up, with collision set to "Visible Mesh". In 1.1 I'd have just drawn them with the simple geometry creation tool but in Mac-land we only have 1.0.1 so we make do with what we have.


Step 3: Create a new AIPlayer datablock. To keep things simple just add this to game/art/datablocks/aiPlayer.cs at the end:

datablock PlayerData(SwarmPlayer : DefaultPlayerData)
{
   shootingDelay = 2000;
};

This creates our SwarmPlayer (so we can override functions) using the DefaultPlayerData as the parent. We could have just as easily used DemoPlayer but we may want to change things later?


Step 4: Time to get an overhead camera.
In game/scripts/server/gameCore.cs, at the end of GameCore::onClientEnterGame we're going to add:

// Make the camera overhead
   %client.setCameraObject(%client.camera);
   %client.camera.setOrbitObject(%client.player, mDegToRad(80) @ " 0 0", 15, 25, 25, false, "0 0 1", true);
   %client.setFirstPerson(false);

Those should happen right AFTER the %game.preparePlayer(%client); call

I spent a little more time on this playing with different distances, could have been much quicker but whatever. Current time is 1:00pm


Step 5: Reworking controls, take 1.
We currently have the standard game controls which are fine, BUT since we're in a top-down game we don't want to be aiming up / down (especially as it's difficult to really judge where we're aiming). To fix this we go into game/scripts/client/default.bind.cs and find the line:
moveMap.bind( mouse, yaxis, pitch ); and just comment it out, so it reads:
//moveMap.bind( mouse, yaxis, pitch );
(you're adding the // at the beginning)
Now we always aim straight ahead.
Make sure to delete your prefs.cs/config.cs files to make this take effect!


Step 6: Spawning enemies!
After all this we need to kill things, right?
Again we're going into game/scripts/server/gameCore.cs
At the VERY end of the file, we're going to add this function:
function GameCore::spawnFromGenerator(%game)
{
   %groupName = "MissionGroup/Generators";
   %group = nameToID(%groupName);

   if(%group != -1)
   {
      %count = %group.getcount();
      if(%count != 0)
      {
         %index = getRandom(%count-1);
         %spawn = %group.getObject(%index);

         %player = new AiPlayer()
         {
            dataBlock = SwarmPlayer;
         };
         MissionCleanup.add(%player);
         %player.setShapeName("Monster");
         %player.setTransform(%spawn.getTransform());
      }
   }

   %game.schedule(5000, "spawnFromGenerator");
}
And, in the same file at the end of GameCore::onMissionLoaded (just under %game.startGame();) we add:
%game.schedule(5000, "spawnFromGenerator");
This will get our enemies spawning from the spawnpoints we laid down. Now we just have to kill them!
Current time, 1:20pm (so just over an hour so far, and that includes the time I took to make my son lunch!)

Step 7: A fix needed...
The default Gideon player death doesn't...well...work. We need to go into game/scripts/server/player.cs and find the Player::playDeathAnimation(%this) method.
Change the last line in that function to simply
%this.setActionThread("Death1");
Now we can actually kill our enemies. Now we just need them to try to kill us.

Step 8: Come and get me!
Now we're going to make the enemies swarm. We'll use an EXTREMELY simple swarming / moving algorithm (if you can even call it that) but it's one that's worked well in the past for me, so why not stick with it.
The algorithm is:
First, get the direction to the player.
Cast a ray to see if we can move a small amount in that direction without colliding with anything. If so, move a small amount in that direction. If we DO collide with something (likely another enemy) then try 30 degrees to the left, and 30 degrees to the right. Then we try 60s, then 90s. If any of those succeed move in that direction, else we just wait.
Most importantly, if that ray hits our target, deal some damage (we're melee swarming unarmed Gideons apparently)

Big function first, goes at the bottom of game/scripts/server/aiPlayer.cs:
function AIPlayer::checkMovement(%this)
{
   %mask = -1;

   %index = %this.getNearestPlayerTarget();
   if(%index == -1)
   {
      %this.schedule(2000, "checkMovement");
      return;
   }
   %client = ClientGroup.getObject(%index);
   %target = %client.player;
   %playerPos = %target.getPosition();
   %pos = %this.getPosition();
   // Get the vector to the player
   %vec = VectorSub(%playerPos, %pos);
   %vec = VectorNormalize(%vec);

   // Move our position 1 unit up in the air so we're not dragging on the ground
   %pos = VectorAdd(%pos, "0 0 1");
   %raytest = containerRayCast(%pos, VectorAdd(%pos, %vec), %mask, %this);
   if(%raytest $= "0")
   {
      // We didn't hit anything so we can move forward! Moving in 2-unit scales so we don't constantly start/stop
      %this.setMoveDestination(VectorAdd(%pos, VectorScale(%vec, 2)));
   }
   else
   {
      // We hit something - IF it's our target, deal damage, otherwise try to move around the obstacle
      if(getWord(%raytest, 0) == %target)
      {
         %target.damage(%this, %targetPos, 1, "melee");
         %this.schedule(500, "checkMovement");
         return;
      }

      %angle = mAtan(getWord(%vec, 1), getWord(%vec, 0));

      for(%offset = 30; %offset <= 90; %offset += 30)
      {
         %testangle = %angle + mDegToRad(offset);
         %newVec = mCos(%testangle) SPC mSin(%testangle) @ " 0";

         %raytest = containerRayCast(%pos, VectorAdd(%pos, %newVec), %mask, %this);
         if(%raytest $= "0")
         {
            // We didn't hit anything so we can move forward! Moving in 2-unit scales so we don't constantly start/stop
            %this.setMoveDestination(VectorAdd(%pos, VectorScale(%newVec, 2)));
         }
         %testangle = %angle - mDegToRad(offset);
         %newVec = mCos(%testangle) SPC mSin(%testangle) @ " 0";

         %raytest = containerRayCast(%pos, VectorAdd(%pos, %newVec), %mask, %this);
         if(%raytest $= "0")
         {
            // We didn't hit anything so we can move forward! Moving in 2-unit scales so we don't constantly start/stop
            %this.setMoveDestination(VectorAdd(%pos, VectorScale(%newVec, 2)));
         }
      }
   }

   %this.schedule(500, "checkMovement");
   return;
}

And we need to make sure this gets fired off, so in game/scripts/server/gameCore.cs, in our spawnFromGenerator function at the bottom we're going to add:
%player.schedule(1000, "checkMovement");
Just AFTER the
%player.setTransform...
line
Current time is 2:15pm. In 2 hours we have a mostly functional game demo with swarming enemies.

Taking a short break and when I come back we have to fix a problem of the camera not properly following a new player if we die, and do something about these pretty terrible controls.

About the author

President and Founder of The Muteki Corporation Makers of such fine iPhone games as MazeFinger, Topple 2, and The Battle of Pirate Bay!


#1
02/26/2011 (3:23 pm)
awesome stuff Bryan,
keep it up
#2
02/26/2011 (4:46 pm)
good start, look forward to seeing more.
#3
02/26/2011 (5:16 pm)
Just realized I maybe should've put this in the resources section not under blogs...If we can get it moved sweet otherwise the followups will be there :)
#4
02/28/2011 (2:50 pm)
All taken care of. It's a resource now.
#5
02/28/2011 (5:49 pm)
Thanks Scott!
#6
03/03/2011 (7:22 am)
I'm new to T3D and I think that's exactly the kind of stuff we beginners need, quick setup for something simple and working. Thanks a lot!
#7
03/08/2011 (6:28 pm)
Great Resource Bryan! I think resources like this are great for new people that are just getting started with Torque 3D. Once I finish some projects that I've been working on I plan on putting up some entry level and advanced level resources to help out the community. Keep it up man so far it's looking good!
#8
04/27/2011 (12:53 am)
Hi Bryan, I am new to this so i just wanted to know what you mean by "Full template" and what kind of enemies will spawn if you can change them ?

I don't know if you can help me out, but I was woundering if you could upload an example ? i'm working on an fps/rpg game and also woundering if you know any tutorial about experience/leveling.


Let's say that there is an spwan point and I have my hero FPS with an weapon. Each time i kill an enemie i get experience and after i reach an new level (200/200 experience) i can upgrade my character ? with multiplayer. is that hard to do ? and could you maybe point me at the right direction ?
#9
09/22/2011 (5:06 pm)
Hi Bryan I was wondering if you had any problems with the character respawning and not showing the orbit camera as control?
#10
09/22/2011 (6:21 pm)
take the , false, "0 0 1", true); off the end and the camera will follow the player, any idea about how to reattach this to the player upon death.
That probably clarifies my question a bit.
#11
01/13/2012 (8:02 am)
I just tried this out and it works fairly well.

As for the respawning issue I had to make a few changes in the scripts.

In game/scripts/server/camera.cs, at the end of Observer::onTrigger(%this,%obj,%trigger,%state)
we're going to comment out one line of code.

// comment out this line at the bottom
//%this.setMode(%obj,"Observer");


In game/scripts/server/gameCore.cs, at the end of GameCore::preparePlayer(%game, %client)
we're going to add:

// Make the camera overhead
%client.setCameraObject(%client.camera);
%client.camera.setOrbitObject(%client.player, mDegToRad(80) @ " 0 0", 15, 25, 25, false, "0 0 1", true);


Btw, if you added the camera code to the end of GameCore::onClientEnterGame you will
want to remove that as well.

That should get you going in the right direction.