Thanks, @John, @binaryblues , and @dave1707!
I’ve attached in the meantime my workaround for not having callbacks to detect collisions by using the rayCast method that I mentioned previously. However, it turned out to be a bit more complicated than the initial steps I suggested above—specifically, it required another line of code to get the origin of the ray from the camera element attached to the scene.camera before rayCasting (I figured that out after reading a Unity tutorial on the same). Anyways, at least from my initial trials, the rayCast method of detecting obstructions to movement seems to work. I’ve attached the zip of the fully edited project (credit to dave1707 for helping me refine the 3D FPS movement and for making a nice button class for movement input) and listed my commented code below. The 3D maze obj file is too large to attach in the zip file but can be downloaded for free at:
https://www.cgtrader.com/items/2215957/download-page
You’ll also need a texture for the 3D maze; I didn’t include it so feel free to choose your own texture for the walls.
Code:
-- CraftMaze
viewer.mode=FULLSCREEN
function setup()
btn={}
rectMode(CENTER)
-- euler Angles of camera rotation: ex, ey, ez
ex,ey,ez=0,0,0
-- speed = # of units to move on vector in space with forward/back/up/down button; rY= rotation around Y axis euler degrees
speed=0
-- rY = rotation around Y axis in euler degrees
rY=0
-- rX = rotation around X axis euler degrees
rX=0
-- dirSign is direction of movement: +1 forward, -1 = backward
dirSign = 0
-- set the camera position (arbitrarily set -2 leftward and -40 back from maze to view maze properly)
cameraX,cameraY,cameraZ=-2,0,-40
scene = craft.scene()
scene.camera.position = vec3(cameraX,cameraY,cameraZ)
scene.camera.eulerAngles = vec3(ex,ey,ez)
scene.sun.rotation = quat.eulerAngles(0,0,0)
scene.ambientColor = color(90,90,90)
skyMaterial = scene.sky.material
skyMaterial.horizon = color(0, 203, 255, 255)
-- obstruction is variable then gets set if Codea finds something in front of player movement
obstruction = ""
-- create floor of maze
createFloor()
-- import 3D model of Maze(.obj format) from document folder in Codea (maze model & Stonewall texture are free downloads)
Maze = scene:entity()
Maze.model = craft.model(asset.documents.Maze)
Maze.material = craft.material(asset.builtin.Materials.Standard)
Maze.material.map = readImage(asset.documents.StoneWall)
Maze.position = vec3(0,-0.5,0)
Maze.scale = vec3(1,1,1)
-- add a static rigid body structure to the maze entity so can detect when maze wall obstructs movement
Maze:add(craft.rigidbody, STATIC)
Maze:add(craft.shape.model, Maze.model)
-- create a table called "btn" of "button" class objects & initatilize buttons
table.insert(btn,button(WIDTH/20,150,"Left",function() rY=-.3 end))
table.insert(btn,button((WIDTH/20 + 110),150,"Right",function() rY=.3 end))
table.insert(btn,button(WIDTH - (WIDTH/20),175,"Forward",function() dirSign = 1 speed=.1 end))
table.insert(btn,button(WIDTH - (WIDTH/20),125,"Backward",function() dirSign = -1 speed=-.1 end))
table.insert(btn,button((WIDTH/20 + 55),200,"Up",function() rX=.3 end))
table.insert(btn,button((WIDTH/20 + 55),100,"Down",function() rX=-.3 end))
end
function draw()
-- draw scene, then draw buttons, then update the CameraPosition (= player in this FPS project) then move on screen
scene:draw()
for a,b in pairs(btn) do
b:draw()
end
updateCameraPos()
update(DeltaTime)
end
function update(dt)
scene:update(dt)
scene.camera.position = vec3(cameraX,cameraY,cameraZ)
scene.camera.eulerAngles=vec3(ex,ey,ez)
end
function touched(t)
--if screen is touched then iterate over the btn table and call the button:touched()
for a,b in pairs(btn) do
b:touched(t)
end
end
function sizeChanged()
--if screen rotates vertically need to move right sided buttons inward
moveButtonCloser()
end
-- update CameraPos in this FirstPerson perspective game is actually what moves player in 3D environment
function updateCameraPos()
-- camera euler y rotation (ey) = ey - rY (rY is degrees changed when hit left/right button)
-- *Note: by Euler convention + Euler angle is counterclockwise rotation around an axis
ey=ey-rY
ex=ex-rX
-- *Before actually move check to be sure there isn't any obstruction if proceed with movement at variable speed
checkObstruction()
-- if there is an obstruction to forward or backward movement don't update or move the camera
if obstruction then return else
-- x is perpendicular to z axis; calculate x vector magnitude to move based on sin of rad(ey) degrees off of z axis
-- note: the further angled off of the z axis the greater the magnitude of perpendicular x vector
-- then multiple by speed set as general magnitude of all hmovement
-- cos of rad (ey) is the lefover vector to move z; when ey = 0 degrees move full vector on z
x=speed*math.sin(math.rad(ey))
z=speed*math.cos(math.rad(ey))
y=speed*math.sin(math.rad(ex))
-- cameraX is actual camera's x position
cameraX=cameraX+x
cameraY=cameraY-y
-- this line prevents camera from moving below plane (may want to remove for other projects)
if cameraY<1 then
cameraY=1
end
cameraZ=cameraZ+z
end
end
function moveButtonCloser()
-- when screen held vertical need to move the right-sided buttons inwards
btn[3].x = (WIDTH - WIDTH/20)
btn[4].x = (WIDTH - WIDTH/20)
draw()
end
function createFloor()
local c1=scene:entity()
c1.model = craft.model.cube(vec3(50,.1,50))
c1.position = vec3(0,0,0)
c1.material = craft.material(asset.builtin.Materials.Standard)
c1.material.map = readImage(asset.builtin.Surfaces.Desert_Cliff_Color)
end
function checkObstruction()
-- first, need to actually access the camera element attached to the entity called scene.camera
local cam = scene.camera:get(craft.camera)
-- then pass the x,y display coordinates of the center of the camera's viewport (0.5,0.5 in fullscreen view) to screenToRay
origin, direction = cam:screenToRay(vec2(0.5,0.5))
-- next Codea sends out a "ray" from the camera in a forward or backward direction (based on dirSign variable from forward/backward button) a specified distance to see if there is any obstruction ("hits"); arbitrarily set distance to 0.1 since that is the speed of the movement variable set that is used to determine how far the camera moves when press forward or backward button
obstruction = scene.physics:raycast(origin, dirSign*direction, 0.1)
end
-- useful "button" class courtesy of @dave1707
button=class()
function button:init(x,y,n,a)
self.x=x
self.y=y
self.name=n
self.action=a
end
function button:draw()
fill(255)
rect(self.x,self.y,80,40)
fill(0)
text(self.name,self.x,self.y)
end
function button:touched(t)
if t.x>self.x-40 and t.x<self.x+40 and t.y>self.y-20 and t.y<self.y+20 then
if t.state==BEGAN then
self.action()
end
-- once stop pressing key, resets to no turning (rY), no fwd/bck movement, no elevation/depression mvmnt (rX)
if t.state==ENDED then
rY,speed,rX=0,0,0
end
end
end