Sanity check on general DOTS game Design

I was honestly planning on manipulating the CameraRig game object on the main thread of the CameraSystem : SystemBase (In my testing I used an IJobEntity on InputSystem). This reads in a CameraRig singleton which has variables fed in from the InputSystem. Right now, I can’t imagine why I would need more than a few Singleton components to control the camera in this game. This camera doesn’t respond to objects or participate in physics or follow a player character. I assume you could need Parallel jobs and the ability to see a lot of transforms for that?

All the camera does is respond to rotation requests (touch swipes) and then zoom to an object the user taps on (and manipulates menus. hence why taps must work with swipes). Then returns to pre-zoom position when closing the menu. I will probably cap the rotation angle (right now it’s fully 360 degrees orbit) to some amount to prevent being able to go fully vertical and flip to the other side (either direction). I don’t think any of that even justifies the IJobEntity I used in testing?

One thing I was going to do at some point is adjust the camera distance to be able to see all level objects. Not sure how to do that, but it would be largely a level initialization problem (unless levels gain the ability to change in play)

As always, I could be missing something.

One thing I genuinely like: code brevity. I just split the camera out of the input system so it’s using component data “for real”. Both the input system and the camera system key aspects are 20 lines of code each with generous formatting.

Will it need niceties like smoothing or other techniques? Sure. But it is genuinely simple. The camera system will eventually be filled with logic about how to smoothly animate the camera based on user input. That’s about it.

The input system will be filled with handoff code via components to other systems.

Hopefully I can animate/render visuals as clean and nice as this code. :wink:

Also, I am not sure that there is a lot of value between a separate input system from camera system in this case. Since the camera is so simple, just reading in InputActions directly in the camera system may just make sense. The biggest value of the input system may be doing things like differentiating click vs drag (assuming the new unity input system can’t do it before sending callbacks incorrectly). Or possibly smoothing input. The input system would then have to act as a buffer to distinguish user input and then add to the relevant components.

Lesson learned today: Do all rotation in Quaternions. Don’t question this. Just do it.
Lesson two: Threading is complicated.

Threading: Using a RefRW and ValueRW doesn’t prevent thread conflicts with something else in a Job? My Input System writes to a component (mouse/touch delta) read by my Camera system. The input system was using an IJobEntity. The camera system resets that component value to 0 after applying it (via Quaternion now!). The Camera System couldn’t be in the IJobEntity because of managed objects (maybe there is a way?). I don’t care, cause it’s a singleton with minimal processing. But, it also meant I couldn’t use IJobEntity in the Input System as I kept getting an access error for something in another job in the camera system. This seems fine on the main thread. I wouldn’t mind if I could use a parallel friendly solution, but it seems like it’s not critical for this particular thing. I could use dependency.complete but that would effectively be the same as running on the main thread? I am not sure if there is any advantage to using an IJobEntity on a singleton running on the main thread or via dependency complete.

Anyway, lots of progress/learning today! Unfortunately, possibly back to the touch/drag issue.

Is the new Input System actually useful for mobile touch devices? For keyboards and game pads universally, it seems pretty awesome. The abstraction is fantastic. But for mobile? I feel like that was a use case that was mostly ignored. The inability to disambiguate between actions that are similar is just not functional when primary input is a single finger. This seems to be documented. I really hope I am missing something.

Anyway, I am deciding between trying Rewired and writing my own support under the classic input system (and possibly with enhanced touch). As I long time iOS dev, the enhanced touch support looks a lot like the older generation touch system. Going to try rewired first. It is well worth the money to take this problem off the table.

Ok, unity input is seriously challenging. I decided to try enhanced touch support since my primary platform is mobile. I enable it via EnhancedTouchSupport.Enable(); and I comment out the Input Actions so those methods aren’t called (and they aren’t)

Now, no input is registered in game window regardless of the simulated touch setting. Then I realize it is registering input if I click on unity outside the game window. So my click and drag mostly works as long as I am clicking outside the game window to start the drag. At the same time, if I stop my drag motion it continually sends a delta and my world just spins.

