All of our maps go drastically over the standard edict limit to little loss in performance. Adding another 5120 entities though would not be ideal.
that could be nice idea
It's adorable, I love it
Hello fellow research associates! It’s nice to get in contact with you all again. It’s been a while since they’ve let me out of the labs. For those unfamiliar, my name is Chris Onorati - a programmer, level designer, and a few other things for Operation: Black Mesa and Guard Duty.
The most recent task I’ve taken on is revisiting the Crush Depth chapter. I had finished my first pass on the chapter quite some time ago, but I felt it was time to revisit the area with my improved skillset, the increased arsenal of assets we have, and a fresh pair of eyes. The chapter is fairly short for those who haven’t played it for a while.In my initial pass on the chapter, I kept to the books and didn’t dive deeper into what we could do with this section of the game, no pun intended. This time, I’ve put a focus into expanding the chapter while keeping the entire segment of the game interesting and fresh.
One thing I am a fan of for games that have a chapter format is to have mechanics that complement the theme of that gameplay segment. Outside of obtaining the Shockroach fairly late into the third map, there isn’t really anything new on the table that you as a player get to mess around with.After tossing around ideas, and prototyping a few, I came to something I’m quite pleased with: Xen Urchins.
The goal of this development blog will be explaining the process I went through to get one of the Xen Urchin’s several abilities it shares with the Shockroach working.
DESIGNING THE URCHINS
A Xen Urchin from a gameplay standpoint is a little creature that you can pick up and throw about just like any other physics props – however the Xen Urchins have some interesting properties, only a handful of which I’ll be talking about here. One of these properties is that if the Xen Urchin is in water, it will emit an electrical pulse that will damage everything around it with a fairly nasty zap. That mechanic may sound a little familiar to one weapon previously mentioned, the Shockroach. This weapon’s bullets also have a nasty ability to electrocute anything around them that’s wet– including you!
It’s not a coincidence that the Xen Urchin shares many traits with the Shockroach – that’s intentional. A modern practice in game design is to give mechanics arcs. Below are some bullet points on the steps of a good arc:
- Setup: Introduce the mechanic.
- Hook: Make the mechanic do something to engage and interest the player.
- Development: Allow practice and growth with the mechanic.
- Turn: Add some new twist onto the mechanic to recapture the excitement felt with the Hook.
- Resolution: Have some final moment with the mechanic that results in player mastery.
That’s not to say all mechanics have to follow this scheme, but strong ones generally do.The Xen Urchin allows this arc to exist for the introduction and application of electrical weaponry, as outlined below:
- Setup: See the Xen Urchins interacting with the environment and being studied in several labs.
- Hook: Use the Xen Urchin to solve a puzzle.
- Development: More time showing the different possibilities of Electric damage in the game.
- Turn: Obtaining the Shockroach – now you have full control over this new power.
- Resolution: I think I’ll leave this a surprise…
Now that we have some context – onto what I wanted to discuss – the electric area of effect attack that come with the Xen Urchin and the Shockroach!
Fun fact: when starting out the implementation, a watermelon was used instead of the Urchin itself while the art team figured out the design of the creature.AREA OF EFFECT DAMAGE STAGE 1
In Half-Life: Opposing Force the Shockroach amplifies damage it dishes out when shot underwater – that makes sense, right? Electricity and water don’t mix in a healthy way. For Operation: Black Mesa, one thing I wanted to explore was expanding this functionality to give the Shockroach more time to shine. There are more sources of water than just pools of it lying around in our game.Some rooms have sprinklers going off to put out fires – that’s water. Sometimes a pipe is leaking water – that’s water. Or maybe there’s just a puddle on the ground somewhere near a tipped over mop bucket – that’s water. My goal was to make the Xen Urchin and Shockroach affect all of these sources of water – which meant I had a fairly interesting programming problem on my end.
My first approach to this problem was simply checking if the Shockroach or Xen Urchin was in a new type of trigger called a trigger_shockzone (at first it was named trigger_rain – but this was a bit nondescript of the actual purpose of this entity), or underwater.If it was – great! That meant the Urchin or Shockroach bullet should damage anything within a certain radius.This was a fast and easy to implement approach – but it had quite a few flaws – with the most critical one being that a player could be standing outside of a pool of water, shoot their Shockroach or throw a Xen Urchin into it, and they would still get damaged – not good.
It was a quick and dirty approach – so to be fair, I didn’t really expect it to work out. But it at least got the ball rolling – and honestly that’s the most important thing in game development, to keep working and iterating and testing and prototyping. So now I needed to come up with a different approach – a much more complex one that could actually detect if the thing the Xen Urchin or Shockroach wanted to damage should actually be damaged. At first I could not figure out a simple way to get the mechanic working – everything I came up with felt overly complicated with dynamic mesh spawning and vertex manipulations to detect contact points. Coincidentally in my day job, working at the DigiPen Institute of Technology, I did a playthrough of Bioshock for a class. And guess what? BioShock has a lot of water in it…that you can electrify…time to do some research!
Above you can see the "idle" skin of a Xen Urchin.
For those who don’t know Bioshock– within the first 5 minutes of gameplay you gain the ability to shoot electricity from your hand. If you shoot electricity at water, it becomes electrified. Playing around with this, I took some notes on what I thought was going on behind the scenes, since I couldn’t look at the code myself.
- Separate triggers exist for each instance of water that you can electrify.
- Electricity lasts a few seconds in the water, likely controlled by some separate entity.
- Particles are generated and applied to the top of the surface randomly.
- It does not matter where you hit the trigger – once electrified, anywhere in the trigger is dangerous even if it is 10,000 feet away.
- Triggers are flat and rectangular shape. This is likely an AABB approach to generating the effect (more on that later).
BioShock and Half-Life are very different mechanically, so I instantly knew there were some issues with BioShock’s approach I would need to address…
- It would be very odd for a Xen Urchin or a Shockroach bullet to zap you if it was on the other side of a huge pool of water.
- The player can go underwater and swim which you can’t in BioShock, so I need to account for depth as well in my approach.
- The triggers are not guaranteed to always to be rectangular. Pools of water can come in all sorts of interesting shapes.
AREA OF EFFECT DAMAGE STAGE 2
BioShock proved to be a great reference, giving me a more concrete plan to get version 2.0 working:
- Get the position of the Shockroach bullet or the Urchin.
- Check if the entity is in water by having the entities check collision for a trigger_shockzone.
- If either is in a trigger_shockzone, and a certain amount of time has passed, zap the area.
- To zap the area, set up a radius (say 256 units) and a density (say 32).
- In that radius, pick a number of random points equal to that density
- At each point, spawn a new entity (say, env_electricity_damage - something that won't appear in hammer).
- Each of these env_electricity_damage then checks if it is actually in the trigger_shockzone.
- If the env_electricity_damage is in the trigger, spawn it for real and have it shoot out the particle effect.
- If something touches the env_electricity_damage, and the thing that touches the env_electricity_damage is also in the trigger, apply damage.
- Note that we need to check that the thing that touched the env_electricity_damage is in the same trigger – you could have two puddles next to one another, or two pools separated by a wall. While both of these cases would fit all requirements above – being within range and being in a trigger_shockzone, the electricity needs to stay confined to one trigger to be fair to the player and to not make the weapon confusing to use.
- Delete the entity quick thereafter - like 0.25 seconds tops.
So that was the next plan of implementation, which I carried out, but new problems appeared.Namely performance ones.Yes, this would work, but now I am spawning up to 32 entities every 0.1 seconds for each Shockroach bullet. In Multiplayer that would mean 10 bullets per player * 16 players * 32 env_electricity_damage entities = 5120 entities per 0.1 seconds being created.Goodbye frames.
Above you can see that a trigger_shockzone is a separate entity from world water brushes. It is its own brush entity, with a tool texture by Brandon Smith.
AREA OF EFFECT DAMAGE STAGE 3
So – we have this mechanic working, albeit with huge performance problems, which is great. Something we advocate to students at DigiPen is to have everything “In but bad”, and that’s exactly where I now was. Luckily, a solution to the huge entity overhead came to me within a couple hours after lying in bed thinking. I just needed to modify how the env_electricity_damage (renamed env_electrical_discharge) functioned. Below is the new plan, with some notable tweaks.Get the position of the Shockroach bullet or the Urchin.
- Check if the entity is in the water by having the entities check collision for a trigger_shockzone.
- If either is in the trigger_shockzone, and a certain amount of time has passed, zap the area.
- To zap the area, create an env_electrical_discharge and give it a radius (say 256 units).
- Every 0.1 seconds. The env_electrical_discharge picks 15 random points in that range.
- For each of the 15 points, check if the point is in the trigger_shockzone via a raycast (a fancy way of doing collision checking) instead of making a physical collision box.If it is, great. If not, generate a new random point.Should the game pick a random point 4 times in a row that is within the radius but not in the trigger_shockzone, just default to wherever the Xen Urchin or Shockroach bullet is.
- At each of these points create a particle effect and check if anything is nearby (within a 64 unit radius).
- If something is nearby, check if it is also in the same trigger_shockzone.Also check to make sure it has not been already damaged this frame by the electricity to avoid doing ridiculous damage.
- If all of the above conditions check out, apply the damage.
This approach has some pretty solid gains. For starters, each Xen Urchin or Shockroach bullet now only generates a single additional entity, meaning the math is now 1 entity * 10 bullets * 16 players = 160 entities. That is nearly 5000 less entities than our previous worst possible scenario. The other huge gain is we no longer need to do real collision checks with the env_electriacal_discharge entity – which also significantly boosts performance.
For clarity, in the above pictures, each white orb at the center of an electrical particle represents a point selected by the env_electrical_discharge entity
Up to 15 of these are generated every 0.1 seconds by teleporting the entity around 15 times a frame.
AREA OF EFFECT DAMAGE STAGE 4?
The area of effect damage is now working for pools of water, puddles, sprinklers, and much more.It also no longer drags down the framerate of the engine just by having a Xen Urchin or a Shockroach bullet exist inside a trigger_shockzone.Still, there is still one significant area of improvement that could be made:
- Create an algorithm to guarantee a random point that is always inside the trigger_shockzone. This could save up to 7200 random point generations every 0.1 seconds.
The tricky part with this is the only “easy” solution would be to use an AABB check (AABB stands for Axis Aligned Bounding Box) as mentioned in the BioShock research. This would mean I could guarantee a point is always generated within the bounding box of a trigger. So why don’t I just do this?
Say we had a pool of water that is L shaped like in the above image. The orange is the trigger that would be placed inside the water to represent the shock zone, and the grey is solid ground. An AABB check does not care about the shape of the trigger – it just creates a box that encompasses the entire region the trigger exists in. In other words, the random point generation area would encompass the land as well in the above example. This solution will not work, or at least, require a near equal level of computational power under a worst case scenario.
For those of you who are not technically inclined, I applaud you making it to the end of this rather dry development blog. Perhaps our next one will be a bit flashier and not so mechanically driven. For those who are curious and want to chat more about this implementation – feel free to join our official Discord server! I skimmed over and omitted a fair amount of the intricacies for the sake of time, and to avoid making this blog even more confusing or dry.
Until I am let out of the lab again, this is Chris Onorati signing off.
P.S. Special thanks to research associate Brandon Smith for helping with the pictures and diagrams – I’m not on the art team for a reason…
For those curious, below are some code snippets to demo how some of this stuff works. None of this is the super interesting stuff, though – but fellow programmers can probably piece together what the rest of the code may look like.
Note that we do not plan to, and cannot legally, release our codebase (especially parts of the codebase written by Valve employees), so this very well may be the only code we ever show to the public for Operation: Black Mesa or Guard Duty. Enjoy!
- Setup: Introduce the mechanic.