Creating a VR Teleporter: Part 3
The final step to our VR teleporter mechanic is to make the beam arc. This allows us to climb up to areas above our head.
The trick to this is to take our single beam picking mechanic and split it up into a lot of little segments, for both the intersection test and the visual display. I decided to make a kind of simple physics simulation for this, rather than using bezier curves or another method. The basic idea is you have a starting position and a velocity. You move a point along that velocity, and then add gravity to the velocity and repeat. You don't have to worry about resistance or anything like that. The result is a nice arc that mimics a ball thrown into the air.
--Loop through segments making an arc for n=1,self.maxbeamsteps do p1 = p0 + velocity * speed velocity.y = velocity.y + gravity end
The other feature I added was smooth movement during teleportation. It's very fast, taking less than a second, but it does a really good job of communicating motion. I myself am sensitive to motion sickness and am able to use this method of locomotion with no problems.
--Update offset position local pos = VR:GetOffset() local d = self.targetoffset:DistanceToPoint(pos) local speed = 1.0 if speed>d then speed=d end pos = pos + (self.targetoffset - pos):Normalize() * speed VR:SetOffset(pos)
I also decided to make the teleportation beam and indicator green or red, depending on its state, because the meaning of that is universal and easily understood. Here's the complete script, in only 150 lines of code:
function Script:Start() --Initialize VR if VR:Enable()==true then VR:SetTrackingSpace(VR.Roomspace) else System:Print("Error: Failed to initialize VR environment.") end --Create the player camera self.camera = Camera:Create() self.camera:SetPosition(self.entity:GetPosition(true)) self.camera:Translate(0,1.6,0) self.camera:SetRotation(self.entity:GetRotation(true)) VR:CenterTracking() --Create teleporter beam local beam = Sprite:Create() beam:SetViewMode(6) beam:SetSize(0.05,20) beam:SetColor(1,2,1) local mtl = Material:Load("Models/VR/teleport3.mat") if mtl~=nil then beam:SetMaterial(mtl) mtl:Release() mtl = nil end beam:Hide() --Create beam segments self.maxbeamsteps=32 self.beam = {} self.beam[1] = beam local n for n=2,self.maxbeamsteps do self.beam[n] = tolua.cast(self.beam[1]:Instance(),"Sprite") end --Load the teleport indicator prefab self.teleportindicator = Prefab:Load("Models/VR/teleport.pfb") self.teleportindicator:SetColor(1,2,1) self.teleportindicator:Hide() --Initialize offset self.targetoffset = VR:GetOffset() end function Script:UpdateWorld() local window = Window:GetCurrent() if window~=nil then if window:KeyHit(Key.F2) then VR:CenterTracking() end end --Check if teleporter is active and the button was released if self.teleportindicator:Hidden()==false then if VR:GetControllerButtonDown(VR.Right,VR.TouchpadButton)==false then local offset = VR:GetOffset() local pos = self.teleportindicator:GetPosition() local campos = self.camera:GetPosition(true) pos.x = pos.x - campos.x + offset.x pos.y = pos.y - 0.05 pos.z = pos.z - campos.z + offset.z self.targetoffset = pos end end --Hide beam and indicator self.teleportindicator:Hide() for n=1,self.maxbeamsteps do self.beam[n]:Hide() self.beam[n]:SetColor(2,1,1) end local controller = VR:GetControllerModel(VR.Right) if controller~=nil then --Activate teleporter if VR:GetControllerButtonDown(VR.Right,VR.TouchpadButton)==true then local world = self.entity.world local pickinfo = PickInfo() local p0 = controller:GetPosition(true) local velocity = Transform:Normal(0,0,-1,controller,nil) local speed = 1 local n local gravity = -0.1 --Loop through segments making an arc for n=1,self.maxbeamsteps do p1 = p0 + velocity * speed velocity.y = velocity.y + gravity self.beam[n]:Show() self.beam[n]:SetPosition((p0+p1)*0.5,true) self.beam[n]:AlignToVector(p1-p0,2) self.beam[n]:SetSize(0.05,p0:DistanceToPoint(p1)+0.02) if world:Pick(p0, p1, pickinfo, 0, true)==true then --Correct the length of the last beam segment self.beam[n]:SetSize(0.05,p0:DistanceToPoint(pickinfo.position)+0.02) self.beam[n]:SetPosition((p0+pickinfo.position)*0.5,true) --Cancel if slope is too steep local slope = 90 - Math:ASin(pickinfo.normal.y) if slope>35 then break end --Show the indicator self.teleportindicator:SetPosition(pickinfo.position) self.teleportindicator:Translate(0,0.05,0) self.teleportindicator:Show() --Recolor the beam for n=1,self.maxbeamsteps do self.beam[n]:SetColor(1,2,1) end break end p0 = p1 end end end --Update offset position local pos = VR:GetOffset() local d = self.targetoffset:DistanceToPoint(pos) local speed = 1.0 if speed>d then speed=d end pos = pos + (self.targetoffset - pos):Normalize() * speed VR:SetOffset(pos) end function Script:Detach() self.teleportindicator:Release() self.teleportindicator = nil for n=1,self.maxbeamsteps do self.beam[n]:Release() end self.beam = nil self.camera:Release() self.camera = nil end
And here it is in action. This will be standard in Leadwerks Game Engine 4.5:
1 Comment
Recommended Comments