Elevator

From Blue Mars Developer Guidebook

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

Contents

Overview

The ARElevator entity allows an avatar to travel between two floors. It is a dynamic object, which means it is synchronized across clients (other clients will see the avatar riding the elevator), and can be activated after an avatar obtains "ownership". Upon activation, the elevator may need to travel to the floor it was called from, then the avatar is placed in the elevator, automatically taken to the other floor, and caused to exit through the front or rear of the elevator.

  • Usage is described in the Setup in City Editor section
  • The Lua script is included below as a dynamic object example (and as a starting point for developers who want to make their own versions - more than 2 floors, with different controls, etc.)

Setup in City Editor

The updated ARElevator entity will be included in the next release; this update is not available in the currently released City Editor.
1. Place an ARElevator entity in a level, found in the Rollup Bar Entity Browser under Elevators>AR

Image:elev_0_rollupbar.png


Properties

  • FloorHeight - default 3, the height difference between floor 1 and floor 2. We recommend a value of 25 or less, for the general scenario. A higher value may work, but due to the dynamic object behavior we don't officially guarantee it.
  • Model - the elevator model .cgf file
  • RearExit_F1 - default False, determines exit direction on bottom floor (F1)
  • RearExit_F2 - default False, determines exit direction on top floor (F2) - set to True if the avatar should exit through the rear
  • TripDuration - default 3
  • WalkAwayDistance - default 3, the distance the avatar is caused to walk away upon exit
    • tip: avoid too low a value to ensure a smooth exit
  • AttachOffset - default {x=0,y=0,z=0}, use this offset to specify where the avatar should stand, if different than the .cgf's pivot point
    • tip: if the avatar gets stuck in the model, increase the z offset to raise the avatar enough
  • syncPeriod - inherited dynamic object Property - if not defined in script, default is 1000 (1 update per second)

Image:elev_3_props1.jpg


2. Specify your elevator Model and FloorHeight, and determine the exit direction for each floor. The defaults should be used when the exit direction for both floors is out the front, shown above (RearExit_F1 and RearExit_F2 set to False). In this scenario, the avatar walks out the front on Floor 2, pictured below:

Image:elev_seq_front.jpg


If your elevator has a rear exit on Floor 2, set RearExit_F2 to True, shown below. Note that for a simple setup, the elevator does not have doors, and for a rear exit the simple solution would be to use a model that is open in both the front and the rear. This also allows continuous view of your avatar without changing the camera.

Image:elev_3_props2.jpg

Pictured below, the avatar walks out the rear on Floor 2:

Image:elev_seq_rear.jpg

Testing

The ARElevator should be tested in the client (BlueMars>Export>Check city in BlueMars from the Editor) rather than in the Editor. The Dynamic Object Testing section explains that dynamic objects do not work in the Editor without a network connection.


Flow Graph Setup

3. On each floor, the avatar needs a way to activate or call the elevator. This setup uses the ARItem (placed in a level from the Rollup Bar Entity Browser Items > AR > ARItem); the ARAvatarTrigger (set to OnlyLocalAvatar) could also be used. Make sure both events aren't triggered by the same item at the same time.
Each item's Select Output is connected to the respective ARElevator EnterFromFloor Input. In the above screenshots, the items use the demo box model.

Image:elev_1_fgInput.png


The ARElevator ArrivedFloor1 or ArrivedFloor2 Output is triggered when the elevator arrives at a floor carrying an avatar.
These optional outputs provide a way to trigger additional events upon changing floors.

Image:elev_2_fgInputOutput.png


After clicking an elevator floor item, the result is one of the following cases, further described in the Script section:

  • Case 1: if the elevator is on that floor, the avatar will take ownership if possible, ride to the other floor, exit and release ownership
  • Case 2: if the elevator is on the other floor, the avatar will take ownership if possible, call the elevator to the current floor, then ride to the other floor, exit and release ownership
  • Case 3: if another avatar is riding the elevator, the avatar will not take ownership; the "Another avatar is riding the elevator" message will be displayed



