You may have recently seen Google’s “Zerg Rush” Easter Egg.
I thought it’d be fun to try doing something like this myself. See my
demo before you read on. As you can see it’s not as polished as the
Google version but the basic behaviour is the same.
I haven’t looked at Google’s source so I’m not sure how they chose to do it,
but for me there was no other route more obvious than elementFromPoint
,
a DOM method that returns the element at any defined coordinate within the
current viewport.
So, I create a bunch of little Zergling instances. Each Zerling searches for “targets”,
i.e. any DOM element nearby. A Zergling instance will begin by looking in its close
proximity, calling elementFromPoint
at eighth-degree turns around a
steadily increasing radius:
// (Zergling.prototype.findTarget) for (radius = 10; radius < Zergling.VISION; radius += 50) { for (degree = 0; degree < 360; degree += 45) { x = this.x + halfWidth + radius * cos(PI/180 * degree) - scrollLeft; y = this.y + halfHeight + radius * sin(PI/180 * degree) - scrollTop; if (Zergling.isSuitableTarget(el = doc.elementFromPoint(x, y))) { // We have a viable target // ... break; } } } |
Any random element might not be a suitable target though. For example, we want to avoid
other Zergling instances on the page. We also want to avoid anything too big, like
the BODY
element. I also added in an antiZerg
feature so
elements on the page can protect themselves from the zergs:
Zergling.isSuitableTarget = function isSuitableTarget(candidate) { var targetData; if (!candidate) { return false; } // Make sure none of its ancestors are currently targets: for (var parent = candidate; parent = parent.parentNode;) { if ($.data(parent, Zergling.DATA_KEY) || /antiZerg/i.test(parent.className)) { return false; } } targetData = $.data(candidate, Zergling.DATA_KEY); candidate = $(candidate); return !/zergling/i.test(candidate[0].nodeName) && !/antiZerg/i.test(candidate[0].className) && // Make sure it's either yet-to-be-a-target or still alive: (!targetData || targetData.life > 0) && // Make sure it's not too big candidate.width() * candidate.height() < Zergling.MAX_TARGET_AREA; }; |
Once we have a viable target we begin moving towards it.
calcMovement: function() { var target = this.target, // Move towards random position within the target element: xDiff = (target.position.left + random() * target.width) - this.x, yDiff = (target.position.top + random() * target.height) - this.y, angle = atan2(yDiff, xDiff); // Assign deltaX/Y (i.e. how much we move {x,y} on each step) this.dx = this.speed * cos(angle); this.dy = this.speed * sin(angle); }, //... |
On every step the Zergling needs to check whether it’s reached the target yet:
hasReachedTarget: function() { var target = this.target, pos = target.position; return this.x >= pos.left && this.y >= pos.top && this.x <= pos.left + target.width && this.y <= pos.top + target.height }, //... |
When a Zergling reaches its target it begins killing it:
if (this.hasReachedTarget()) { this.isKilling = true; return; } |
And the actual killing:
if (this.isKilling) { if (target.life > 0) { // It's still alive! Pulsate and continue to kill: target.life--; this.pulsate(); target.dom.css('opacity', target.life / Zergling.LIFE); } else { // It's DEAD! target.dom.css('visibility', 'hidden'); this.pulsate(0); this.isKilling = false; this.target = null; } return; } |
Setting this.target
to null
means that the Zergling
will begin searching for a new target on its next step.
To manage all the Zerglings I put together a ZergRush
class:
function ZergRush(nZerglings) { var me = this, zerglings = this.zerglings = [], targets = this.targets = []; for (var i = 0; i < nZerglings; ++i) { zerglings.push( new Zergling( Math.random() * 100, Math.random() * 100, this ) ); } this.intervalID = setInterval(function() { me.step() }, 30); } |
All Zerglings start at random positions in the top-left of the page
(anywhere from {0,0}
to {100,100}
).
As I said, it’s not very polished. The Zerglings are currently just little
red dots, made like so:
// (in Zergling constructor): // <zergling> element used to avoid CSS conflicts and because its cool.. this.dom = $('<zergling>').css({ width: this.width, height: this.height, position: 'absolute', display: 'block', background: 'red', left: x, top: y, borderRadius: '5px', zIndex: 9999 }).appendTo(body); |
Please visit the demo, see the source on Github, or save this bookmarklet for the future.
If I’m honest, I prefer long strategy but once in a while rushing is fun!
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
Genius!
Awesome man… =D
Cool Stuff, nice technique to do it 🙂
Its awsome. But I have one thing to ask. How to add some targets for those zerglings?
Thanks, cool post!
Seems pretty easy to just use existing pages like google, and turn them into games, since its all CSS manipulation.
I was wondering if you or anyone might know code for the shattering text after the result’s HP goes to 0?
I want to integrate it in my Python/Pygame game.
Thanks.
email: [email protected]