#### Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

# 3D First Person Class

Posts: 160

Hello, everyone!
I have just started learning 3D yesterday and I'm already having a blast! Today, I made a First Person project with joystick controls! It took all day since I'm not that good with math. But that's why I'm giving the code away to all those folks who need a simple way of making a First Person Camera.

## Here's the code:

``````displayMode(FULLSCREEN)
-- Use this function to perform your initial setup
function setup()
Camera = FPCam({x = 0, y = 200, z = 0, limitYRot = true, fov = 90})

Ground = mesh()
Ground.vertices = {vec3(-700,0,-700),vec3(-700,0,700),vec3(700,0,-700),
vec3(700,0,-700),vec3(-700,0,700),vec3(700,0,700)}
Ground.texCoords = {vec2(0,0),vec2(0,1),vec2(1,0),vec2(1,0),vec2(0,1),vec2(1,1)}

UI = {
JSRot = Joystick({x = WIDTH-150, y = 150, size = 170, colour = color(255,190,0)}),
JSMov = Joystick({x = 150, y = 150, size = 170, colour = color(255,0,0)})
}
touches = {}
end

-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)

do
local RotJs = UI.JSRot
if RotJs.stickPos ~= vec2(0,0) then
Camera.rotation.x = Camera.rotation.x + (RotJs.value.x*0.0005)
Camera.rotation.y = Camera.rotation.y + (RotJs.value.y*0.0005)
end
local MovJs = UI.JSMov
if MovJs.stickPos ~= vec2(0,0) then
local Movement = (MovJs.value/10):rotate(-Camera.rotation.x)
Camera.pos.x = Camera.pos.x + Movement.y
Camera.pos.z = Camera.pos.z + Movement.x
end
end
Camera:view()
Ground:draw()

viewMatrix(matrix())
ortho()
for _,ui in pairs(UI) do
ui:draw()
end
for _,t in pairs(touches) do
for _,ui in pairs(UI) do
if ui.touched then
ui:touched(t)
end
end
if t.state == ENDED or t.state == CANCELLED then
touches[t.id] = nil
end
end
end

function touched(t)
touches[t.id] = {state=t.state,x=t.x,y=t.y,id=t.id}
end

FPCam = class()
function FPCam:init(data)
self.pos = vec3()
self.pos.x = data.x or 0
self.pos.y = data.y or 100
self.pos.z = data.z or 0
self.fov = data.fov or 75
self.rotation = vec2()
self.rotation.x = data.xRot or 0
self.rotation.y = data.yRot or 0
self.limitYRot = data.limitYRot or false
self.lookAt = vec3()
self.lookAt.x = data.xLook or 0
self.lookAt.y = data.yLook or 0
self.lookAt.z = data.zLook or 0
self.camM = data.camMode or 1
end

function FPCam:view()
local posx,posy,posz = self.pos.x,self.pos.y,self.pos.z
perspective()

if self.camM == 1 then
if self.limitYRot then
if self.rotation.y > math.pi/2 then
self.rotation.y = math.pi/2
elseif self.rotation.y < -math.pi/2 then
self.rotation.y = -math.pi/2
end
end
self.lookAt.x = posx+math.cos(self.rotation.x)
self.lookAt.y = posy+math.tan(self.rotation.y)
self.lookAt.z = posz+math.sin(self.rotation.x)
end
camera(posx,posy,posz,self.lookAt.x,self.lookAt.y,self.lookAt.z)
end

Joystick = class()
function Joystick:init(data)
self.pos = vec2(data.x,data.y)
self.stickPos = vec2(0,0)
self.c = color(data.colour.r,data.colour.g,data.colour.b,data.colour.a)
self.size = data.size or 100
self.visible = true
if data.initialValue then
self.value = data.initialValue * 1
else
self.value = vec2(0,0)
end
end

function Joystick:draw()
if self.visible then
pushStyle()
pushMatrix()

fill(0,0)
stroke(self.c)
strokeWidth(self.size/20)
ellipseMode(CENTER)

translate(self.pos.x,self.pos.y)
ellipse(0,0,self.size)

fill(self.c)
noStroke()

translate(self.stickPos.x,self.stickPos.y)
ellipse(0,0,self.size/1.5)

popStyle()
popMatrix()
end
end

function Joystick:touched(t)
if self.visible then
local tv = vec2(t.x,t.y)
local inside = tv:dist(self.pos) <= self.size/2

if (t.state == ENDED or t.state == CANCELLED) and self.touching == t.id then
self.touching = nil
self.stickPos = vec2(0,0)

elseif self.touching == t.id or (t.state == BEGAN and inside) then
if t.state == BEGAN and inside then
self.touching = t.id
end
if inside then
self.stickPos = tv - self.pos
else
self.stickPos = (tv - self.pos):normalize() * (self.size/2)
end
self.value = self.stickPos
end
end
end
``````

## How to use the class:

The class has 2 modes:
1. Look at mode (Forces the player to look at a 3D point)
2. Rotate mode (The x and y rotation of the camera can be set)

