2024-08-12 - VFX custom simulation space
I enjoyed preparing the special post and clip montage last week, looking back at one year of development, but now it's time to get back at work.
Since week 20, I've been reintroducing the enemies into the game, following the same principle where I swap out a crude, throwaway implementation done for the PoC, for a new, robust and well engineered implementation.
At the end of week 23, I was faced with a problem: I do an "invisible" move of the game world elements when entering a bridge platform, so that the current zone is always roughly at the world origin. But this movement impacts the calculation implicitly done in the visual effects, causing a glitch in the particles update. At least, that's my assumption, but I'm reasonably sure that's the problem.
Let's see what options do I have, and pick one.
ignore the issue: it's a visualization thing, so I could live with it and go back at it later, when working on the presentation
change the gameplay to hide the issue: make the enemies appear only after the player enters the zone, so they would be hidden during the zone repositioning
remove the zone repositioning: I did it thinking about the classic problem of float precision degrading with distance from zero, and to ease debugging in the editor. But it's not a "real" fix - ultimately, I'd like to use fixed point arithmetic and have deterministic calculations for both time and space
handle the problem in the VFX: by passing as parameter the needed information (the velocity vector, or maybe the base transform of the zone) it should be possible to adjust the calculation so that it doesn't glitch
I think I'm going to try the last option. But why?
I don't want to ignore the issue, because this is not a glitch that would disappear just using prettier models and better visual effects down the road. I don't want to take gameplay decisions because of technical problems, so option two is also off the table. Option three would be great, but it would be a lot of work to implement fixed point arithmetic. And it would be wasted work if I end up ditching Unity (as I hope to do) at a later stage of development, or if they add it themselves (I read somewhere that it might get introduced to support deterministic physics in DOTS, or something like that).
Option four, instead, has these advantages:
gaining more experience with the VFX graph would be useful anyway
this kind of manual adjustment of the position could help me to also understand how to control the time delta of the simulation, something I wanted to do last week (I ended up "just pausing" the VFX).
Ok, enough writing and thinking, let's try to get it done.
Before trying to do it from scratch, I googled a bit and found out I'm not the only one looking to do this kind of thing. Actually, there's an example on the Unity forums that should show how to work around this limitation of the VFX graph. The "old" system, Shuriken, offered not only "local" or "world" simulation space, but also a third option, "custom", that let you specify a transform to use as basis for the particle system. Which is basically what I need to do with the "zone parent" node, which is the one I apply the offset to when entering a zone.
I tried the example and it should be doing what I want, but it's not straightforward to apply it to my enemy VFX, and it's getting late. I'm going to try it first thing tomorrow.
2024-08-13 - Playing (and failing) with the VFX graph
Let's play a bit with the VFX graph and see if I manage to apply the "custom simulation space" fix I found yesterday.
Unfortunately, it looks like it's "one of those days" where things just don't want to work.
I tried implementing the workaround in my enemy VFX, but it's not working properly.
After an afternoon of fiddling with nodes (which is something I hate), I'm not sure if I simply have some errors in my setup, or if what I'm trying to do is intrinsically wrong.
I think I'm almost there, but I'm at a point where to get it working with the workaround I have implemented, I should change the scene hierarchy so that the nodes with the `VisualEffect
` component attached aren't descendants of the zone nodes, and I hate the idea.
While I managed to decouple the transform that should affect the particle simulation (the local movement of an enemy inside a zone) from the transform that should not affect it (the transform of the zone, that I adjust when crossing a portal from one zone to another), the current node hierarchy gets in the way.
Still, I feel like this is taking too much time and is not bringing me a "clean" fix but an ugly mess. Some test nodes are disconnected, but this is a summary image of my miserable day:
These two days haven't been completely wasted, because I definitely understood some new things about the VFX graph. But I think it's time to take a step back, because I can foresee lots of additional work to have each effect implementing my dirty workaround.
What step back? Well, I think I will fall back to "option three": disabling the zone repositioning. I explained that it wasn't a real fix anyway, so I'll get rid of it and implement the fixed point arithmetic someday (not in the prototype).
2024-08-14 - Disabling the zone repositioning
I started the day by tidying up a bit after the last two days of struggle.
I committed my "almost done" version and then disabled the additional calculations I introduced, practically going back to the previous version of the VFX.
Now, it's time for node-editor detox and finally doing some progress.
Disabling the zone repositioning took a few minutes and, of course, avoided the glitch.
I put the feature behind a flag, so if I want to re-enable I just need to change a constant.
That might be useful for debugging, or if end up finding a clean fix on the VFX graph side of things.
// from WorldManagerBhv.cs
private void swapZones() {
Zone rPrev = m_rCurrentZone;
m_rCurrentZone = m_rAdjacentZone;
m_rAdjacentZone = rPrev;
if (PR_Defs.bZONE_REPOSITIONING_ON) {
Vector3 vOffset = -m_rCurrentZone.getOffset();
m_rAdjacentZone.adjustOffset(vOffset);
m_rCurrentZone.adjustOffset(vOffset);
}
m_iZoneLayerSwitch01 = 1 - getZoneLayerSwitch();
}
Now what?
Well, I added back the possibility to damage and destroy enemies. I think it's reasonable to also reintroduce the player health management, and have the enemies able to attack and destroy the player.
Before thinking about adding real "attacks", let's do what I did in the PoC: the enemies damage the player when they're very close.
As first step, I'm going to implement a test behaviour to send, via inspector, the input events to the player state manager. I did it for the enemies, as shown at the end of week 21, and I think I'm going to do it for each element that can be tested individually.
I started by duplicating the `TestEnemyBhv
` to a `TestPlayerBhv
` and changing it accordingly, but that showed an obvious opportunity for refactoring so that the common part of any test class is factored out to an abstract base class (`ATestBhv
`).
The refactoring was easy, but a small issue remains: the `RTLocation
` type I defined (wrapping a zone identifier and a platform identifier) can't be automatically serialized by Unity, so this is what I get into the inspector (I marked the problems with a red X):
Tomorrow I'll try to fix it implementing a custom "property drawer" or changing the definition of `RTLocation
` so that it can work with the default serialization.
2024-08-15 - Player refactoring
Well, it looks like removing the `readonly
` attribute from the fields of `RTLocation
` is enough to make the default Unity serialization work and have the field shown in the inspector.
I don't like it, but I can live with it, and it was quick.
I could probably keep the `readonly
` and customize the visualization in the inspector so that it is actually shown as a non-editable text field, and doesn't take multiple lines, but I won't spend time on a debug feature that will only be used by me. At least, not today!
About the use of `RTLocation
` parameter in the `teleport
` call activated by the ProButton: I will just expose two integers and convert to my custom data type inside the button code:
// from TestPlayerBhv.cs
[ProButton]
void teleport(int iZoneId, int iPlatformId) {
RTLocation dest = new RTLocation(
new Id<RTZone>(iZoneId),
new Id<RTPlatform>(iPlatformId)
);
getStateManager().teleport(dest);
}
And here's the updated panel.
During weeks 3/4 of advanced prototyping, as I explained at the time, I just ported the minimal code needed to have the player movement working. Then, when adding the body tracked avatar, I did a little more work in that area.
Now, finally, it's time to properly refactor the player management so that it fits the current architecture.
The movement and teleportation should keep working in the same way, and I will add new stuff to handle health and mana levels.
This will naturally flow into the handling of the "high level" game loop, because something will need to happen when the health level reaches zero. A game over? A restart from the latest checkpoint? A mandatory gameplay "rewind", Braid style? I don't know yet.
Anyway, let's get started.
I implemented the handling of the "input signals" (`adjustHealth
`, `adjustMana
`, `teleport
`) so that they correctly affect the player state, and checked (in editor) that they work properly, by attaching the `TestPlayerBhv
` to the player and using the buttons.
Now, I should be ready to add to the enemies logic the part which actually causes the health adjustment on the player entity.
Tomorrow I'm off for a day out, but I plan to make up for it on Saturday.
2024-08-17 - Enemy attacks
I'm going to implement the basic "energy leeching" attack I had in the PoC.
This should mean, essentially:
add the logic which lowers the player health when the enemy is close enough
update the presentation of the enemies so that they play another sound and change color while they're dealing damage
Let's go!
At least for now, in the enemy lifecycle state machine (discussed in week 21), I decided to simply split the `core
` state into:
`
looking_for_player
``
damaging_player
`
What logic did I implement?
In the `looking_for_player
`, the enemy simply steers towards the player. When the distance is less than a certain threshold, the state machine switches to the `damaging_player
`, which applies the damage and also keeps steering towards the player, but with different acceleration, so that it feels a bit "sticky" to the target.
On the presentation front, I just had to handle the new state, setting the VFX color to the player cyan (as if they're stealing the player health) and playing a different sound effect.
I also had to fix the calculation of the "enemy to player" vector, which was not properly implemented and broke after disabling the zone repositioning a couple of days ago.
That's basically it! Let' complete the week with a short video showing two enemy attacking and affecting the health bar on the left wrist (shown around second 10):
Next week I have to catch up with a few tasks, so I'm going to take a break from Particular Reality.
I should be back at work the following week, so stay tuned!