Voxel usage in Snowman Scuffle — for switching voxel platforms?

The voxel system I use for the snow in Snowman Scuffle is no longer maintained. In fact my customized version is probably the most advanced variant of it. This is good and bad. It’s also a pure Marching Cubes approach — a technique that has been somewhat obsoleted by more modern techniques. As such, I’d be quite keen to replace it if I could find a viable (performance, cost & integration time) way to do it. As such, I keep my eye on the forum for mentions of other systems that might fit.

One came up a few months ago by @PawfessorFluff. The tech certainly looks good so, at his/her request I posted my needs for consideration. Here’s a summary of the use of voxels in Snowman Scuffle — both existing and potential future. I’ll probably update this post with clarifications as @PF requests.

Why replace?

Un-maintained software requires the user to fix the bugs (which I’ve done) and extend with any desired new functionality (which I’ve also done).

However the problem is probably one of performance.  Here’s a table of performance results with different setups.  TerraVol has no LOD facility so these numbers are relatively consistent (depending on camera position).

https://docs.google.com/spreadsheets/d/18Fu1yoWm3hXfDkIign-FbKm8c14h7EEP-nRMoWGFcNM/pubhtml?gid=2030552354&single=true

A quick comment on viewpoint

@PawfessorPuff observed that the SnwScf camera is basically top-down. In case relevant to his understanding, I’ll clarify/correct slightly.

The current camera in SnwScf generally tries to be pointing up the Z-axis pointing down at around 60 degrees. It’s distance if determined by the distance between the camera’s targets (generally players but also certain power-ups, etc). When targets become closer and lie close to similar Z values (i.e. paralleling the X-axis), the camera moves down to present a more side-on view.

The probable single-player mode might use more of a follow-cam approach if it doesn’t confuse players moving between the two modes. 

Current voxel usage

The voxels in Snowman Scuffle are currently solely used to represent the snow (not the terrain or obstacles).

At level start the snow is built by one of 3 methods:

  1. Procedurally using callbacks to an interface I provide — currently giving a pseudo-random Perlin noise based using some parameters to control depth, amplitude, frequencies, etc.
  2. Loaded heightmap.
  3. Loaded previously-serialised data.

During gameplay both create and destroy operations are used at present. There are however two complexities:

The first complexity is that, to maintain the appearance of snow, all operations must produce smooth edges across their surface (e.g. a sphere cut from a plane should look like exactly that boolean operation). When I initially implemented, the default approach modified the isovalues to their ‘most outside’ value (-1 in TerraVol) which produced ridges in the sphere. To solve this I apply different isovalue modifications within the sphere (full removal) to on at the surface (linearly interpolated towards the outside). With a little tuning of the sphere overall size, this produced the smooth outlines I wanted.

The second complexity is wishing to know which player destroyed what snow. For some game modes, the amount of snow destroyed factors into scoring. For rolling (described below), it is also used to add health to the snowman. I implemented this as a passed delegate approach where some delegates cascade (if both score and health info are needed) and said delegates can be built at the start of the level to save GC load.

Destructive weapons

Rockets (called Carrockets in SnwScf), Mines, future flamethowers, human tanks, etc.
A simple one to understand, they currently all do one sphere destroy operation each.

Snowballs landing

Another simple one, this is one voxel create operation. The value added is tuned to approximate the size of the snowball that has landed.  Small snowballs solidify on landing whereas big, rolling snowballs solidify when their velocity drops too low.

Rolling

Rolling was trickier to implement. For this I wrote a capsule destroy operation. Both sufficiently large snowballs and snowmen with the correct button held can roll. As a snowball/snowman rolls, the snow that has been passed is periodically destroyed along the vector just passed with the radius of the snowball that’s rolling. It had to be the snow ‘just passed’ otherwise the roller would be immediately dropped into the trench it had just cut then stopped or launched into the air by the ramp right in-front of it! The tricky part is tuning this operation so it doesn’t lag too much. It had to be periodically done to make the operation not too heavy on processing. For now, I have it done every certain distance travelled (though janks and bugs can cause long furrows to be cut as the capsule code notices two distant points need connecting!).

Futures

Global warming

I considered a potential future use of ‘smooth’ operation for melting snow. My current voxel system (TerraVol) doesn’t support that so I’d have to write it myself — as such, its low priority meant I chose not to (yet).

Terrain

In many respects, I would *love* to have the landscape be voxel-based as well. I have destructive weapons (rocket launchers, mines, etc) that would benefit from a bit more effect and it would [hopefully] be easy to tie into the existing operations. Sadly, I believe the performance impact would be too great with my existing voxel approach due to causing too much load. The reason for this is a change I made to TerraVol to allow it to create sufficiently detailed snow models — specifically the size of my smallest-snowballs. To do that, I scale the whole TerraVol system by 0.1. This allows nice shaping of snow blobs but means 10 times as much geometry all round. I did a little optimizing knowing I’d be dealing with only snow (including disabling multiple materials and vertex colours disabling mud/grass/rock distinctions).

Were I to switch to voxel-based terrain, I’d probably want similar approaches as those mentioned by a recent poster saying things like heightmaps of importing from Unity Terrain / MapMagic / Gaia.

Streaming / roaming

I have a single-player mode in early development that allows players to roam. I haven’t determined how viable it is yet. TerraVol allows for on-the-fly generation / destruction at a radius from a specified point (the camera’s target area in my case). I have one nit in my modified version of TerraVol that causes the snow operations to not be played-back properly on the voxel space. They’re stored but it’s not replaying them in the proper place yet — something in the scaling is yet messing it up.

So, there you have a summary of voxel usage by SnwScf. Maybe not stereotypical voxel usage but it does make for some pleasant gameplay possibilities.

Here’s hoping @PawfessorFluff has some awesome voxel powers to make it even better!

Advertisements

Anthropomorphic environment?

I’ve been wondering whether I should make the game’s environment more anthropomorphic, i.e. add faces (and possibly some capability for action) to ‘alive’ parts of the environment.  This might act as an extra nudge towards the game’s conservationist message and provides a more immediate feedback loop for the environmental message.

As an example, all trees would have a face.   Maybe they’re always asleep to start with?  Nearby noises wake them and they then look at activity.  Innocuous things mean they eventually go back to sleep.  Worrying things like fires get them worried.  Catching on fire causes them to make terrified faces (and maybe little blowing motions)?  Having the fire put out causes them to make a relieved (re-leaved? 😛 ) face.

A non-obvious ‘live’ thing could also be fire!  An evil cheeky face on each one — with the fire graphic switched from the semi-real it is now to a cartoon style?  Even less sure about this part.  Guess I’ll have to see (a) whether can come up with a suitable look and (b) how it feels?  I’ve mostly finished the fire’s code now so don’t really want to change it from spreading in the natural-sort of way it does.

Anyway, I haven’t fully decided yet.  As always, all thoughts welcome!

p.s. Attached picture was while looking after kids.  Whaddya think?  Trees might be a bit ‘red neck’ with their big foliage-like moustaches? :-{D

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 😉

 

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