Animated textures in Quake run at 5fps, which is just a bit too low to ever look convincing. Even the models run at 10fps, which is much better for the illusion of motion. Today’s hack will create a
func_wall which (in conjunction with specially prepared textures) will animate at 10fps, that’s 100% faster than intolerably slow!
Download the example map clock.bsp and run it. Yes, ’tis one of my better fullbright boxmaps. You’ll notice the specially prepared texture for the tutorial, a clock with 10 pips on it. Dead centre you can see our objective for the day, where all ten pips light up in sequence every second. Flanking it on either side are two versions of the texture playing at the normal speed.
If you study the left and right versions of the clock closely, you will notice that the left clock is cycling through 1, 3, 5, 7 and 9 pips, while the right clock covers 2, 4, 6, 8 and 10. This is the heart of the trick, dividing the animation you want to play into the odd and the even frames. The odd frames are stored in textures
+4clock, and the even frames in
+eclock (if you’ve never seen this idea before, having letters greater than ‘a’ in an animated texture name allows you to animate the secondary pattern on a button). One last thing to note is that the left and right sequences are in lock-step. We haven’t done anything clever for this, the engine runs all animated texture on a global timer.
So once you have two animated textures like this, the trick is to switch our
func_wall between the two sets of textures every 0.1 seconds, in sync with the global animation timer every 0.2 seconds, so that each frame displays for half the time. What we need is a metronome, and here is a simple way to create one:
"classname" "trigger_relay" "target" "ten_hz" "targetname" "ten_hz" "delay" "0.1" "nextthink" "1" "think" "SUB_UseTargets"
The idea is that we have an entity which triggers itself every 0.1 seconds, so it perpetually fires at a regular interval. The last two keys are there to trigger it initially at a precise time – this precision is necessary to get in step with the global timer on the animations.
Now we just create a
func_wall with the clock texture applied, and with a
"ten_hz". The built-in, often overlooked use function for a
func_wall toggles its textures between primary and secondary animations. Now load it up and it should be working – the only issue you may have is that the even frames display before the corresponding odd frames. I ran into this, and it can be fixed simply by adding a key
"frame" "1" to the
When you tested the hack, it worked beautifully. Then you actually start making the map, and you notice 15 minutes into a playthrough something’s gone wrong. The frames changes have become desynchronized with the global timer, and the sequence is jumping backwards and forwards erratically. What happened to our metronome?
Well, the metronome has fallen prey to the challenges of floating point mathematics. You may already be aware that 0.1 cannot be stored exactly as a floating point number. The slight imprecision adds up over the duration of the map, and because we need to be precisely in sync with the global timer, this is an issue we cannot ignore. We need to be a bit smarter with our metronome, and the rate we choose to run it at.
Running the metronome at 2 Hz instead of 10 Hz will be safe, because 0.5 in floating point is exact. So change that
"classname" "trigger_relay" "target" "two_hz" "targetname" "two_hz" "delay" "0.5" "nextthink" "1" "think" "SUB_UseTargets"
For each tick of our new metronome, we need to trigger the
func_wall 5 times. So we create 5 more
trigger_relay entities, all of which should have
"targetname" "two_hz" and
"target" "ten_hz", and having
0, 0.1, 0.2, 0.3 and
0.4. Take a look at the example map file to see the exact arrangement.
This second design is much more reliable than the first. I’ve left the map running the whole time I’ve been writing this post, and it’s still in perfect sync at the end. Although we’ve had to make the metronome a bit complicated in terms of entities, remember that you only need 1 such metronome for your entire map. All the
func_wall entities you want to animated at 10fps can be triggered off a single metronome. Have fun, I look forward to seeing this hack in use.