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 🙂 )