-
Notifications
You must be signed in to change notification settings - Fork 1
RTSPrototype
datablock PlayerData(BoomBotData : DefaultPlayerData) { shapeFile = "art/shapes/actors/BoomBot/BoomBot.dts"; boundingBox = "1.1 1.2 2.5"; pickupRadius = "1.2"; }; Next, in scripts/server/gameDM.cs we'll change the default player class and datablock. In DeathMatchGame::initGameVars() make the following changes: $Game::defaultPlayerClass = "AiPlayer"; $Game::defaultPlayerDataBlock = "BoomBotData"; This gives us the ability to tell our unit where to go and let the AI class handle getting it there. It also lets us have our default player be the BoomBot without having to specify it at the spawnpoint. First we're going to set up our camera mode framework. In scripts/server/commands.cs we're going to add some functions to set and toggle our camera modes. The following code can sit at the end of the script file: // ---------------------------------------------------------------------------- // Camera commands // ---------------------------------------------------------------------------- Additionally, in scripts/client/default.bind.cs we'll add some keybinds and utility functions to assist in controlling the camera: function toggleCameraMode(%val) { if (%val) commandToServer('toggleCamMode'); } Ensure that any other binds for zaxis and alt m are commented out to avoid conflicts. Also, if the file scripts/client/config.cs exists it will need to be deleted before changes to default.bind.cs can take effect. The following code will change the way mouse input affects movement and click interaction. Normally, the camera is controlled by an actor in FPS (aim) mode. To focus on just mouse and camera work, we need to change how the default camera is controlled. Open game/scripts/server/gameCore.cs. In function GameCore::preparePlayer(%game, %client), locate the following line: %game.spawnPlayer(%client, %playerSpawnPoint);
%game.spawnPlayer(%client, %playerSpawnPoint, false);
Immediately below the %game.spawnPlayer() function, add the following code: // Set camera to Overhead mode commandToServer('overheadCam'); If you run the game, you will now be using an orbit camera instead of an FPS view controlled by the actor. Next, we need to be able to control the on/off state of the in-game mouse cursor. Open game/scripts/client/default.bind.cs. At the end of the file, add the following: // Turn mouse cursor on or off // If %val is true, the button was pressed in // If %val is false, the button was released function toggleMouseLook(%val) { // Check to see if button is pressed if(%val) { // If the cursor is on, turn it off. // Else, turn it on if(Canvas.isCursorOn()) hideCursor(); else showCursor(); } }
noCursor = "0";
First, open art/gui/PlayGui.gui and find new GuiControl(DamageHUD) toward the end of the file and remove the entire block of code (ensure that you do not delete the main block's closing brace - it's the one with the semi-colon after it). It occupies the center of the screen when you are in play mode and it will block mouse clicks to that area.
// onMouseDown is called when the left mouse // button is clicked in the scene // %pos is the screen (pixel) coordinates of the mouse click // %start is the world coordinates of the camera // %ray is a vector through the viewing // frustum corresponding to the clicked pixel function PlayGui::onMouseDown(%this, %pos, %start, %ray) { // If we're in building placement mode ask the server to create a building for // us at the point that we clicked. if (%this.placingBuilding) { // Clear the building placement flag first. %this.placingBuilding = false; // Request a building at the clicked coordinates from the server. commandToServer('createBuilding', %pos, %start, %ray); } else { // Ask the server to let us attack a target at the clicked position. commandToServer('checkTarget', %pos, %start, %ray); } }
function serverCmdcreateBuilding(%client, %pos, %start, %ray) { // find end of search vector %ray = VectorScale(%ray, 2000); %end = VectorAdd(%start, %ray); // set up to look for the terrain %searchMasks = $TypeMasks::TerrainObjectType; // search! %scanTarg = ContainerRayCast( %start, %end, %searchMasks); // If the terrain object was found in the scan if( %scanTarg ) { // get the world position of the click %pos = getWords(%scanTarg, 1, 3); // Note: getWord(%scanTarg, 0) will get the SimObject id of the object // that the button click intersected with. This is useful if you don't // want to place buildings on certain other objects. For instance, you // could include TSStatic objects in your search masks and check to see // what you clicked on - then don't place if it's another building. // spawn a new object at the intersection point %obj = new TSStatic() { position = %pos; shapeName = "art/shapes/building/orcburrow.dts"; collisionType = "Visible Mesh"; scale = "0.5 0.5 0.5"; }; // Add the new object to the MissionCleanup group MissionCleanup.add(%obj); } }
// button is clicked in the scene
// %pos is the screen (pixel) coordinates of the mouse click
// %start is the world coordinates of the camera
// %ray is a vector through the viewing
// frustum corresponding to the clicked pixel
function PlayGui::onRightMouseDown(%this, %pos, %start, %ray)
{
function serverCmdmovePlayer(%client, %pos, %start, %ray) { //echo(" -- " @ %client @ ":" @ %client.player @ " moving"); // Get access to the AI player we control %ai = %client.player; %ray = VectorScale(%ray, 1000); %end = VectorAdd(%start, %ray); // We want to allow the AI Player to walk on TSStatics, Interiors, Terrain, etc., so // I broadened the search mask selection. %searchMasks = $TypeMasks::TerrainObjectType | $TypeMasks::StaticTSObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::ShapeBaseObjectType | $TypeMasks::StaticObjectType; // search! %scanTarg = ContainerRayCast( %start, %end, %searchMasks); // If the terrain object was found in the scan if( %scanTarg ) { %pos = getWords(%scanTarg, 1, 3); // Get the normal of the location we clicked on %norm = getWords(%scanTarg, 4, 6); // Set the destination for the AI player to // make him move %ai.setMoveDestination( %pos ); } }
function spawnAI(%val) { // If key was pressed down if(%val) { // Create a new, generic AI Player // Position will be at the camera's location // Datablock will determine the type of actor new AIPlayer() { position = LocalClientConnection.camera.getPosition(); datablock = "DefaultPlayerData"; }; } } // Bind the function spawnAI to the keyboard 'b' key moveMap.bind(keyboard, b, spawnAI);
Currently, we have a player we can control, and targets that can die. Let's give the player some combat skills. In game/scripts/server/commands.cs, add the following two functions to the bottom of the script: function serverCmdcheckTarget(%client, %pos, %start, %ray) { %player = %client.player;
You might have noticed some flaws with the base code:
// Tell our AI object to fire its weapon %player.setImageTrigger(0, 1);
// Tell our AI object to fire its weapon in 100 milliseconds %player.schedule(100, "setImageTrigger", 0, 1);
simObject.schedule(time, command, arg1...argN)
// Tell our AI object to fire its weapon in 100 milliseconds %player.schedule(100, "setImageTrigger", 0, 1);
// Stop firing in 150 milliseconds %player.schedule(150, "setImageTrigger", 0, 0);
The death animation code can be found in game/scripts/server/player.cs. Open this file, then scroll down to the following function: function Player::playDeathAnimation(%this) { if (isObject(%this.client)) { if (%this.client.deathIdx++ > 11) %this.client.deathIdx = 1; %this.setActionThread("Death" @ %this.client.deathIdx); } else { %rand = getRandom(1, 11); %this.setActionThread("Death" @ %rand); } }
In simpler terms, we do not need to use the death index (.deathIdx) or %client variables. We can simply call the first death animation available. Change the ::playDeathAnimation(...) function to the following: function Player::playDeathAnimation(%this) { %this.setActionThread("Death1"); } Now, when the target AI loses all its health it will play a death animation and eventually disappear.
// Specify mount point & offset for 3rd person, and eye offset // for first person rendering. mountPoint = 0; firstPerson = false; useEyeNode = false; animateOnServer = true; // Add these to get BoomBot to aim correctly useEyeOffset = true; eyeOffset = "0 0 -0.35";
{ mapTo = "unmapped_mat"; diffuseMap[0] = "art/markers/g_marker.png"; alphaTest = "1"; alphaRef = "80"; };
To create a marker decal, run the World Editor and then open the Decal Editor.
Click on the New Decal Data button ( ), next to the garbage bin ( ), and name your new entry "gg_decal".
datablock DecalData(gg_decal) { textureCoordCount = "0"; Material = "gg_marker"; }; Now that we have a destination marker, we need to add it upon clicking on the terrain and then delete it when our player reaches its destination. Start by opening game/scripts/gui/playGui.cs. Find the PlayGui::onRightMouseDown function. At the end of this function, add the following code: %ray = VectorScale(%ray, 1000); %end = VectorAdd(%start, %ray);
The last thing we need to do is erase the destination marker when our AI player gets to it. Open the game/art/datablocks/BoomBot.cs file, then add the following: // This is a callback function // This is automatically called by the engine as part // of the AI routine // %this - The BoomBotData datablock // %obj - The instance of this datablock (our AI Player) function BoomBotData::onReachDestination(%this, %obj) { // If there was a decal placed, then it was // stored in this %obj variable (see playGui.cs) // Erase the decal using the decal manager if( %obj.decal > -1 ) decalManagerRemoveDecal(%obj.decal); }
Now that you've got control of your character, it's time to discuss the camera controls. We've decided on a two-mode approach so that you can use the Overhead mode to observe the battlefield and the OrbitObject mode so that you can follow a specific unit.
function serverCmdorbitCam(%client) { %client.camera.setOrbitObject(%client.player, mDegToRad(20) @ "0 0", 0, 5.5, 5.5); %client.camera.camDist = 5.5; %client.camera.controlMode = "OrbitObject"; }
Cameras used by RTS games are slightly different from the Hack & Slash or Fly cameras. They are characterized by a camera that moves laterally along the x and y axis, but generally not in z. This can be realized in T3D by using the "Overhead" camera mode.
function serverCmdoverheadCam(%client) { %client.camera.position = VectorAdd(%client.player.position, "0 0 30"); %client.camera.lookAt(%client.player.position); %client.camera.controlMode = "Overhead"; }
// Adjusts the height of the camera using the mouse wheel function serverCmdadjustCamera(%client, %adjustment) { if(%client.camera.controlMode $= "OrbitObject") { if(%adjustment == 1) %n = %client.camera.camDist + 0.5; else %n = %client.camera.camDist - 0.5; Notice that this function catches the camera mode and uses an appropriate method for adjusting the camera's position by checking the controlMode member's value.
Now that the functions are set up, all that is left is creating a key bind to call them. Back in default.bind.cs we added the following binding to the script: moveMap.bind( mouse, zaxis, mouseZoom ); This allows you to zoom in and out on your actor using your mouse's scroll wheel in orbit mode and adjust camera height in overhead mode..
function serverCmdorbitCam(%client) { %client.camera.setOrbitObject(%client.player, mDegToRad(20) @ "0 0", 0, 5.5, 5.5); %client.camera.camDist = 5.5; %client.camera.controlMode = "OrbitObject"; } function serverCmdoverheadCam(%client) { %client.camera.position = VectorAdd(%client.player.position, "0 0 30"); %client.camera.lookAt(%client.player.position); %client.camera.controlMode = "Overhead"; } function serverCmdtoggleCamMode(%client) { if(%client.camera.controlMode $= "Overhead") { %client.camera.setOrbitObject(%client.player, mDegToRad(20) @ "0 0", 0, 5.5, 5.5); %client.camera.camDist = 5.5; %client.camera.controlMode = "OrbitObject"; } else if(%client.camera.controlMode $= "OrbitObject") { %client.camera.controlMode = "Overhead"; %client.camera.position = VectorAdd(%client.player.position, "0 0 30"); %client.camera.lookAt(%client.player.position); } }
commandToServer('orbitCam'); commandToServer('overheadCam');
function serverCmdcheckTarget(%client, %pos, %start, %ray) { %ray = VectorScale(%ray, 1000); %end = VectorAdd(%start, %ray); // Add new typemasks to the search so we can find clicks on barracks too %searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::StaticTSObjectType | $TypeMasks::StaticObjectType; // Search! %scanTarg = ContainerRayCast( %start, %end, %searchMasks); // If an enemy AI object was found in the scan if( %scanTarg ) { // Get the enemy ID %target = firstWord(%scanTarg); if (%target.class $= "barracks") { serverCmdspawnTeammate(%client, %target); } else if (%target.getClassName() $= "AIPlayer") { if (%target.team != 1) { // Cause our AI object to aim at the target // offset (0, 0, 1) so you don't aim at the target's feet if (isObject(Team1List)) { %c = 0; %unit = Team1List.getObject(0); while (isObject(%unit)) { if (%unit.isSelected) { %unit.mountImage(Lurker, 0); %targetData = %target.getDataBlock(); %z = getWord(%targetData.boundingBox, 2) * 2; %offset = "0 0" SPC %z; %unit.setAimObject(%target, %offset); // Tell our AI object to fire its weapon %unit.setImageTrigger(0, 1); } %c++; %unit = Team1List.getObject(%c); } } } else { if ($SelectToggled) { multiSelect(%target); } else { cleanupSelectGroup(); %target.isSelected = true; %target.isLeader = true; } } } else { serverCmdstopAttack(%client); if (!$SelectToggled) cleanupSelectGroup(); } } else { serverCmdstopAttack(%client); if (!$SelectToggled) cleanupSelectGroup(); } }
%player.team = 1; if (!isObject(Team1List)) { new SimSet(Team1List); MissionCleanup.add(Team1List); } Team1List.add(%player);
function serverCmdstopAttack(%client) { // If no valid target was found, or left mouse // clicked again on terrain, stop firing and aiming for (%c = 0; %c < Team1List.getCount(); %c++) { %unit = Team1List.getObject(%c); %unit.setAimObject(0); %unit.schedule(150, "setImageTrigger", 0, 0); } }
function serverCmdcreateBuilding(%client, %pos, %start, %ray) { // find end of search vector %ray = VectorScale(%ray, 2000); %end = VectorAdd(%start, %ray); %searchMasks = $TypeMasks::TerrainObjectType; // search! %scanTarg = ContainerRayCast( %start, %end, %searchMasks); // If the terrain object was found in the scan if( %scanTarg ) %obj = getWord(%scanTarg, 0); %pos = getWords(%scanTarg, 1, 3); // spawn a new object at the intersection point %obj = new TSStatic() { position = %pos; shapeName = "art/shapes/building/orcburrow.dts"; class = "barracks"; collisionType = "Visible Mesh"; scale = "0.5 0.5 0.5"; }; // Add the new object to the MissionCleanup group MissionCleanup.add(%obj); // Set up a spawn point for new troops to arrive at. if (!isObject(Team1SpawnGroup)) { new SimGroup(Team1SpawnGroup) { canSave = "1"; canSaveDynamicFields = "1"; enabled = "1"; }; MissionGroup.add(Team1SpawnGroup); } %spawnName = "team1Spawn" @ %obj.getId(); %point = new SpawnSphere(%spawnName) { radius = "1"; dataBlock = "SpawnSphereMarker"; spawnClass = $Game::DefaultPlayerClass; spawnDatablock = $Game::DefaultPlayerDataBlock; }; %point.position = VectorAdd(%obj.getPosition(), "0 5 2"); Team1SpawnGroup.add(%point); MissionCleanup.add(%point); } }
function serverCmdspawnTeammate(%client, %source) { // Create a new, generic AI Player // Position will be at the camera's location // Datablock will determine the type of actor %spawnName = "team1Spawn" @ %source.getId(); // Defaults %spawnClass = $Game::DefaultPlayerClass; %spawnDataBlock = $Game::DefaultPlayerDataBlock; // Overrides by the %spawnPoint if (isDefined("%spawnName.spawnClass")) { %spawnClass = %spawnName.spawnClass; %spawnDataBlock = %spawnName.spawnDatablock; } else if (isDefined("%spawnName.spawnDatablock")) { // This may seem redundant given the above but it allows // the SpawnSphere to override the datablock without // overriding the default player class %spawnDataBlock = %spawnName.spawnDatablock; } %spawnProperties = %spawnName.spawnProperties; %spawnScript = %spawnName.spawnScript; // Spawn with the engine's Sim::spawnObject() function %newBot = spawnObject(%spawnClass, %spawnDatablock, "", %spawnProperties, %spawnScript); %spawnLocation = GameCore::pickPointInSpawnSphere(%newBot, %spawnName); %newBot.setTransform(%spawnLocation); %newBot.team = 1; %newBot.clearWeaponCycle(); %newBot.setInventory(Lurker, 1); %newBot.setInventory(LurkerClip, %newBot.maxInventory(LurkerClip)); %newBot.setInventory(LurkerAmmo, %newBot.maxInventory(LurkerAmmo)); %newBot.addToWeaponCycle(Lurker); if (%newBot.getDatablock().mainWeapon.image !$= "") { %newBot.mountImage(%newBot.getDatablock().mainWeapon.image, 0); } else { %newBot.mountImage(Lurker, 0); } // This moves our new bot away from the front door a ways to make room for // other bots as we spawn them. %x = getRandom(-10, 10); %y = getRandom(4, 10); %vec = %x SPC %y SPC "0"; %dest = VectorAdd(%newBot.getPosition(), %vec); %newBot.setMoveDestination(%dest); addTeam1Bot(%newBot); }
function addTeam1Bot(%bot) { // We'll create a SimSet to track our Team1 bots if it hasn't been created already if (!isObject(Team1List)) { new SimSet(Team1List); MissionCleanup.add(Team1List); } // And then add our bot to the Team1 list. Team1List.add(%bot); }
function serverCmdtoggleMultiSelect(%client, %flag) { if (%flag) $SelectToggled = true; else $SelectToggled = false; } function multiSelect(%target) { if (!isObject(Team1List)) { new SimSet(Team1List); MissionCleanup.add(Team1List); } %leader = findTeam1Leader(); if (isObject(%leader)) { %target.destOffset = VectorSub(%leader.getPosition(), %target.getPosition()); } else { %target.destOffset = "0 0 0"; %target.isLeader = true; } %target.isSelected = true; } function findTeam1Leader() { if (!isObject(Team1List)) { new SimSet(Team1List); MissionCleanup.add(Team1List); } for (%c = 0; %c < Team1List.getCount(); %c++) { %unit = Team1List.getObject(%c); if (%unit.isLeader) return %unit; } return 0; } function cleanupSelectGroup() { if (!isObject(Team1List)) { new SimSet(Team1List); MissionCleanup.add(Team1List); } for (%c = 0; %c < Team1List.getCount(); %c++) { %temp = Team1List.getObject(%c); %temp.isSelected = false; %temp.isLeader = false; %temp.destOffset = "0 0 0"; } }
function PlayGui::onRightMouseDown(%this, %pos, %start, %ray) { commandToServer('movePlayer', %pos, %start, %ray); %ray = VectorScale(%ray, 1000); %end = VectorAdd(%start, %ray); // only care about terrain objects %searchMasks = $TypeMasks::TerrainObjectType | $TypeMasks::StaticTSObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::ShapeBaseObjectType | $TypeMasks::StaticObjectType; // search! %scanTarg = ContainerRayCast( %start, %end, %searchMasks); if (%scanTarg) { %obj = getWord(%scanTarg, 0); // Get the X,Y,Z position of where we clicked %pos = getWords(%scanTarg, 1, 3); // Get the normal of the location we clicked on %norm = getWords(%scanTarg, 4, 6); // Create a new decal using the decal manager // arguments are (Position, Normal, Rotation, Scale, Datablock, Permanent) // We are now just letting the decals clean up after themselves. decalManagerAddDecal(%pos, %norm, 0, 1, "gg_decal", false); } }
function serverCmdmovePlayer(%client, %pos, %start, %ray) { //echo(" -- " @ %client @ ":" @ %client.player @ " moving"); // Get access to the AI player we control %ai = findTeam1Leader(); %ray = VectorScale(%ray, 1000); %end = VectorAdd(%start, %ray); // only care about terrain objects %searchMasks = $TypeMasks::TerrainObjectType | $TypeMasks::StaticTSObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::ShapeBaseObjectType | $TypeMasks::StaticObjectType; // search! %scanTarg = ContainerRayCast( %start, %end, %searchMasks); // If the terrain object was found in the scan if( %scanTarg ) { %pos = getWords(%scanTarg, 1, 3); // Get the normal of the location we clicked on %norm = getWords(%scanTarg, 4, 6); // Set the destination for the AI player to // make him move if (isObject(Team1List)) { %c = 0; %end = Team1List.getCount(); %unit = Team1List.getObject(0); while (isObject(%unit)) { if (%unit.isSelected) { %dest = VectorSub(%pos, %unit.destOffset); %unit.setMoveDestination( %dest ); } %c++; if (%c < %end) %unit = Team1List.getObject(%c); else %unit = 0; } } else %ai.setMoveDestination( %pos ); } }
function addSelect() { $SelectToggled = true; commandToServer('toggleMultiSelect', true); } function dropSelect() { $SelectToggled = false; commandToServer('toggleMultiSelect', false); } moveMap.bindCmd( keyboard, "ctrl x", "addSelect();", "dropSelect();" );
for (%i = 0; %i < Team1List.getCount(); %i++) { %unit = Team1List.getObject(%i); if (!isObject(%obj)) { %unit.target = ""; %unit.setAimObject(0); %unit.schedule(150, "setImageTrigger", 0, 0); } }
The purpose of this tutorial was to show you some of the more advanced capabilities of TorqueScript, and combine the language with Torque 3D's visual editors to create a prototype game. As you just experienced, getting a non-FPS prototype game started does not take long.
You can download the completed scripts by CLICKING HERE.
|
var links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) if (links[i].className == 'livethumbnail') { var img = links[i].getElementsByTagName('img')[0]; img.state = 'small'; img.smallSrc = img.getAttribute('src'); img.smallWidth = parseInt(img.getAttribute('width')); img.smallHeight = parseInt(img.getAttribute('height')); img.largeSrc = links[i].getAttribute('href'); img.largeWidth = parseInt(img.getAttribute('largewidth')); img.largeHeight = parseInt(img.getAttribute('largeheight')); img.ratio = img.smallHeight / img.smallWidth; links[i].onclick = scale; }
function scale() { var img = this.getElementsByTagName('img')[0]; img.src = img.smallSrc;
if (! img.preloaded)
{
img.preloaded = new Image();
img.preloaded.src = img.largeSrc;
}
var interval = window.setInterval(scaleStep, 10);
return false;
function scaleStep()
{
var step = 45;
var width = parseInt(img.getAttribute('width'));
var height = parseInt(img.getAttribute('height'));
if (img.state == 'small')
{
width += step;
height += Math.floor(step * img.ratio);
img.setAttribute('width', width);
img.setAttribute('height', height);
if (width > img.largeWidth - step)
{
img.setAttribute('width', img.largeWidth);
img.setAttribute('height', img.largeHeight);
img.setAttribute('src', img.largeSrc);
window.clearInterval(interval);
img.state = 'large';
}
}
else
{
width -= step;
height -= Math.floor(step * img.ratio);
img.setAttribute('width', width);
img.setAttribute('height', height);
if (width < img.smallWidth + step)
{
img.setAttribute('width', img.smallWidth);
img.setAttribute('height', img.smallHeight);
img.src = img.smallSrc;
window.clearInterval(interval);
img.state = 'small';
}
}
}
}
</script>