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!

 

Status

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.

 

Fluidity

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.

Bug?

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.

Flame-spread-around

Switched to a 3D filling pattern — works well!

Flame-spread-3D.gif

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?

(later)

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.

 

 

AI getting stuck, part 4

Continuing from yesterday’s work, Justin PM’d me back about the event finding code saying it finds parent class ones as well.  Checked.  He’s right!  He said he’d investigate better way to implement that will fulfil both needs (not finding the version from Task but finding both leaf and intermediate implementations).  Feeling bad (and curious), I decided to investigate as well.  Found a simple solution.  Posted solution to GitHub.

Ported changes back to SnwScf actual.  Looks good.

Lunch then swimming lesson with 1.5 year old this afternoon.

Sadly ill in the afternoon and early to bed.  Ill again today but gonna struggle on.

Gonna let myself off the hook from posting this blog post and finish it today — Bank Holiday Monday in the UK 🙂

Right, where were we?  Oh yes, the dev version keeps hanging due to an Assertion failure of mine — trying to start a Shot’s Coroutine when the Shot is inactive?!  Hm, I wonder if its Collider is hitting something on the frame of its creation — perhaps the prefab should have disabled Collider which is enabled during shoot()?  Investigating.  So gonna need to account for setSelfHit() not working when Colliders disabled.

Also, the Snowman AI likely needs to use different PID values for rolling vs. sliding.  It seems to work OK for now but, once I get that assertion failure fixed, this will probably be revealed as ‘feeling’ wrong.  It’ll probably look like it moves too slowly since in the AIDev project, the rolling defaults (0.25, 0.225, 0.4) didn’t feel good whereas a simple (1, 0, 0) actually seemed OK … but won’t handle external forces, etc.

Had an idea while investigating the assertion failure:

Idea: Logging that is silent until assertion failure

Inspired by DTrace, I wonder if one could contrive a combined logging and assertion system where one logs as much as one likes but nothing is output until an assertion failure (or other event).  Obviously, there needs to be zero production-build effect and, in the Editor, there’s minimal effect until needed.  There’d be configuration to declare for which things this is enabled at which times.  As example, perhaps you leave it disabled by default then when on seeing an odd exception, you crank-up the dials to enable it on everything and wait for the exception again at which point all relevant detail is dumped.  Interestingly, I have most of this already in my logging and assertion systems.  I just need to tie my logging into a per-origin StringBuffer which the Assertion framework would need to pull from.

In fact, I wonder if one could even retrospect it atop Unity’s logging (since Unity exposes Log.logger)? Probably not without one’s own Assertion library *and* some concept of logging origin.

Anyway, back to investigation.  Yep, it’s colliding and being destroyed.
Relates to how I enable and disable Colliders and their interactions during firing.  I currently have:

  • currentlyPreparingShot.setSelfHit(false) — which prevents a Shot from hitting its owner.
  • startSelfHitCoroutine() which starts disableSelfHitCoroutine() — together they make the Shot enable its Collider’s interaction with the owner’s Collider shortly after launch.

I’ve now changed it so the Shot’s Collider is disabled in OnSpawned() (which might not affect some unpooled things like that reside in the level, e.g. SnowFallers — snow that drops when disturbed).  It is then re-enabled in onBeingFired() and in startSelfHitCoroutine().  (might be double-call but doubt it’ll cause problems.)

Yep, seems to fix 2 bugs in this area I was seeing.  However, this is one of those areas with odd subtitles, timings and interactions.  Time for a run of the integration tests!  (yes, I rather wish they could be auto-run in the background after compile!)

Not done integration tests yet because manually testing Shovel and WetFishSlap (you do know those are weapons in SnwScf, right?) revealed a couple of easy fixes.  Done.

Dinner time!

OK, committed all those changes.  I can now see I was definitely right about the PID values for sliding vs. rolling.  Gonna need those to change mid-use.  In PID Controllers, this is apparently called “Gain scheduling“.  Whether it should retain sets of values to interpolate/swap between or simply be assigned a new set of values?  YAGNI: I’ll try the simple option first.  I wonder if I could just swap the values over directly?  I did have the PID values as readonly.

Added OnRollStateChange event to SnowmanRoller which an SnowmanAIController subscribes to.  Added PIDControllerSettings to encapsulate changes.  Nice and neat.

Question: C# Generics [optionally overloaded] method constrained to either type or interfaces?

Hmm, just came across some of my generics code where I hadn’t constrained the type down and I really should have.  Oddly, I corrected this (and its brethren) and got an extra 2 compile-time errors where (here’s the odd part) I’ve never had a runtime error!?  Does this imply that code has never run?!  No.  But surely it should have failed in the same way!  Ahha, the runtime error indicates that the types allowed include interfaces … which is what I am supplying in the extra error case.  Hmm… how to specify a specific type OR any interface.  If memory serves, there was a way to say interfaces are fine.  In fact, it’s probably used in the runtime method I’m calling — GetComponent<T>().  Let’s decompile for a quick answer.  Oh, they don’t restrict.  Hmm…  Ah yes, use “class” for “class or interface” and “struct” for a “value types (like ints)”.  Ah, but how to combine?  Tried…

public static T GetComponentNonNull<T>(this Component comp) where T : class, Component {
}

…but no.  Hmm… so you can’t say both a type and interfaces (ahh, because generics is an “and” of all conditions — makes sense).  You can’t even do it with overloading (due to Erasure, I assume.  Er, I mean erasure).  Oh! C# doesn’t use erasure!  Well, learn something new every day!  Cool!  (Did I mention that I’m from a Java background?)  Hmm… still can’t contrive single-line solution.  Sigh, so had to solve with a hack: 2 methods, one with “Interface” stuck on the end whose type is constrained to “class”.  It’s close enough.  If anyone knows a better solution, I’d love to hear! (comment now and we’ll throw in a fine tweet free — not available in any stores!!)

 

Hmm… after adding those changes, I’m getting an assertion failure about there being no Rigidbody on my Snowman … which there isn’t until it rolls — but that’s not changed.  It would make sense if I had changed some initialization order thing but I haven’t.  Hmm… it’s the MoveWithPID which is a subclass of MoveToWithForceAndPID whose OnAwake() leads to the GetComponentNonNull<Rigidbody>() … which I have changed.  Could something here be acting differently due to the generics changes?  Seems unlikely.  All the same, I can’t disagree that MoveToWithForceAndPID shouldn’t be getting a Rigidbody in OnAwake().  For Snowman, which adds its Rigidbody when rolling starts, it needs doing at that time.  Viable since adding the OnRollStateChange event to SnowmanRoller just now.  Still odd though.  Perhaps better would be having the Rigidbody always present but switching it to Kinetic when the CharacterController is in use?  Would that work?  I’ll muse on this overnight — perhaps the morrow will make obvious what’s eluded me tonight.