Look at mode:
With the look at mode, you can set a 3D point for the camera to look at. You can switch your camera to look at mode with the `.init()` function (`C = FPCam({camMode=0})`) or with the `.camM` variable (`C.camM = 0`). set it to anything that isn't `1` because mode `1` is rotate mode.

To set the x,y,z position to look at, you can do it like this

``````C = FPCam({xLook=100,yLook=0,zLook=300})
``````

or like this

``````C.lookAt = vec3(100,0,300)
``````

Rotate mode:
With the rotate mode, you can just rotate the camera rather than setting a location to look at. You can switch your camera to rotate mode, the same way you would switch your camera to look at mode except that you have to set it to mode 1 (example: `C = FPCam({camMode=1})`).

To change the rotation of the camera, all you have to do is this

``````C = FPCam({XRot=100,YRot=-200})
``````

or this

``````C.rotation = vec2(100,200)
``````

Note that the rotation is in radians, not degrees.
Also note that setting your camera to rotate mode and then running `FPCam.view()` will overwrite `.lookAt`.

You can stop your "head" from going below `-math.pi` and above `math.pi` (straight down and straight up) by setting limitYRot to true in either the `.init()` function or with the `.limitYRot` variable.

Other settings:
You can set the x,y,z position of the camera like this

``````C = Camera({x=0,y=300,z=400})
``````

or like this

``````C.pos = vec3(0,300,400)
``````

You can also set the FOV of your camera either like this

``````C = Camera({fov = 90})
``````

or like this

``````C.fov = 90
``````

Enjoy! Tagged:

• Posts: 5,396

You're having fun! I greatly enjoyed 3D too, even though the math hurt my brain • Posts: 160

Here's the same thing but with different controls!:

``````--# Main

displayMode(FULLSCREEN)
-- Use this function to perform your initial setup
function setup()
Camera = FPCam({x = 0, y = 200, z = 0, limitYRot = true, fov = 90})
Cube = RectangularPrism({x = 0, y = -100, z = 0, w = 1000, h = 200, d = 1000})
Cube.mesh:setColors(255,255,255,255)

UI = {
--JSRot = Joystick({x = WIDTH-150, y = 150, size = 170, colour = color(255,190,0)}),
JSMov = Joystick({x = 150, y = 150, size = 170, colour = color(255,0,0)})
}
touches = {}
end

-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)

do
local MovJs = UI.JSMov
if MovJs.stickPos ~= vec2(0,0) then
local Movement = (MovJs.value/10):rotate(-Camera.rotation.x)
Camera.pos.x = Camera.pos.x + Movement.y
Camera.pos.z = Camera.pos.z + Movement.x
end
end
Camera:view()
Cube:draw()

viewMatrix(matrix())
ortho()
for _,ui in pairs(UI) do
ui:draw()
end
for _,t in pairs(touches) do
local touchingUI = false
for _,ui in pairs(UI) do
if ui.touched then
touchingUI = ui:touched(t) or touchingUI
end
end
if not touchingUI then
Camera.rotation.x = Camera.rotation.x + (t.deltaX*0.01)
Camera.rotation.y = Camera.rotation.y + (t.deltaY*0.01)
end
if t.state == ENDED or t.state == CANCELLED or not touchingUI then
touches[t.id] = nil
end
end
end

function touched(t)
touches[t.id] = {state=t.state,x=t.x,y=t.y,id=t.id,deltaX=t.deltaX,deltaY=t.deltaY}
end

FPCam = class()
function FPCam:init(data)
self.pos = vec3()
self.pos.x = data.x or 0
self.pos.y = data.y or 100
self.pos.z = data.z or 0
self.fov = data.fov or 75
self.rotation = vec2()
self.rotation.x = data.xRot or 0
self.rotation.y = data.yRot or 0
self.limitYRot = data.limitYRot or false
self.lookAt = vec3()
self.lookAt.x = data.xLook or 0
self.lookAt.y = data.yLook or 0
self.lookAt.z = data.zLook or 0
self.camM = data.camMode or 1
end

function FPCam:view()
local posx,posy,posz = self.pos.x,self.pos.y,self.pos.z
perspective()

if self.camM == 1 then
if self.limitYRot then
if self.rotation.y > math.pi/2 then
self.rotation.y = math.pi/2
elseif self.rotation.y < -math.pi/2 then
self.rotation.y = -math.pi/2
end
end
self.lookAt.x = posx+math.cos(self.rotation.x)
self.lookAt.y = posy+math.tan(self.rotation.y)
self.lookAt.z = posz+math.sin(self.rotation.x)
end
camera(posx,posy,posz,self.lookAt.x,self.lookAt.y,self.lookAt.z)
end

RectangularPrism = class()
function RectangularPrism:init(data)
self.pos = vec3()
self.pos.x = data.x
self.pos.y = data.y
self.pos.z = data.z
self.w = data.w
self.h = data.h
self.d = data.d
self.rotation = vec3()

self.mesh = mesh()

do -- A do loop because we won't be needing these local variables later on in this function.

-- Below, we are setting up a few variables that will be needed a lot.
local hlfW = self.w*0.5 -- the width will be along the x axis,
local hlfH = self.h*0.5 -- the height will be along the y axis,
local hlfD = self.d*0.5 -- and the depth will be along the z axis.

