Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Devlog: Just Tanks

A topic by bdero created Oct 08, 2019 Views: 417 Replies: 2
Viewing posts 1 to 3
(1 edit)

Been in a bit of a funk for the past few weeks, so I've been thinking about trying some new stuff in a game jam to mix things up. A few days prior to October 1st, I made a note to participate in this jam, but ended up forgetting about it until about a week in. Better late than never, I guess!

I've been working on an engine project for the past couple of months, as well as a couple of UE4 pet projects, but Unity Copenhagen just happened not too long ago and DOTS is starting to look more practical with each passing day -- so I'm giving that another shot with a new project.

FYI, I can't seem to post the whole thing because this keeps happening on itch:


So I'll post in parts. (Also, apparently inline images don't work either, so I'll need to upload them one at a time instead)

I know it's not a requirement, but I just really feel the need to finish something, so I'm going to focus in and build up a fairly simple vertical that I should be able to scale out quickly. In particular, I'm going to make a little browser twin-stick, in the same vein as Wii Tanks.


(Screenshot from this video)

So here's how it went today (7/10/2019):

I fired up Unity Hub, downloaded 2019.3.0b6, and templated a new Universal RP project called "Just Tanks". Immediately tested a WASM build to see if it even works (historically I've never had a beta version actually produce a working WASM build), and it worked.. so far so good.

Next, fired up the package manager, scrolled through the preview packages, picking out all the ECS stuff I was pretty sure I needed. A LOT has changed since I first played with DOTS a few months ago, but I was pleasantly surprised to see most of the old methods I tried to use had unambiguous deprecation notes. Ended up getting confused that there seemed to be no mesh rendering component while writing code to spawn some test entities, and so I skimmed the ECS release notes to find that they moved the mesh renderer I was looking for into the "Hybrid Renderer" package. I don't completely know why, but it seems to have something to do with the scriptable render pipeline not being fully built for DOTS, and I guess that package has a bunch of tape and glue.

Now, here's where the trouble starts: From here, I looked over the current official ECS samples to see what's new, and noticed that most of the samples seem to be doing hybrid-y editor friendly things. For example, the samples seemed to be authoring GameObjects in the editor with regular components and writing a special MonoBehavior implementing "IConvertGameObjectToEntity" and sporting an ominous "[RequiresEntityConversion]" attribute. Then I remembered something from the talks I watched: Wasn't there just a script I could shove on pretty much anything and it'd do most of the ECS conversion work for me? Sure enough, I found "Convert to Entity", which I promptly slapped on my silly placement tank.



Alright, so looking at the Entity Debugger, at this point I had a couple of Entities getting built, and my little tank GameObject hierarchy was also properly converting into a pile of entities.


It was time to try another WASM build -- and BOOM out of bounds exceptions. I tried doing a debug build to get a legible trace, but no dice, we've got a Heisenbug on our hands. I spent the next 2 hours removing plugins and deleting code and rebuilding to figure out what caused it, with each WASM build taking > 10 minutes each. Turns out removing "Convert to Entity" fixes the problem. Taking a quick look, it's not doing anything too crazy, it's mostly just using utilities in "GameObjectConversionUtility", which is also how one of the ECS examples converts prefabs into Entities. Anyways, that's all the time I have for today. I'll have to try some other conversion methods tomorrow, or failing that I'll just go with writing factories to generate entities per archetype. At least it's building now!



Bask in the professionalism of my 【cornflower blue】 .

I decided to ignore the WASM problems, since clearly there are some blocking bugs for WASM builds using the GameObject-to-Entity conversion. That workflow is kind of a key convenience measure being pushed in the modern Unity ECS implementation and examples, and I don't really want to deal with working around it by writing tons of factory functions to replace the workflow.

I'll probably do a bug report if I can still reproduce it later and collect more detailed information.

So, I've written a few simple systems to handle input and tank movement, tag components to distinguish between player/enemy, and components and systems from the new ECS Unity physics package.

For movement, I have a pretty simple and naive system setup to start, where the tanks themselves are dynamic physics bodies with a physics shape, and I'm just simulating force impulses based on the direction of input across all tanks every system update in a parallel job.

    public class TankMovementSystem : JobComponentSystem
    {
        private struct TankMovementJob : IJobForEach<TankInputComponent, PhysicsVelocity, PhysicsMass>
        {
            public void Execute(
                [ReadOnly] ref TankInputComponent tankInput,
                ref PhysicsVelocity physicsVelocity,
                [ReadOnly] ref PhysicsMass physicsMass)
            {
                var movementMagnitude = math.min(1, math.distance(float2.zero, tankInput.movement));
                var movementDirection = math.normalizesafe(tankInput.movement);
                var movement = movementMagnitude/5 * movementDirection;
                physicsVelocity.ApplyLinearImpulse(
                    physicsMass,
                    new float3(movement.x, 0, movement.y));
            }
        }
        
        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            var job = new TankMovementJob().Schedule(this, inputDeps);
            return job;
        }
    }     [UpdateBefore(typeof(TankMovementSystem))]
    public class PlayerInputSystem : ComponentSystem
    {
        protected override void OnUpdate()
        {
            Entities.ForEach((ref PlayerTankComponent playerTank, ref TankInputComponent tankInput) =>
                {
                    tankInput.movement = new float2(
                        Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
                });
        }
    }

This lead me into Yet Another set of mysterious problems:

TypeLoadException: Recursive type definition detected
Unity.Jobs.JobHandle:ScheduleBatchedJobsAndComplete(JobHandle&)
Unity.Jobs.JobHandle:Complete()
Unity.Entities.ComponentJobSafetyManager:CompleteReadAndWriteDependencyNoChecks(Int32) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentJobManager.cs:374)
Unity.Entities.ComponentJobSafetyManager:CompleteDependenciesNoChecks(Int32*, Int32, Int32*, Int32) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentJobManager.cs:200)
Unity.Entities.ComponentSystemBase:CompleteDependencyInternal() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:699)
Unity.Entities.ComponentSystemBase:AddReaderWriters(EntityQuery) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:608)
Unity.Entities.ComponentSystemBase:GetEntityQueryInternal(ComponentType*, Int32) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:622)
Unity.Entities.ComponentSystemBase:HasSingleton() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:549)
Unity.Physics.Systems.StepPhysicsWorld:OnUpdate(JobHandle) (at Library/PackageCache/com.unity.physics@0.2.4-preview/Unity.Physics/ECS/Systems/StepPhysicsWorld.cs:93)
IndexOutOfRangeException: Index 41 is out of range of '4' Length.
Unity.Collections.NativeArray`1[T].FailOutOfRangeError (System.Int32 index) (at <5e35e4589c1948aa8af5b8e64eea8798>:0)
Unity.Collections.NativeArray`1[T].CheckElementReadAccess (System.Int32 index) (at <5e35e4589c1948aa8af5b8e64eea8798>:0)
Unity.Collections.NativeArray`1[T].get_Item (System.Int32 index) (at <5e35e4589c1948aa8af5b8e64eea8798>:0)
Unity.Physics.BoundingVolumeHierarchy.Refit (Unity.Collections.NativeArray`1[T] aabbs, System.Int32 nodeStartIndex, System.Int32 nodeEndIndex) (at Library/PackageCache/com.unity.physics@0.2.4-preview/Unity.Physics/Collision/Geometry/BoundingVolumeHierarchyBuilder.cs:571)
Unity.Physics.BoundingVolumeHierarchy+FinalizeTreeJob.Execute () (at Library/PackageCache/com.unity.physics@0.2.4-preview/Unity.Physics/Collision/Geometry/BoundingVolumeHierarchyBuilder.cs:850)
Unity.Jobs.IJobExtensions+JobStruct`1[T].Execute (T& data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at <5e35e4589c1948aa8af5b8e64eea8798>:0)
Unity.Jobs.JobHandle:ScheduleBatchedJobsAndComplete(JobHandle&)
Unity.Jobs.JobHandle:Complete()
This issue appears to be identical to an issue that two different abandoned threads in the Unity forums

were referencing. around ~90% of the time when I hit play, the console is flooded with errors and the tank falls right through the floor. The other ~10% of the time, there are no errors and collision works. Because these errors continually spam each frame from start-time onwards, I'm not super sure this is actually due to a race condition. I suspected what might be happening here is that the system ordering is not really deterministic and might be executing in some hashed order for any systems which don't explicitly specify ordering rules. And so maybe there is some kind of physics initialization system that needs to run before jobs depending on physics systems, and that's not happening.

Now, removing my own physics-reliant systems doesn't fix it, nor does removing my custom step physics entity.

Taking a look at the EntityDebugger, there seem to be 4 physics-related systems running continuously... let's see if there's a problem by unpacking the system ordering constraints:

  • BuildPhysicsWorld - No order constraints
  • EndFramePhysicsSystem - [UpdateAfter(typeof(BuildPhysicsWorld)), UpdateAfter(typeof(StepPhysicsWorld)), UpdateAfter(typeof(ExportPhysicsWorld))]
  • ExportPhysicsWorld - [UpdateAfter(typeof(StepPhysicsWorld)), UpdateBefore(typeof(EndFramePhysicsSystem)), UpdateBefore(typeof(TransformSystemGroup))]
  • StepPhysicsWorld - [UpdateAfter(typeof(BuildPhysicsWorld)), UpdateBefore(typeof(ExportPhysicsWorld))]

I'm not seeing any inconsistencies or ambiguity with this ordering - it should be executing like this:

                +----------------------+
                |                      |
                |  BuildPhysicsWorld   |
                |                      |
                +------+---------------+
                       |
                       v
                +------+---------------+
                |                      |
                |  StepPhysicsWorld    |
                |                      |
                +------+---------------+
                       |
                       v
                +------+---------------+
                |                      |
         +------+  ExportPhysicsWorld  +------+
         |      |                      |      |
         |      +----------------------+      |
         |                                    |
         v                                    v
+--------+----------------+   +---------------+--------+
|                         |   |                        |
|  EndFramePhysicsSystem  |   |  TransformSystemGroup  |
|                         |   |                        |
+-------------------------+   +------------------------+

So clearly, looking at the stacktraces, most of them stem from OnUpdate calls in StepPhysicsWorld, but the exceptions are firing from within the job system itself, which unfortunately I have no visibility into other than a decompiled interface before crossing over into native compiled instructions. The ECS components may be visible to me, but the job system is a complete blackbox... And that kind of sucks, because I probably would have figured out what the bug is and fixed it by now if it wasn't. Based on the totally wonky numbers that the out-of-index is returning (huge negative and positive numbers) I'm assuming that there is either some uninitialized or corrupt memory being used as some indexing value by the job system, but I have no way of really debugging it unless I want to spend weeks trecking through instructions in IDA.

Next I tried adjusting several knobs randomly just to see if there would be any affect, like switching to standalone PC build mode, using .Net 2 instead of 4, Mono instead of IL2CPP, switching from Unity to Havok physics mode, etc. No dice.


I removed all the starter cruft from my project and tossed it in a public git repo just to make sure I can keep track of these issues in a minimal project. Tomorrow I'll probably report these bugs with repro instructions on the public Unity bug tracker.

By the way, itch.io's rich text editor seems to have severe consistency and predictability issues. Things would be a lot easier if they allowed for doing all text editing in markup-mode (markdown, BBC, HTML, whatever format they're storing this text to the database with). Especially for pasting unformatted text. Maybe this would make for a good suggestion to the itch.io team.. I think there's a forum for that, somewhere. :)