The simulator does not register input whether I click outside the window or inside regardless of simulated touch setting (which shouldn’t matter when using the simulator?).

To test this I re-enabled the Input Actions and disabled the enhanced touch support, and everything works normally again. Only, I can’t determine the difference between a click and a click+drag.

Crazy.

I found a work around. For the record, polling with:

if (t.phase == UnityEngine.InputSystem.TouchPhase.Moved) {
....
}

simply doesn’t work. like, it’s bat crap crazy. No idea what it is doing but only registers clicks outside the game window and reports insane data.

however if you register event methods like I did with the input actions like so:

            UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown += TouchDown;
            UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerMove += TouchMove;
            UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerUp += TouchUp;
            EnhancedTouchSupport.Enable();

Both the game screen and the simulator start (mostly) working. AND it’s dirt simple to see the difference between a tap and a drag for my use case. and it seems to be working. So, at least my primary platforms may be taken care of.

One bad thing: stuttering. I had this with the mouse and figured it was gimbal lock. Once I switched to Quaternions, it was smooth as butter. Now, I assume it’s back because the touch system needs normalizing? Anyway, that’s the next problem. When moving slowly it’s very blinky. But quickly, it seems pretty smooth.

This is the function logging the screenshot below:

        private void TouchMove(Finger finger)
        {
            cameraRotate = finger.currentTouch.delta;
            Debug.Log("new delta: " + cameraRotate.ToString());
        }

You can see the delta almost swapping the values from just moving slowly in one direction. x and y will swap from positive to negative and back on each additional delta. This makes the camera movement quite jittery.

Not sure what to do about that yet.

9441287--1324493--Screenshot 2023-10-30 at 7.04.50 PM.png

I found a workaround to the above here:

For me, it looks like this:

        private void TouchMove(Finger finger)
        {
            // cameraRotate = finger.currentTouch.delta;
            cameraRotate = Touchscreen.current.touches[finger.index].delta.value;
            Debug.Log("new delta: " + cameraRotate.ToString());
        }

Back to smooth motion.

I have read that this solution will have a problem as the resolution of screens change. And I will have to normalize by screen dpi. Haven’t gotten to that yet.

Hopefully I can finally work on adding functional components! I have been drafting a game flow hierarchy that keeps getting more stuff since I am stuck on basics. :wink:

I am finally building interactions. And my first raycast is not going well. I added a collider to each of my entities via this code:

            SphereGeometry sphereGeometry = new SphereGeometry {
                Center = float3.zero,
                Radius = radius
            };
            BlobAssetReference<Unity.Physics.Collider> sphereCollider = Unity.Physics.SphereCollider.Create(sphereGeometry, CollisionFilter.Default);
            ecb.AddComponent<PhysicsCollider>(index,e, new PhysicsCollider{Value = sphereCollider});

The above is done in a a SpawnJob: IJobParallelFor. The radius is pulled from a mesh:
radius = meshes[0].bounds.size.x

Do you need any other components to have the collider function?

In my InputSystem where I detect taps I have this:

        private void TouchUp(Finger finger)
        {
            if (finger.currentTouch.isTap) {
                var p = finger.currentTouch.screenPosition;
                Debug.Log("logged a tap at: " + p.ToString());
                Ray ray = Camera.main.ScreenPointToRay(p);
                RaycastHit raycastHit;
                if (Physics.Raycast(ray, out raycastHit)) {
                    Debug.Log("Something Hit");
                } else {
                    Debug.Log("Not Hit");
                }
            }
        }

Nothing is ever hit.

I can’t find a “simple” example of ecs physics in code. Most involve converting from game objects.

In this case I am pretty sure I am just missing something obvious. Like an additional component that must be present to become a physics entity?

Any thoughts?

For the record, I do have this as well:

ecb.AddSharedComponent<PhysicsWorldIndex>(index,e, new PhysicsWorldIndex { Value = 0 });

but still no hits.