-- We'll assume North is positive X, East is positive Z, South is negative X and West is negative Z,
-- to make things easier to understand.
-- Below, we are setting variables that define the corners in a rectangular prism.
local topNorthWest = vec3(hlfW, hlfH, -hlfD)
local topNorthEast = vec3(hlfW, hlfH, hlfD)
local bottomNorthWest = vec3(hlfW, -hlfH, -hlfD)
local bottomNorthEast = vec3(hlfW, -hlfH, hlfD)

-- The southern side of the rectangular prism will have the same corners except their x axis will be negative.
local topSouthWest = vec3(-hlfW, hlfH, -hlfD)
local topSouthEast = vec3(-hlfW, hlfH, hlfD)
local bottomSouthWest = vec3(-hlfW, -hlfH, -hlfD)
local bottomSouthEast = vec3(-hlfW, -hlfH, hlfD)

-- Now that we know all 8 corners in our prism, it should be easy to set the mesh vertices.

-- Below, we are setting the vertices for the mesh.
self.mesh.vertices = {
topNorthWest,topNorthEast,topSouthWest,-- triangle 1 on top
topNorthEast,topSouthWest,topSouthEast,-- triangle 2 on top
bottomNorthWest,bottomNorthEast,bottomSouthWest,-- triangle 1 on bottom
bottomNorthEast,bottomSouthWest,bottomSouthEast,-- triangle 2 on bottom
bottomNorthWest,bottomNorthEast,topNorthWest,-- bottom triangle to North
bottomNorthEast,topNorthWest,topNorthEast,-- top triangle to North
bottomNorthEast,bottomSouthEast,topNorthEast,-- bottom triangle to East
bottomSouthEast,topNorthEast,topSouthEast,-- top triangle to East
bottomSouthWest,bottomSouthEast,topSouthWest,-- bottom triangle to South
bottomSouthEast,topSouthWest,topSouthEast,-- top triangle to South
bottomNorthWest,bottomSouthWest,topNorthWest,-- bottom triangle to West
bottomSouthWest,topNorthWest,topSouthWest,-- top triangle to West
}
end

-- Below we set some variables for the texture coordinates.
local topLeft = vec2(0,1)
local topRight = vec2(1,1)
local bottomLeft = vec2(0,0)
local bottomRight = vec2(1,0)

-- Below, we are setting the texture coordinates.
self.mesh.texCoords = {
topLeft,topRight,bottomLeft, -- triangle 1 on top
topRight,bottomLeft,bottomRight, -- triangle 2 on top
topLeft,topRight,bottomLeft, -- triangle 1 on bottom
topRight,bottomLeft,bottomRight, -- triangle 2 on bottom
bottomLeft,bottomRight,topLeft, -- bottom triangle to North
bottomRight,topLeft,topRight, -- top triangle to North
bottomRight,bottomLeft,topRight, -- bottom triangle to East
bottomLeft,topRight,topLeft, -- top triangle to East
bottomRight,bottomLeft,topRight, -- bottom triangle to South
bottomLeft,topRight,topLeft, -- top triangle to South
bottomLeft,bottomRight,topLeft, -- bottom triangle to West
bottomRight,topLeft,topRight -- top triangle to West
}

end

function RectangularPrism:draw()
pushMatrix()

translate(self.pos.x,self.pos.y,self.pos.z)
rotate(self.rotation.x, 1, 0, 0)
rotate(self.rotation.y, 0, 1, 0)
rotate(self.rotation.z, 0, 0, 1)

self.mesh:draw()

popMatrix()
end

Joystick = class()
function Joystick:init(data)
self.pos = vec2(data.x,data.y)
self.stickPos = vec2(0,0)
self.c = color(data.colour.r,data.colour.g,data.colour.b,data.colour.a)
self.size = data.size or 100
self.visible = true
if data.initialValue then
self.value = data.initialValue * 1
else
self.value = vec2(0,0)
end
end

function Joystick:draw()
if self.visible then
pushStyle()
pushMatrix()

fill(0,0)
stroke(self.c)
strokeWidth(self.size/20)
ellipseMode(CENTER)

translate(self.pos.x,self.pos.y)
ellipse(0,0,self.size)

fill(self.c)
noStroke()

translate(self.stickPos.x,self.stickPos.y)
ellipse(0,0,self.size/1.5)

popStyle()
popMatrix()
end
end

function Joystick:touched(t)
if self.visible then
local tv = vec2(t.x,t.y)
local inside = tv:dist(self.pos) <= self.size/2

if (t.state == ENDED or t.state == CANCELLED) and self.touching == t.id then
self.touching = nil
self.stickPos = vec2(0,0)

elseif self.touching == t.id or (t.state == BEGAN and inside) then
if t.state == BEGAN and inside then
self.touching = t.id
end
if inside then
self.stickPos = tv - self.pos
else
self.stickPos = (tv - self.pos):normalize() * (self.size/2)
end
self.value = self.stickPos
end
return self.touching and self.touching == t.id
end
end
``````