Fixing and 99% improvement in voxel replay

Catch-up: For campaign mode, SnwScf needs bigger arenas/levels.  This means (1) much wider voxel snow coverage (e.g. across mountains) which mandates LOD levels (level of detail) and (2) removing/replacing snow as the player(s) move.  All in all, lots of optimizations, etc.  Some of the funnest work 😀

This one’s all about fixing then optimizing the replay of actions (dig, build, etc).  This is used when voxels are (a) tidied due to being further away than we wish to keep or (b) when we switch their LOD levels = a lot more common!

At the start, it didn’t work.  This was mostly due to other work I’d done on the codebase while not worrying about this functionality since I hadn’t been using it.  With small arenas I never let the snow be tidied — I just kept it all!  Obviously this meant larger load on CPU, Memory and GPU!  In past posts (12 & 3) I talked about improving with LOD, etc.  Now the voxels can be swapped-out to lower LOD levels so we definitely need it!

Here’s out test patch of snow.  To keep things consistent, I saved the snow so I can reload it every time.  This is a slight over-complication — it actually stores all of the generation settings and simply loads them then does generation and gets the same result every time!

 

screenshot-2016-09-16-10-47-22

Here are the numbers:

Total time (ms) Percentage improvement Num replay calls Num DoOperation() calls Comments
(didn’t work) Original (didn’t work)
4552 0% 486 31104 Original (fixed)
752 -83% 78 4992 (1) Switched to Chunk-per-call (rather than whole height each time)
(not-timed) 6 4144 (2) Optimized what was considered actually modifying a ChunkData.
53 -99% 6 1120 (3) Bounded start & end bounds by original sizes
8 Blocks to a side in a Chunk
8 Height of this operation (Chunks)
-3 MinY
1 MaxY
4 World height (Chunks)

And some comments on what I did:

  1. The original code found all ActionData instances that had affected a Chunk then re-ran it for every column… but it didn’t bound the height of the column so it was affecting each Chunk as many times as the number of vertical Chunks it had affected!! (e.g. ActionData touched 3 Chunks high, all 3 Chunks would be done 3 times!?)  This seems a bug in the original code.
  2. The original ActionData code considered a Chunk touched if it was within certain bounds even if it didn’t change the isovalue.  This might have been for painting?  Even that guess is a bit of a stretch — I can’t really see any reason for it so I ‘fixed’ it.  I don’t use it anyway so meh.  It’s easy enough to revert if I offer these changes back to the die-hard users of TerraVol (if there are any).
  3. So previously we’d operated on *every* Block within the Chunk.  For the edge ones, that was superfluous.  Instead, I bounded the Blocks affected by the original size affected.  Great improvement — albeit most useful on edge Chunks which this test-case has lots of.  A larger operation will be more inner ones but I have ideas for that!
    (care to guess along at home … or write in on a stamped self-addressed envelope 😉 )

Here are a couple of pictures of the operation.  The white boxes are Chunk boundaries and the purple boxes are the reduced bounds that are now operated upon 🙂  If you’re wondering why the purple boxes extend outside the green capsule, it’s because the isovalues are smoothed over that range from no-effect to full-effect to produce a smooth result at about the threshold where the green capsule is drawn.

screenshot-2016-09-16-10-44-54

screenshot-2016-09-16-10-45-37

53ms is actually still a long time and needs reducing / parallelizing but it’s a lot better.  To give you an idea — this is a tiny operation.  Most are much larger.  This one’s equivalent to rolling a small snowman 1 meter.  A carrocket hit at smallest level would be about 100 times larger.

Now this is all done off the main thread so it’s not so bad however it’s all done on a Generation thread (which has responsibility for getting the data ready before the Builder threads turn that into a mesh).  Sadly, the way things are structured at the moment, the Builder can time-out if the Generation takes too long and won’t notice a change until the camera moves sufficiently far.  Also generally I’ve tended to only need 1 or 2 Generation threads whereas what we’re really doing here is ActionData application — which I have tended to have 4 threads for since it’s done a lot!  It would feel a lot cleaner to move this re-application of ActionData to the ActionDataThread then get the BuilderThread to be informed when the Chunks are ready for meshing.  So that’s next!

Onwards and faster-wards 😉

New environment WIP: Floating polar ice

I’ve always planned that some levels would take place on polar ice, floating at sea. The latest Unity Asset Store Madness Sale includes the Ceto Water system that I’d long thought I might use for the sea. However I already have another called “Realistic Water” so thought it was time to evaluate that to decide whether to buy Ceto (or another, possibly PlayWay).

Here’s the very first integration of water into Snowman Scuffle with floating polar ice.

Obviously there are plenty of things that need fixing, e.g:

  • Functional:
    • The “replace-on-floor-if-you-slip-beneath-it” function needs disabling.
    • The character controller doesn’t handle moving platforms yet!
  • Aesthetic:
    • the bloom’s blown out.
    • the sky-box needs rotating.
    • The water surface could do with more details (e.g. specular highlights).

