Several people have asked about how I did some stuff, so I figured I would write this up.
First and foremost. If you have not ran through my entry, I suggest you do so now before you continue reading.
You can find it over here. Check out the others while you are at it, too.
< obligatory blank space to reduce accidental spoilage >
Starting with the simplest thing first, we have the mysterious "Top Secret" door that slides down (or is it up?) the wall as you approach... and then returns if you back away.
Related but irrelevant picture #1 This post ended up being very wordy, so...
It is accomplished with a point_proximitysensor measuring to the player, constrained to the length-wise axis of the hallway. This produces an absolute distance in units, which is fed into a math_remap that compresses the range from 0 - 496 (the distance at which it is activated) down into 0 - 1, which is the valid range of input for func_movelinear's SetPosition, used to move the doorframe across the wall.
To actually measure the player, I had to give the player's entity a name. This was done by giving the trigger_once covering the spawn point (I used it for many things, instead of a logic_auto) an output of:
Output|Target|Input|Parameter
OnTrigger|!activator|AddOutput|targetname theplayer
The other caveat is that point_proximitysensor will commit suicide if it spawns and it's target to measure doesn't exist, which it doesn't, since the player isn't yet named when the entities are loaded. To get around this, the sensor is put into a point_template and respawned once the player reaches the area, long since having been named.
Finally, when the player reaches the sensor there is a trigger that kills the sensor and sends a final input to the movelinear to force it all the way down. This is because the sensor is absolute, no positive or negative, so the door would begin rising as the player passed the sensor.
- - - - -
The clock zone. I think the only part of this that could use explaining is the floor breaking away. Obviously it's just a bunch of func_breakable (92, if you were wondering) but setting up 92 outputs to break them in series would be incredibly boring and tedious, not to mention a nightmare to change the rapidity with which they break.
Instead it is done by using env_beam's random targeting ability to damage the breakables. All the tiles have the same name, and there is an env_beam (two, actually) beneath the floor with the Ending Target set to the name of the tiles. When the beam's lifespan is set to less than infinite, every time it fires it will choose a different entity with the name in question. So with next to no effort I have a completely random destruction of the entire surface.
Related but irrelevant picture #2
To accelerate the destruction I used a few inputs to shorten the lifespan of the beam, and turn the other one on. Laser 1 started at 0.5 lifespan, Laser 2 is always at 0.1
Output|Target|Input|Parameter|Delay
OnTrigger|clock_laser_1|TurnOn||0
OnTrigger|clock_laser_1|AddOutput|life .2|3.00
OnTrigger|clock_laser_1|AddOutput|life .1|4.50
OnTrigger|clock_laser_2|TurnOn||5.20
- - - - -
A brief note before the big stuff, both the shattering hallway and the clock zone have situations where the player may no longer be standing on anything. To prevent (or reduce) falling, I used trigger_gravity to make the players float the instant they lose footing, and some gentle trigger_push to keep them centered in the hall or nudged toward the clock. The may fail to work if the player is moving at a rapid rate in some direction when it occurs, but I'd rather be gentle and allow the rare failure than be harsh (playerclips, loss of control, etc) to prevent it entirely.
- - - - -
The Shattering Hallway. I put the most work into this, by far, and it turned out better than I expected in more ways than one.
Every chunk of the hall is a func_physbox. Their mass scale is set to 0.02 (large concrete physboxes get extremely massive) and their Rendermode is set to Texture with an FX Amount (alpha) set to 0. They also have the following flags set:
- Start Asleep
- Motion Disabled
- Prevent motion enable on player bump
- Only break on trigger
- Don't take physics damage
This is all so that they will not move, will not break, and are invisible until I otherwise wish.
Why invisible? Physboxes have some ugly dynamic shading on their edges when crammed together, and awhile I could have turned off shadows the seams would still have been visible. Thus, there are identical looking func_brushes in their places that are removed at the instant the shatter occurs.
Related but irrelevant picture #3
The entire area is covered by a trigger_vphysics_motion that gives the chunks zero-gravity and limits their speed to a max of 200. The speed limit allowed me achieve the effect of the hall rapidly coming apart but give the eeriness of them slowly drifting off. I also added a tiny amount of angular acceleration increase, to help maintain the random spinning of the pieces.
The shattering force itself is simply seven env_physexplosion in a row along the hallway.
The fade-to-black is done with a math_colorblend, which is essentially the same as a math_remap except the output values are a trio of RGB values instead of a single integer. You can blend between any two colors, but I only need to do white-to-black (color tinting entities is subtractive, white represents normal).
However, like the remap, it only takes one input and sends one output at a time. I wanted a continuous stream of outputs to get a smooth fade.
Once again I had a use for a point_proximitysensor, except I was merely measuring the distance to a func_movelinear (though any mobile entity would work). This gives me a steady smooth flow of numbers for the colorblend's input. Since the colorblend remaps any range to the color range, the speed and distance of the movelinear is irrelevant as long as the ratio between the two is what you desire.
Virtually the same method was used for the cracks of light that appear prior to the shattering, the difference being I wanted to go from invisible to solid, instead of white to black. The solid white lines on the floor use the white_fill material, and rather than a colorblend I used a regular math_remap to feed my proximity value (increasing this time) into the alpha input of the func_illusionary.
I couldn't use alpha for the gradient shining out of the lines because I already set it to 10 to dim them to the desired brightness (the raw VTF is solid white at the one edge), rather than manually editing the VMT just for this map.However, due to the nature of the material I could use color and a colorblend to achieve invisibility, because black is invisible in additive rendering.
Lastly, the floor/ceiling flip is just a func_door_rotating, but due to the player floating in zero-g and the lights moving with it, the illusion of whether the floor is moving or the player is flipping becomes mushy and confusing (as intended).
- - - - -
I think that's everything abnormal to cover on the technical side. Though no amount of words could explain the mind that devised them. If there is anything more let me know and I'll tack on an addendum.
First and foremost. If you have not ran through my entry, I suggest you do so now before you continue reading.
You can find it over here. Check out the others while you are at it, too.
< obligatory blank space to reduce accidental spoilage >
Starting with the simplest thing first, we have the mysterious "Top Secret" door that slides down (or is it up?) the wall as you approach... and then returns if you back away.
Related but irrelevant picture #1 This post ended up being very wordy, so...
It is accomplished with a point_proximitysensor measuring to the player, constrained to the length-wise axis of the hallway. This produces an absolute distance in units, which is fed into a math_remap that compresses the range from 0 - 496 (the distance at which it is activated) down into 0 - 1, which is the valid range of input for func_movelinear's SetPosition, used to move the doorframe across the wall.
To actually measure the player, I had to give the player's entity a name. This was done by giving the trigger_once covering the spawn point (I used it for many things, instead of a logic_auto) an output of:
OnTrigger|!activator|AddOutput|targetname theplayer
The other caveat is that point_proximitysensor will commit suicide if it spawns and it's target to measure doesn't exist, which it doesn't, since the player isn't yet named when the entities are loaded. To get around this, the sensor is put into a point_template and respawned once the player reaches the area, long since having been named.
Finally, when the player reaches the sensor there is a trigger that kills the sensor and sends a final input to the movelinear to force it all the way down. This is because the sensor is absolute, no positive or negative, so the door would begin rising as the player passed the sensor.
- - - - -
The clock zone. I think the only part of this that could use explaining is the floor breaking away. Obviously it's just a bunch of func_breakable (92, if you were wondering) but setting up 92 outputs to break them in series would be incredibly boring and tedious, not to mention a nightmare to change the rapidity with which they break.
Instead it is done by using env_beam's random targeting ability to damage the breakables. All the tiles have the same name, and there is an env_beam (two, actually) beneath the floor with the Ending Target set to the name of the tiles. When the beam's lifespan is set to less than infinite, every time it fires it will choose a different entity with the name in question. So with next to no effort I have a completely random destruction of the entire surface.
Related but irrelevant picture #2
To accelerate the destruction I used a few inputs to shorten the lifespan of the beam, and turn the other one on. Laser 1 started at 0.5 lifespan, Laser 2 is always at 0.1
OnTrigger|clock_laser_1|TurnOn||0
OnTrigger|clock_laser_1|AddOutput|life .2|3.00
OnTrigger|clock_laser_1|AddOutput|life .1|4.50
OnTrigger|clock_laser_2|TurnOn||5.20
- - - - -
A brief note before the big stuff, both the shattering hallway and the clock zone have situations where the player may no longer be standing on anything. To prevent (or reduce) falling, I used trigger_gravity to make the players float the instant they lose footing, and some gentle trigger_push to keep them centered in the hall or nudged toward the clock. The may fail to work if the player is moving at a rapid rate in some direction when it occurs, but I'd rather be gentle and allow the rare failure than be harsh (playerclips, loss of control, etc) to prevent it entirely.
- - - - -
The Shattering Hallway. I put the most work into this, by far, and it turned out better than I expected in more ways than one.
Every chunk of the hall is a func_physbox. Their mass scale is set to 0.02 (large concrete physboxes get extremely massive) and their Rendermode is set to Texture with an FX Amount (alpha) set to 0. They also have the following flags set:
- Start Asleep
- Motion Disabled
- Prevent motion enable on player bump
- Only break on trigger
- Don't take physics damage
This is all so that they will not move, will not break, and are invisible until I otherwise wish.
Why invisible? Physboxes have some ugly dynamic shading on their edges when crammed together, and awhile I could have turned off shadows the seams would still have been visible. Thus, there are identical looking func_brushes in their places that are removed at the instant the shatter occurs.
Related but irrelevant picture #3
The entire area is covered by a trigger_vphysics_motion that gives the chunks zero-gravity and limits their speed to a max of 200. The speed limit allowed me achieve the effect of the hall rapidly coming apart but give the eeriness of them slowly drifting off. I also added a tiny amount of angular acceleration increase, to help maintain the random spinning of the pieces.
The shattering force itself is simply seven env_physexplosion in a row along the hallway.
The fade-to-black is done with a math_colorblend, which is essentially the same as a math_remap except the output values are a trio of RGB values instead of a single integer. You can blend between any two colors, but I only need to do white-to-black (color tinting entities is subtractive, white represents normal).
However, like the remap, it only takes one input and sends one output at a time. I wanted a continuous stream of outputs to get a smooth fade.
Once again I had a use for a point_proximitysensor, except I was merely measuring the distance to a func_movelinear (though any mobile entity would work). This gives me a steady smooth flow of numbers for the colorblend's input. Since the colorblend remaps any range to the color range, the speed and distance of the movelinear is irrelevant as long as the ratio between the two is what you desire.
Virtually the same method was used for the cracks of light that appear prior to the shattering, the difference being I wanted to go from invisible to solid, instead of white to black. The solid white lines on the floor use the white_fill material, and rather than a colorblend I used a regular math_remap to feed my proximity value (increasing this time) into the alpha input of the func_illusionary.
I couldn't use alpha for the gradient shining out of the lines because I already set it to 10 to dim them to the desired brightness (the raw VTF is solid white at the one edge), rather than manually editing the VMT just for this map.However, due to the nature of the material I could use color and a colorblend to achieve invisibility, because black is invisible in additive rendering.
Lastly, the floor/ceiling flip is just a func_door_rotating, but due to the player floating in zero-g and the lights moving with it, the illusion of whether the floor is moving or the player is flipping becomes mushy and confusing (as intended).
- - - - -
I think that's everything abnormal to cover on the technical side. Though no amount of words could explain the mind that devised them. If there is anything more let me know and I'll tack on an addendum.
Last edited: