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 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 😉


Idea: Analogue trigger pull rate could affect shot scale?

I just had an idea for my weapon charging control:

Make the controller’s analogue trigger more viscerally control the shot scale (e.g. the snowball size)!

As background, I should explain a few things.

In Snowman Scuffle one can fire different sized shots which do different amounts of damage.  Some, like the snowball, act slightly differently — it rolls when above 70% of maximum size and, when rolling on snow, will get larger just like a snowman.

At the moment that’s controlled by how long the player holds the trigger down for.  Tuning that “time to fully charge” was a pain but I think it’s about right now — around 1 second to fully charge.

The 2 failings of this approach are:

  1. Few players notice this facility until it is pointed out.
    By pointed-out, I mean during the game — it’s mentioned in the start-of-setup overlay GUI showing the controller but that isn’t enough.  That GUI probably shouldn’t be dismissed until all players have pressed a button to do so… but regardless, I still doubt many players will remember it later if it is only mentioned there.
    At the moment, the game doesn’t have any of the important 3 teachers:

    1. a solo campaign (where controls and skills could be mastered).
    2. an in-game reminder system (which could pop-up a little bubble if a players isn’t sufficiently using a certain feature of the game).
    3. a control-teaching multi-player arena (where the match doesn’t start until all players have used each important feature at least once).
  2. It lacks visceral appeal.  By this I mean that holding a button doesn’t physically relate to rolling a snowball.
    If the control for a game feature can somehow ‘feel’ more like that feature, it ought to add to the ease of use and engagement.  Some obvious examples are digital and analogue joysticks mapping to digital and analogue movement, the track-ball for Missile Command, the quick swiping for Fruit Ninja and the swinging for Wii Sports Tennis.

(It has also been questioned why keep the shot/snowball charging — whether anyone uses it.  I highlight that this is part of the Mastery fun for the game — a master player doesn’t button mash but charges a shot and unleashes it when the aim is right.  I use this a lot and find it much more satisfying.  I worried I was mistaken until, at one play-testing event, I saw a 13 year old boy wipe the floor with all players including myself — this was one of his techniques.)

So, on to the idea!

On controllers that have an analogue trigger (most of them do), use it!

The triggers aren’t a boolean on/off — instead they’re a continuous value (e.g. 0.0 -> 0.2 -> 0.65 -> 1.0).  If it could be done, I wonder how it would feel to have the rate that one pulls the trigger be inversely proportional to snowball size?  I.e. quick pull = small snowball, slow pull = large snowball.
An extra part which probably wouldn’t help the ‘fun’ could be how the release curve affects the throw (e.g. elevation is inversely proportional to release rate, i.e. quick release = straight throw, slow release = lob).  As I said, probably overly complicated.  The reason I consider it is because previously some players have complained that up-and-down aren’t controllable.  The flip side is that most ignore up-and-down and expect Doom-like aiming — if a target is in-front of you, your shot should connect.  It was to satisfy this wish that I implemented the aim-assist facility.  See [THIS OTHER POST I HAVEN’T FINISHED YET] regarding the feedback on that.  Anyway, back to analogue triggers…

The risk of this idea is that it’s ‘too much’ — all this might be fun in something like a Wii game but, in the middle of a quick-fire fight, remembering to slowly pull the trigger is hard and judging how quickly to release it is impossible.

What do you think?

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? 😀

Fire, part 3

Welcome back!  Time to finish the fire in the test project and get it into SnwScf and being dangerous!


Phone tangents

First, the bad news — I’ve been letting these posts back-up a bit.  I have 2 from during the week that need finishing, proofing and posting.  Today’s Saturday again, but a really late start due to No. 1 Son getting sick this morning.  …well, that and getting lost setting up my new phone!!11!!1! 😉  Upgrade time and I went for the SGS7.  I’m justifying it as a good dev/test device for VR until I splurge on a Vive (probably) — finally time to build that Google Cardboard @DevilQube kindly gave me about a year ago that I haven’t been able to use due to my SGS5 having a dead accelerometer after the Lollipop upgrade!  (known issue)  As always, I’m quite pleased with the deal I managed to find … but it took 45 minutes on the phone with my phone provider to get it.  I always look at TCO for phone contracts and this time, it worked out cheaper to go SIM-only (which they discounted) and buy the phone outright.  Nicely, they discounted the SGS7Edge to the SGS7 price so all-in-all, it was an excellent deal!  Anyway, more on the new phone once Samsung’s Smart Switch‘s “Transfer from Galaxy device” eventually moves past 99% “Searching <old phone name> for content to transfer…” — it’s been stuck there for about 30 minutes!  Nope, still not finished — time to cancel.  Think I’ll retry without the 64GB SD card in the old phone!