But ignoring these, it’s actually not a bad first step. That moving platform one is probably most tricky but, compared to how bad I feared this would be, it’s barely noticeable! Once fixed, it should be good!

How good will fighting, CTF’ing, racing, etc on blocks of floating ice feel!  (I’m kind’a hoping the answer will be “great!” 😉 ) Let me know your thoughts!

Voxel LOD generation

LOD (Level of detail) now working for voxel *generation* as well as building.

Here’s a YouTube at full res for the LOD-lovers like me out there 😉

As discussed in the last post, the generation phase is deciding on the matrix (8x8x8 for LOD0) of numbers that comprises a voxel ‘ChunkData’ whereas the building phase is turning those numbers into a ‘Chunk’ with a ‘Mesh’ — a thing you can actually see and interact with.

This new work means:

  • LOD0 (white boxes) contain 512 floats and 128 triangles = unchanged.
  • LOD0i (yellow boxes) contain 512 floats but only 2 triangles = unchanged.
  • LOD1 (cyan boxes) contain 1 float and 2 triangles = the NEW bit!

You can see how that’s much less memory and much better performance — especially for covering *much* larger arenas!

Yep, open(er) world campaign mode, here we come!

p.s. As with most gamedev or even programming, this took a great deal of frustrating tweaking including upgrading Unity and several assets then creating a whole new way to investigate details about voxel spaces that are generated on threads.  I know, implementation details.  I’m happy to discuss and the answer will involve phrases like “Marching Cubes”, “Isovalues” and lots about “neighbours”.  Ask if you’re curious / suffering from insomnia 😛

Oh and there’s still plenty of work to do before someone asks “is it done yet”.  There’s a reason I don’t show the Snowmen moving around in it yet 😉

Looking further, TerraVol performance work, part 2

Hi!

This blog post is gonna be lots of deep code stuff to optimize the performance of TerraVol.  Why?  Well I’d like larger arenas for SnwScf!  Huh?  How do those relate?  Well the voxel snow system I use proecdurally generates snow.  A larger arena requires more snow coverage.  More snow coverage requires more system resources (CPU, memory, GPU).  However if we increase the efficiency of the system, we can reduce that increase and make the game run better!  The uber-goal here being a campaign mode that takes place on a wide open terrain with long view distances.  To do that, I’m introducing LOD (Level of Detail) generation — building fewer snow pieces that cover larger areas with fewer details (vertices) — they’ll be atop far mountains you never reach, etc so they just don’t need a gazillion vertices up there!  …well, until you visit them — then they’ll be all beautifully detailed!  This is a common optimization in games but isn’t part of the voxel engine I used — TerraVol — so I’m adding it piece at a time.

So, a quick introduction to voxels in TerraVol.

Chunks, Blocks and GameObjects — oh my!

A cubic section of the voxel space is called a Chunk which consists of a 8x8x8 grid of Blocks.  Each Chunk has a GameObject and, at the start of all of this, 4096 were created at level start and sat in the scene.  (Yes, they were originally all Active GameObjects!  I can’t imagine Unity thinks much of 4096 active GameObjects sitting in its scene when not actually needed!  I switched them to all be inactive to start with.)  When some snow is needed in an area, all the Chunks from the highest Chunk that’s ever been to the lowest that’s allowed are queued for ‘generation’ in a thread.  This does some mathematics to determine height, values are assigned to each Chunk’s Blocks (8x8x8 = 512) and left.  Another thread notices the values are ready but no mesh has been made so it goes through each column and builds a mesh for each Chunk therein.  These two phases “Generation” and “Building” conclude and you can see the Chunks — snow in my case — in the game.

First we’ll do some work on Building then we’ll do some work on Generating.  First however, as noted at the bottom of the last post, since we can’t apply the Unity Profiler here, we need to measure manually!

Measuring time

Chunks are built on threads (I use 4).  Before my introduction of LOD, the average time to build (not generate) was 0.6ms per Chunk (where 19 out of 20 Chunks really need no actual meshing since all Blocks within have the same value = all above or all below a surface!).  For LOD1 columns the average to build becomes 0.01ms per column — a 60 times improvement in load!  (since 1 Chunk at LOD0 needs 8×8 = 64 runs through Marching Cubes, etc whereas 1 Chunk at LOD1 needs 1 run through!)

If I introduce a LOD2 or LOD3 covering 2×2 and 3×3 Chunks respectively, the ‘1 Chunk’ call cost should drop to 0.0025 and 0.001!  At this point the overhead of queuing and locking will probably exceed my measurement ability!

Aside: Unfair?

An odd thing to note: BuilderThread-02 is taking about twices as long as the other 3 threads?!  I recall the dispatching is round-robin not single-pool.

To explain: when a Chunk needs building (turning from numbers into a mesh) the work is placed onto a queue-per-thread rather than all placed in a single queue that all threads take from.

I imagine the original author did this since it avoids lock contention at work-retrieval time and can reduce lock contention for the dispatching thread (since the lock it needs to take only has 1 thread vying for it vs. 4 in a single-pool approach).

