Bowling Spectators

From Blue Mars Developer Guidebook

Jump to: navigation, search
There are security restrictions on this article


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)


In the updater, we check ownership and release ownership at the end of a game with


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()
  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}

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);
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);

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.av = self.SyncParameters.av;
      self.ballMtl = self.SyncParameters.ballMtl;
      self.ballName = self.SyncParameters.ballName;
      if (self.av) then
       self.avEnt = System.GetEntityByName(self.av);
      if (self.ballName) then
        self.ball = System.GetEntityByName(self.ballName);
      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;
  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;
      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});

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);

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

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
  elseif (state == "SitInChairs") then
  elseif (state == "Aiming") then

Problems with this wiki page? Contact us either by: Support Email or Support Ticket System

Blue Mars Guidebook Privacy Policy
Blue Mars Guidebook Community Guidelines

Personal tools