So, since last time, I’ve refactored Fire out from FireSpreader — the latter now isn’t a MonoBehaviour, just a Serializable and a field in Fire — better separation of concerns while maintaining ease of settings (not that I’m using it much since most settings are in the FireManager — still, it’s handy to have during dev).  Regardless, both suffered from 2 problems: (1) Instantiate() finding no ‘transform.parent’ in Awake() or OnEnable() and (2) the Collider causing OnTriggerEnter() to fire as soon as spawned — sometimes at the origin (which might be the cause for the odd fire at the origin?).  The obvious answer to (1) is doing it later and to (2) is having it initially disabled.  Easy.  So when to enable them?  Either: (1) an explicit call or (2) after an automated delay.  Since I want this to potentially work without explicit code (e.g. during dev), (2) is the obvious answer — a 0.1 second disabled period on first spawning won’t adversely affect the experience (and is even slightly more realistic since a recently spawned fire might not catch on nearby things).

Next hitch is that, since the Collider is initially disabled, fires started during the disabled period no longer detect each other so I’m getting lots of overlaps = redundancy (went from 40 fires in my test setup to 100).  I’d been thinking to throttle fire spreading more anyway so let’s do that now.

Actually, a better way might be to avoid the problem by doing both (1) and (2) above — code and delayed.  The latter only if the prior isn’t done.  Done.  Works but I’m still getting 63 fire instances.  63 particles systems seems like kind’of a lot.



Spent a chunk of time seeing whether I could get Fluidity to do the fire instead.  Obviously it’d likely be a high-end option but wow it looks good!

Sadly it seems too inefficient to use a single simulation for the whole arena and I don’t see an obvious/easy way to tie multiple proximal fires into a single simulation.  Perhaps there’s a better way to do it — asked question on the forum.  Right back to medium ‘end’ approach 😉


Physics.*Cast* indicates overlaps with ‘distance = 0’ etc!

Ahha, resolved the ‘spawned at the origin’ problem!  It was caused by my SphereCastAll() overlapping a Collider at the start of the sweep.  The documentation explains that this causes the hitInfo to have distance 0 (which I now check for) along with direction negated and position zeroed!  It’s obviously one of those hacks (like most of UNIX source using -1 to indicate failure) where the semantics of a function were re-used for efficiency but, without reading the documentation, it’s not obvious that it’s there!


Particle Playground

Switched to Emit() on Particle Playground.  I’ve previously done work with emitting straight from Shuriken so I know I can do that if this doesn’t work but PP adds nice noise disturbances to its particles so I’d prefer it.  Mostly working.

To improve the efficiency, implemented a ‘timeBetweenEmits’ concept — throttles particles nicely!

Comparing Advanced Normal Mapped Shaders and Particle Shaders, Vol 1.  After much demo trying and deliberating, I decided to buy the more ambitious (and expensive) ANMS in the hope that the dev will live up to his forum thread promises.  Posted request for his textures though.

Particle Playground integration proving tricky.  I keep running into the problem that, after changing a Particle Playground system a bit, it ceases working properly and switches to flickering the particles (they appear and disappear).  I’ve see it once with the PP default Torch and now I’m seeing it with the Torch adapted with ANMS.  Sure, I’d believe I’d made a mistake but (a) it works fine when not firing from a script and (b) an initial ‘puff’ of particles works in the first-ish frame.  This makes me worry it might be something in PP that’s broken.

Running binary2text on both and diff’ing in Vim:

  • the sourceTransforms are different.
  • Lots of m_RotationOrder stuff.
  • UV setup, max particle size and sort mode correctly different.

Added a “FireManager.doSpreading” bool to disable it for a bit.  Now I don’t even get the flitterring particles.  Hmm…  Let’s sleep on it.


Fire, part 2

Continuing from last time.