It would seem ’02 is just getting more of the harder work!?  Odd.  Will this cause a problem?  Well, it will mean that 3 threads will be sitting idle when they could be doing work ’02 has been given.  This might mean things take slightly longer than they should.  What’s odd is that I’d expect it would average-out in the long run — but it’s always ’02!?  I ought to try a single-pool approach (where all work is taken from a single queue).

After introducing ChunkSimpleState the average for both LOD levels is 0.55ms per Chunk.  Now that looks bad since it’s an increase from 0.01ms however we’ve lost all extraenous queueings and meshings of Chunks that didn’t actually need them so this is pure building that needed to be done.  Although the per-Chunk time has gone up, the system as a whole ought to be faster!  It also paves the way for reducing the number of GameObjects the system uses.

Model vs. View

Now the original developer had obviously once thought to do this but hadn’t finished it.  The system does separate ‘data’ (a.k.a. Model) from ‘visualization’ (a.k.a. View) so a ‘ChunkData‘ represents that large block of space but has no visualization until meshed as a ‘Chunk’ — a MonoBehaviour with a static builder method that creates the GameObject and other associated Components (MeshFilter, MeshRenderer, MeshCollider).  As it stands, a pool of ChunkData are created and each associated with a Chunk at creation time — hence 4096.  I removed that immediate association and made it lazy, based on whether ChunkSimpleState indicates a given ChunkData actually needs a mesh!  Now a 20x20x20 grid of Chunks still needs 8000 ChunkData instances but likely only 400 Chunks!  (assuming no caves or overhangs.)

Of course when the pool is exhausted, each are created on-demand.  Given I wish to move all of this off to a separate thread shortly, I can’t be trying to create the Chunk (and its GameObject, etc) at use point (Unity doesn’t allow things affecting its internal systems to be done off its main thread).  Instead let’s add another pool and pull from that.  Now Chunks are returned to their pool and disassociated from the ChunkData.

Data efficiency and Model-View separation

Now recall that each Chunk consists of 8x8x8 Blocks?  To be more precise each ChunkData consists of 8x8x8 BlockData.  ChunkData and BlockData are classes — instances are associated when needed.

Aside: Packed?

It strikes me the BlockData might be better as a packed format, maybe a struct that would result in a cache-friendly array for ChunkData?  Obviously that ‘associating’ approach would need addressing.  I’ll probably come back to that.

For now, what concerns me — why we’re doing all of this in the first place — is increasing the draw distance for the voxel snow, largely by making the system more efficient, especially by introducing efficient LOD usage.  So at the moment, although we’ve now got LOD1 generating a greatly simplified mesh (e.g. 2×2 vertices for LOD1 rather than 8×8 for LOD0), that’s all in the Chunk (built mesh).  The ChunkData still has 8x8x8 BlockData.  For LOD1+, that’s not needed — we only need 1 BlockData at each ChunkData’s local coordinate (0,0,0).  So let’s see whether we can get generation time to only do Blocks that are needed!  Let’s measure first!

Actually when I went to measure and fix this, I found the generation code is neither optimal nor conducive to this new need.  It’s getting the ChunkData from the TerraMap (via a ThreadLocal cache I previously added) each inner step of a 3-layer deep for loop (loop y, loop x, loop z).  I previously fixed so we looped down y first in preparation for this (and in preparation for storing MaxY per Chunk2D — column).  Now we can clearly see each ChunkData only needs retrieving every 8 Blocks — the X & Z coords never affect which ChunkData we need since they’re only operating in a single column!  So I re-structured to only get a new ChunkData every 8 Blocks and then make the ChunkData’s direct methods to change Block values (rather than going through the TerraMap’s lookups each time).

After that change, times to generate in seconds/column with minHeight 20 becomes:

0.002568627 +
0.003196078 +
0.003450980 +
0.003176471 +
0.003803922 +
0.003578431 +
0.008921568 + <-- odd spike
0.003617647 +
0.003803922 +
0.003696078
====
Average: 0.0039813724

You may recall from the previous post that we’d got it down from 0.015 s/column to 0.005 s/column so this 0.001 s/column improvement (on my fast machine) is within measurement error bounds.  Still it’s not going the wrong direction; still might be something and, as highlighted above, it paves the way for the next big steps!

Generating LOD data

Next is generating at the LOD.  This means that:

LOD 0 columns: (minheight - minY) * blocks per Chunk * Y block per chunk * Y blocks per
 chunk
LOD 1 columns: (minheight - minY) * 1 * 1

For minHeight:20 and minY:-3, that’s:

LOD 0 columns: 23 * 8 * 8 * 8 = 11776
LOD 1 columns: 23 * 1 * 1 * 1 = 23

That’s 11753 fewer loops, psuedo-random number calls for perlin values, etc and 11753 fewer BlockData assigned!

0.002441176 + LOD0
0.003137255 + LOD0
0.002166667 + LOD0
0.000784313 + mixed
0.000970588 + LOD1
0.000862745 + LOD1
0.000745098 + LOD1
0.000941176 + LOD1
0.001000000 + LOD1
0.000941176   LOD1
====
Average: 0.0013990194 s/column for both

