Torque 2D/GenreTutorials/StrategyActions
From TDN
[edit] Creating Scripted Actions/Behaviors[edit] IntroductionIn this tutorial step we are going to extend our object's actions and behaviors. We already have a single action set up, the moveToLoc action. We will extend this to combat and RTS related actions to add interativity to our game. |
|
[edit] Creating the Soldier actionsIn this step we are going to create the Attack and Follow actions. They seem the most principal for the Soldier unit in our game. Follow is needed for Attack since you follow while attacking. Also our Attack action is going to be heavily integrated with our follow action, so we are going to script them both at the same time. [edit] Creating the start of the Attack and Follow system[edit] We will start by creating a new script fileWe're going to start this action in a new script file. So go to your "Strategy/gameScripts/scripts/game" folder and create a script file there called "actions.cs." All of our actions (minus the moveToLoc that we already have created) will be created in this script file (for organizational purposes). [edit] Exec() the script fileWe can't forget the very important step that must follow the creation of a new script file, we must exec() it in our main.cs... so add this line to your main.cs.
exec("./gameScripts/scripts/game/actions.cs");
[edit] Lets add our first function in our script fileTO start out this script file we will add the attack action as our first function. So add this function to your actions.cs.
// --------------------------------------------------------------------
// Soldier::attack()
//
// This is the attack action attached to our Soldier class,
// this function will trigger the needed attack steps and then calls
// the follow action (since the attacker needs to follow)
// --------------------------------------------------------------------
function Soldier::attack(%this, %obj)
{
// set the proper bool and target values
%this.attacking = true;
%this.attackTarget = %obj;
// call the follow action passing the target (to be followed)
%this.action("follow", %obj);
}
First we store that the object is attacking (by setting it to true) and then store the target on the object itself. After this we call the follow action, passing the target to be followed. Now we must create the follow action which will handle all following aspects, as well as many of the attacking aspects. Now add this function.
// --------------------------------------------------------------------
// Soldier::follow()
//
// This is the follow action, you specify the object to be followed and
// then the following function is triggered
// --------------------------------------------------------------------
function Soldier::follow(%this, %obj)
{
// store the object and the bool
%this.followObj = %obj;
%this.following = true;
// call the following function
%this.following();
}
This function initiates the following process, it is a very short and simple function... the .following() function we will create next will do almost all the work. Lets start by adding this function frame.
// --------------------------------------------------------------------
// Soldier::following()
//
// This is the following event, this is the meat of the following system
// this will keep triggering while following, this will also handle
// a large portion of the attack triggering system
// --------------------------------------------------------------------
function Soldier::following(%this)
{
}
Lets flesh out our function with these initial lines.
// check if we are actually following
if(%this.following)
{
// first we grab the follow object's positon, then our position
// we then get the vectorBetween us and our follow object, next we normalize
// this vector so we can proceed and scale it up by the specified distance,
// after that we add our scaled vector to our follow object's position
// this creates a tailing distance vs. following on the exact location
// of the object
%objPos = %this.followObj.getPosition();
%pos = %this.getPosition();
%vecBetween = t2dVectorSub(%pos, %objPos);
%vecNorm = t2dVectorNormalise(%vecBetween);
%scaled = t2dVectorScale(%vecNorm, %this.followDistance);
%result = t2dVectorAdd(%scaled, %objPos);
The comments explain this code pretty well, but I'll go over it quickly... we grab the position of the object we're following and then we grab our own position. We get the vector between us and the object we're following, noramlize it, scale it by our specified followDistance and then add it to our follow object's position... basically all this line of math does is get is a follow distance closeby our target, not on top of it. Continue on by adding this.
// we calculate the distance between the objects, then subtract our follow distance
%distToObject = t2dVectorLength(%vecBetween);
%dist = %distToObject - %this.followDistance;
// if we are following an object and -Not- attack it we do this adjustment and
// comparisson to add a slight slow down to ease up to our ally we're following
if(%dist >= %this.speed || %this.attacking)
{
%speed = %this.speed;
} else
{
%div = %this.speed;
%speed = %this.speed - (%div - %dist);
}
In this set we first grab the distance to our object from us, then subtract our follow distance from that (getting our distance to our offset follow location). We then do some comparissons to define whether or not if we aren't attacking the object (so we'd be following an object for another reason) and if so we slowly reduce the speed, this gives us a nice ease into our destination. Now lets flesh out our function some more, lets add this.
// Check if we are attacking and the object we're attackign isn't dead
if(%this.attacking && !%this.attackTarget.dead)
{
// we compare the sceneTime minus last attack time to the attackDelay... this
// gives us a nice delay in attacking
if(($strategyScene.getSceneTime() - %this.lastAttackTime) > %this.attackDelay || %this.lastAttackTime $= "")
{
// grab the enemies position and distance to them
%enemyPos = %this.attackTarget.getPosition();
%distToEnemy = t2dVectorLength(t2dVectorSub(%pos, %enemyPos));
// check our attack range
if(%distToEnemy <= %this.attackRange)
{
// if within attacking range, attack the target
%this.action("attackTarget");
}
}
}
We start off checking if we are attacking and also making sure our target isn't dead. If so we then compare the present SceneTime minus the last attack timeto our attack delay... this results in us getting a good attackDelay system (note some of these properties like attackDelay and dead we will define after this. We then get a fresh distance to enemy calculation, we then check to see if the enemy is range with this value comapred to the .attackRange, if so we call the action "attackTarget." Now lets add the final section of this function.
// lets move to our resulitng position
%this.action("moveToLoc", %result);
// schedule our following function to be called again
%this.followSchedule = %this.schedule(500, "following");
}
we finish this function by calling our moveToLoc action and setting a schedule to recall this function. The effect will be this check being called roughly every half second to make sure we keep following. Before we move on to creating our "attackTarget" action (that is called on a successful attack check), lets add some of these properties that I had you refer to into our Soldier class. So open your objectTypes.cs and make your Soldier class look like this.
new t2dSceneObjectDatablock(Soldier : Entity){
superClass = Entity;
size = "5 5";
creationTime = 4;
attackRange = 7;
attackPower = 15;
attackDelay = 2;
followDistance = 5;
};
We added attackRange, attackPower, attackDelay, and followDistance. All of these are fairly self explanitory. Now update your Entity object to this (we added a dead equals false value)...
// lets create the base unit object called Entity
new t2dSceneObjectDatablock(Entity){
isSelectable = true;
name = "Entity";
speed = 10;
creationTime = 2;
hitPointsMax = 100;
hitPointBarScaleFactor = 3;
team = "Circles";
isMoveable = true;
dead = false;
};
This dead value will be triggered when an object is lowered to zero or less hitpoints, so we set this to false to start off alive. Ok now that we have all the values already referenced in our previous function (attackRange and attackDelay) as well as a value we will utilize in our next function, the attackPower. Time to create our "attackTarget" action. So add this function to your actions.cs.
// --------------------------------------------------------------------
// Soldier::attackTarget()
//
// This is the action attached to the Soldier that will actually perform
// the attacking sequence. This action is only called if a target is
// within range and is to be attacked, so we call damage on the target
// and store the time for the delayed attack system
// --------------------------------------------------------------------
function Soldier::attackTarget(%this)
{
// store the scene time when we attacked for our attack delay
%this.lastAttackTime = $strategyScene.getSceneTime();
// call the damage action on the target using the power
// of the attacking unit
%this.attackTarget.action("damage", %this.attackPower);
}
Here we do two things, first we store the "SceneTime" on the object, this allows our attack delay to work properly (reference back to our following() function to see again how we reference and calculate the scene time utilizing our object's "attackDelay")... Then we call the "damage" action on the target, passing the attackPower of our attacking unit. As you might have guessed our next function to add will be the damage action. This action will be attached to every unit though so we will put it on Entity, since damage will be the function that actually damages an object and we want all of our entities, even the workers, to be able to be damaged. Lets start out by adding the frame for our damage() function after our attackTarget() function.
// --------------------------------------------------------------------
// Entity::damage()
//
// This is the action attached to all of our entities that performs
// the actual hitpoint reduction calculation and subtraction
// --------------------------------------------------------------------
function Entity::damage(%this, %ammount)
{
}
Now lets add our first lines inside the function, add this.
// first check to see if this object is already dead
if(%this.dead)
{
return;
}
// lets call our blood effect
playBloodEffect(%this.getPosition());
// subtract the hit points based on the ammount passed (usually the
// attacking unit's power)
%this.hitPoints -= %ammount;
First we check to see if the object is already dead, if so we exit, then we call a function called "PlayBloodEffect()" this is a function we will create that will spawn a "blood" particle effect. Next we wreduce the object's hitPoints by the %ammount passed. Next add this.
// if our object reaches 0 or less hitpoints then we want to do a
// few things to ensure it is dead
if(%this.hitPoints <= 0)
{
// first we will call a reset to all actions, to prevent our object
// from continually performing any actions
%this.action("resetAllActions");
// set the hitpooints to zero
%this.hitPoints = 0;
// toggle the object's dead to true
%this.dead = true;
// turn our label invisible
%this.labelGui.setVisible(false);
// disable our label object
%this.labelObj.setEnabled(false);
// disable our object
%this.setEnabled(false);
}
Here we do a few things, first we only enter this if statement of our life is zero or less, so this means this is called if the damage kills the object. We then call an action "resetAllActions" as you may realize, this action among our "PlayBloodEffect()" need to yet be created. You can guess the function of the action though, it will simply reset all actions, we will call this in other places as well. Then we set the hitPoints to zero, this is incase we get lower than zero. We set the objects dead bool to true (to store that it is indeed dead). Finally we toggle our lebelGui to visible false, we disable the labelObj, and we disable our now dead object. You can alternatively delete the GUI object and .safeDelete() the labelObj and our obj. Add this last call to finish up this function.
// call the update of our hit points display
%this.action("updateHitPointsDisplay");
We call an action, "updateHitPointsDisplay"... this is another action we need to add, though its also self-explanitory, this action will update our hit points bar. So right now we need to add three functions, UpdateHitPointsDisplay, resetAllActions, and playBloodEffect. Lets start with the first listed here. Start by adding function after our previous damage() function.
// --------------------------------------------------------------------
// Entity::updateHitPointsDisplay()
//
// This is the action that updates the visual bitmap hit points bar and
// the border bar on the unit
// --------------------------------------------------------------------
function Entity::updateHitPointsDisplay(%this)
{
// first we grab the position and then we
// divide it up into X and Y variables, then we
// grab the extent and divide it into width and height
%pos = %this.hitPointsBorderBar.getPosition();
%posX = getWord(%pos, 0);
%posY = getWord(%pos, 1);
%extent = %this.hitPointsBorderBar.getExtent();
%extentWidth = getWord(%extent, 0);
%extentHeight = getWord(%extent, 1);
// here we use the hitpoints max to set the hitPoints border bar extent
%extentWidth = mFloor(%this.hitPointsMax/%this.hitPointBarScaleFactor);
// we dynamically size the GUI bitmap bar by using the resize() command
%this.hitPointsBorderBar.resize(%posX, %posY, %extentWidth, %extentHeight);
// first we grab the position and then we
// divide it up into X and Y variables, then we
// grab the extent and divide it into width and height
%pos = %this.hitPointsBar.getPosition();
%posX = getWord(%pos, 0);
%posY = getWord(%pos, 1);
%extent = %this.hitPointsBar.getExtent();
%extentWidth = getWord(%extent, 0);
%extentHeight = getWord(%extent, 1);
// here we use the hitpoints to set the hitPoints bar extent
%extentWidth = mFloor(%this.hitPoints/%this.hitPointBarScaleFactor);
// we dynamically size the GUI bitmap bar by using the resize() command
%this.hitPointsBar.resize(%posX, %posY, %extentWidth, %extentHeight);
}
The comments describe the function fairly well and the first and second section are almost identical, the first one sizes the border bar, which represents the max hitpoints, while the second part represents the hitpoints bar. We simply grab the position and extent and then change the extent based on the hitPoints to resize our hitPoints guiBitmapCtrl. Now lets add the resetAllActions function, add this function after our previous one... this function is attached to our Soldier since it will have different actions than the worker.
// --------------------------------------------------------------------
// Soldier::resetAllActions()
//
// This action will reset all actions, we basically put calls to the stop
// actions here that are specific to the class
// --------------------------------------------------------------------
function Soldier::resetAllActions(%this)
{
// stop the attacking action
%this.action("stopAttacking");
}
This function is pretty simple right now, we just call a stopAttacking action that we need to create. Instead of calling stopAttackign we call this resetAllActions, that way if we add more actions we can simply add another call here and its handled. Add this stopAttacking action after the resetAllActions.
// --------------------------------------------------------------------
// Soldier::stopAttacking()
//
// This action will stop the attacking process properly
// --------------------------------------------------------------------
function Soldier::stopAttacking(%this)
{
// first we toggle attacking to false, we reset the attackTarget to
// nothing, set the lastAttackTime to nothing, and then call the
// stopFollowing action
%this.attacking = false;
%this.attackTarget = "";
%this.lastAttackTime = "";
%this.action("stopFollowing");
}
Nothing too special, we set attacking to false, set the attackTarget to nothing, set the lastAttackTime to nothing, and then we call the stopFollowing action (that we will create next). Add this after our stopAttacking() action.
// --------------------------------------------------------------------
// Soldier::stopFollowing()
//
// This action will stop the following process properly
// --------------------------------------------------------------------
function Soldier::stopFollowing(%this)
{
// reset the followObj and the following bool
%this.followObj = "";
%this.following = false;
}
This function is pretty simple, we set the followObj to nothing and set following to false (which means when the following looping function hits again it will kick out of it). At first this may seem like a large and useless chain of functions, why not do one single function? Well the answer to that is extendibility. Seperating our functionality allows us to utilize the same functions/actions in different ways. We have one final function left to add... the PlayBloodEffect() function. Since this function is directly called from damage() we will place it in this same file (even though it might make sense elsewhere). So add this function.
// --------------------------------------------------------------------
// playBloodEffect()
//
// Used to play the blood spurt/spray particle effect
// --------------------------------------------------------------------
function playBloodEffect(%pos)
{
// store the effect object
%eff = new t2dParticleEffect() { sceneGraph = $strategyScene; };
// load th effect file
%eff.loadEffect("Strategy/data/particles/bloodspurt.eff");
// set the effect for the first layer
%eff.setLayer(0);
// set the effect to be destroyed on completion
%eff.setEffectLifeMode("KILL", 1.0);
// set the efects position with the position passed
%eff.setPosition(%pos);
// play the effect
%eff.playEffect();
}
The function first creates the t2dParticleEffect, then it loads the bloodspurt.eff into it. We then set the layer to zero so it will be displayed in front of everything. Next we set the effect's life mode to kill and one second, that way in one second the effect will be deleted from memory. Finaly we set the position to the position passed and start the effect with .playEffect(). We need to add one more thing before we can test this, right now it should work, though we need to have it reset the actions whenever we right click, that way two actions won't interfere with eachother, this works well since we can click and move our object to stop them from doing something such as attacking... So open up your "Strategy/gameScripts/scripts/data/objectTypes.cs" and find your onRightClick() function attached to Entity. Add these lines to the start of it to make it look like this.
// --------------------------------------------------------------------
// Entity::OnRightClick()
//
// This is the main class onRightClick function, it just tell the object
// to move to the location passed using the speed stored on the object
// --------------------------------------------------------------------
function Entity::OnRightClick(%this, %worldPos, %mode)
{
// reset all of our actions
%this.action("resetAllActions");
echo("first worldPos = " @ %worldPos);
if(%this.isMoveable)
%this.action("moveToLoc", %worldPos);
}
That way when we right click it will reset our actions and take our new one. We also want to tag the same two lines at the start of the attack() function, that way if attack's are triggered another way it will reset them as well... so make your attack() function in actions.cs look like this.
// --------------------------------------------------------------------
// Soldier::attack()
//
// This is the attack action attached to our Soldier class,
// this function will trigger the needed attack steps and then calls
// the follow action (since the attacker needs to follow)
// --------------------------------------------------------------------
function Soldier::attack(%this, %obj)
{
// reset all of our actions
%this.action("resetAllActions");
// set the proper bool and target values
%this.attacking = true;
%this.attackTarget = %obj;
// call the follow action passing the target (to be followed)
%this.action("follow", %obj);
}
Ok, now we should have properly added two different functions to our Soldiers... follow and attack. [edit] Time to testIts now time to test it. [edit] Fire up TGBFirst fire up TGB. You need to change the config block on the test blocks to "CirclesSoldier" and "SquaresSoldier". Press play. You should see something like this. [edit] Make one BoB attack anotherNow highlight and select "BoB"... with him still selected fire up the console again and type this command and press enter. Once you type your command and hit enter, close out of your console quickly.
BoB2.action("attack", BoB);
you should see your "BoB2" running towards your selected "BoB"... let him get close and hit you a few times (note our followDistance value works), you should see the blood particle effect trigger and the life bar lower... like this. Now since you have "BoB" selected, try and run away, you should see him follow... like this. Another test before "BoB" dies is to make him run far and quickly select "BoB2" then click away to try and get him to cancel his attack... something like this (though certain things like this are hard to display in an image). Our combat system works! Right now you may asking why teammates are attacking, well it wouldn't be hard to prevent teammates from attacking, though I personally like RTS games that allow you to attack your teammates, nothing like having teammates cause problems or use up resources and -not- being able to get rid of them yourself. |
|
[edit] Creating the Worker actionIn this step we are going to create the Mining. In our Worker class Mining is the probably the most important action and the best purpose of the Worker. [edit] Starting the Mining system[edit] We will start by adding new values to our Worker classTo do this we need to find our Worker class in the objectTypes.cs. Make your Worker class look like this.
// now lets create our second level of classes, Worker and Soldier
new t2dSceneObjectDatablock(Worker : Entity){
imageMap = WorkerImageMap;
superClass = Entity;
speed = 12;
hitPointsMax = 60;
goldMiningAmmount = 20;
miningDelay = 3000;
};
We added goldMiningAmmount, which will serve as the ammount of gold the unit can mine and carry back to the base. We also add miningDelay which is the miliseconds in which it will take the Worker to mine the set ammount of gold. [edit] Our first class function, mineGold()In our first function we will initiate the gold mining process... so add this function to the end of your actions.cs.
// --------------------------------------------------------------------
// Worker::mineGold()
//
// This action will start the Worker mining gold
// --------------------------------------------------------------------
function Worker::mineGold(%this, %mine)
{
// first we check to see of the mine's .type is
// in fact a goldMine
if(%mine.class !$= "GoldMine")
{
// we have encountered an invalid mine
error("not a valid gold mine");
return;
}
// store the mine on the object and set mining to true
%this.mineTarget = %mine;
%this.mining = true;
// now we call our next action, goToMine
%this.action("goToMine");
}
We first check to see if the mine passed in is in fact a mine, if not we return out of the function. We then store the mine on the object for later reference and then toggle .mining to true. We end this function with the action goToMine. [edit] Create our goToMine() actionNow we can create our goToMine() action... add this after our previous function.
// --------------------------------------------------------------------
// Worker::goToMine()
//
// This action will send our Worker to the mine
// --------------------------------------------------------------------
function Worker::goToMine(%this)
{
// first we check if we have a mining target
// if we don't, we set mining to false and exit
if(%this.mineTarget $= "")
{
error("no valid mine selected to mine");
%this.mining = false;
return;
}
// grab the mine stored on the object
%mine = %this.mineTarget;
// toggle headingToMine to true
%this.headingToMine = true;
// send our Worker to the mine, the last true is to
// enable the callback
%this.moveTo(%mine.getPosition(), %this.speed, true, true);
}
Here we do another check to make sure our mineTarget isn't empty, if it is we set mining to false and exit. We then grab the mine object, toggle .headingTomine as true and then call the moveTo() function. The last true in the moveTo() call is telling TGB to give a callback when it has reached it's destination. Now lets insert our callback and forward the callback to our own class system, so add this function after our previous one.
// --------------------------------------------------------------------
// t2dSceneObject::onPositionTarget()
//
// This is the generic callback by TGB when an object has reached
// it's destination
// --------------------------------------------------------------------
function t2dSceneObject::onPositionTarget(%this)
{
%this.onReachDestination(%this);
}
We call the object's onReachDestination. In the case of our Worker it would be Worker.onReachDestination and for our Soldier it would be Soldier.onReachDestination(), so that will be our next function to create. Add this function after our previous one.
// --------------------------------------------------------------------
// Soldier::onReachDestination()
//
// This is called when a Soldier has reached its destination
// --------------------------------------------------------------------
function Soldier::onReachDestination(%this)
{
}
We load an empty version of this function for our Soldier that way it won't error out that there isn't a version for the Soldier. Now add the worker onReachDestionation() function.
// --------------------------------------------------------------------
// Worker::onReachDestination()
//
// This is called when a worker has reached its destination on a
// moveTo call that has the callback option set to true
// --------------------------------------------------------------------
function Worker::onReachDestination(%this)
{
// here is the check if the worker is mining
if(%this.mining)
{
// check if we're heading home, if so then we have returned
// from mining... if we aren't heading home then we have reached
// the mine and must start mining
if(%this.headingHome)
{
%this.headingHome = false;
%this.action("returnedFromMining");
} else if(%this.headingToMine)
{
%this.headingToMine = false;
%this.action("startMining");
}
}
}
This first checks to see if our object is mining, if so it then check to see if its heading home. If this Worker is heading home then it must be returning from mining. If this Worker is not heading home then it must be heading to the mine, so we call the appropriate actions and toggle the proper values. Now lets create further functions. Add this function.
// --------------------------------------------------------------------
// Worker::startMining()
//
// This is the action to get the Worker to start mining
// --------------------------------------------------------------------
function Worker::startMining(%this)
{
// grab the miningDelay
%delay = %this.miningDelay;
// schedule the completion of the mining process
%this.miningSchedule = %this.schedule(%delay, "action", "finishMining");
}
This funciton is fairly simple, we grab the mining delay and then schedule a call to finish the mining after the duration of the mining delay has passed... now lets create our finishMining() function. Add this function.
// --------------------------------------------------------------------
// Worker::finishMining()
//
// This is the action that is called when the Worker has finished
// it's mining process, we then store gold on the object and tell
// it to return home
// --------------------------------------------------------------------
function Worker::finishMining(%this)
{
// store that our object is carrying gold
%this.carrying = "gold";
// store the mining ammount of gold on it
%this.carryingAmmount = %this.goldMiningAmmount;
// time to send it home
%this.action("returnHome");
}
We do three things when we finish mining, first we store that are unit is carrying gold. We then store the ammount of gold it is carrying based on its object type value. Lastly we send it home. Now lets create our returnHome action, so add this function.
// --------------------------------------------------------------------
// Worker::returnHome()
//
// This action sends our object back home
// --------------------------------------------------------------------
function Worker::returnHome(%this)
{
// toggle that it is heading home
%this.headingHome = true;
// grab the object's base
%home = %this.team.base;
// tell it to moveTo home and trigger a callback
%this.moveTo(%home.getPosition(), %this.speed, true, true);
}
Our returnHome function is fairly simple as well (you might notice our mining system is made up of a lot of simple functions). We toggle that it is indeed heading home, we grabe it's base, we then send it to move towards it base with a toggle for the callback. We are nearly done, one of our last functions is what is called when a Worker has reached it's destination and headingHome is toggled to true... Add this function.
// --------------------------------------------------------------------
// Worker::returnedFromMining()
//
// This is called when our object has reached it's destination
// and headingHome is toggled to true
// --------------------------------------------------------------------
function Worker::returnedFromMining(%this)
{
// We check if it is carrying gold
if(%this.carrying $= "gold")
{
// lets deposit the gold into it's base and reset what it is carrying
%this.team.base.depositGold(%this.carryingAmmount);
%this.carrying = "";
%this.carryingAmmount = 0;
// we end by sending our object back to mine
%this.action("goToMine");
}
}
We first check to see if it's carrying gold. If it is we deposit the gold into the base (a function we need to add) and reset what it is carrying, we end by starting the sequence over by sending it to mind again. Surprisingly we are fairly close to finishing the mining system, it really isn't that complicated. One of our last worker functions is a function to stop mining, just like our stopAttacking function... so add this function.
// --------------------------------------------------------------------
// Worker::stopMining()
//
// This will tell our worker to cease all mining activities and
// resets the proper values
// --------------------------------------------------------------------
function Worker::stopMining(%this)
{
// we reset all the values needed
%this.mining = false;
%this.carrying = false;
%this.carryingAmmount = 0;
%this.headingHome = false;
%this.headingToMine = false;
// here we check if a mining event is pending, if so we cancel it
if(isEventPending(%this.miningSchedule))
cancel(%this.miningSchedule);
}
We reset all the values we were just manipulating in our previous functions, we end this function by checking if our mining schedule is still pending, if so we cancel it. We now need to add a resetAllActions, like we do with our Soldier... so add this function.
// --------------------------------------------------------------------
// Worker::resetAllActions()
//
// This will reset all of our actions
// --------------------------------------------------------------------
function Worker::resetAllActions(%this)
{
// stop mining
%this.action("stopMining");
}
We simply tell it to stop mining. We are now done with our Worker mining functions, there is two things we need to still do, the first is to add the withdrawl and deposit functions to our bases... the second is to actually add mines to our game! So to start the first task out we need to open our objectTypes.cs. We will add our base functions there to deposit and withdrawl gold. Add these functions to the end of the script file.
// --------------------------------------------------------------------
// Base::depositGold()
//
// This is a base function that will deposit the specified ammount of gold
// --------------------------------------------------------------------
function Base::depositGold(%this, %ammount)
{
// add the gold to the team's base
%this.team.base.goldCount += %ammount;
}
// --------------------------------------------------------------------
// Base::withdrawlGold()
//
// This is a base function that will withdrawl the specified ammount of gold
// --------------------------------------------------------------------
function Base::withdrawlGold(%this, %ammount)
{
// subtract the base's gold
%this.team.base.goldCount -= %ammount;
}
As you can see these functions are rather simple, they simple take the ammount of gold to be increased or decreased by and it does so. Add this to objectTypes.cs, near the other t2dSceneObjectDatablock configurations.
new t2dSceneObjectDatablock(GoldMine)
{
class = "GoldMine";
Layer = 10;
GraphGroup = 10;
isRightClickable = true;
};
Now you can drop something in the level editor and set its config block to GoldMine. Ok, now we are finished with our gold mining system. [edit] Time to test itNow lets test our work to make sure our Worker's can mine gold. [edit] Fire up TGBFire up TGB. Make the two boxes CirclesWorker and SquaresWorker again. You should see Your base, a Worker by your base called "BoB" and the goldMine should be in view... it should look something like this. Now type this command in the console and press enter.
BoB.action("mineGold", "goldMine1"); // Or whatever you named your goldMine.
Now you should see BoB move towards your gold mine... like this. Let him go back and forth a few times, you should notice the mining delay. After your Worker has had some time to mine some gold type this command in the console to see your gold ammount. echo(gameData.Circles.base.goldCount); You can do it a few times as he brings gold back to see if it is adding properly. Our gold mining system works! [edit] Gold mining script file referencesIf your's does not work like this then compare your files to these.
[edit] Where do we start?[edit] Adding new GUI Profiles |
|
[edit] Triggering the actions on right clickingIn this step we are going trigger our previous actions while right clicking, fortunately because we set it up the way we did getting the Soldier and Worker doing their different actions on right clicking isn't hard. [edit] Triggering the Soldier's proper right click response[edit] Modifying our Soldier's onRightClickThe way we set it up we have an Entity right click that calls the moveToLoc action, we also have the Soldier and Worker onRightClicks that then call the Entity one. Well in our soldier's case if we right click on an enemy we want to attack the enemy and not move to the location. To accomplish this we first need to add some addition properties to our object types in objectTypes.cs. [edit] Adding isAttackableIn your objectTypes.cs make your Entity class look like this.
// lets create the base unit object called Entity
new t2dSceneObjectDatablock(Entity){
isSelectable = true;
speed = 10;
creationTime = 2;
hitPointsMax = 100;
hitPointBarScaleFactor = 3;
isMoveable = true;
dead = false;
isAttackable = true;
};
Now all of our Entities will have isAttackable set to true :) [edit] Modifying onRightClickNow we need to modify our Soldier's onRightClick in the same file... Make yours look like this.
// --------------------------------------------------------------------
// Soldier::OnRightClick()
//
// This is the Soldier onRightClick() callback, it basically just forwards
// the action to the Entity class.
// --------------------------------------------------------------------
function Soldier::OnRightClick(%this, %worldPos, %mode, %objList)
{
// get the count of the object list
%objCount = getWordCount(%objList);
// toggling attack to false
%attack = false;
// we loop through all of our objects in our right click point
for(%i=0;%i<%objCount;%i++)
{
// grab the current object
%obj = getWord(%objList, %i);
// check for attackable objects
if(%obj.isAttackable)
{
// check if the object's team is our own
if(%this.team != %obj.team)
{
// its attackable and not on our team, we can
// attack, so set attack to true
%attack = true;
// lets reset anything we were doing
%this.action("resetAllActions");
// lets attack the object
%this.action("attack", %obj);
}
}
}
// if we didn't find an enemy to attack then pass it
// off to the parent class
if(!%attack)
{
Entity::OnRightClick(%this, %worldPos);
}
}
We do a lot of new things in here, we first get the count of the objects in %objList, we start %attack off as false and then we kick into a loop that goes through ever object in %objList. We check if the object is attackable, if it is we check if it isn't on our team, if so then we can attack! So we set attack to true, reset our actions, and call the attack action. We end this function that checks if we attacked, if not we then call our parent class onRightClick function which will simply move us. [edit] Lets test it![edit] Fire up TGBTime to test our results, fire up TGB. Either create two more soldiers or change the current ones to solders (and back again afterwards!). You should see something like this. Drag and select and BoB, now right click EvilBoB to attack! You should see BoB attack EvilBoB like this. It worked! [edit] Triggering the Worker's proper right click response[edit] Modifying our onRightClickNow its time to modify the Worker's onRightClick, fortunately it isn't all that different than the Soldiers. One of the only differences is that we aren't checking for isAttackable objects of a different team, we are checking for object's with isRightClickable and then has .type equal to "goldMine." So find your Worker's onRightClick function in your objectTypes.cs and make your function look like this.
// --------------------------------------------------------------------
// Worker::OnRightClick()
//
// This is the Worker onRightClick() callback, it basically just forwards
// the action to the Entity class.
// --------------------------------------------------------------------
function Worker::OnRightClick(%this, %worldPos, %mode, %objList)
{
// get the count of the object list
%objCount = getWordCount(%objList);
// toggling mining to false
%mine = false;
// we loop through all of our objects in our right click point
for(%i=0;%i<%objCount;%i++)
{
// grab the current object
%obj = getWord(%objList, %i);
// check for right clickable objects
if(%obj.isRightClickable)
{
// check if the object is a gold mine
if(%obj.class $= "GoldMine")
{
// its rightclicabke and a goldmine, we can
// mine, so set mine to true
%mine = true;
// lets reset anything we were doing
%this.action("resetAllActions");
// lets mine
%this.action("mineGold", %obj);
}
}
}
// if we didn't find a goldMine to mine then pass it
// off to the parent class
if(!%mine)
{
Entity::OnRightClick(%this, %worldPos);
}
}
As you can see the only differences in this than in the Soldier version is %attack is now %mine, .isAttackable is now .isRightClickable... and instead of checking team we check if its class is "GoldMine"... [edit] Lets test it![edit] Fire up TGBTime to test our results, fire up TGB. Then type these commands in the console.
gameData.spawnObject(CirclesWorker, Circles, "85 -40", "BoB");
SceneWindow2D.setCurrentCameraPosition("85 -30");
You should see something like this. Drag and select and BoB, now right click the gold mine... your Worker BoB should start mining like this. It worked! [edit] Right click script file referencesIf your's does not work like this then compare your file to these. |
Categories: T2D | TGB | Tutorial