Getting some odd results from positions saying a Collider that is no-where near the world-origin (global:0,0,0) is being hit at the world-origin?!   This is a pain since it causes a random spurious fire to appear at the world-origin.  I can special-case it in the code but I shouldn’t have to.  What’s going on?!

Distribution thoughts

Trying to ensure my fires are appropriately distributed, I want to check candidate positions aren’t too near an existing fire.  I’m using Physics.CheckSphere() like so:

Physics.CheckSphere(pos, fireRadius * fireCheckFactor, fireManager.fireLayer, QueryTriggerInteraction.Collide);

However that means the sphere is checking whether this sphere overlaps any other fire’s (trigger) Collider.  Those Colliders are intentionally larger than the visualization to represent the fire’s area of influence (for damaging and spreading).  However the fire should spread below that area to indicate it growing in intensity.  Hmm… writing this, I’ve just thought of the same thing you probably did — which I didn’t when I was writing the code!  If you allow fire instances closer than their Colliders, you’ll have overlapping Colliders which is wasteful.  Better would be growing the visualization to represent the intensity growth.  Heck, perhaps grow both visualization and Collider size (since a bigger fire has greater area of effect).  Of course, growing the Collider when initial placement of spread fires was based on the original size means they’ll overlap once grown but the total number will be much less.  What’d be totally efficient is spotting geometrically neatly shaped contiguous blocks of fires and combining them into single Colldiers + representations.  That’s a future optimization if necessary for now.

OK well that probably makes redundant the code I had otherwise been using which I’ll include here just for interest.

 int numFound = Physics.OverlapSphereNonAlloc(pos, fireRadius * fireCheckFactor, resultantColliders, fireManager.fireLayer, QueryTriggerInteraction.Collide);
 if (0 >= numFound)
   return false;

 // The above says that the other's Collider touched fireRadius * fireCheckFactor sphere from pos. Now check exactly how far away.
 var minRangeSqr = fireRadius * fireCheckFactor;
 minRangeSqr *= minRangeSqr;

 // return true if any of those within fireRadius * fireCheckFactor
 for (int i = numFound - 1; i >= 0; i--) {
   var rangeSqr = (resultantColliders[i].transform.position - pos).sqrMagnitude;
   if (rangeSqr < minRangeSqr)
     return true;

 return false; // none close


Right, I’ll commit this then switch to increasing the size to backfill areas… I guess it’d be more realistic to only grow the fire with the original mechanism after the fire has ‘grown’ this other way!  That makes things even more efficient — I like!

(Surface) normal fire

Fire might need to consider surface normals to inform how to position/orient.  Perhaps fire should simply always be aligned to surface normal?  This would probably be best if the particle system is appropriately designed (initial velocity along local ‘up’ and a world-up force (or negative gravity influence)). E.g. if this is a vertical face, orienting with surface normal sticking out sideways, the fire would seem to be billowing from the side rather than appearing partially within the object. …although maybe that way is good for some situations (e.g. translucent objects — Colliders are used to position but a fence or tree has huge holes despite being fundamentally blocking)?

Addendum to the ‘growing’ idea from the last section — maybe it should only grow orthogonally to the surface normal?  (i.e. it’ll spread sideways on flat areas and up-down-left-and-right on walls.)  Of course, there’s risk here about overlapping edges but that’s probably ignorable if we don’t use silly values since the fire could conceivably be billowing from the sides or something.  The nice part is that’s all free if I align the prefab to the surface normal.

Doing the 2 things above…

Added FireVisualization interface so can abstract particle effect or initial mesh visualization scaling.  Using an AnimationCurve to map input (arbitrary scale value ranging from 0 (initial fire size) to 1 (final fire size)) to output (mesh transform local scale).

Next, do scaling by time in FireSpreader (or possibly a separate class) and populate to FireVizulation).

Optimization dilemma

I started writing InvokeRepeating() call in FireSpreader and realized this might become unreasonably inefficient very quickly.  Rather than having all FireSpreaders doing the serialization operation (or creating a Coroutine each), it might be better if they all register with the FireManager which does one for loop for all of them.