BUT just the LOD1 values:
Average: 0.0009101305 s/column at LOD1

 

That’s a 30x improvement!  Super!

Now admittedly there’s still some work to do since the boundary between LOD0 and LOD1 now looks a little odd — the LOD0 building code is probably looking for values along the LOD1 boundaries.  Unless I can contrive a cheat, I might need to generate the edge values for LOD1.  Hmm… perhaps a new LOD0i for columns on the interface between LOD0 and LOD1 which does this (or probably build at LOD1 but generate at LOD0 since it’ll make the code much cleaner).

Well, off to a BBQ this afternoon so I’ll schedule this to post later in case I forget.  Maybe inspiration will strike while I’m wolfing down hog roast and chit-chatting with people IRL!

p.s. Nope, decided to go with LOD0i 🙂

3 times increase in voxel generation speed

TerraVol’s procedural terrain generation is thrashing its internal ChunkData cache!

The loops are ordered:

  1. outside: work across X & Z
  2. inside: down Y!

But this means every 8 Blocks done (SIZE_Z), another ChunkData must be found (flushing the cache) and that repeats 8×8 = 64 times for a single Chunk2D column!  Better would be going down Y as outer loop and across X & Y as inner loops.  This should give 512 hits before a new ChunkData must be found.

Let’s measure performance before and after.

This is all done in threads so we’ll need thread-local stopwatches that, in a lock, add their value to a global and increases a numDone count.  Periodically lock, average, dump and clear.

Timing results before:

With minHeight 1.4 (default in PlayerSetupArena):

0.0001 seconds/column  = not terrible but just about the *best* case possible — barely 3 chunks high!

With minHeight 20:

 0.004490196+
 0.006598039+
 0.009725491+
 0.01311765 +
 0.02141177 +
 0.01319608 +
 0.02912745 +
 0.02162745 +
 0.01719608 + (note worryingly increasing!)
 ===
 Average: 0.01516

After (still minHeight 20):

 0.003186275 +
 0.004343137 +
 0.006480392 +
 0.004676471 +
 0.007215686 +
 0.004666667 +
 0.00472549  +
 0.008745098 +
 0.00472549  +
 0.004921569 +
 ===
 Average: 0.00537

I make that roughly a 3 times improvement!  Real-time use shows FPS now much closer to being pegged at 60!

This also paves the way to my next optimization which should drastically reduce the load on both the Builder threads *and* (most importantly) the Unity Main Thread!  (all this will improve the local-multiplayer game but, more importantly, make the campaign able to use much wider areas of voxel snow!)

Before that, a quick aside: a further improvement might be retrieving the ChunkData outside the X,Z loops since they’re the same for all X & Z for a given Y!  It’s cached (threadlocal) but it’s 4 deep in method calls and involves some struct allocations, etc that just aren’t needed.  Think I’ll leave for now but…

The difficulty here is that, since this is all threaded code, I cannot profile it!

I’ll repeat: No profilability!  Unprofilable!  Profligate!  (check it)  Gah!

Unity’s profiler doesn’t record data for anything but the Unity Main Thread!  (which it can do excellently btw and also does awesome things for GPU, memory (better coming), etc.)  Furthermore since Unity isn’t Microsoft’s .Net runtime, one cannot apply Visual Studio (or others aimed at MS’).  As such, one’s reduced to personal timing tests like my Stopwatch use here.  That requires knowing where potential problems might be!  Recalling that “Premature optimization is the root of all evil“, this is a hard pill to unswallow!

By that I mean that, for gamedevs like me, we find it hard not to write things optimally as we go — but Knuth’s Adage means we must try to focus on code clarity (for maintainability).  That’s the hard pill.  Trying to unswallow it… yuck!  Even worse!

Just added all my remaining feedback points to the Unity Enhancement Request for Profiling MultiThreaded code.  Who’s with me!

Terrain/Gaia learning 2016/07/24

This weekend, I experimented with Gaia, RTP, DirectX11 Grass, SpeedTrees, Scion Post FX, Amplify Motion, Sonic Ether’s AORealistic Water, etc. mostly to learn about making good looking Terrain for (primarily single-player campaign in) Snowman Scuffle.

For my levels, I’m using Unity Terrain as the ground.  It’s overkill for the tiny arenas but for the potential campaign levels, I feel it ought to make most sense.  I say ought because that’s pending performance acceptability.

Gaia is one of several terrain-building assets I’ve bought (while on sale 😉 ) in anticipation of this.  It’s got great reviews and seemed to have a quick learning curve *and* be able to tailor its results to the shapes I’m picturing so I thought I’d start with that.

I’ve previously used the Uber shader with its DX11 tesselation options for some simple-mesh floors and walls and know it produces amazing results so hoped its ancestor-sibling — RTP v3 — would be as good for Terrain.

I’ve long wanted to play with DX11 vertex generation for things like grass but knew it would take me down a scarily long rabbit hole so decided to jump-start with the excellent DirectX 11 Grass Shader.

Generating some initial terrain with Gaia was great.  Simple and easy.  As I write this, I’m just about to try my hand at manual stamp use to make something more specific to my needs but I suspect that’ll be smooth and easy too.  Yep — was easy!

