Oculus VR template: /player.lua — code sample - Stingray Lua API Reference

Oculus VR template: /player.lua — code sample

Code

require 'core/appkit/lua/class'
require 'core/appkit/lua/app'
local SimpleProject = require 'core/appkit/lua/simple_project'
local ComponentManager = require 'core/appkit/lua/component_manager'
local UnitController = require 'core/appkit/lua/unit_controller'
local UnitLink = require 'core/appkit/lua/unit_link'
local CameraWrapper = require 'core/appkit/lua/camera_wrapper'

Project.Player = Appkit.class(Project.Player)
local Player = Project.Player -- cache off for readability and speed

-- cache off for readability and speed
local Keyboard = stingray.Keyboard
local Vector3 = stingray.Vector3
local Matrix4x4 = stingray.Matrix4x4
local Matrix4x4Box = stingray.Matrix4x4Box
local Pad1 = stingray.Pad1
local Quaternion = stingray.Quaternion
local Unit = stingray.Unit
local World = stingray.World
local Level = stingray.Level

local free_cam_move_speed = stingray.Vector3Box(Vector3(3.8,3.8,3.8))
local free_cam_sprint_speed = stingray.Vector3Box(Vector3(9,9,8))
local free_cam_yaw_speed = 0.085
local free_cam_pitch_speed = 0.075

local character_move_speed = stingray.Vector3Box(Vector3(3.5,3.5,3.5))
local character_sprint_speed = stingray.Vector3Box(Vector3(7,7,7))
local character_cam_yaw_speed = 0.1
local character_cam_pitch_speed = 0.0 -- character model does not pitch

local default_character_eye_height = 1.7

function Player.set_default_character_eye_height(height)
    default_character_eye_height = height
end

local function play_spawn_sound()
    if stingray.Wwise then
        stingray.Wwise.load_bank("content/audio/default") -- temporarily necessary for autoload mode to work
        local wwise_world = stingray.Wwise.wwise_world(SimpleProject.world)
        stingray.WwiseWorld.trigger_event(wwise_world, "sfx_spawn_sound")
    end
end

local function spawn_freecam_player(self, level, view_position, view_rotation)
    view_position = view_position or Vector3(0, 0, default_character_height)
    view_rotation = view_rotation or Quaternion.identity()

    local world = Level.world(level)
    local unit = SimpleProject.camera_unit

    local player_camera = self.player_camera
    player_camera.unit = unit

    -- Camera
    local camera_wrapper = Appkit.CameraWrapper(player_camera, unit, 1)
    camera_wrapper:set_local_position(view_position)
    camera_wrapper:set_local_rotation(view_rotation)
    camera_wrapper:enable()

    -- Add camera input movement. Starts enabled.
    local controller = UnitController(player_camera, unit, self.input_mapper)
    controller:set_move_speed(free_cam_move_speed:unbox())
    controller:set_yaw_speed(free_cam_yaw_speed)
    controller:set_pitch_speed(free_cam_pitch_speed)

    -- Give free cam ability to attached to character for walking mode. Starts disabled.
    local unit_link = UnitLink(player_camera, level, unit, 1, nil, 1, false)

    self.is_freecam_mode = true
end

-- Main Player Spawn function.
-- Player starts in Free Cam mode, and can toggle to spawning a character and back to Free Cam.
function Player.spawn_player(level, view_position, view_rotation, custom_input_mapper)
    if not level then
        print "ERROR: No current level - cannot spawn"
        return
    end

    local player = Player()
    if custom_input_mapper then
        player.input_mapper = custom_input_mapper
    end

    spawn_freecam_player(player, level, view_position, view_rotation)
    play_spawn_sound()

    -- allow appkit to manage update and shutdown
    Appkit.manage_level_object(level, Player, player)
end

function Player:init()
    -- The Basic Project gameplay design is for the player character to spawn, but the 
    -- camera is initially set to freecam and the player character will be stationary.
    self.player_camera = {}
    self.land_character = {}
    self.is_freecam_mode = false
    self.saved_freecam_pose = Matrix4x4Box(Matrix4x4.identity())
    self.input_mapper = Appkit.input_mapper
end

local function is_character_spawned(self)
    return self.land_character.unit ~= nil
end

local function despawn_character(self)
    local land_character = self.land_character
    if land_character and is_character_spawned(self) then
        ComponentManager.remove_components(land_character)
        World.destroy_unit(SimpleProject.world, land_character.unit)
        land_character.unit = nil
    end
end

local function despawn_freecam(self)
    local player_camera = self.player_camera
    if player_camera then
        local world = SimpleProject.world
        ComponentManager.remove_components(player_camera)
        player_camera.unit = nil
    end
end

function Player.shutdown(self, level)
    despawn_character(self)
    despawn_freecam(self)
end

