|
@@ -0,0 +1,249 @@
|
|
|
+Callbacks and Modules
|
|
|
+===
|
|
|
+
|
|
|
+In the previous example we wrote some code in the `lovr.draw` function to draw text on the screen.
|
|
|
+This is an example of a **callback** because we wrote a function and LÖVR "called back" into that
|
|
|
+function later. Defining a callback lets you specify how your project behaves when a specific event
|
|
|
+occurs.
|
|
|
+
|
|
|
+In the previous example we also used the `lovr.graphics.print` function to render text to the
|
|
|
+screen. This is an example of using the `lovr.graphics` **module**. LÖVR has several modules and
|
|
|
+each one contains functions related to a certain area of functionality. For example, there's the
|
|
|
+`lovr.graphics` module for rendering graphics, the `lovr.audio` module for playing sounds, and the
|
|
|
+`lovr.headset` module for getting information about connected VR hardware.
|
|
|
+
|
|
|
+We can define **callbacks** and call functions from **modules** to make things with LÖVR.
|
|
|
+
|
|
|
+Callbacks
|
|
|
+---
|
|
|
+
|
|
|
+There are various callbacks that can be used for interesting things. Three of the most used ones
|
|
|
+are `lovr.load`, `lovr.update`, and `lovr.draw`. A simple project skeleton might look like this:
|
|
|
+
|
|
|
+```
|
|
|
+function lovr.load()
|
|
|
+ -- This is called once on load.
|
|
|
+ --
|
|
|
+ -- You can use it to load assets and set everything up.
|
|
|
+
|
|
|
+ print('loaded!')
|
|
|
+end
|
|
|
+
|
|
|
+function lovr.update(dt)
|
|
|
+ -- This is called continuously and is passed the "delta time" as dt, which
|
|
|
+ -- is the number of seconds elapsed since the last update.
|
|
|
+ --
|
|
|
+ -- You can use it to simulate physics or update game logic.
|
|
|
+
|
|
|
+ print('updating', dt)
|
|
|
+end
|
|
|
+
|
|
|
+function lovr.draw(eye)
|
|
|
+ -- This is called twice every frame (once for each eye).
|
|
|
+ --
|
|
|
+ -- You can use it to render the scene.
|
|
|
+
|
|
|
+ print('rendering the ' .. eye .. ' eye')
|
|
|
+end
|
|
|
+```
|
|
|
+
|
|
|
+By filling in the different callbacks you can start to define the behavior of an app.
|
|
|
+
|
|
|
+To see a list of all the callbacks and read more about their specifics, check out the "Callbacks"
|
|
|
+section on the sidebar to the left.
|
|
|
+
|
|
|
+Modules
|
|
|
+---
|
|
|
+
|
|
|
+You might be wondering what code to write in the different callbacks. Inside callbacks you'll often
|
|
|
+call into different modules to get LÖVR to do useful things.
|
|
|
+
|
|
|
+A module is just a Lua table that exposes a set of functions you can call. Each module is
|
|
|
+responsible for a specific area of functionality. Some modules are relatively low level and, though
|
|
|
+useful, they often aren't used in smaller projects. The most commonly used modules are:
|
|
|
+
|
|
|
+1. `lovr.graphics`
|
|
|
+2. `lovr.headset`
|
|
|
+3. `lovr.audio`
|
|
|
+4. `lovr.physics`
|
|
|
+
|
|
|
+Each module is described briefly below.
|
|
|
+
|
|
|
+lovr.graphics
|
|
|
+---
|
|
|
+
|
|
|
+The graphics module is the most exciting module, and is also the largest. Most functions in
|
|
|
+`lovr.graphics` should be used in `lovr.draw`, since that's where rendering happens.
|
|
|
+
|
|
|
+`lovr.graphics` has a set of handy **graphics primitives** for rendering basic shapes and text.
|
|
|
+These can be used to quickly prototype a scene without needing to create or load assets.
|
|
|
+
|
|
|
+There are lots of different rendering-related objects that can be created using `lovr.graphics`,
|
|
|
+such as `Model`, `Texture`, `Font`, `Shader`, `Skybox`, and more. Every function to create a new
|
|
|
+object is prefixed with `new`, so to create a 3D model object you can use `lovr.graphics.newModel`.
|
|
|
+
|
|
|
+> Note: Creating graphics objects uses memory and can slow things down if done every frame. For
|
|
|
+> this reason, it's recommended to create objects only once in `lovr.load` before using them!
|
|
|
+
|
|
|
+Another important component of `lovr.graphics` is **graphics state**. The graphics renderer has a
|
|
|
+number of state variables that can be changed, like the color of rendered objects, the font in use,
|
|
|
+or the coordinate system. These functions usually have prefixes of `get` or `set`, so to change the
|
|
|
+active color you can use `lovr.graphics.setColor`. It's important to keep in mind that this state
|
|
|
+is **global**, so changing the color will affect all subsequent drawing operations until it's
|
|
|
+changed again.
|
|
|
+
|
|
|
+Finally, we'll talk about the coordinate system. LÖVR uses a 3D coordinate system with values
|
|
|
+specified in meters. Negative z values are in front of the camera, positive y values are above the
|
|
|
+ground, and negative x values are to the left. By default, the coordinate system maps to the VR
|
|
|
+play area, so the origin is on the ground in the middle of the play space.
|
|
|
+
|
|
|
+You've already seen `lovr.graphics.print`, but here's another example:
|
|
|
+
|
|
|
+```
|
|
|
+function lovr.load()
|
|
|
+ -- Load a model with a texture
|
|
|
+ model = lovr.graphics.newModel('monkey.obj', 'monkey.png')
|
|
|
+end
|
|
|
+
|
|
|
+function lovr.draw()
|
|
|
+ -- Use a dark grey background
|
|
|
+ lovr.graphics.setBackgroundColor(50, 50, 50)
|
|
|
+
|
|
|
+ -- Draw the model
|
|
|
+ lovr.graphics.setColor(255, 255, 255)
|
|
|
+ model:draw(-.5, 1, -3)
|
|
|
+
|
|
|
+ -- Draw a red cube using the "cube" primitive
|
|
|
+ lovr.graphics.setColor(255, 0, 0)
|
|
|
+ lovr.graphics.cube('fill', .5, 1, -3, .5, lovr.timer.getTime())
|
|
|
+end
|
|
|
+```
|
|
|
+
|
|
|
+lovr.headset
|
|
|
+---
|
|
|
+
|
|
|
+The headset module lets you interact with VR hardware. You can get pose information for the HMD and
|
|
|
+controllers, and also query the input state of controllers to see if buttons are pressed. You can
|
|
|
+also retrieve information about the configured play area so you know how much available space there
|
|
|
+is to place objects.
|
|
|
+
|
|
|
+Pose information consists of the position and orientation of a tracked device, which is useful
|
|
|
+because it lets you know where the device is and which way it's facing. To get the position of the
|
|
|
+HMD, you can call `lovr.headset.getPosition` which returns 3 numbers corresponding to an xyz
|
|
|
+position in 3D space. You can also call `lovr.headset.getOrientation` which returns four numbers
|
|
|
+representing a rotation in angle/axis format.
|
|
|
+
|
|
|
+The `lovr.headset.getControllers` function returns a list of all connected controllers. You can
|
|
|
+query pose info for controllers using `Controller:getPosition` and `Controller:getOrientation`,
|
|
|
+similar to the HMD. There are also `Controller:getAxis` and `Controller:isDown` functions for
|
|
|
+determining input state.
|
|
|
+
|
|
|
+Several callbacks are relevant to the headset module. The `lovr.controlleradded` callback is run
|
|
|
+whenever a new controller is connected, and there is a similar `controllerremoved` callback. Also,
|
|
|
+there is the `lovr.controllerpressed` callback for when a button on a controller is pressed, with
|
|
|
+a similar callback for `controllerreleased`.
|
|
|
+
|
|
|
+Here's an example that draws a sphere in the "opposite" position of the headset:
|
|
|
+
|
|
|
+```
|
|
|
+function lovr.draw()
|
|
|
+ local x, y, z = lovr.headset.getPosition()
|
|
|
+ lovr.graphics.sphere(-x, y, -z, .1)
|
|
|
+end
|
|
|
+```
|
|
|
+
|
|
|
+lovr.audio
|
|
|
+---
|
|
|
+
|
|
|
+Sound can be played with `lovr.audio`. Audio is spatialized, so sounds can have positions and
|
|
|
+directions, which are used to make things sound realistic as the headset moves and rotates.
|
|
|
+
|
|
|
+Each instance of a sound is called a `Source`. To create a sources, use `lovr.audio.newSource` and
|
|
|
+pass it an ogg file. You can then call `play` on the source to play it.
|
|
|
+
|
|
|
+```
|
|
|
+function lovr.load()
|
|
|
+ ambience = lovr.audio.newSource('background.ogg')
|
|
|
+ ambience:setLooping(true)
|
|
|
+ ambience:play()
|
|
|
+end
|
|
|
+```
|
|
|
+
|
|
|
+See the `Source` page for more information.
|
|
|
+
|
|
|
+lovr.physics
|
|
|
+---
|
|
|
+
|
|
|
+Adding a physics simulation to a scene can make it feel more realistic and immersive. The
|
|
|
+`lovr.physics` module can be used to set up a physics simulation.
|
|
|
+
|
|
|
+> Note: Physics engines can be tricky to set up. There are lots of knobs to turn and it may take
|
|
|
+> some tweaking to get things working well.
|
|
|
+
|
|
|
+The first step to creating a simulation is to create a `World` using `lovr.physics.newWorld`. After
|
|
|
+a world is created you can add `Collider`s to it, using functions like `World:newBoxCollider` or
|
|
|
+`World:newCylinderCollider`. Each collider represents a single entity in the simulation and can have
|
|
|
+forces applied it. The world should be updated in `lovr.update` using the `dt` value.
|
|
|
+
|
|
|
+Here's an example that makes a tower of boxes that you can knock down with controllers:
|
|
|
+
|
|
|
+```
|
|
|
+function lovr.load()
|
|
|
+ world = lovr.physics.newWorld()
|
|
|
+
|
|
|
+ -- Create the ground
|
|
|
+ world:newBoxCollider(0, 0, 0, 5, .01, 5):setKinematic(true)
|
|
|
+
|
|
|
+ -- Create boxes!
|
|
|
+ boxes = {}
|
|
|
+ for x = -1, 1, .25 do
|
|
|
+ for y = .125, 2, .25 do
|
|
|
+ local box = world:newBoxCollider(x, y, -1, .25)
|
|
|
+ table.insert(boxes, box)
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Each controller is going to have a collider attached to it
|
|
|
+ controllerBoxes = {}
|
|
|
+end
|
|
|
+
|
|
|
+function lovr.update(dt)
|
|
|
+ -- Synchronize controllerBoxes with the active controllers
|
|
|
+ for i, controller in ipairs(lovr.headset.getControllers()) do
|
|
|
+ if not controllerBoxes[i] then
|
|
|
+ controllerBoxes[i] = world:newBoxCollider(0, 0, 0, .25)
|
|
|
+ controllerBoxes[i]:setKinematic(true)
|
|
|
+ end
|
|
|
+ controllerBoxes[i]:setPosition(controller:getPosition())
|
|
|
+ controllerBoxes[i]:setOrientation(controller:getOrientation())
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Update the physics simulation
|
|
|
+ world:update(dt)
|
|
|
+end
|
|
|
+
|
|
|
+-- A helper function for drawing boxes
|
|
|
+function drawBox(box)
|
|
|
+ local x, y, z = box:getPosition()
|
|
|
+ lovr.graphics.cube('line', x, y, z, .25, box:getOrientation())
|
|
|
+end
|
|
|
+
|
|
|
+function lovr.draw()
|
|
|
+ lovr.graphics.setColor(255, 0, 0)
|
|
|
+ for i, box in ipairs(boxes) do
|
|
|
+ drawBox(box)
|
|
|
+ end
|
|
|
+
|
|
|
+ lovr.graphics.setColor(0, 0, 255)
|
|
|
+ for i, box in ipairs(controllerBoxes) do
|
|
|
+ drawBox(box)
|
|
|
+ end
|
|
|
+end
|
|
|
+```
|
|
|
+
|
|
|
+Next Steps
|
|
|
+---
|
|
|
+
|
|
|
+To explore a module or callback in more detail, see the reference page for the `lovr` global.
|
|
|
+
|
|
|
+There are also a number of <a data-key="Libraries">Libraries</a> you can use that may come in handy.
|