2024-01-22 - Bridge portals animations
Here we go with another exciting Particular Reality week.
We have bridge portals that can lock and unlock, now we need to handle
loading of the target zone
use of the portal when the loading is complete
The "use", at least in terms of logic (I'm thinking about something different for the visualization) should be similar to what we already have with the "normal" portals. So, let's focus on the loading.
In terms of animation, we need to take into account that a wait of unknown time is involved.
To avoid glitching and jumps, but at the same time keep the interaction responsive, we need to be smart and impose some restrictions on the animations.
Let's consider two boolean inputs:
`
LNeeded
`: loading needed, `true
` or `false
` depending on if access to the portal is desired (the player is properly positioned/looking in the right direction)`
LDone
`: loading completed, `true
` only when all the data needed to render "the other side" of the portal is ready
For now, I'm going to have a very brief "pop" (a scale change with an easing function) of the green hexagon to signal to the player that they can activate the portal (or not).
Then, I'm going to have the color of the "portal placeholder hexagon" pulsate between green and white while the loading is in progress. If the loading is aborted, I will stop the pulsation on green. If instead it completes, I will stop with a white flash and the disappearance of the placeholder (at that point, I will show the actual opened portal, containing the render of the adjacent zone).
If the player stop showing interest for the portal (moving/looking towards other platforms), I will quickly make the placeholder reappear, fading in to green, and stop there. At the same time, independently, the pop will be reversed.
Note that on the "happy path", when everything is already loaded, we will skip the loop between `green
` and `white
`, and we'll have a single `green -> white -> invisible
` sequence.
Otherwise, we'll have a series of pulsations that, in any case, will end correctly.
An important detail is that we need to have two concurrent sub-state machines running: they both take the same input signals, but execute independently.
To simplify things, we could linearize the execution and make it so the "loading pulse" can only happen after the pop-out, and that the pop-in can only execute after `green
` has been reached. But that would make everything more clunky. To implement satisfying gameplay, it's important to have the player input affect as soon as possible the game state, especially in VR, so I'm taking some extra care.
So, let's start implementing the scale change and see what happens.
While implementing the "pop" scale change, I got annoyed by the fact that the logic was essentially the same already implemented for the portal lock/unlock: one boolean input, two main states and two transition states that can switch between each seamlessly.
So, I decided to factor out that logic to a separate FSM that I can use twice (and probably elsewhere, in the future).
This is how `ReversibleAnim
` and `ReversibleAnimStateManager
` were born.
I hooked up the `LNeeded
` input to the `L
` key, and this is the result:
Notice that the scale change and the unlock can work in parallel.
2024-01-23 - State machine parameters
As I explained yesterday, I factored out the `ReversibleAnim
` logic to handle that kind of thing using the same state machine in different contexts.
But I didn't really complete the job.
As explained in the past, I divided between state machine definition and execution, where one can have multiple executions of the same state machine acting on different data.
In the game state (and, consequently, in the save game) I only store the data, while the definition is part of the hardcoded game logic.
But factoring out the `ReversibleAnim
` logic a limitation of my current implementation surfaced: I wanted to be able to use the same state machine definition, in terms of states, input signals and transitions, but I wanted a bit of customization, namely the possibility to use different speeds for the state transitions.
If I have two `ReversibleAnimStateManager
` for a `BridgePortal
`, one handling the scale change and one handling the unlock, I need to be able to configure the two instances so that, maybe, the pop is faster than the unlock, or things like that.
The lame fix, if I didn't want to touch my implementation, would have been storing the speeds into the data, which was accessible from the FSM execution.
But I didn't consider doing it for more than 2 seconds: it would work, but it wouldn't make sense and would waste savegame space with data that really shouldn't be there. So let's try something different.
I extended my `FSMExecution
` class so that it holds not only some data but also optional parameters.
When I initialize the two `ReversibleAnimStateManager
`, I pass them not only different data items (part of the game state), but also different parameters (the different transition speeds to use).
While conceptually simple, the change required quite a bit of refactoring in many places.
Let's see just the part where the discussed setup happens:
public BridgePortalStateManager(BridgePortalFSMExec rExec)
: base(new BridgePortalFSM("BridgePortalFSM", rStates), rExec) {
ReversibleAnimFSMExecData rLockingFSMExecData =
rExec.getExecData().getData().m_rLockingFSMExecData;
m_rLockingStateManager =
new ReversibleAnimStateManager(
new ReversibleAnimFSMExec(
rLockingFSMExecData,
new ReversibleAnim.Params(
Params.fLOCKING_SPEED, Params.fUNLOCKING_SPEED
)
)
);
ReversibleAnimFSMExecData rPopFSMExecData =
rExec.getExecData().getData().m_rPopFSMExecData;
m_rPopAnimStateManager =
new ReversibleAnimStateManager(
new ReversibleAnimFSMExec(
rPopFSMExecData,
new ReversibleAnim.Params(
Params.fSCALE_UP_SPEED, Params.fSCALE_DN_SPEED
)
)
);
}
I hoped to also implement the "pulsation" state machine, but I'm out of time. Tomorrow is the day!
2024-01-24 - Target zone loading loop
After the work done in the last two days, we're ready to implement the "pulsation" animation that will be bound to the target zone loading.
We already defined the basic logic:
Now, let's add more details so that we can implement the state machine properly. I'm going to use `G
` for `green
`, `W
` for `white
` and `H
` for `hidden
`.
A transition state between `green
` and `white
` will be labelled `G_to_W
`, etc. We're going to use two values, `fG2WProgress01
` and `fW2HProgress01
`, to track the progress during such states.
Two simple notes about the diagram:
I've simplified the `
(LNeeded && !LDone) || !LNeeded
` predicate to the equivalent `!LNeeded || !LDone
`Notice that there's no transition from `
W_to_G
` to `G_to_W
`: while similar to it, the first part of the diagram (between `G
` and `W
`) is not exactly a `ReversibleStateAnim`: the fade between white and green must always complete, or the state machine can't loop properly
Let's try implementing this and see how it turns out.
Implementing the behaviour took a while, but it feels quite stable and responsive.
I added another test key, `J
`, which flips a flag indicating if the loading is complete.
So now:
`
U
` locks/unlocks portal`
L
` indicates if we are interested in using the portal (`LNeeded` flag)`
J
` sets/resets the `LDone` flag
Let's see a short video.
Notice that I added some logic preventing the placeholder scaling when the portal is locked, so to start testing the loading I must first unlock the portal with `U
`.
Then, I express interest in using the portal pressing `L
`. If I keep it pressed, the placeholder pulsates between green and white.
When I finally press `J
` to indicate that the loading has been completed, at 00:25, the placeholder completes a transition to `white
` and then disappears.
If I leave the `LDone
` flag set, the placeholder never loops back from `white
` to `green
` when I press `L
` (see 00:28), and the execution follows the happy path with minimum wait.
At 00:36, I reset `LDone
` pressing `J
` again, and we go back to the green/white pulsation.
The last missing piece of the puzzle is the actual portal, which should appear behind the green placeholder and, according to what I have in mind, be pulled to the player with the portal activation gesture. Will I manage to complete all this this week? We'll see.
2024-01-25 - Portal opening vs activation
Today I'm going to work on the actual portal.
Until now, I've had a single portal moving around with the player, working both as portal between adjacent platform in the same zone, and between bridge platforms between two zones.
Now, I need to make it possible for that single portal to behave differently when it is acting as a "bridge portal".
Basically, the item I've been working on in the last few days can be considered as a portal "cover": when the loading is done, it disappears, and the actual (single) portal shows up from behind it.
This can sound a little convoluted, but it makes sense. The player can only use one portal at a time. And this is also important for performance reasons, as rendering a portal requires a bit of extra rendering work (that might change in the future, using a stencil buffer based implementation).
So, I'm using a single portal, moving it around where is needed. But I have the portal "cover" objects for the bridge portals, communicating different things to the player:
is the passage locked or unlocked?
if unlocked and selected, is the target zone ready or still loading?
Let's get started.
Ok, I somewhat did it!
It needed a bit of refactoring here and there, but I have decoupled the portal "opening" in terms of visibility from the portal "activation" tied to the player gesture.
While for a "normal" portal (between platforms in the same zone) the portal opens when the player activates it, for a bridge portal I want it to stay open (when the target zone is ready), so that the player can get a glimpse of the other side.
This poses the question "so what does the gesture controlled activation do"?
As I anticipated, my idea is "pulling" the portal towards the player, so that it reaches the middle of the current platform as usual. Unless I overlooked something, I should get it done tomorrow.
It's late, so no test video today, sorry!
2024-01-26 - Portal pulling
The portal "pulling" should be quite easy, as the logic is already there: it's the portal activation logic. I just have to use that 0-1 value to also control the "movement" of the portal from a resting point (the center of the adjacent platform in the other zone) to the center of the player platform.
It worked as expected, and I quite liked the effect.
Actually, while trying it, I decided that it also works somewhat better for the "normal" portals to have them quickly originate from the target platform. In that case, at least for now, I'm keeping the portal scaling animation too.
As it always happens, new ideas appear while trying out things, and once in a while a happy accident can show you something that makes you think "of course it's better, that's how I should have done it since the beginning".
Now, if I manage to bind it to the gameplay logic, dropping the use of test keys, I could end the week with a proper VR test.
Ok, I wrote a bunch of dirty code that I'm going to tidy up next week, but I have it working!
Let's see all the pieces working together in a short clip:
There's a couple of minor glitches (not shown in the video), but I'm quite happy of the result.
It doesn't show the unlocking (I had no logic to unlock the portals in VR, so I started with everything unlocked), but at 0:04 and at 0:24 you can see the important bit: the loading flash with the disappearance of the cover leaves the actual portal, automatically opened, visible.
At that point, the activation gesture pulls the portal towards the player platform, so that they can walk into it.
Let's recap:
if the player is on a bridge platform, the target zone loading starts
depending on the player locomotion direction, a normal portal or a bridge portal can be available
if a normal portal is available, the activation gesture opens it and pulls it
if a bridge portal is available, the portal cover disappears as soon as the target zone loading is complete, showing the opened portal, which can then be pulled with the activation gesture
What's missing? The zone loading is still unchanged, and so it's instant and synchronous. This is why we get a single white flash of the portal cover, with no looping pulsation.
I need to work on that, and also do a bit of refactoring and clean-up, removing bugs and glitches and making the code prettier and more robust.
Next week I have something coming up, so (unless I change my mind) see you in two weeks!