This is one of those “premature optimization is the root of all evil” (Donald Knuth) situations.  As game developers, we have to balance this risk with our experience that you’ll always want things that are called frequently to have been coded to be more efficient (where “frequently” often means every frame when you’re trying to hit 60+ FPS).  The resolution of this problem lies in the detail of “things” — things that make it into the final build will want to be faster.  However if you optimize all “things”, some of them might be cut.  When one is first starting coding, it’s also difficult to know what code is fast or slow.  Hence the recommendation to code cleanly first, profile, then optimize (rinse and repeat).

Given I’m not absolutely sure that scaling the fire will work out looking good, I’m gonna let this be mediocre optimization and almost-maximum readability for now.  (I say “almost-maximum” since for optimization patterns I find readable due to familiarity, I’ll use them rather than less optimal more generally readable ones.)

What to do if there is no parent?  Well that’s easy — how can fire burn if there’s nothing for it to burn?  No parent = no fire!  (i.e. it shouldn’t be allowed 🙂 )

A dilemma… Or burn it all!

I’m facing a hard question at the moment:  What to work on next.  It’s always tricky.  The AI is sufficient for play-testing but needs proper trials to reveal the most important areas to improve it next.  There are many areas I’d like to improve it but for showing the game, AI isn’t the most important.  For that, full experience is most important.

Maybe this arena is exhausted?  By that I mean a given environment prompts certain ideas, reveals certain failings, etc.

Hmm, no — I still haven’t done what I’d originally intended with the trees — set fire to them and have them fall down! (A nice destructible environment adds to the fun, I feel.)

So, enough wondering for now.  Fire time!

So, how to implement spreading?  Want lots and efficient.  A GameObject per fire centre might kill things.  Lowest is a single ParticleSystem (perhaps a “Particle Playground” or “PA Particle Field” one for the turbulence?)  Use one-off sphere-cast to detect areas to spread to.  Of course we actually want things affected by the fire so this might be ‘too efficient’.  Also it would be nice to have parts to burn-out — ideally from exhausting fuel which we’ll need to specify per thing affected.  Let’s call YAGNI and do simplest implementation in a new project and test performance.  Then bring into SnwScf actual and see how it performs there.

First version simply has a trigger and spawns on anything entering the trigger. This worked but needed limiting to not overlay itself (since new instances generated new OnTriggerEnter() calls). Also, since triggers give a Collider (not a Collision), I needed to ray-cast to find the actual point on the Collider. I used the obvious and cast from own centre to Collider’s Transform centre but then realized the fire would only spread towards the centre of the subject!

Next switched to a random point from cardinal directions above (picture 8 points at cardinal points around point but car down to surface from above).
To make it more organic, I limited it to one of these points and, after listening to more of this episode of Game Developer’s Radio on juiciness where they discuss good randomness, I decided to implement the Grab Bag Random facility Devon talked about. I’ve put the source up since it’s kinda handy if you want this sort of randomness.

This worked but looked OK on the fence I was testing it on but looked like a game of snake on a flat surface!

Needed to switch to all three points! This fills a surface but would look less good on other shapes and doesn’t handle going down.


Switched to a 3D filling pattern — works well!


Or, setting spread range to the radius produces this rather dense result!

Screenshot 2016-05-09 00.43.40.png

And, replacing the sphere with some simple Particle Playground flames


Of course the prefab I quickly pasted in isn’t a good final version but it shows the idea. The final one will need to build incrementally (so the flames don’t seem to suddenly jump) and generally look better (include some dark multiply particles for contrast or ideally use a volumetric shader).

Anyway, that’s it for Sunday.


Rocketed so hard it broke the camera

Who taught that AI to fire a Carrocket Launcher?!

I’d expected to need to specifically code for each of the power-ups.  While that may be true, I just got beaten by the AI who accidentally collected a Carrocket Luancher power-up!  He blasted me off the screen!  Nice! 😀

This reminded me I need a new way to check players are still in-play.  Think I’ll add a infrequent Invoke() to check position validity within a per-arena ‘Bounds’.

(a few days later with no dev due to parenting load.)

I just integrated the WorldBounds system and it seems to work.  Snowmen are removed up to 0.5 seconds after escaping play area.  Decided not to apply to Shots, etc since they have time limitations anyway.  Removal timing and Bounds will need tuning per-arena since, in some, the play area intentionally has no floor so that Snowman who miss the edge can be seen watched falling like Wile E. Coyote to the sounds of slide whistle for a bit before dying.