Next, configuring RTP for Terrain.  It’s super-powerful but its documentation is tricky (big and somewhat assuming) and the flow is also tricky.  Don’t let that put you off — it’s great, just have a good supply of coffee beforehand 😉  I’ll write about some of the options I fiddled with later (TODO).

Next I added DirectX 11 Grass Shader.  I’ve not used it before.  Will I use it in SnwScf?  Not sure — it’d be super-cool to place it on terrain ‘under’ my voxel snow and have it pop-up when all snow is removed.  That’d require writing from snow to a texture map.  Viable but yet another thing, y’know!  Anyway, this is a learning experiment so for now, I deicded to throw it in there!  Wow.  Initially it appeared everywhere out to some arbitrary draw distance.  (Detailed instructions on fixing this below since people on the forum thread seem to have difficulty here.)  A bit of investigating revealed it took a map of heights to draw.  Getting said map also took some investigating so I’ve noted that at the bottom as well.

Results

Here’s a video of the results

DirectX11 Grass shader feedback

A couple of ideas for the grass shader’s fidelity & performance improvements (that I fedback on the forum)…

A little background for those not familiar:

Since the grass shader is a vertex shader, it requires all the geometry for whatever surface you wish to show the grass upon.  For terrain, that means duplicating your terrain and swapping the grass shader in for the Custom Material.  Right, on to my feedback…

1: Reducing load and mucky shadows

I notice that the duplicated terrain renders beyond the furthest fade distance(s).  A nice option would be whether to return single verticies *at all* for values beyond the max fade distance(s).  When one does the “duplciate terrain & swap Material” trick, if you enable shadow-casting on the grass terrain, it produces yucky patches in the distance where ‘Y-fighting’ is occurring.  With this new option, it wouldn’t.  Until then, one can ameliorate by moving the grass terrain down (y:-0.1) but (a) you’re wasting [CG]PU and (b) it still causes some of the effect in the far far distance.

2: Reducing popping

Regarding ‘popping’, in addition to the pixel accuracy change mentioned in the docs, also increase the “Base Map Dist.” value.  I set it to the “Grass Fade End” value.  In fact here are the values I used that seemed to work well (pending realisitc performance analysis) …

Good DirectX11 Grass settings for use with Terrain

For other users, here’s what I found to work well (so far):

  • Terrain:
    • BaseMapDist: 120
    • Tree & Detail Objects: False (don’t need since you already have the orignal terrain)
    • Remove the collider (you’ve got one on the original terrain)
  • DX11 Grass:
    • LOD Start: 10
    • LOD End: 50
    • Max LOD: 5
    • Grass Fade Start: 50
    • Grass Fade End: 120
    • Min Height: 0

How to get the Colour map from the Terrain

Here’s my flow:

  1. use this script to get the splat map.
    (Note: Gaia has this facility and more in its Utilities too!  Either export Texture Splatmap as before *or* Grass Splatmap — this latter would require a slightly different workflow but allows varying foliage types!)
  2. pull it into Gimp (or whatever layer-capable drawing program)
  3. extracted the green channel (whichever happens to your grass terrain layer)
  4. Use channel selector to turn channel to ‘selection’
  5. Create a new layer
  6. Colour white over the whole image (with that selection set).
    Since the selection keeps an ‘alpha’ like value, your ‘full white’ colouring comes out as levels of white.
  7. Export as PNG and bring into Unity.
  8. Set as DX11 Grass Color Map.

Obviously if you’re using multiple grass types, you’ll need to tweak but I’m sure the same process will work.

HTH, Rupert.

Final Gaia notes for Snowman Scuffle

Here are some notes for future me on what I’ll likely need to do when using Gaia with my game!

Configure different terrain textures.  Likely need to do in both (a) Gaia and (b) its spawner rules (specifically the Coverage Texture Spawner) so that it (a) configures the Terrain correctly and (b) places correctly.  Started work on this — it’s kind’a hard to work with.  This seems a weakness in Gaia.  Might try TerrainComposer v2 which makes *this* part easier but probably makes other things harder(?)

Similarly, I’ll probably need to reconfigure many of the spawners to give distributions I desire for my levels.  That’s ‘designing’ when using Gaia!

If using the ‘circle of stones/whatever’ idea, could probably build a Gaia spawner for just that!

Right, time to try out Terrain Compser v2 to see how that compares!

Arc trajectory bug fixing, unit tests and MapMagic tangents

Wednesday morning and Saturday work.

Investigating MapMagic — Wednesday’s Unity Asset Store Daily Deal.  Looks like a super procedural generator — both Editor and runtime.  It outputs to a Unity Terrain but the forum suggests it could go otherwise.  I’ve always wondered about a single-player mode that places interesting things at points in an otherwise procedurally generated world.

Decided to buy it.  Lost a few hours to fiddling with it.  Man!  Messing with procedural generation and terrains is a surprising amount of fun!  Kind’a tempted to drop the asset straight into SnwScf project and hack-up a single-player mode.  Not now!  😉