Entirely the wrong API. Physics class is for PhysX, not Unity Physics. Use CollisionWorld from the entities World.
https://docs.unity3d.com/Packages/com.unity.physics@1.1/api/Unity.Physics.CollisionWorld.html
Code examples for basic things like physics queries have been in the package documentation and the EntityComponentSystemSamples repository for a long time.
https://docs.unity3d.com/Packages/com.unity.physics@1.1/manual/collision-queries.html
https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/PhysicsSamples
For completeness, example of creating body from scratch:
https://docs.unity3d.com/Packages/com.unity.physics@1.1/manual/create-body.html

The create body example is what I am using to add the collider. The Sphere blob and components were copied straight out of that.

I have that physics samples project open in another unity editor.

How is that the wrong API?

Please read what I wrote. The Physics class is not for Unity Physics. Use CollisionWorld through code like given in the collision-queries link.

Ah, got it. so my ray casting is wrong, not my colliders. Cool. Will set that up.

Thanks!

Thanks! I got it working. I do have one dumb question though. The point of a ray is to have an origin and a direction but can be represented infinitely.

This DOTS/ECS implementation requires an endpoint of the ray. I got that using Ray.GetPoint(SOME_FLOAT). Now, I can just put a large number in there (at least larger than my world) so I don’t have to worry about reaching the end. But is there a way to represent infinity? (Mathf.Infinity caused an error to be thrown)

Thanks again!

I am working on separation of system concerns especially around animation.

Is there any documented component system patterns that work well?

As an example, in my game the user can target an object. When that object is targeted, several systems will respond. The overhead display will change. The camera will animate into a position focusing on the targeted object.

Focusing only on the camera, to perform this operation, some system needs to take the camera and slerp/lerp it around to the new position and orientation.

Things to consider:

InputSystem recognizes target request and sets up a target component.
Camera System (and others) see target component created by input system and start reacting.
The camera system now needs to track original position, target position and time to perform the slerp/lerps. (this will involve rotation and position changes)
The camera system also needs to track that it has responded to the target change, and start listening for another target change/dismissal.
In my case I need to store the original camera position and rotation to animate back to on dismissal (or possibly cancel).

I could do all this in the single “CameraSystem” I have now. But there will be a pattern of animations that need to complete before the next action can happen across many systems. (In starcraft the siege tank needs to complete the change to siege mode to fire long distance and can’t do anything else while switching modes)

It seems to me that it might be a best practice for a “CameraControlSystem” to kick off animation systems. There may even be queuing of sequential animations. Etc.

It’s not clear to me what “good” solutions to these issues are. Only that putting it all in a single system could overload that system pretty quickly. That may be the way to do it, but I am hesitant.

I could also have a component like “CameraSlerpTarget” which tracks the entire movement operation (both position and rotation) with a dedicated system. Add that component, with associated variables and the camera does its thing. That may be too specific.

This says nothing about the fact that my targets may have their own motion at some point. This means at least the position system may need to be constantly tracking the target and matching the camera to it.

Anyway, trying these things out.

Interesting issue. I started using the system handle to store “singletons” for system state as documented here: [Define and manage system data | Entities | 1.0.16

W](Define and manage system data | Entities | 1.0.16)hen using the system handle, code like the below no longer finds those components.

foreach (var target in SystemAPI.Query<RefRO<SelectTarget>>()) { ... }

The purpose of this change was to allow the owning system have easy access to their own components (via a simple getComponent style call involving state.SystemHandle). It also should allow enabelable components while GetSingleton does not. And it takes all these singleton state components off my one “GameConfig” entity.

Based on the language in the above documentation, these components should still be accessible normally? But they clearly are not in my case. That foreach never fires.

If I move these components to a regular entity, all queries work exactly as is.

This seems odd to me. It’s like the system gets private components when registered this way.

Thoughts?

EntityQueryOptions.IncludeSystems

ha! It would be good if this were mentioned on the doc page suggesting using system handles. :wink:

For the record it looks like this:

foreach (var target in SystemAPI.Query<RefRO<SelectTarget>>().WithOptions(EntityQueryOptions.IncludeSystems)) { ... }

Works great! Thanks!