2024-07-22 - Back at work
Hello again! Yes, I'm alive, and Particular Reality too.
Unfortunately, I had to take a pretty long break from the project development: my latest DevLog entry stopped at May 31. Almost two months! I feel awful about it.
That said, it's the harsh reality of this kind of "passion project" that, when other priorities arise, it is necessary to put things on hold.
But now, finally, I'm back, and hopefully I can stay on track for a while.
Now, were was I?
Before my (unwanted and unexpected) break, I was working on the enemies management.
More specifically, I had took care of the enemies creation and destruction, pairing it to the "zones" creation and destruction. But there was still a lot to do.
Let's check the TODO list from last time:
go back to the VFX graph stuff and see how to control the VFX simulation time, then bind it to my gameplay system
handle basic interaction with the enemies, so that the player can destroy them and clear waves
I'm going to start from the second task.
In the PoC, the player could destroy enemies by punching them (so, with a direct collision) and indirectly, by shooting a fireball.
We're not really focusing on combat techniques, and I'm not even sure I want the fireballs in the final game, so I'm going to keep it simple for now, and only allow destruction via direct hit.
Still, as we're now dealing with the "layered" architecture, it's going to be a little more complicated that what I did at the beginning.
Let's go back and see what I can "recycle", and what must be done completely different.
I'm doing (on purpose) a pretty basic usage of Unity, but I definitely want to at least use the collision detection system. This means having `Collider
` components on the game objects in the scene, together with scripts with collision handler callbacks (that the engine will call when a collision happens).
The idea is that in my case these collision callbacks will do very little: essentially, record the event so that the simulation update can read it and take it into account in the update logic.
How does this fit the current architecture?
The core "problem" is that the presentation layer, by definition, should only render the state of the game. But the collider component and collision handling script are on the enemy game object, in the Unity scene, and so it comes natural to think about those elements as part of the presentation.
Ignoring that feeling and thinking about their functionality (detecting events happening in the simulation, and altering the state consequently), I can consider them as a source of "internal input" data to push towards the state machines of the "logic elements" these "scene elements" are bound to.
Specifically, let's say I update the position of an "scene enemy" by setting it to the "enemy state" position (read from the `EnemyStateManager
`). If the new position triggers a collision, I can generate (and store) a logic event that I will fetch and process during the calculation of the following game state (shown in the next frame).
Until now, I have only been dealing with this kind of data flow (coming from the scene) for the player input (fetching the body tracking data). Now, I'm going to try to do the same with the enemies.
How to do that in practice? For today, I'm going to just read a bit of code I don't remember anymore, and tomorrow I'm going to start implementing the changes.
2024-07-23 - Implementing the collision data flow
Ok, a quick overview of what I plan to do before starting to write some code.
As I anticipated, the core point is that I have the state management separated from the presentation.
Let's consider a single enemy:
it has an `
EnemyStateManager
`, implementing its logic, which operates on an `Enemy.Data
` element (the part that goes into savegames)it has a subtree of nodes in the Unity scene, with an `
EnemyBhv
` on the subtree root.
`EnemyBhv
` fetches the FSM execution state, and updates the scene nodes so that they visualize correctly such state.
Now, we need to have some data flowing in the opposite direction. Why?
As discussed yesterday, the collision detection happens in the scene, with Unity calling some callbacks when `Collider
` components are properly setup on the relevant nodes.
When a valid collision happens, I will store its data into an input signal that will be sent to the `EnemyStateManager
`, so that it can properly update the enemy state according to the collision data.
Ok, it took me a while (this is what happens when you stop working on something for almost two months!), but I almost got things working.
I added an `hit()
` method to the enemy, and also updated the testing script that shows up in the video at the end of Week #21.
Using the testing behaviour, I can "hit" the enemy pressing a button in the inspector, and see that the enemy velocity changes because of the applied force.
So why I wrote that I almost got it working?
Wearing the headset and trying to hit the enemy with a punch, like I did in the initial prototype, does not work..
This means I have a problem in the part which actually detects the collision in the scene.
The problem might be on the enemy side, but could also be related to the hands.
I remember using a sphere collider approximating each hand, for quick and easy collisions. But maybe something broke while I was working on the "particular" avatar?
It's late: we'll find out tomorrow.
2024-07-24 - Restoring hand colliders
This is a good moment to make a copy of the repository pointing it at the end of the proof of concept development: while working on the new architecture and features, I had to break a good portion of it. Adding stuff is important, but cleaning up old cruft on the way is also necessary, so I removed or disabled many things. That said, having a working copy of the PoC can be useful, to use it as reference/guide while replacing subsystems and components with the new, improved implementation.
The "PoC" project works properly (you never know until you try), and I went in and checked how I handled the enemies/hands interactions.
Of course, something must always come up to interfere with working peacefully: Air Link doesn't work. I guess that some update broke something at some point in the last months. I already noticed it while working on another project, but I ignored the problem hoping it would fix itself with another update. That hasn't happened yet, unfortunately.
Irritating, but I don't want to waste the day trying to debug the issue, so I'll keep using the cable for a while.
As I guessed yesterday, while working on the player avatar I had skipped the invisible "hitter" spheres attached to the hands. Restoring them, the collision gets registered and the enemies react to the collision.
But something is off in the way the enemies move: there's a delay, and they go in the wrong direction. I need to dig deeper.
I went back to the PoC project and started porting some of the logic I implemented there, mainly the snippets handling enemy movement and its adjustment after a collision.
During the process, as I hoped, I had an idea about the odd behaviour I noticed while testing the new collision handling: I did not transform the "hit" vector (coming from the collision detection in the Unity scene) from the "scene" world space to the "zone relative" space that I use in the state management layer.
Additionally, I noticed that I don't keep track of the player position in such space: until now, I only needed the platform and zone identifiers. But now I need it, so I can steer the enemies not only towards the "player platform" but towards the exact player body position. No doubt that it will soon be useful in other contexts, too.
Tomorrow I'll try to take care of both issues.
2024-07-25 - From world to zone space
Following the intuition I had yesterday, I started coding and added a few methods to handle the transformation of vectors and quaternions from world space to the Zone local space.
Then, I used these methods in the "right place", which is the point of the code where the collision data recorded by the behaviour (on the scene node) is fetched and used to send an hit input to the FSM execution. This way, the "hit" event processed by the state management layer contains properly transformed data.
I also used this approach to steer the enemies towards the player, as I did in the PoC.
This time, I added a minimum distance check, to simplify testing: the enemies start going towards the player only when it's close enough.
This way I won't find myself submerged by all the enemies populating the current zone.
Well, to my surprise, things started working right after the code started compiling. At least in terms of positions and directions. I'll show you what I mean...
While the direction the spheres move towards looks reasonable, there's an insane delay from the moment I hit the spheres to the instant they react to the collision.
A one frame delay was expected considering the data flow I implemented, but this is way worse.
Tomorrow I'll revisit my steps and hopefully fix the problem.
2024-07-26 - Debugging the interaction delay
I'm going to think about possible causes for the delay in the interaction I observed yesterday and try to fix it.
As you know if you've been reading the past entries, I'm "all in" on visual debugging.
So, for starters, I visualized colliders for hands and enemies when in debug mode.
Then, I added back in the "collision wave" feedback to show hits, and I also changed the enemy particles color to white when a collision is happening. I'm cheating a little, because I'm managing such waves directly in the visualization layer. This would not work in terms of frame-accurate load or rewind system, but it's good for debugging.
I also capped the enemy velocity to a low value, so hitting an enemy doesn't send it too far away, and it can come back to be punched again.
While doing these changes, I also found that I had left both the "old" and new movement calculation (before and after porting the PoC code). That definitely didn't help.
This is the result: as you can see, the collision detection works properly, but the delay is still there.
My enemy "testing" behaviour currently only takes "hit" signals through the inspector buttons, and that works instantly, there's no observable delay.
Let's also bind the "hit" signalling to the hands collision and see what happens.
Ha! Now we're talking!
Now I know the enemy state manager works properly too, because the testing behaviour calls into it too. In theory, the enemy state manager update logic should be called every `FixedUpdate
` both in the testing code and in the enemies that get spawned in a `Zone
` according to the level description. But something is obviously wrong, because the non-testing enemies react to hits with a massive delay.
Testing again, I feel like the delay is around 2 seconds, which makes no sense. Unless... oh no, I think I know what's going on.
Yep, found it.
I don't remember if I wrote about this, but currently my state data is organized hierarchically in three layers, like this:
`AppState` -> `GameState` -> `SimState[]`
Basically, `AppState
` contains some transient data and one `GameState
`, which is what gets saved/loaded. `SimState
` (which contains the player/enemy/world state of a certain instant) is the data structure that gets visualized by the presentation layer, basically representing a frame.
The `GameState
` contains a "window" of a certain number of `SimState
` structures implemented through a ring buffer. I constantly write to this buffer, overwriting old entries so that I only keep track of the latest `iSIM_HISTORY_SIZE
` states. Currently, I had `iSIM_HISTORY_SIZE` set to 120... do you see where I'm going?
The current logic, when going from frame `x
` to frame `x+1
`, copies the previous state and then executes the update. But it doesn't copy the queued input, which is kept separated... and not cleared. So, it then gets processed after a full iteration into the `SimState[]
` array has happened, which is a little less than two seconds later.
It's the first time I notice this because, I guess, it's the first time I queue an input to a state machine to the next frame. Yikes!
To verify that the problem is that, I just had to momentarily set
public const int iSIM_HISTORY_SIZE = 2;
...and guess what, "it works".
As it always happens, it looks obvious now, and I feel very dumb for not having thought about it instantly. But then again, after two months far from the project, I didn't even remember about this simulation state "window".
It's too late for a proper fix, but I'm happy enough to know exactly where to start on Monday.
I'm going to
fix this state machine execution anomaly, a critical point of the whole game architecture
proceed with the enemies interaction, handling their destruction so that I can finally test the "waves" system
And let's not forget I have another task from the previous TODO list:
go back to the VFX graph stuff and see how to control the VFX simulation time, then bind it to my gameplay system