Noticed an oddity: A rolling snowball hitting a snowman hits assertion failure:

SnowballShot !spawned but being destroyed. Not merely done twice in same frame, now:8504, spawnedChangedFrameNumber:8502

Not a problem but indicative of some mistake / mistaken understanding.  Have to come back to this since…

Darn, while capturing screen-shots for this post, I’ve just noticed the aim-assist going completely wrong!?  What looks like a straight shot is actually doing a lob, arcing waaaay over the top of the target.  Great 😐

Screenshot 2016-05-25 09.59.39.png

“Excuse me old bean, I don’t mean to criticize but I’m down here.”

Oddly it really is fine most of the time — I can only seem to prompt this when I’m trying for a screenshot!?  Riiiiiight.  Hopefully it’s something related to pausing the Unity Editor but I can’t really trust that without investigating.

Sigh, I thought that functionality was all sorted.  Guess I need more integration tests … ok, admission time:  By that I mean /some/ integration tests on this area 😦  They broke when I upgraded Unity most recently and I haven’t invested the time to resolve what happened.  Shame on me.  That is one of the downsides of my integration tests — they feel rather too brittle and aren’t runnable without jumping to the integration test scene.  OK, probably the better answer is that this area should be unit tested rather than as well as integration tested.)

So, I did warn this was going to basically be my dev notes.  Often they’re high-level discussions but, “when the going gets maths, the notes get mathsy”.  Erm?  Fair warning: Here be details!

Let’s get some values in here:

Shooter transform:
  pos: 11.05, 0.75, -0.23
  ori: 4.22, 90, 0

Target transform:
  pos: 16.11, 0.64, 0.53
  ori: 355.36, 155.00, -0.00

Targeting logging:
  (targetPos:(16.1, 1.6, 0.5), rbl.velocity:(0.0, 0.0, 0.0), transformToAffect.pos:(11.1, 1.5, -0.7), bulletSpeed:9.857765, shooterVelocity:(0.0, 0.0, 0.0), shotObj.aimingShouldConsiderGravity:True, maxTTL:2)
  => timeToImpact:0.5230598, launchVector:(4.8, 8.5, 1.2)

Obviously launchVector y component 8.5 is causing the lob over the target’s head.  Either enable more detailed logging or debugger.  Going with latter.

targetPos = "(16.1, 1.6, 0.5)"
bulletPos = "(11.1, 1.5, -0.7)"
Follows the "stationary target" code path.
Follows the "gravity-compensating projectile lob" code path and calls my ArcTrajectoryUtils.getLaunchVector() code.
planeVectorToTarget = "(5.0, 1.3)", range = 5.15528f, height = 0.0974731445f
then does getLaunchAngle(range, height, projectileInitialSpeed, gravity), i.e.
getLaunchAngle(x = 5.15528f, y = 0.0974731445f, u = 9.8219f, g = -9.81f) -- this might be the part that needs working, perhaps use as unit test?
for +/-, minus = -58.2228546f so used plus = 77.86665f.
Result from getLaunchAngle() angle = 1.04258835f.  Hm, that's radians = ~60 degrees elevation!
Just to carry this through, it then calls convert3SpaceScalarsToVector(vectorToTarget = "(5.0, 0.1, 1.3)", projectileInitialSpeed = 9.8219f, angle.Value = 1.04258835f)
Eventual launchVector = "(4.8, 8.5, 1.2)"

So, looks like there’s an error in getLaunchAngle(), perhaps its use of +/-.  Unit test time.  Also it’d be nice to get a code path for working situation — is it that the ‘minus’ case always works  when used and the ‘plus’ case always fails when used?

The formula it’s using is from this gamedev.net post.  It’s working well most of the time.  Hmm.

