Queuing Movement Markers
by Justin Woods (ESAL) · in RTS Starter Kit · 04/27/2006 (11:27 pm) · 6 replies
Hello everyone. I am just starting to adventure into the RTS kit, but I have been working with TGE for a bit more than a year.
Anyway, I'm wondering about queuing up commands and having the selected unit, or units, execute them in the order they were given. The prime example for this would be Warcraft III. Where you can hold down shift and then add a new command to the que.
Also, from Warcraft III, I would like a little marker to appear on the screen in each of the spots I have set a command for that unit. These markers would only be shown when I have the unit selected, and disappear as each one is fulfilled.
Does anyone think they could help me with this little project? I think it would be an excellent resource for the RTS community here, as it is getting to become a standard feature in most RTS games.
Anyway, I'm wondering about queuing up commands and having the selected unit, or units, execute them in the order they were given. The prime example for this would be Warcraft III. Where you can hold down shift and then add a new command to the que.
Also, from Warcraft III, I would like a little marker to appear on the screen in each of the spots I have set a command for that unit. These markers would only be shown when I have the unit selected, and disappear as each one is fulfilled.
Does anyone think they could help me with this little project? I think it would be an excellent resource for the RTS community here, as it is getting to become a standard feature in most RTS games.
#2
05/31/2006 (1:12 am)
Has anyone made any progress on something like this designed for the RTS kit?
#3
Is this something better in the engine or script?
This is on my list of things to work on so I wondered what everyone else thought would be better. I can share it when I'm done. I'm thinking the best method would be to add the commands to a queue when shift-rightclick, and clear the queue then add command with the right-click.
10/07/2008 (7:57 pm)
Hey, I'm back after life gets in the way. Is this something better in the engine or script?
This is on my list of things to work on so I wondered what everyone else thought would be better. I can share it when I'm done. I'm thinking the best method would be to add the commands to a queue when shift-rightclick, and clear the queue then add command with the right-click.
#4
Please note that some of the default function names might have changed. I try to code to a specific standard so I might have changed the names to follow the style.
Queue code: (to be placed in player.cs)
What this does is give you a way to push a task into the line. (if needed) Push a task to the front of the line, which I use to get my units to stop what they are doing and start attacking a close enemy. Execute the task, take a task out of the queue once it is done being executed, and clear the entire line of tasks.
All you need to do is instead of calling a function (like %unit.AttackObject(%target), for example) you called %unit.pushTask("AttackObject", %target);
In Commands.cs you can find "serverCmdIssueAttack(%client, %targetID)" at the end change the code to read:
The $Selection::Modifier is set whenever you hold down the shift key (unless you have changed that). so for every unit selected, if the shift is held the task is pleased onto the queue. If no shift, the list is cleared and the task is placed onto the queue. Why place the task onto the queue if we aren't holding down the shift key? So that if we issue a second (third, fourth, etc.) eveything will work well.
Notice that I commented out that "%cl.sendAttackEvent(%client.selection, %target);" It was annoying me as it sends an attack event to all units at once through the engine code. Since my game is a single player game I just took this out and decided to deal with it when I go network.
Issue move is the same way: ("serverCmdIssueMove(%client, %formation, %bool, %x, %y, %z)")
To get your units to change tasks, you need to place the "executeNextTask()" (with the proper tag of your RTSUnit, like %this. or %obj) whenever your actions end. For example, in the "onReachDestination" function in player.cs I put my executeNextTask after checking the current goal:
Exactly where these should be placed is up to you.
I hope this helps, and again I welcome any questions, comments or concerns.
Commander
10/21/2008 (5:48 pm)
Ok. Due to memory management issues and flexability, I decided to write the queue in torque script. There are several queue functions and changes to other functions. Along with this I implemented the shift-click abilities. Any changes/suggestions are more than welcomed. Please note that some of the default function names might have changed. I try to code to a specific standard so I might have changed the names to follow the style.
Queue code: (to be placed in player.cs)
function RTSUnit::pushTask(%this, %func, %arg)
{
//increments index then fills that spot with the new task method
%this.QIdx++;
%this.queueList[%this.QIdx] = %func;
%this.argList[%this.QIdx] = %arg;
if(%this.QIdx == 0)
%this.executeTask();
}//end push task
function RTSUnit::pushFront(%this, %func, %arg)
{
//increments index then fills that spot with the new task method
if(%this.queueList[0] !$= %func && %this.argList[0] != %arg)
{
for(%i = %this.QIdx; %i >= 0; %i--)
{
%this.queueList[%i + 1] = %this.queueList[%i];
%this.argList[%i + 1] = %this.argList[%i];
}
%this.QIdx++;
%this.queueList[0] = %func;
%this.argList[0] = %arg;
}
%this.executeTask();
}//end push front
function RTSUnit::popTask(%this)
{
//Place our queue into a temp buffer,
//but without the recently executed first spot.
if(%this.QIdx > -1)
{
for(%i = 0; %i < %this.QIdx ; %i++)
{
%bufferList[%i] = %this.queueList[%i + 1];
%bufferArg[%i] = %this.argList[%i + 1];
}
%this.queueList[%this.QIdx] = "";
%this.argList[%this.QIdx] = "";
//Put the queue back into the buffer.
for(%i = 0; %i < %this.QIdx; %i++)
{
%this.queueList[%i] = %bufferList[%i];
%this.argList[%i] = %bufferArg[%i];
}
%this.QIdx--;
error("Popped to:" SPC %this.QIdx);
}
}//end pop task
function RTSUnit::clearTasks(%this)
{
cancel(%this.attackEvt);
while(%this.QIdx > -1)
{
%this.queueList[%this.QIdx] = "";
%this.argList[%this.QIdx] = "";
%this.QIdx--;
}
}//end clear task
function RTSUnit::executeNextTask(%this)
{
%this.popTask();
%this.executeTask();
}//end execute next task
function RTSUnit::executeTask(%this)
{
if(%this.QIdx > -1)
{
%func = %this.queueList[0];
%arg = %this.argList[0];
eval(%this.getId() @ "." @ %func @ "(\"" @ (%arg) @ "\");");
}
}//end Execute TaskWhat this does is give you a way to push a task into the line. (if needed) Push a task to the front of the line, which I use to get my units to stop what they are doing and start attacking a close enemy. Execute the task, take a task out of the queue once it is done being executed, and clear the entire line of tasks.
All you need to do is instead of calling a function (like %unit.AttackObject(%target), for example) you called %unit.pushTask("AttackObject", %target);
In Commands.cs you can find "serverCmdIssueAttack(%client, %targetID)" at the end change the code to read:
for (%i = 0; %i < ClientGroup.getCount(); %i++)
{
%cl = ClientGroup.getObject(%i);
//%cl.sendAttackEvent(%client.selection, %target);
}
for(%i=0; %i<%client.selection.getCount(); %i++)
{
if($Selection::Modifier)
%client.selection.getObject(%i).pushTask("attackObject", %target);
else
{
%client.selection.getObject(%i).clearTasks();
%client.selection.getObject(%i).pushTask("attackObject", %target);
}
}The $Selection::Modifier is set whenever you hold down the shift key (unless you have changed that). so for every unit selected, if the shift is held the task is pleased onto the queue. If no shift, the list is cleared and the task is placed onto the queue. Why place the task onto the queue if we aren't holding down the shift key? So that if we issue a second (third, fourth, etc.) eveything will work well.
Notice that I commented out that "%cl.sendAttackEvent(%client.selection, %target);" It was annoying me as it sends an attack event to all units at once through the engine code. Since my game is a single player game I just took this out and decided to deal with it when I go network.
Issue move is the same way: ("serverCmdIssueMove(%client, %formation, %bool, %x, %y, %z)")
for (%i = 0; %i < ClientGroup.getCount(); %i++)
{
%cl = ClientGroup.getObject(%i);
//%cl.sendMoveEvent(%client.selection, %x SPC %y);
}
if($Selection::Modifier)
%obj.pushTask("setMoveGoal", %dest);
else
{
%obj.clearTasks();
%obj.pushTask("setMoveGoal", %dest);
}To get your units to change tasks, you need to place the "executeNextTask()" (with the proper tag of your RTSUnit, like %this. or %obj) whenever your actions end. For example, in the "onReachDestination" function in player.cs I put my executeNextTask after checking the current goal:
function RTSUnit::onReachDestination(%this, %dest)
{
if(%this.curGoal $= "")
{
%this.executeNextTask();
echo(%this @ " - Arrived!");Exactly where these should be placed is up to you.
I hope this helps, and again I welcome any questions, comments or concerns.
Commander
#5
Good work!
10/22/2008 (7:49 am)
Excellent approach, we've been considering using this very same mechanics, based upon the stock AIPlayer task's code, which is pretty similar to your own (I asume you used that).Good work!
#6
10/22/2008 (6:02 pm)
Yeah, it was originally a stack; which I prefer, but those don't work here. However, that is why I needed to add a push front and execute next task. If you notice I don't pop the old after I execute it, just before I go to get the next task. This was done so that I can have the first task (or push front) execute the task without worrying about accidently poping to a bad task.
Torque 3D Owner Chris Kuykendall