local function enable_free_mode(self)
    local player_camera = self.player_camera

    -- detach camera from character
    local unit_link = UnitLink.manager:get(player_camera)
    unit_link:unlink()

    despawn_character(self)

    -- enable camera movement and yaw
    local freecam_controller = UnitController.manager:get(player_camera)
    freecam_controller:set_move_speed(free_cam_move_speed:unbox())
    freecam_controller:set_yaw_speed(free_cam_yaw_speed)

    -- set freecam to saved off camera position
    local camera_wrapper = CameraWrapper.manager:get(player_camera)
    camera_wrapper:set_local_pose(self.saved_freecam_pose:unbox())

    self.is_freecam_mode = true
end

local function spawn_player_character(self, pose)
    local world = SimpleProject.world
    local land_unit = World.spawn_unit(world, "content/models/character/character", pose)

    local land_character = self.land_character
    land_character.unit = land_unit

    -- Add Character input movement.
    local controller = UnitController(land_character, land_unit, self.input_mapper)
    controller:set_move_speed(character_move_speed:unbox())
    controller:set_yaw_speed(character_cam_yaw_speed)
    controller:set_pitch_speed(character_cam_pitch_speed)
    controller:set_mover("body")
    controller:set_gravity_enabled(true)
end

local function enable_walk_mode(self)
    local player_camera = self.player_camera

    -- save off freecam camera position
    local camera_wrapper = CameraWrapper.manager:get(player_camera)
    self.saved_freecam_pose:store(camera_wrapper:local_pose())

    -- disable movement and yaw on camera
    local freecam_controller = UnitController.manager:get(player_camera)
    freecam_controller:set_move_speed(Vector3(0,0,0))
    freecam_controller:set_yaw_speed(0)

    -- spawn character
    local character_pose = camera_wrapper:local_pose()
    local forward = Matrix4x4.forward(character_pose)
    Vector3.set_z(forward, 0) -- remove pitch
    local rot = Quaternion.look(forward)
    local no_pitch_character_pose = Matrix4x4.from_quaternion(rot)
    Matrix4x4.set_translation(no_pitch_character_pose, Matrix4x4.translation(character_pose))
    spawn_player_character(self, no_pitch_character_pose)

    -- attach camera to character
    local land_unit = self.land_character.unit
    local unit_link = UnitLink.manager:get(player_camera)
    unit_link:set_parent(land_unit, 1)
    unit_link:link()

    -- offset camera to eye height
    local pose_offset = Matrix4x4.identity()
    Matrix4x4.set_translation(pose_offset, Vector3(0,0,default_character_eye_height))
    camera_wrapper:set_local_pose(pose_offset)

    self.is_freecam_mode = false
end

function Player.update(self, dt)
    -- freecam toggle input
    local index = Keyboard.button_id("f2")
    if index and Keyboard.pressed(index) then
        if self.is_freecam_mode then
            enable_walk_mode(self)
        else
            enable_free_mode(self)
        end
    end

    -- jump ability
    if not self.is_freecam_mode then
        local is_keyboard_jumping = Keyboard.pressed(Keyboard.button_id("space"))
        local is_pad_jumping = Pad1 and Pad1.active() and Pad1.pressed(Pad1.button_id(Appkit.Util.plat(nil, "a", nil, "cross")))
        if is_keyboard_jumping or is_pad_jumping then
            local controller = UnitController.manager:get(self.land_character)
            if controller and controller.velocity.z == 0 then
                controller:add_impulse(Vector3(0,0,14))
            end
        end
    end

    -- sprint ability
    local is_keyboard_sprinting = Keyboard.button(Keyboard.button_id("left shift")) > 0 
                                    and Keyboard.button(Keyboard.button_id("w")) > 0 
                                    and Keyboard.button(Keyboard.button_id("a")) == 0 
                                    and Keyboard.button(Keyboard.button_id("d")) == 0
    local input = Appkit.input_mapper:get_motion_input()
    local is_pad_sprinting = Pad1 and Pad1.active() 
                                and Pad1.button(Pad1.button_id(Appkit.Util.plat(nil, "left_thumb", nil, "l3"))) > 0
                                and input.move.y > 0.9
    if is_keyboard_sprinting or is_pad_sprinting then
        if is_character_spawned(self) then
            local controller = UnitController.manager:get(self.land_character)
            controller:set_move_speed(character_sprint_speed:unbox())
        else
            local controller = UnitController.manager:get(self.player_camera)
            controller:set_move_speed(free_cam_sprint_speed:unbox())
        end
    else
        if is_character_spawned(self) then
            local controller = UnitController.manager:get(self.land_character)
            controller:set_move_speed(character_move_speed:unbox())
        else
            local controller = UnitController.manager:get(self.player_camera)
            controller:set_move_speed(free_cam_move_speed:unbox())
        end
    end

    -- level exit input
    if Appkit.is_standalone() then
        local index = Keyboard.button_id("f5")
        local b1_pressed = index and Keyboard.pressed(index) 
        local index = Keyboard.button_id("esc")
        local b2_pressed = index and Keyboard.pressed(index) 
        if b1_pressed or b2_pressed then
            stingray.Application.quit()
        end
    end
end

return Player