Preview
Basic camera movement and animation
Tutorial
Initialization
Of course, the first step is importing the library.
-- import library
local Pine3D = require("Pine3D")
To move the camera around, we will need to define the speed at which the camera moves and turns.
-- movement and turn speed of the camera
local speed = 2 -- units per second
local turnSpeed = 180 -- degrees per second
Next up, we can define the
ThreeDFrame.
-- create a new frame
local ThreeDFrame = Pine3D.newFrame()
Now that we have a ThreeDFrame, we can define our own camera and pass the values on to the
ThreeDFrame.
-- initialize our own camera and update the frame camera
local camera = {
x = 0,
y = 0,
z = 0,
rotX = 0,
rotY = 0,
rotZ = 0,
}
ThreeDFrame:setCamera(camera)
Finally, we can define the table of objects we wish to render. For this example, we will be rendering a single pineapple model.
-- define the objects to be rendered
local objects = {
ThreeDFrame:newObject("models/pineapple", 0, 0, 0), -- pineapple at 0, 0, 0
}
Camera movement
To move around the camera, we will have to be able to check when a key is being pressed down. We will run a few functions in parallel to handle the full program. The following is used to keep track of which keys are being pressed down in "keysDown".
-- handle all keypresses and store in a lookup table
-- to check later if a key is being pressed
local keysDown = {}
local function keyInput()
while true do
-- wait for an event
local event, key, x, y = os.pullEvent()
if event == "key" then -- if a key is pressed, mark it as being pressed down
keysDown[key] = true
elseif event == "key_up" then -- if a key is released, reset its value
keysDown[key] = nil
end
end
end
Now that we are keeping track of keys that are being pressed down, we can define a function that updates the camera position based on the time passed, the speed of the camera and which keys are being pressed down:
-- update the camera position based on the keys being pressed
-- and the time passed since the last step
local function handleCameraMovement(dt)
local dx, dy, dz = 0, 0, 0 -- will represent the movement per second
-- handle arrow keys for camera rotation
if keysDown[keys.left] then
camera.rotY = (camera.rotY - turnSpeed * dt) % 360
end
if keysDown[keys.right] then
camera.rotY = (camera.rotY + turnSpeed * dt) % 360
end
if keysDown[keys.down] then
camera.rotZ = math.max(-80, camera.rotZ - turnSpeed * dt)
end
if keysDown[keys.up] then
camera.rotZ = math.min(80, camera.rotZ + turnSpeed * dt)
end
-- handle wasd keys for camera movement
if keysDown[keys.w] then
dx = speed * math.cos(math.rad(camera.rotY)) + dx
dz = speed * math.sin(math.rad(camera.rotY)) + dz
end
if keysDown[keys.s] then
dx = -speed * math.cos(math.rad(camera.rotY)) + dx
dz = -speed * math.sin(math.rad(camera.rotY)) + dz
end
if keysDown[keys.a] then
dx = speed * math.cos(math.rad(camera.rotY - 90)) + dx
dz = speed * math.sin(math.rad(camera.rotY - 90)) + dz
end
if keysDown[keys.d] then
dx = speed * math.cos(math.rad(camera.rotY + 90)) + dx
dz = speed * math.sin(math.rad(camera.rotY + 90)) + dz
end
-- space and left shift key for moving the camera up and down
if keysDown[keys.space] then
dy = speed + dy
end
if keysDown[keys.leftShift] then
dy = -speed + dy
end
-- update the camera position by adding the offset
camera.x = camera.x + dx * dt
camera.y = camera.y + dy * dt
camera.z = camera.z + dz * dt
ThreeDFrame:setCamera(camera)
end
Game loop
We will handle the rendering of the objects separately from the rest of the game to make sure that the logic can run in small time steps, even if the framerate is lower due to the performance impact of the rendering.
The gameLoop function computes the time that passed for each step and pass it on to the handleGameLogic and handleCameraMovement functions.
-- handle game logic
local function handleGameLogic(dt)
-- set y coordinate to move up and down based on time
objects[1]:setPos(nil, math.sin(os.clock())*0.25, nil)
-- set horizontal rotation depending on the time
objects[1]:setRot(nil, os.clock(), nil)
end
-- handle the game logic and camera movement in steps
local function gameLoop()
local lastTime = os.clock()
while true do
-- compute the time passed since last step
local currentTime = os.clock()
local dt = currentTime - lastTime
lastTime = currentTime
-- run all functions that need to be run
handleGameLogic(dt)
handleCameraMovement(dt)
-- use a fake event to yield the coroutine
os.queueEvent("gameLoop")
os.pullEventRaw("gameLoop")
end
end
Rendering
The rendering function simply loads the objects we defined into the
Buffer of the
ThreeDFrame and draws it in a loop.
-- render the objects
local function rendering()
while true do
-- load all objects onto the buffer and draw the buffer
ThreeDFrame:drawObjects(objects)
ThreeDFrame:drawBuffer()
-- use a fake event to yield the coroutine
os.queueEvent("rendering")
os.pullEventRaw("rendering")
end
end
Now that we have defined all constants and functions to handle the input, logic and rendering, we can start them all in parallel:
-- start the functions to run in parallel
parallel.waitForAny(keyInput, gameLoop, rendering)
Full example
local Pine3D = require("Pine3D")
-- movement and turn speed of the camera
local speed = 2 -- units per second
local turnSpeed = 180 -- degrees per second
-- create a new frame
local ThreeDFrame = Pine3D.newFrame()
-- initialize our own camera and update the frame camera
local camera = {
x = 0,
y = 0,
z = 0,
rotX = 0,
rotY = 0,
rotZ = 0,
}
ThreeDFrame:setCamera(camera)
-- define the objects to be rendered
local objects = {
ThreeDFrame:newObject("models/pineapple", 0, 0, 0), -- pineapple at 0, 0, 0
}
-- handle all keypresses and store in a lookup table
-- to check later if a key is being pressed
local keysDown = {}
local function keyInput()
while true do
-- wait for an event
local event, key, x, y = os.pullEvent()
if event == "key" then -- if a key is pressed, mark it as being pressed down
keysDown[key] = true
elseif event == "key_up" then -- if a key is released, reset its value
keysDown[key] = nil
end
end
end
-- update the camera position based on the keys being pressed
-- and the time passed since the last step
local function handleCameraMovement(dt)
local dx, dy, dz = 0, 0, 0 -- will represent the movement per second
-- handle arrow keys for camera rotation
if keysDown[keys.left] then
camera.rotY = (camera.rotY - turnSpeed * dt) % 360
end
if keysDown[keys.right] then
camera.rotY = (camera.rotY + turnSpeed * dt) % 360
end
if keysDown[keys.down] then
camera.rotZ = math.max(-80, camera.rotZ - turnSpeed * dt)
end
if keysDown[keys.up] then
camera.rotZ = math.min(80, camera.rotZ + turnSpeed * dt)
end
-- handle wasd keys for camera movement
if keysDown[keys.w] then
dx = speed * math.cos(math.rad(camera.rotY)) + dx
dz = speed * math.sin(math.rad(camera.rotY)) + dz
end
if keysDown[keys.s] then
dx = -speed * math.cos(math.rad(camera.rotY)) + dx
dz = -speed * math.sin(math.rad(camera.rotY)) + dz
end
if keysDown[keys.a] then
dx = speed * math.cos(math.rad(camera.rotY - 90)) + dx
dz = speed * math.sin(math.rad(camera.rotY - 90)) + dz
end
if keysDown[keys.d] then
dx = speed * math.cos(math.rad(camera.rotY + 90)) + dx
dz = speed * math.sin(math.rad(camera.rotY + 90)) + dz
end
-- space and left shift key for moving the camera up and down
if keysDown[keys.space] then
dy = speed + dy
end
if keysDown[keys.leftShift] then
dy = -speed + dy
end
-- update the camera position by adding the offset
camera.x = camera.x + dx * dt
camera.y = camera.y + dy * dt
camera.z = camera.z + dz * dt
ThreeDFrame:setCamera(camera)
end
-- handle game logic
local function handleGameLogic(dt)
-- set y coordinate to move up and down based on time
objects[1]:setPos(nil, math.sin(os.clock())*0.25, nil)
-- set horizontal rotation depending on the time
objects[1]:setRot(nil, os.clock(), nil)
end
-- handle the game logic and camera movement in steps
local function gameLoop()
local lastTime = os.clock()
while true do
-- compute the time passed since last step
local currentTime = os.clock()
local dt = currentTime - lastTime
lastTime = currentTime
-- run all functions that need to be run
handleGameLogic(dt)
handleCameraMovement(dt)
-- use a fake event to yield the coroutine
os.queueEvent("gameLoop")
os.pullEventRaw("gameLoop")
end
end
-- render the objects
local function rendering()
while true do
-- load all objects onto the buffer and draw the buffer
ThreeDFrame:drawObjects(objects)
ThreeDFrame:drawBuffer()
-- use a fake event to yield the coroutine
os.queueEvent("rendering")
os.pullEventRaw("rendering")
end
end
-- start the functions to run in parallel
parallel.waitForAny(keyInput, gameLoop, rendering)