predict enemy unit movement
Predicting enemy unit movement is easy and fun, and it helps micro a lot. Now that I’ve implemented a version in Steamhammer, I recommend it for everyone.
Adding this feature dramatically improved Steamhammer’s micro in some situations. It affects almost every fight; with prediction, your units do less moving and more shooting. It’s most obvious in a chase. When chasing the enemy scouting worker, zerglings with prediction don’t pursue endlessly, but catch it. The most extreme case may be playing against Stone once zerglings come out. Stone’s SCVs flee like the wind, or they twist and turn trying to evade, but they can’t escape. Steamhammer wins with much less useless chasing.
making the prediction
Here’s my implementation in Steamhammer: Assume that the enemy unit will keep moving in a straight line at a constant speed, and predict a fixed number of frames ahead. It’s dead simple. ClipToMap()
makes sure that we return a valid position—we may predict that the enemy falls off the edge of the world. ClipToMap()
simply restricts x
and y
to valid values,. We predict a position closer to where the enemy will end up if it bumps into the edge of the map.
// Predict a visible unit's movement a given number of frames into the future, // on the assumption that it keeps moving in a straight line. // If it is predicted to go off the map, clip the prediction to a valid position on the map. BWAPI::Position PredictMovement(BWAPI::Unit unit, int frames) { UAB_ASSERT(unit && unit->getPosition().isValid(), "bad unit"); BWAPI::Position pos( unit->getPosition().x + int(frames * unit->getVelocityX()), unit->getPosition().y + int(frames * unit->getVelocityY()) ); ClipToMap(pos); return pos; }
There are fancier ways to predict. You can take terrain into account; an enemy ground unit will probably not pass through unwalkable terrain (though there are ways to do that). You may want to take the chasing unit into account—can it catch up? I believe Iron calculates the closest interception point where the chasing unit can catch the enemy, and heads straight there. That is a more elaborate calculation, but not too difficult, and there are several ways to do it.
using the prediction
How you use the prediction depends on your code, of course. In Steamhammer, it is as simple as making the prediction. Steamhammer normally calls Micro::AttackUnit()
to issue an attack command. I added Micro::CatchAndAttackUnit()
which attacks if we are able to, and otherwise issues a move command toward the predicted enemy position 8 frames in the future. Here is the working part (I took out irrelevant stuff like error checking and debug drawing).
if (!target->isMoving() || !attacker->canMove() || attacker->isInWeaponRange(target)) { AttackUnit(attacker, target); } else { BWAPI::Position destination(PredictMovement(target, 8)); // the number is how many frames to look ahead Move(attacker, destination); }
Then I substituted Micro::CatchAndAttackUnit()
for Micro::AttackUnit()
throughout the code wherever it made sense. The condition !attacker->canMove()
means there is no problem if you call it for an inappropriate unit, like a burrowed lurker or a sieged tank, because then it behaves just like the original call. (But I didn’t call it for inappropriate units.)
I chose 8 frames fairly arbitrarily. I think a range of values will work. With 8 frames, we aim for a spot a little ahead of where the enemy is, and not so far ahead that they can throw us off by turning. Dan Gant points out in a comment that PurpleWave predicts a variable number of frames ahead depending on when the chasing unit will be able to attack; the goal is to come into range at the same moment that we are ready to attack.
It makes sense to predict further into the future if the enemy is far away. Calculating the interception point like Iron is the ultimate version of that. Maybe the ideal method is to find the interception point when far away, and switch to the Purple idea when approaching attack range.
There is a disadvantage to the change in Steamhammer. There seems to be a higher rate of units getting stuck, unable to move. I haven’t yet looked into why. It affects protoss the most. It’s possible that the change makes protoss play worse overall.
Comments
Dan on :
It's hard to avoid the sticking itself, partly because it's hard to detect when the attack has failed. A few bots get around this by just detecting stuck units and un-sticking them with a Stop command.
Jay Scott on :
Bruce on :
Here is my code to detect stuck dragoons, if it helps: https://github.com/bmnielsen/Locutus/blob/master/Steamhammer/Source/LocutusUnit.cpp#L525-L545
Bruce on :
One of the big issues with Steamhammer's dragoon handling is it changing targets right before the queued attack will be issued, which ends up in a lot of aborted attacks. I'm not sure if it affects Zerg units though.
Arrak on :