Added unit test for all this code (ArcTrajectoryUtils).  Also added unit tests for general ProjectileAimingUtils.  For the latter, I also wrote an IEqualityComparer<Vector3> so I could use NUnit’s constraint-based “EqualTo().Using()” testing approach with an epsilon value (i.e. the allowed proximity).  (I find it odd that C# doesn’t allow inline implementation of interfaces — had to add an actual concrete implementation to my VectorUtils!?)  Anyway, it allows this:

assert.That(v1, Is.EqualTo(v2).Using(VectorUtils.buildVector3Within(epsilon)));

Covered the area in unit tests.

Ahha!  Found the problem — I’d slightly mistranslated the equation!  Bracket in the wrong place was giving the wrong result in some cases!  Fixed and committed!

Since it’s a bank holiday weekend here in the UK, I’m calling Saturday an early night to pick up for something more fun than bug fixing tomorrow — maybe more fire?

p.s. since I was lost in the depths bug fixing (and parenting) all #ScreenShotSaturday, I tweeted this and was kind’a pleased it got as many Likes as most of my SSS pics!  I get the strong impression all gamedevs wish they could do proper software engineering with things like testing, etc 😉

 

Fire, part 6 and GameCamp 8

Continuing from yesterday’s post.

Decided to circumvent problem of explosions not hitting scenery for now by making the rocket itself start fires.  Worked well — fire spread up tree and onto the floor (as predicted due to the layer situation).  I’ll address with the Component idea I had (rather than splitting to another layer).  That’ll also allow things to be partially inflammable without arbitrary splitting models if needed later.  Done — made a Flammable component.  Yep, that works well.  Here’s a video of current state:

 

Performance is pretty heavy so far — it’ll need tuning to get a sensible number of fires — probably tweak the range values.

One oddity is smoke looking orange.  Obviously it’s caused by my sunlight being that colour since it’s low.  However, the smoke is in shadow so it looks wrong to be orange all the way down.  Will it accept shadows?  Hmm… apparently not (yet) — dev says too heavy performance.  I’ll have to investigate that another time.  Perhaps an interim kludge will be to tweak the smoke colour so it looks more right?  Think that’s also another time since I need to get going to GameCamp!  (Already late due to no. 1 son waking in the night and my consequent oversleeping!)

A new build sees the players dying when they join in the PlayerSetupArena!?  Great.  I’ll debug that on the train on the way in.

(from train)

Yep, the WorldBounds changes I’d made a few weeks back weren’t tuned for other arenas and were killing players at their spawn points!  Fixed in all arenas in build.

Aaaand, in case I forget later (due to being in a pub), I’m going to set this to auto-publish later today!  (what could possibly go wrong?! :-O )

p.s. I enabled Twitter sharing for this blog yesterday so people will actually know what rubbish I write.  OK, they’ll have more evidence 😛

Edit: (on train back from GameCamp 8) Had a lovely time despite only arriving at 3pm! Had a good play testing session with lots of good feedback. In fact, given how I’m trying to be more open and forthcoming, perhaps I ought to copy it all to this blog later! My favourite was one games professor (@drdavient) suggesting an actual viable solution to a common complaint regarding the control system! We’ll have to see how it feels (perhaps as a selectable option per player?)

Fire, part 5

Continuing from last time, I decided to sink an hour clearing the blog post backlog (all those previous fire posts were stuck in draft!  Hopefully there aren’t too many inconsistencies in timings, etc!  I know it’s mostly for my benefit but it’d be nice if someone else can read it and it make some sort of sense!)

Tomorrow I’m going along to GameCamp 8.  I intend to take SnwScf to do some play-testing.  Obviously I’ll need a good & recent build.  Sensible plan is to do the build first but, since the last good build, the only integrated change is the AI, I’m going to push forward with fire sinc, while the AI needs feedback, I’d like more about the fire.  (and adding an AI player doesn’t have good UX yet — it’s a shortcut key still!  I’m not sure what’s a good way to do this yet — perhaps I’ll ask people there! 🙂 )

Right, so next up: integrate into SnwScf!  That consists of several steps:

  1. Basic presence and visualization
  2. Fire starting from Carrockets and Mines (and perhaps some other game-start situations to lend a feeling of time pressure in certain arenas?  Man!  That feels like those old games where the screen started filling-in if you stayed alive!)
  3. Damaging the players!
  4. Extinguishing: burning things really ought to indicate when they’ve been exhausted.

(4) is kind of a back-and-forth relationship so I think it makes sense to use another Component — Burnable which the fire will check for.  This might augment the layer requirement since in SnwScf, inflamable and in-inflamable things (*1) must reside in the same layers (for other physics reasons).  So, that’ll be getting a few things burnable in the dev project and having them burn-out.  First though, let’s get the fire into SnwScf and see how it works!

*1: Is it just me that finds “inflamable” ridiculous.  Perhaps it’s from “inflame”?  Yeah, I could look this up but NOT NOW!!  😉

Well, importing worked OK.  Had to do some fix-up work in my own Assertion library (since that nicely took precedence).  It’s not spreading since I have the wrong layers.  Fixed.  Now need to notify Snowmen and injure them!  Then add damaging the scene!

While I was committing, I realized I had a Fire sound already in the project waiting!  Great — I’ll need that integrating … but in SnwScf since I use Master Audio (with a home-brewed solution to having everything be dynamic) in SnwScf and it’d likely be a pain to set it up for the dev project :-\

Ha, I also noticed the DryGrass texture I have.  Wouldn’t it be cool if the grass might burn and turn brown afterwards?  Yeah anyway!

Hmm, that’s odd — the fire isn’t spreading on a single block in SnwScf despite the same values (I checked).  It’s detecting (doing the rays right, etc) but considers all points found “too close to another fire”.

Argh!  I just realized — Physics layer interactions!  They wouldn’t have been brought over by the package export/import so things are interacting wrongly!  Let’s fix that!  Layer “Fire” only interacts with layers it needs to trigger, i.e. those it spreads-to or notifies.  I guess I could have done this in code with Physics.IgnoreLayerCollision().  Oooh, but I can assert correctness-of-settings for future blunders with Physics.GetIgnoreLayerCollision() which gets values from code *AND* inspector!  For each layer, check Physics.GetIgnoreLayerCollision() is != presence in the FireManager masks.

Done but took longer than I’d have liked.  Revealed some bugs in my layer checking code though so overall = hopefully worth it.

TODO: Resolve fire not spreading enough

I’m getting fewer “too close to another fire” oddities but I am still getting some.  I’ll have to punt on those for now.  It’s time to trigger fire from explosions!  Neatly IIRC both the Carrocket and Mine use the same explosion prefab atm so that simplifies things.

TFW code makes sense

Don’t you love it when an integration is easy (*1) through an accretion of good code, util libraries, etc.  Starting a Fire from an Explosion was as simple as:

if (startsFires && null != FireManager.singleton && FireManager.singleton.layersToSpreadTo.layerMaskContainsLayer(other.layer)) {
    LogR.log("Fire", logOptions, "{0} starting fire at {1} on {2}", this, pos, other);
    FireManager.singleton.spawnFireAtOn(pos, other.transform);
}

*1: I haven’t tested it when I wrote this :-\

Hmm… floor that isn’t snow, walls and other things are in ScenaryStatic physics layer which I’d intended to set fire to.  Perhaps move flamable things into a different layer?

Hmm… (yes, all my musings start that way) Explosion.OnTriggerEnter() is being called for WallUnderneath and Snowman but not for the static and dynamic trees.  Both dynamic trees and Explosion have a Rigidbody (a common cause of lack of OnTriggerEnter() calls).  I guess I need to investigate what’s different between (a) WallUnderneath and Snowman vs. (b) Trees.

Maybe a job for tomorrow morning.


Here’s a couple of things I noticed while I was working earlier but didn’t want to disturb the ‘prose’ flow.

TODO: Resolve RandomGrabBag un-use

TODO: Hmm, just realized I’ve kind-of made my use of the RandomGrabBag redundany since I’m basically spreading all ways each time.  I should either:

  1. remove it (cheaper but more deterministic fire spreading) or
  2. reduce the number of directions the fire might spread in a given spreadTo() and call only once
  3. do spread to all but split the calls between multiple calls to spreadTo() — requires a RandomGrabBag per Fire = not terrible.  The multiple calls would have random time between them (over a curve distribution defined by an AnimationCurve — it’s waaay too widely useful for that name!).

Think (3) would look most realistic = it spreads all ways but might wait a bit to catch in a certain direction.

Next big steps after fire and more interactive environs: more fun! (modes)

Just reviewing the site About page — it’s a bit out of date but does remind me that a large part of the fun was expected to be the different game modes.  Just shooting (thwacking with wet fish, firing carrots, etc) at each other repeatedly is a bit dull.  I need to finish the Treasure Hunt mode (which is mostly done) and get on to Tag, etc.  That’ll probably help the pacing issues as well.

Fire, part 4

Continuing from last time.

Did a little more investigating on the ANMS fire/cloud not working and realized it wasn’t displaying when the Particle Playground System had a non-1.0 Time effect specified!  (lots of trial and error)

I’m still not seeing normal mapping but I’m now wondering if, given it works when not Emit()ing from script, it’s related to something about Emit() and how it generates the particles — something that interacts badly with the ANMS.  I guess this could be a question to ask the PP author.  Done (and linked on ANMS thread).

Next day, I was investigating the problem and realized the API call I was using mandated supplying a colour:

Emit()(int quantity, Vector3 randomPositionMin, Vector3 randomPositionMax, Vector3 randomVelocityMin, Vector3 randomVelocityMax, Color32 giveColor)

Switched it from the default “white” to black and got shadowing but still lacked yellow, red and orange.

However I *am using* the PP “Rendering (Source) | Color” section to specify a “Lifetime Color” (which ranges from yellow, through orange, red and black to transparent).  I’d hazard that it’s not applying when I’m using this Emit() variant.

Checking the source, it looks like I should have “COLORSOURCEC colorSource” set to “COLORSOURCEC.LifetimeColor”.  Ahha!  Changed the “Color Source” field 2 above and bingo!  It’s perfect.
Fed back on threads.

Here’s how it looks now.
Screenshot 2016-05-17 23.50.38.png
Sadly it looks better static than moving.  When moving, the smoke looks great but the flames flicker too fast.  The original flames looked much better.  Partly it’s the shader (Additive vs. normal), partly it’s the change in spritesheet size.  I guess I could double-up on flames (and/or cut-down on smoke) but this feels like a kludge.  Really, I need that timeScale value to work.  Fed back on PP forum thread to ask.

Out of time tonight.

Just accidentally discovered Alt-Escape minimizes a window in Windows.

Also just accidentally discovered that in the Unity AnimationCurve window, pressing Return allows you to enter an absolute value for a key!

Spent another night fiddling with texture sheets and animation curves in the pursuit of great fire!
It looks good standalone but now looks less good scattered by the script.  Allow it more items or scale these up?  Let’s try the latter.  Yeah, bit better and even feel can halve the number of particles again.  Here it is before that scaling.  (Ignore the fake snowman model — I needed to ensure the fire was the right scale.  Also probably best to ignore the odd music — is it just me that ends up, after screen-capture, realizing you can hear typing sounds and that it’s time to tap the free audio? 😀