Play-testing the WorldBounds change, I noticed another amusing bug.  In the interest of knocking a Snowman out-of-bounds, I gave myself the Carrocket Launcher, charged-up a full power carrocket and fired at the AI.  Amusingly 2 things happened:

  1. I’d forgotten the AI had a cricket-bat which can bat back to the sender, carrockets (or most other projectiles) and it happened to swing at that moment!
  2. that carrocket hit me so hard, it broke the camera!
    By that, I mean the camera was off-kilter due to camera-shake losing something and leaving the camera un-centered!
    Obviously this is a bug to fix but it also made me think about making super-powerful hits add a camera crack that takes a while to be repaired!  Hmm!  Silly idea?


A related camera issue, I was musing on that problem of the camera being inside geometry s in the picture.  The top shows game-view where the player would think they can move towards the camera.  The bottom shows debug view where we can see the player’s actually against a front wall.

Screenshot 2016-05-03 20.48.49.png

I’m wondering if I can re-use / adapt something I did for the Romeo & Juliet game we made for a previous Ludum Dare 30.  There, the world geometry would be faded-out when the camera entered it.  In SnwScf, the camera knows its ‘target’ (by a constantly calculated bounding volume consisting of all players and other CameraTargerables).  The centre of this volume is where the camera is aiming.  If I line-cast from that point to the camera’s position, any static geometry (and maybe other physics layers later) should have some action taken.  Romeo & Juliet did a simple swap to a transparent version of the shader and faded out.  It could get away with this since its a very simple world — all potentially blocking models were the same shader.  SnwScf will need to be smarter.  Perhaps:

  1. A general purpose default which checks the Shader being used and does a reversable swap
  2. A special-case per-shader action in a lookup table
  3. Optional explicit logic that takes precedence

All these will need to be reversable to ensure we don’t leave lots of models with un-shared materials (which breaks dynamic batching).  I suspect I’ll need all the options

Mountain biking this afternoon.  Not been for ages due to knee injury.  Would rather it weren’t on a #ScreenShotSaturday but I’ll try to make it up later.

Aside: Still have an ongoing assertion failure related to Shots being destroyed shortly after creation and their coroutines still trying to be active.  Added logging of which snowman is charging which Shot so can later determine cause of error.

Just seen an interesting error.  A* indicates it failed to find a path to its destination which means the AI cannot route to its desired destination.  Interestingly, A* logged a warning about the problem including mention that it had searched “all 2 nodes of the graph” (the graph should have several hundred nodes!).  I wonder if the problem is that, while rolling, the snowy floor is modified and an update to the A* graph is requested.  My experience of the A* graph updating so far has been that it’s pleasantly quick so I haven’t moved it onto another thread yet (just an option in the Editor).  Maybe if the timing is sufficiently unlucky, could the graph updating result in no graph when the AI requests a routing such that the graph appears much smaller — like 2 nodes?  (or is it something else entirely?)  Continuing from the error I saw a total of 13 instances of the error before routing requests succeeded.  Perhaps this is sufficient.  But perhaps I might need to implement something that spots repeated routing failures and switches to a direct movement approach until routing starts succeeding.  The Pathfinding component has probably retained its last successful path but, due to the pathfinding error, the Task (MoveSnowmanWithPID) is returning failure so something else can be tried… in this case, itself again.  Hmm… perhaps that’s best but the BT could keep a routing/movement failure count and do direct navigation if necessary.  I’ll leave as is for now and see if this happens much.

Whoops, another bug — the AI is a really good play-tester! 😀
Switching weapons while charging a Shot results in the Shot being Destroy()ed along with the Weapon — which breaks the pooling (of the Shots).
Ahha, as I thought, I had handled this situation — I was correctly depooling the Shot however Weapons re-parent Shots to themselves while charging so also needed to add the restore-parent call to the droppedBy() code.  Yep, that fixed it.

AI getting stuck, part 5

Continuing from yesterday’s oddity of the assertion failure, a half day today.

