From Blue Mars Developer Guidebook

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



The overall flow of the bowling game is diagrammed below. We'll highlight specific features that are generally applicable to the creation of minigames, rather than stepping through the whole script which is available in the sample data on the Tool Download page. Please see the Golf page for a complete minigame code walk-through, and Multiplayer Golf for details on how the multiplayer communication works. The accompanying level setup is explained on the Bowling Level Setup page.

The Bowling minigame demonstrates the following:

  • Starting up an in-city game (as opposed to golf, which automatically starts upon loading the golf level)
  • Accessing avatars with Multiplayer_Minigame_API functions
  • Updating spectators with game events by integrating a Dynamic_Object with the minigame, the "bowling updater"
  • Using a character attachment and animation timing to manage holding and throwing the bowling ball
  • Linking entities to the minigame entity and using Entity functions to get pins, chairs, and shape areas
  • Physics simulation: using Entity functions

We register the game scripts (single player, master and multiplayer, along with the supervisor, updater and bowling camera) with .ent files in our Entities directory. These XML files point to the location of the .lua files, in this case Levels/AR/Common/Scripts/Entities/Minigames/Bowling/.


General in-city minigame startup

The difference between bowling and golf boils down to how the game is triggered and whether it is started in a level that contains an ARDefaultCamera. Bowling is launched from and takes place within Beach City, in contrast to the way Golf is automatically launched upon loading a golf level. And since the golf levels do not contain an ARDefaultCamera, avatars are not automatically spawned or controlled, and the standard HUD is not automatically displayed.

The ARBowlingSupervisor, an example of a minigame supervisor, is placed in the level and manages initialization and game startup. An ARAvatarTrigger is placed near the bowling lane and used to trigger a dialog to launch bowling. After single player or multiplayer mode is chosen and the subsequent player entity (ARBowlingGame_SP, or ARBowlingGame_MP) is spawned by the supervisor.

Accessing avatar entities

The Multiplayer_Minigame_API convenience functions GetLocalPlayerNumber and GetPlayerCount report a client's player number and the number of players in the game, while GetAvatarData is used to find each player's AvatarID in ARBowlingGame_MP:GetEntities():

function ARBowlingGame_MP:GetEntities()

  --get avatars
  self.partySize = self:GetPlayerCount();
  self.myPlayerNum = self:GetLocalPlayerNumber();
  for i = 1, self.partySize do
    local data = self:GetAvatarData(i);
    self.Bowler[i].info.ClientID = data.ClientID;   
    self.Bowler[i].info.AvatarID = "avt" .. data.ClientID;
    self.Bowler[i].info.Name = data.Name;
    self.Bowler[i].info.Gender = data.Gender;
    self.Bowler[i].chair = self.svEnt:GetChairFromSV(i);

