Jump to content
  • entries
    941
  • comments
    5,894
  • views
    867,894

Creating a VR Teleporter: Part 3


Josh

1,711 views

 Share

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.

Image1.jpg.df8356f46b3a9d9ed37bdfd895c76

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:

 

 Share

1 Comment


Recommended Comments

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...