Script

This is the ARElevator script, which uses the dynamic object synchronization framework. It relies mainly on state synchronization (the state is controlled by the owner avatar riding the elevator, automatically sync'ing once per syncPeriod), along with the Synchronized Parameter t, which represents the traveling position and is sync'd with the owner 's value before traveling to the next floor (in Up and Down OnBeginStates).

Here's a description of what happens:

When a user clicks on the Floor1 item, the EnterFromFloor1 Event is triggered; if there is no current owner, this avatar takes ownership (going to the WaitForOwnership state until confirmed). Once ownership is confirmed we call UpdateState("F1") to cause an immediate state sync across clients to the F1 state, which calls the EnterFromFloor1 Event again. This time we have ownership, so we Enter the elevator, use the AttachAvatar function, and call UpdateState("Up") to cause all clients to send the elevator up. In another case, if the elevator is on Floor2, the bCallingFromF1 flag is set and the elevator is brought down before the avatar enters.
After reaching Floor2 in the Up state, the avatar Leaves, using the DetachAvatar function and walking away according to the Properties fWalkAwayDistance and bRearExit_F2 (whether Floor2 has a rear exit), and ownership is released.

Note: as described in Entity Properties, the Property bool is treated as 1 or 0 rather than true or false, but shows up in the Editor Properties as a True/False checkbox.

----------------------------------------------------------------------------
-- ARElevator - example of dynamic object
----------------------------------------------------------------------------

ARElevator = {
  Properties = {
    fileModel = "",
    vAttachOffset = {x=0,y=0,z=0},
    fFloorHeight = 3,
    fTripDuration = 3,
    fWalkAwayDistance = 3,
    bRearExit_F1 = 0, -- walk off direction on floor 1
    bRearExit_F2 = 0, -- walk off direction on floor 2 (1=through to other side)
  },
  
  States = {
    "F1", "Up", "Down", "F2", "WaitForOwnership",
  },
  
  Editor = {
    Model = "Editor/Objects/T.cgf",
    Icon = "ARBasicItem.bmp",
    ShowBounds = 1,
  },
  
  bCallingFromF1 = false,
  bCallingFromF2 = false,
  bWaitingForOwnership_F1 = false,
  bWaitingForOwnership_F2 = false,
  vPosF1 = {x=0,y=0,z=0},
  vPosF2 = {x=0,y=0,z=0},  
}

function ARElevator:SynchronizedParameters()
  return {t = self.t};
end

function ARElevator:Initialize()
  self.vPosF1 = {x=0,y=0,z=0}; --re-init
  self.vPosF2 = {x=0,y=0,z=0};
  CopyVector(self.vPosF1, self:GetWorldPos());
  self.vPosF2 = {x = self.vPosF1.x, y = self.vPosF1.y, z = self.vPosF1.z + self.Properties.fFloorHeight};
  self.t = 0;
  self.duration = self.Properties.fTripDuration;
  self:LoadObject(0, self.Properties.fileModel);
  self:Physicalize(0, PE_STATIC, {mass = 0, density = -1});
  self:GotoState("F1");
end

function ARElevator:OnSpawn()
  self:Initialize();
end

function ARElevator:OnReset()
  self:Initialize();
end

function ARElevator:OnPropertyChange()
  self:OnReset();
end

function ARElevator:Enter(avatar_ent, rearExit)
  self:AttachAvatar(avatar_ent);
  avatar_ent:SetWorldPos(SumVectors(self:GetWorldPos(),self.Properties.vAttachOffset));
  local vExitDir = {x=1,y=1,z=1};
  if (rearExit == 0) then vExitDir = {x=-1,y=-1,z=1} end
  avatar_ent:SetDirectionVector(ProductVectors(self:GetDirectionVector(), vExitDir));
  avatar_ent.ARAvatarAction:StartIdle();
  avatar_ent:EnableMove(false); --disable user navigation
end

function ARElevator:Leave(avatar_ent, floor)
  local pos = {x=0,y=0,z=0};
  local dir = -1;
  if (floor == "F1") then 
    CopyVector(pos, self.vPosF1);
    if (self.Properties.bRearExit_F1 == 1) then dir = 1 end
  elseif (floor == "F2") then
    CopyVector(pos, self.vPosF2);
    if (self.Properties.bRearExit_F2 == 1) then dir = 1 end
  end
  self:DetachAvatar(avatar_ent);
  avatar_ent.ARAvatarAction:StartIdle()
  local vWalkToPos = {x=0,y=0,z=0}; 
  local vWalkAway = {x=0,y=0,z=0}; 
  FastScaleVector(vWalkAway, self:GetDirectionVector(), self.Properties.fWalkAwayDistance*dir);
  FastSumVectors(vWalkToPos, pos, vWalkAway);
  FastSumVectors(vWalkAway, pos, ScaleVector(vWalkAway, .5));
  avatar_ent:SetWorldPos(vWalkAway);
  avatar_ent:EnableMove(true); --enable user navigation  
  avatar_ent.ARAvatarAction:StartWalk(vWalkToPos);
end

function ARElevator:ClearFlags()
  self.bCallingFromF1 = false;
  self.bCallingFromF2 = false;
  self.bWaitingForOwnership_F1 = false;
  self.bWaitingForOwnership_F2 = false;
end

----------------------------------------------------------------------------
-- States
----------------------------------------------------------------------------

ARElevator.F1 = {
  OnBeginState = function (self)
    self.t = 0;
    self:SetWorldPos(self.vPosF1);
    if (self:IsOwnedByLocalAvatar() and (self.bCallingFromF1 or self.bWaitingForOwnership_F1)) then
      self:Event_EnterFromFloor1(nil, MMO:GetLocalAvatarEntity());
    else
      self:ActivateOutput("ArrivedFloor1", true);
    end
  end,
}

ARElevator.Up = {
  OnBeginState = function (self)
    if (not self:IsOwnedByLocalAvatar()) then
      self.t = self.SyncParameters.t or self.t;
    end
    self:SetTimer(101, 250);
  end,
  OnTimer = function (self, timerId)
    if (timerId == 101) then
      self:Activate(1);
    end
  end,
  OnUpdate = function (self, time)
    self.t = self.t + time
    if (self.t < self.duration) then
      local pos = vecLerp(self.vPosF1, self.vPosF2, self.t / self.duration);
      self:SetWorldPos(pos);
    else
      self:GotoState("F2");
    end
  end,
  OnEndState = function (self)
    self:Activate(0);
    if (self:IsOwnedByLocalAvatar() and not self.bCallingFromF2) then
      self:ReleaseOwnership();
      self:ClearFlags();
      self:Leave(MMO:GetLocalAvatarEntity(), "F2");
    end
  end,
}

ARElevator.Down = {
  OnBeginState = function (self)
    if (not self:IsOwnedByLocalAvatar()) then
      self.t = self.SyncParameters.t or self.t;
    end
    self:SetTimer(102, 250);
  end,
  OnTimer = function (self, timerId)
    if (timerId == 102) then
      self:Activate(1);
    end
  end,
  OnUpdate = function (self, time)
    self.t = self.t - time;
    if (self.t > 0) then
      local pos = vecLerp(self.vPosF1, self.vPosF2, self.t / self.duration);
      self:SetWorldPos(pos);
    else
      self:GotoState("F1");
    end
  end,
  OnEndState = function (self)
    self:Activate(0);
    if (self:IsOwnedByLocalAvatar() and not self.bCallingFromF1) then
      self:ReleaseOwnership();
      self:ClearFlags();
      self:Leave(MMO:GetLocalAvatarEntity(), "F1");
    end
  end,
}

ARElevator.F2 = {
  OnBeginState = function (self)
    self.t = self.duration;
    self:SetWorldPos(self.vPosF2);
    if (self:IsOwnedByLocalAvatar() and (self.bCallingFromF2 or self.bWaitingForOwnership_F2)) then
      self:Event_EnterFromFloor2(nil, MMO:GetLocalAvatarEntity());
    else
      self:ActivateOutput("ArrivedFloor2", true);
    end
  end,
}

ARElevator.WaitForOwnership = {
  OnBeginState = function (self)
    self:Activate(1);
  end,
  OnUpdate = function (self, time)
    if (self:IsOwnedByLocalAvatar()) then
      if (self.bWaitingForOwnership_F1) then
        if (self.bCallingFromF1) then
          self:UpdateState("Down");
        else
          self:UpdateState("F1");
        end
      elseif (self.bWaitingForOwnership_F2) then
        if (self.bCallingFromF2) then
          self:UpdateState("Up");
        else
          self:UpdateState("F2");
        end
      end
    end
  end,
  OnEndState = function (self)
    self:Activate(0);
  end,
}

----------------------------------------------------------------------------
-- Flow node definition
----------------------------------------------------------------------------

function ARElevator:Event_EnterFromFloor1(sender, avatar_ent) --board or call elevator from Floor 1, then automatically go to Floor2
  dump {["ARElevator:Event_EnterFromFloor1"] = {self = self:GetName (),
    sender = sender,
    avatar_ent = avatar_ent and avatar_ent:GetName ()}}
  if (self:IsOwnedByLocalAvatar()) then
    local currState = self:GetState();
    if (currState == "F1") then --elevator is here
      self:Enter(avatar_ent, self.Properties.bRearExit_F2);
      self:UpdateState("Up");
    elseif (self:GetState() == "F2") then --bring down to F1
      self.bCallingFromF1 = true;
      self.bCallingFromF2 = false;
      self:UpdateState("Down");
    end
  else
    if (not self.bWaitingForOwnership_F1) then
      if (not self:GetOwner()) then
        self:TakeOwnership();
        self.bWaitingForOwnership_F1 = true;
        if (self:GetState() == "F2") then
          self.bCallingFromF1 = true;
          self.bCallingFromF2 = false;
        end
        self:GotoState("WaitForOwnership");
      else
        ARDebugMessage("Another avatar is riding the elevator");
      end
    end
  end
end

function ARElevator:Event_EnterFromFloor2(sender, avatar_ent) --board or call elevator from Floor 2, then automatically go to Floor1
  dump {["ARElevator:Event_EnterFromFloor2"] = {self = self:GetName (),
    sender = sender,
    avatar_ent = avatar_ent and avatar_ent:GetName ()}}
  if (self:IsOwnedByLocalAvatar()) then
    local currState = self:GetState();    
    if (currState == "F2") then --elevator is here
      self:Enter(avatar_ent, self.Properties.bRearExit_F1);
      self:UpdateState("Down");
    elseif (self:GetState() == "F1") then --bring up to F2
      self.bCallingFromF1 = false;
      self.bCallingFromF2 = true;
      self:UpdateState("Up");
    end     
  else
    if (not self.bWaitingForOwnership_F2) then
      if (not self:GetOwner()) then
        self:TakeOwnership();
        self.bWaitingForOwnership_F2 = true;
        if (self:GetState() == "F1") then
          self.bCallingFromF1 = false;
          self.bCallingFromF2 = true;
        end
        self:GotoState("WaitForOwnership");
      else
        ARDebugMessage("Another avatar is riding the elevator");
      end
    end  
  end
end

ARElevator.FlowEvents = {
  Inputs = {
    Event_EnterFromFloor1 = {ARElevator.Event_EnterFromFloor1, "entity"},
    Event_EnterFromFloor2 = {ARElevator.Event_EnterFromFloor2, "entity"},
  },
  Outputs = {
    ArrivedFloor1 = "bool",
    ArrivedFloor2 = "bool",
  },
}

----------------------------------------------------------------------------
-- Setup dynamic object functions
----------------------------------------------------------------------------
ARDynamicObject.Setup(ARElevator)
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