Then in SetupPlayer, shown below, we use MMO:GetLocalAvatarEntity to get the local avatar (if the player number matches this client's player number), or we use the AvatarID and System GetEntityByName to access the other players' avatar entities.

Note: outside of the Multiplayer_Minigame_API supervisor setup, you can also get the local avatar entity with MMO:GetLocalAvatarEntity, or with MMO:GetLocalCharacterData which returns a table that includes Entity along with Name, ShortName, ClientID, AvatarID, Gender, etc.

Disabling standard navigation

During the game, the avatar is controlled by the script so the standard navigation (moving via mouse or keyboard actions) is disabled in the Start state with EnableMove:


Later this is reversed in the Finish state:


Avatar control settings: Animation and Look

An automatically spawned avatar will look toward the current view camera and other avatars according to proximity, and be controlled by the default animation control (auto-idling, etc). To disable the default animation control (allowing game-specific animations to play without interference), and the look settings, we use EnableAvatarAnimCtl, ARAvatarAnim:SetLookCamera and ARAvatarAnim:SetLookAround:

function ARBowlingGame_MP:SetupPlayer(i)
  if (i == self.myPlayerNum) then 
    self.Bowler[i].player = MMO:GetLocalAvatarEntity();
    self.Bowler[i].player = System.GetEntityByName(self.Bowler[i].info.AvatarID);
  self.Bowler[i].player.ARAvatarAnim:SetLookCamera(false); --turn off auto look toward current view camera
  self.Bowler[i].player.ARAvatarAnim:SetLookAround(false); --turn off auto look at other avatars
  self.Bowler[i].player:EnableAvatarAnimCtl(false); --disable default animation control

These settings are re-enabled in the ARBowlingGame_MP.Finish state:

for i = 1, self.partySize do
  self.Bowler[i].player:EnableAvatarAnimCtl(true); --enable default animation control
  ActionMapManager.EnableActionMap("ARBowling", false);
  self.Bowler[i].player.ARAvatarAnim:SetLookCamera(true); --turn on look at camera
  self.Bowler[i].player.ARAvatarAnim:SetLookAround(true); --turn on look around & at other avatars

Entity Links

GetLinkTarget setup: Minigame entity needs to target objects

The bowling supervisor entity, placed in the level has links to pins and chairs. Entity GetLinkTarget is used to retrieve the pins and chairs link targets:

These pin and chair link targets were created in the City Editor by first selecting the bowling supervisor entity, then clicking "Pick Target" (in the supervisor's Entity Links section of the RollupBar), and then selecting the pin or chair so it appears in the list. Finally, the Link Name is changed to "pin" for each pin, and "chair1"-"chair4". Side note: we can't use System.GetEntityByName to get a chair entity with the name that appears in the Editor (they are renamed for uniqueness), so this another method of access a chair by script.

Here we demonstrate both methods of calling Entity GetLinkTarget with a particular name (chair1), and looping through a list of links with the same name (pin):

function ARBowlingSupervisor:GetChairs()
  for i = 1, 4 do
    local link = self:GetLinkTarget("chair"..i);
    self.chairs[i] = link;
function ARBowlingSupervisor:GetPins()
  local i = 0; local num = 0; local name = "";
  local link = self:GetLinkTarget("pin", i);
  while (link) do
    name = link:GetName();
    num = tonumber(name:sub(-2, -1));
    self.pins[num] = {
    i = i+1;
    link = self:GetLinkTarget("pin", i);

IsEntityInsideArea setup: Shape Area needs to target minigame entity

Bowling uses Shape Areas to delimit a lane's gutters, pit, etc. Shape areas (RollupBar>Objects Tab>Area>Shape) link to the supervisor entity, with a slightly different setup than the Entity Link Target. The bowling Shape targets were created in the City Editor by first selecting the Shape, then clicking "Pick" (in the Shape Parameters' Target section of the RollupBar) to select the supervisor entity. Then the supervisor entity can use the Entity IsEntityInsideArea to check if any entity is inside a linked Shape Area with a particular AreaId. In this code, the single player game entity calls IsEntityInsideArea on the supervisor entity (since that's the game entity placed in the level and linked to the Shape).

ARBowlingGame_SP.ThrowBall =
  OnUpdate = function(self,time)
    if (self.svEnt:IsEntityInsideArea(AreaId, then
      --ball is in the gutter

Character attachments and animation

Bowling ball attachment

The bowling ball is initially attached to the avatar's right hand (Bip01 Prop1 bone) with the Entity CreateBoneAttachment, SetAttachmentCGF, and SetAttachmentMaterial functions:

function ARBowlingGame_SP:LoadAttachedBall()
  self.Bowler[1].player:CreateBoneAttachment(0, "Bip01 Prop1", "bowling_ball");
  self.Bowler[1].player:SetAttachmentCGF(0, "bowling_ball", "levels/ar/common/objects/bowling/bowling_ball.cgf");
  self.Bowler[1].player:SetAttachmentMaterial(0, "bowling_ball", self.ballMtl);

SetAttachmentCGF initially hides the ball attachment, and thereafter we hide and show the ball in SwapBall with HideAttachment while we do the opposite with the "thrown" ball. We also use Entity GetBonePos and GetBoneDir so the two match up for the swap:

function ARBowlingGame_SP:SwapBall(bThrowing)
  if (bThrowing) then --hide attached ball and unhide rolling ball
    self.ball:SetWorldPos(self.Bowler[1].player:GetBonePos("Bip01 Prop1"));
    self.ball:SetDirectionVector(self.Bowler[1].player:GetBoneDir("Bip01 Prop1")); --line up ball w/attached ball
    self.Bowler[1].player:HideAttachment(0, "bowling_ball", true, true);
  else  --hide rolled ball and unhide attached ball
    self.Bowler[1].player:HideAttachment(0, "bowling_ball", false, false);

Bowling throw animation

Precise timing is achieved with Entity GetAnimationTime in the sequence of playing the throw animation, hiding the attached ball, and unhiding and applying an impulse to the thrown ball. We separate this into a few states, PerformThrow, Backswing, and ThrowBall to be sure the proper animation time is returned and to transition to a new state (to handle the throw) when that time is reached.

  • The animation is played in PerformThrow's OnBeginState, and we transition to the Backswing state after a short timer. If there is any blending into the animation, GetAnimationTime will not report an accurate animation time for the new animation until after that blending transition is over. Though blendTime is 0 in this example, we'll cover either case.
ARBowlingGame_SP.PerformThrow = --timing for avatar throw animation. allow time for blend out of current anim
  OnBeginState = function(self)
    local animFile = "levels/ar/common/animations/human/" .. self.Bowler[1].player:GetAvatarBaseModel() .. "/minigames/bowling/" .. self.Anims.throw;
    self.Bowler[1].player:StartAnimationAR(animFile, {blendTime=0, speed=1, loop=false});
    self:SetTimer(self.BLENDED_TIMERID, 300);
  OnTimer = function(self, timerId)
    if (timerId == self.BLENDED_TIMERID) then

Side note on new animation file system: you may have seen earlier examples of StartAnimationAR which reference an animation name in a .cal file, rather than an animation file as shown here. This usage demonstrates new system which allows developers to directly play an animation .dba file. Here, self.Anims.throw = "ARBOWL_bowl_and_walkback.dba", and GetAvatarBaseModel() returns either Female00 or Male00 to correspond to directory structure according to avatar gender.

  • In the Backswing state, we can be confident that the throw animation is the only one playing, so we check GetAnimationTime in the OnUpdate callback and go to the ThrowBall state when the animation has passed a specific time. See these golf notes for tips on finding this time in the Character Editor.
ARBowlingGame_SP.Backswing =
  OnUpdate = function(self,time)
    local theTime = 0;  
    if (self.Bowler[1].player:IsAnimationRunning(0,0)) then
      theTime = self.Bowler[1].player:GetAnimationTime(0,0);
      if (theTime > 2.26) then

  • Finally, we swap the thrown ball for the attached ball and apply a physics impulse in the ThrowBall state:
ARBowlingGame_SP.ThrowBall =
  OnBeginState = function(self)
function ARBowlingGame_SP:Throw(impulseDir)
  self.ball:AddImpulse(-1, nil, impulseDir, self.impulsePow, 0, impulseDir, self.spinImp);

Physics simulation: manipulating the bowling ball and pins

Ball roll

Entity AddImpulse is used in the ThrowBall state to "fake the curve". According to user input (aim, spin and speed), we apply one main initial impulse when the ball leaves the avatar's hand, and smaller repeated impulses to add spin after the "breaking point" as it continues down the lane (angular impulses in an OnUpdate callback). Note that the dynamic object bowling updater handles the subsequent angular impulses, to allow updated information about the throw to be communicated to spectators not participating in the game. See below for more details regarding bowling's use of the dynamic object.

AddImpulse(partId, pos, dir, impulse, scale, angularImpulse, massScale) usage:

Here's the initial impulse we saw in the Throw function above

self.ball:AddImpulse(-1, nil, impulseDir, self.impulsePow, 0, impulseDir, self.spinImp);

Here are the subsequent angular impulses which create the curve (in ARBowlingUpdater.ThrowBall.OnUpdate)

self.ball:AddImpulse(-1, nil, g_Vectors.v000, 0, 0, self.vThrowFwdDir, self.spinImp * .8);

And since we know whether the ball is in the gutter from the area check, we speed it toward the pit in that case

self.ball:AddImpulse(-1, nil, self.vAlleyDir, 2, 0);

Enabling and disabling pin physics

Pins physics is enabled and disabled with Entity EnablePhysics to limit unwanted interaction with the pins. Below is our EnablePins function which we call to enable physics in the PerformThrow state and disable physics in the ResetAfterThrow and Finish states.

function ARBowlingGame_SP:EnablePins(bEnable)
  for i,v in pairs(self.pins) do

Spectators: integrating a Dynamic Object with the game script

The Bowling Spectators page continues with details about using the Dynamic Object synchronizing framework to allow spectators to watch a bowling game.

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