Continuing to think about it, I don’t believe MoveSnowmanWithPID should need a Rigidbody since it’s all delegated through SnowmanInputController and SnowmanRoller.
(funny how one thinks one is making progress in the night but morning brings clarity, eh.)
Makes me wonder why it was working before but let’s see if this fixes it.
MoveToWithForceAndPID should really be an abstract class MoveTo with MoveToWithForceAndPID and MoveSnowmanWithPID as concrete subclasses.  This would be much easier if this was just code — with all the serialization mess of values persisted in behaviour trees, etc it’ll be a bit of a quagmire of changes.  Probably best to manually record the values first (screenshot them all).  Not much time right now so gonna leave as is.  The cost is minor technical debt + an unused Rigidbody variable in the Snowman version.  Not too bad.

Yep, that fixed it.

Right, time for some play-testing.  Started game, played for a bit.  Turned down some logging.  Yep, AI player is not a complete push-over but… when I decided to actually push him — get up-close and attack — I got a couple of oddities!  Once, I pushed him while he was rolling and he got fatter after entering a narrow canyon and got stuck!Screenshot 2016-05-03 11.54.41.png

That’s actually kind’a reasonable!  The AI kept wriggling trying to get out but all the same as a human player.  Guess I would know to try shooting to reduce my size.  Perhaps something to detect and implement but perhaps it’s also funny to let the players spot and attack the sitting fat duck!

The other pushing oddity, I somehow pushed him out of this world!  Checking the debug view, it’s a bit of a Laputa moment!Screenshot 2016-05-03 12.43.47.pngOh!  Googling for a link, I didn’t know Lapupta was originally from Gulliver’s Travels!?

Sidebar: GameDev intro background: Physics keeps you in!

Anyway, this sort of error is pretty common in 3D game development.  If you think about it, all games take place within some 3D geometry that bound you in but, if you could look at it from outside, you’d see it’s all floating in an endless (*1) space with the skymap behind.  *1: endless within the bounds of computers’ floating point representations!  (A skymap is a static picture for the background — often a cubemap — a picture that wraps all the way around.)  So you’re kept in by the physics system — a series of Colliders and a collision resolution system.  If you escape that accidentally, there’s usually something to ensure you don’t drop forever but are considered dead and restarted / whatever.  I have such a system in my game but my push was somehow strong enough to bypass it!  Good to find now!Screenshot 2016-05-03 12.54.58.png

The green boxes visible here are the Colliders.  They overlap to ensure nothing escapes!  To top it all… well, to bottom it all, that giant bottom Collider should have killed the Snowman!  I wonder what happened!

The bottom Collider is a Unity Trigger with a script WallUnderneath that attempts to reset onto the ground or kills if too far / no ground.  It didn’t even get touched implying the physics system moved the Snowman past its Collider in a single step!  If the pushed snowman was rolling, perhaps something is badly tuned?  (I mostly tuned pushing for when they’re standing and against my physical objects — perhaps the values are too big for rolling?)  I’ll have to investigate this further since it could potentially really break the game!

Anyway, that’s all for this morning.  Maybe an hour tonight.  TTYL.

Time for an evening play test!

Camera-relative steering

Becoming increasingly convinced that controllers should move Snowen relative to camera orientation.  I’d previously discounted this since the camera is expected to be generally facing along the positive Z-axis.  However, it is possible to get the camera pointing up or down the X-axis or even along negative Z-axis if enough of the players move fast enough.  I’ve had this suggested only once by a player — during football mode — which makes some sense since the players rush from one end to the other, together (along with the ball which is also a CameraTargetable).

Actions when AI unable to reach

Had a couple of moments where the AI couldn’t route to the destination it wished.  I’ve written this to pause for now since I want to investigate.  In these cases, it was the AI having been shoved out of the world.  (Oddly enough, can’t navigate when it’s falling through space!)  That said, I could see valid situations where it might happen.  I wonder what I should get it to do — sitting doing nothing isn’t an option.  Thoughts welcome.  I guess the AI should appear to switch to a different approach.  Maybe one or more of:

  • Try harder:
    • find a way to flush them out, e.g. rocket launcher the area!
  • Wait:
    • gather health if low-ish
    • gather other benefits
    • find a tactically advantageous position to wait
  • Give-up:
    • select a different target if possible

(These are the sorts of things I’d probably do if I couldn’t get to my opponent.)  Nicely, given the goal-oriented / reactive planner design I implemented for the AI, this can just be placed in as lower-priority goals with appropriate pre-conditions.  They should then become active whenever higher-priority goals (e.g. attacking, desperately seeking health) aren’t necessary/viable.