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.




One thought on “AI getting stuck, part 4

  1. Pingback: AI getting stuck, part 5 | Snwscf Dev Blog

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s