Bowling Spectators
From Blue Mars Developer Guidebook
|
|
Contents |
Spectators: integrating a Dynamic Object with the game scripts
In the Bowling game, the ARBowlingUpdater uses the Dynamic Object synchronizing framework to allow spectators to see what's going on in a lane. It is placed in the level and runs on each client (for both game players and spectators) and reacts according to whether the client is the owner (single player, or the current turn client in multiplayer), is in the multiplayer game, or is not the owner. This usage of the framework does provide for spectators but is a bit more complicated than for simpler objects like an elevator. Note that the original single and multiplayer scripts were developed before this framework was available, so the updater was integrated with already working scripts (which may or may not be an easier way to work). The updater primarily handles events for a spectator, and some responsibility shifted from the player script to the updater to facilitate sync'ing between updaters across clients.
Some chunks of game updating are handled fully or partially by the updater depending on whether the updater is running on the bowling player's client or the spectator's client:
- Controlling the ball ( player : initial impulse handled by player scripts and roll handled by updater, spectator : initial impulse and roll handled by updater)
- Resetting pins (only for spectators - handled by player script for players)
- Character animation (only for spectators - handled by player script for players)
Dynamic Object: Ownership
From the player scripts, we take and release ownership (referring here to the updater as dynObjEnt)
self.dynObjEnt:TakeOwnership(); self.dynObjEnt:ReleaseOwnership();
In the updater, we check ownership and release ownership at the end of a game with
self:IsOwnedByLocalAvatar(); self:ReleaseOwnership();
Dynamic Object: SynchronizedParameters
The SynchronizedParameters are automatically broadcast by the owner of the updater. Non-owners receive these parameters and refer to the SyncParameters table.
function ARBowlingUpdater:SynchronizedParameters()
System.Log(self:GetName()..":SynchronizedParameters");
self:GrabPinsDown();
return {ballName = self.ballName, ballPos = self.vBallPos, breakPtDist = self.breakPtDist,
spinImp = self.spinImp, vThrowFwdDir = self.vThrowFwdDir,
impulsePow = self.impulsePow, ballVelocity = self.vBallVelocity,
bInGutter = self.bInGutter, pin_info = self.pin_info,
av = self.av, vAimDir = self.vAimDir, vAimPos = self.vAimPos,
ballMtl = self.ballMtl, bInUse = self.bInUse,
obsvrEnts = self.obsvrEnts}
end
Dynamic Object: UpdateState
When the player (also the owner of the updater) starts a turn on one client, its player script sends its updater info and causes the updater to sync some specific params and change states. UpdateState is similar to Entity GotoState, but it also synchronizes the state change across clients:
ARBowlingGame_SP.PrepareToThrow =
{
OnBeginState = function(self)
...
if (not System.IsEditor()) then
self.dynObjEnt:Aim(self.AvatarID, self.ball:GetName(), self.ballMtl);
end
...
function ARBowlingUpdater:Aim(avID, ballName, ballMtl) --sent from Plyr ent in PrepToThrow's OnBeginState
self.av = avID;
self.ballName = ballName;
self.ballMtl = ballMtl;
local params = {
ballName = self.ballName,
ballMtl = self.ballMtl,
av = self.av,
vAimDir = self.vAimDir,
vAimPos = self.vAimPos,
}
self:UpdateState("Aiming", params);
end
Dynamic Object: OnSync
A spectator (non-owner and not in MP game) stores parameters from the SyncParameters table received in the OnSync callback below; and we ensure certain SyncParameters have been updated upon coming to this state by including those params with UpdateState as illustrated above. Also, each time a dynamic object changes states, it is deactivated, so Activate must be called in order to receive the OnUpdate callbacks (included here to illustrate, but only necessary if an OnUpdate callback exists):
ARBowlingUpdater.Aiming =
{
OnBeginState = function(self)
if (not self:IsOwnedByLocalAvatar() and not System.IsEditor() and not self.bIsInMpGame) then
self:Activate(1);
self.av = self.SyncParameters.av;
self.ballMtl = self.SyncParameters.ballMtl;
self.ballName = self.SyncParameters.ballName;
if (self.av) then
self.avEnt = System.GetEntityByName(self.av);
end
if (self.ballName) then
self.ball = System.GetEntityByName(self.ballName);
end
...
self:PrepareAvatarAttachment(self.av, self.ballMtl);
self:SwapBall(false, self.avEnt);
local animFile = "levels/ar/common/animations/human/" .. self.avEnt:GetAvatarBaseModel() .. "/minigames/bowling/" .. self.Anims.idleAim;
self.avEnt:StartAnimationAR(animFile, {blendTime=0, speed=.9, loop=false});
self.bDidSitUponSyncAim = false;
end
end,
OnSync = function(self)
if (not self:IsOwnedByLocalAvatar() and not self.bIsInMpGame and not System.IsEditor()) then
CopyVector(self.vAimDir, self.SyncParameters.vAimDir);
if (not self.avEnt) then
self.av = self.SyncParameters.av;
self.avEnt = System.GetEntityByName(self.av);
self.ballMtl = self.SyncParameters.ballMtl;
end
if (self.avEnt:GetAnimationSpeed(0, 0) ~= .9) then
self:PrepareAvatarAttachment(self.av, self.ballMtl);
self:SwapBall(false, self.avEnt);
local animFile = "levels/ar/common/animations/human/"..self.avEnt:GetAvatarBaseModel().."/minigames/bowling/"..self.Anims.idleAim;
self.avEnt:StartAnimationAR(animFile, {blendTime=0, speed=.9, loop=false});
end
self.avEnt:SetDirectionVector(self.vAimDir);
...
}
While the updater remains in the Aiming state, a spectator continues to receive and set the aiming direction or the bowling player's avatar. The player's avatar position continues to be updated in the standard manner by the server throughout the game, so we don't need to set that. However, we do store that position to know the location of the throw's starting point.
Here's how the owner (player) sets its updater's aim vectors, vAimDir and vAimPos, so all updaters will receive the new aim data when the owner's SynchronizedParameters are broadcast and received OnSync.
--from ARBowlingGame_SP.PrepareToThrow.OnAction self.dynObjEnt:AimUpdate(true, self.Bowler[1].player:GetDirectionVector(), vPlyrPos);
function ARBowlingUpdater:AimUpdate(bSetPos, dir, pos)
CopyVector(self.vAimDir, dir);
if (bSetPos and pos) then
CopyVector(self.vAimPos, pos);
end
end
The ARBowlingUpdater.Aiming.OnSync above illustrates this use of vAimDir in setting the bowling avatar's direction.
Next, the player script (on the client playing the game) will call UpdateState in its updater when it's time to perform the throw, then all updaters will transition to the PerformThrow state.
Dynamic Object setup
The ARBowlingUpdater calls the Setup method to gain access to the dynamic object framework. At this point the syncPeriod is set, defined in the ARBowlingUpdater's Properties (333 = 3 OnSync's per second). If undefined, the default syncPeriod is 1000 (once per second). Note that this framework is intended to handle somewhere in the vicinity of 1 update per second, not significantly higher frequent updating.
--setup dynamic object functions ARDynamicObject.Setup(ARBowlingUpdater)
And the OnSync callback is translated into a state callback like this:
--store the original code
ARBowlingUpdater.OriginalOnSync = ARBowlingUpdater.OnSync
--overwrite & call OnSync in ThrowBall/SitInChairs/Aiming states
function ARBowlingUpdater:OnSync (state, params)
System.Log("ARBowlingUpdater:OnSync " .. state);
self:OriginalOnSync(state, params) --call the original ARDynamicObject function
if (state == "ThrowBall") then
self.ThrowBall.OnSync(self);
elseif (state == "SitInChairs") then
self.SitInChairs.OnSync(self);
elseif (state == "Aiming") then
self.Aiming.OnSync(self);
end
end

