Appkit: /unit_controller.lua — code sample - Stingray Lua API Reference

Appkit: /unit_controller.lua — code sample

Code

--[[

A basic 3d movement controller component, used by the Templates for freecam and
first person movement.

Gravity is disabled by default.

Will use a Mover to move the unit, if the unit has one.

]]--

require 'core/appkit/lua/class'
require 'core/appkit/lua/component_manager'
require 'core/appkit/lua/input_mapper'

Appkit.UnitController = Appkit.class(Appkit.UnitController)
local UnitController = Appkit.UnitController

local Unit = stingray.Unit
local Mover = stingray.Mover
local Vector3 = stingray.Vector3
local Vector3Box = stingray.Vector3Box
local Matrix4x4 = stingray.Matrix4x4
local Quaternion = stingray.Quaternion

local default_gravity = Vector3Box(Vector3(0,0,-9.82))

Appkit.ComponentManager.give_manager(UnitController)

-- The given unit is moved by the controller.
function UnitController:init(entity, unit, input_mapper)
    self.unit = unit
    self.input_mapper = input_mapper
    self.translation_speed = Vector3Box(Vector3(1,1,1))
    self.yaw_speed = 1
    self.pitch_speed = 1
    self.is_enabled = true
    self.mover = nil
    self.velocity = Vector3Box(Vector3(0,0,0))
    self.active_gravity = Vector3Box(Vector3(0,0,0))
    self.world_gravity = default_gravity

    self.pitch_min = -90
    self.pitch_max = 90
    self.touch_scale = 1
    self.gamepad_scale = 5
    self.mouse_scale = 0.05

    UnitController.manager:add_component(entity, self, stingray.Unit.level(unit))
end

function UnitController:set_gravity_enabled(is_enabled)
    if is_enabled then
        self.active_gravity = self.world_gravity
    else
        self.active_gravity:store(Vector3(0,0,0))
    end
end

function UnitController:set_gravity(custom_value)
    self.world_gravity:store(custom_value)
    if self.active_gravity:unbox() ~= Vector3(0,0,0) then
        self.active_gravity:store(custom_value)
    end
end

function UnitController:set_mover(name)
    self.mover = Unit.set_mover(self.unit, name)
end

-- Enable or disable control of unit
function UnitController:set_enabled(enabled)
    self.is_enabled = enabled
end

function UnitController:set_move_speed(speed)
    self.translation_speed:store(speed)
end

function UnitController:set_yaw_speed(speed)
    self.yaw_speed = speed
end

function UnitController:set_pitch_speed(speed)
    self.pitch_speed = speed
end

function UnitController:add_impulse(impulse)
    self.velocity:store(self.velocity:unbox() + impulse)
end

function UnitController:set_pitch_min(min)
    self.pitch_min = min
end

function UnitController:set_pitch_max(max)
    self.pitch_max = max
end

function UnitController:set_touch_scale(scale)
    self.touch_scale = scale
end

function UnitController:set_gamepad_scale(scale)
    self.gamepad_scale = scale
end

function UnitController:set_mouse_scale(scale)
    self.mouse_scale = scale
end

local function compute_rotation(self, input, dt)
    local q_original = Unit.local_rotation(self.unit, 1)
    local m_original = Matrix4x4.from_quaternion(q_original)

    local yaw = 0
    local pitch = 0

    if stingray.Vector3.equal(stingray.Mouse.axis(stingray.Mouse.axis_id("mouse")), stingray.Vector3.zero()) then
        if stingray.Pad1 and stingray.Pad1.active() then
            -- gamepad
            -- apply an acceleration curve so that we move faster as player presses stick further
            yaw = (input.pan.x * input.pan.x * input.pan.x / 100) * self.gamepad_scale * dt
            pitch = (input.pan.y * input.pan.y * input.pan.y / 100) * self.gamepad_scale * dt
        else
            -- touch or other device
            yaw = input.pan.x * self.touch_scale * dt
            pitch = input.pan.y * self.touch_scale * dt
        end
    else
        -- mouse input is something similar to `pixels` moved this frame instead of a normalized value
        -- do not attempt to scale mouse input by delta time
        yaw = input.pan.x * self.mouse_scale
        pitch = input.pan.y * self.mouse_scale
    end

    local q_identity = Quaternion.identity()
    local q_yaw = Quaternion(Vector3(0,0,1), -yaw * self.yaw_speed)
    local q_pitch = Quaternion(Matrix4x4.x(m_original), -pitch * self.pitch_speed)

    local q_frame = Quaternion.multiply(q_yaw, q_pitch)
    local q_new = Quaternion.multiply(q_frame, q_original)


    -- clamp yaw and pitch
    if (self.pitch_min ~= nil and self.pitch_max ~= nil) then
        local pich_angle = math.acos(Vector3.dot(Quaternion.up(q_new), Vector3(0,0,1)))
        local above_horizon = Quaternion.forward(q_new).z > 0
        local adjustment = 0

        if above_horizon then
            local limit = self.pitch_max * math.pi/180.0
            adjustment = math.min(0, limit - pich_angle)
        else
            local limit = self.pitch_min * math.pi/180.0
            adjustment = math.max(0, limit - -pich_angle)
        end

        if adjustment ~= 0 then
            local adjustment_rotation = Quaternion.axis_angle(Vector3(1,0,0),adjustment)
            q_new = Quaternion.multiply(q_new, adjustment_rotation)
        end
    end

    return q_new
end

local function compute_translation(self, input, q, dt)
    local input_move = input.move
    local pose = Matrix4x4.from_quaternion(q)
    local pos = Unit.local_position(self.unit, 1)

    local mover = self.mover
    local frame_vel
    if mover then
        local active_gravity = self.active_gravity:unbox()
        local velocity = self.velocity:unbox()
        local gravity = active_gravity * dt

        self.velocity:store(velocity + gravity)
        frame_vel = velocity * dt + gravity
    else
        frame_vel = Vector3(0,0,0)
    end

    local frame_move = (Vector3.multiply_elements(input_move, self.translation_speed:unbox()) * dt) + frame_vel
    local move = stingray.Matrix4x4.transform(pose, frame_move)

    if mover then
        Mover.move(mover, move, dt)

        if Mover.standing_frames(mover) > 0 then
            local velocity = self.velocity:unbox()
            velocity.z = 0
            self.velocity:store(velocity)
        end

        return Mover.position(mover)
    else
        return Unit.local_position(self.unit, 1) + move
    end
end

function UnitController:update(dt)
    if not self.is_enabled then return end

    local input = self.input_mapper:get_motion_input()
    local q = compute_rotation(self, input, dt)
    local p = compute_translation(self, input, q, dt)

    local unit = self.unit
    Unit.set_local_rotation(unit, 1, q)
    Unit.set_local_position(unit, 1, p)
end

function UnitController:on_level_shutdown()
end

function UnitController:shutdown()
end

return UnitController