fix(view): Implement state for user controlled camera to properly distinguish between scripted and user camera#2363
Conversation
…tinguish between scripted and user camera
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Include/GameClient/View.h | Added user control wrapper functions (userSetPosition, userSetAngle, etc.) using templates, renamed m_viewLockedUntilFrame to m_userControlLockedUntilFrame, added m_isUserControlled flag for state tracking |
| Core/GameEngine/Source/GameClient/View.cpp | Implemented isUserControlLocked() check using frame comparison, updated initialization and reset logic for new user control state members |
| Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp | Main implementation: uses squared length comparison for scroll speed, handles user vs scripted camera state transitions, updated reset() to use direct setters, added logic to treat non-user-controlled camera as scripted movement |
| Core/Libraries/Include/Lib/BaseType.h | Added lengthSqr() helper method to Coord2D for efficient squared length calculation (avoids sqrt) |
| GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp | Converted mouse-driven camera controls to user functions (userSetAngle, userZoom, userScrollBy), uses direct scrollBy() when resetting scroll to zero (non-user action) |
| GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp | Replay Player Camera now uses user functions to enable auto-zoom over terrain, changed lock function to lockUserControlUntilFrame(), added explicit scroll reset |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
Start([User Input: Camera Action]) --> CheckLock{isUserControlLocked?}
CheckLock -->|Yes| Blocked[Return false - Action Blocked]
CheckLock -->|No| StopScripted[stopDoingScriptedCamera]
StopScripted --> SetFlag[setUserControlled = true]
SetFlag --> Execute[Execute Camera Function]
Execute --> Return[Return true - Action Completed]
Script([Scripted Camera Action]) --> AddState[addScriptedState]
AddState --> SetScripted[setUserControlled = false]
SetScripted --> ScriptExecute[Execute Camera Function]
Update([W3DView::update]) --> CheckUserControlled{m_isUserControlled?}
CheckUserControlled -->|false| SetDidScripted[didScriptedMovement = true]
CheckUserControlled -->|true| CheckScriptedState{isDoingScriptedCamera?}
CheckScriptedState -->|true| SetDidScripted
CheckScriptedState -->|false| Normal[Normal Height Adjustment]
SetDidScripted --> PreserveHeight[Preserve Scripted Camera Height]
Last reviewed commit: 34f0439
| Coord3D arbitraryPos = { 0, 0, 0 }; | ||
| // Just move the camera to 0, 0, 0. It'll get repositioned at the beginning of the next game | ||
| // anyways. | ||
| resetCamera(&arbitraryPos, 1, 0.0f, 0.0f); |
There was a problem hiding this comment.
Changed from resetCamera to set function, because resetCamera is meant to be used by scripted actions and would set the user controlled flag to false, which we do not want here.
| m_heightAboveGround = m_currentHeightAboveGround; | ||
| } | ||
|
|
||
| const Bool isScrolling = TheInGameUI && TheInGameUI->isScrolling(); |
There was a problem hiding this comment.
Here I have decoupled the scrolling condition from TheInGameUI. This is necessary, because we cannot trust that the mouse input equals the actual camera scrolling, if the camera is allowed to block user input, like we do for the Replay Player Camera for every full logic frame.
| else | ||
| { | ||
| m_scrollAmount.x = 0; | ||
| m_scrollAmount.y = 0; |
There was a problem hiding this comment.
This now allows to set zero scroll speed, to signal that it is no longer scrolling. Previously it would just leave the last value and we relied on TheInGameUI->isScrolling() to tell whether it is scrolling.
| void W3DView::setZoom(Real z) | ||
| { | ||
| View::setZoom(z); | ||
| m_heightAboveGround = m_maxHeightAboveGround * z; |
There was a problem hiding this comment.
This is necessary, because the m_heightAboveGround setting drives the auto-zoom for terrain elevation. Function setZoomToDefault already sets m_heightAboveGround correctly.
| //------------------------------------------------------------------------------------------------- | ||
| void W3DView::setUserControlled(Bool value) | ||
| { | ||
| if (m_isUserControlled != value) |
There was a problem hiding this comment.
This condition does not make much sense right now but will do later because more code is added in the conditional body.
|
|
||
| if (!m_isUserControlled) | ||
| { | ||
| didScriptedMovement = true; |
There was a problem hiding this comment.
This means that the scripted camera state will prevail even if no script camera action is currently running, for as long as the user did no camera inputs. I cannot recall why this was necessary, but there are definitely gaps in Mission cutscenes where no scripted camera movements happen and it makes sense to keep the scripted state active for as long as the user is not controlling the camera.
| Real x, y; | ||
|
|
||
| Real length() const { return (Real)sqrt( x*x + y*y ); } | ||
| Real lengthSqr() const { return x*x + y*y; } |
There was a problem hiding this comment.
Refactor length to
return (Real)sqrt(lengthSqr())maybe?
There was a problem hiding this comment.
It is fine to have them independent of each other. Their code will never change.
|
|
||
| } | ||
|
|
||
| Bool View::userSetPosition(const Coord3D *pos) |
There was a problem hiding this comment.
I don't think the Bool value is ever used and can be made void instead.
In that case, I would also prefer a refactor reducing duplicate code
Bool View::userSetPosition(const Coord3D *pos)
{
if (userHasControl())
setPosition(pos);
}
Bool View::userSetAngle(Real radians)
{
if (userHasControl())
setAngle(radians);
}
Bool View::userHasControl()
{
if (isUserControlLocked())
return false;
stopDoingScriptedCamera();
setUserControlled(true);
return true;
}There was a problem hiding this comment.
I intentionally put the bool return because it communicates to the programmer that this function CAN fail to set the position.
There was a problem hiding this comment.
Amount of code for user camera functions reduced by using templates.
This change implements a new state for the user controlled camera to properly distinguish between scripted and user camera.
Originally, there is no 100% clear cut between user camera and scripted camera movements. To make a clear cut, all user input camera controls are now explicitly called through "user" functions that set a flag for the user controlled camera. This will be very relevant in later changes when different logic needs to apply in user and scripted camera, because the scripted camera is far more sensitive to changes and to preserve its original look there needs to be a very clear distinction.
The scripted camera state is set by doing one of the 5 scripted camera modes for waypoint path, pitch, zoom, angle and follow object.
m_viewLockedUntilFramewas changed tom_userControlLockedUntilFrame.m_viewLockedUntilFrameno longer worked correctly after #2280 and broke the Replay Player Camera, because the locked view then also applied to the Replay Player Camera, which of course was not intended. Instead, now user control functions are blocked only while the Replay Player Camera is active.Known issues
TODO