Skip to content

Lua scripting

Mikulas Florek edited this page Feb 3, 2018 · 150 revisions

Lua script plugin provides an option to script using Lua. It provides a new type of component, which points to a lua script file. This file is executed when a game starts. Each component has lua sandboxed - it means that all variables and function are not accessible from other components unless they are put in the global table _G or accessed using LuaScript.getEnvironment.

Update function

Each script can contain function update(dt) and this function is called each frame.

Watch video tutorial

API

Engine

Global variables

  • Universe g_universe
  • Engine g_engine

All scenes are visible to script as global variables, each with prefix "g_scene_" and ends with the name of a plugin, e.g.:

  • g_scene_renderer
  • g_scene_animation
  • g_scene_audio
  • g_scene_physics
  • g_scene_lua_script

Properties

Every Vec3, color, decimal, integer and boolean property (you can see properties in the property grid in the editor) can be accessed from Lua. However name of a property is transformed in following way:

  1. Remove all nonalphabetic characters,
  2. If a character is after nonalphabetic character in the source name, it is uppercase in the Lua name.

For example:

-- property Fog color
GlobalLight.setFogColor(g_scene_renderer, cmp, {1, 0, 1})
local color = GlobalLight.getFogColor(g_scene_renderer, cmp) -- color == {1, 0, 1}

Types

Other than standard lua types there following types:

  • Vec3 - a table with 3 numbers
  • Quat - a table with 4 numbers

Functions

  • Scene Engine.getScene(Universe universe, string scene_name)

  • Resource Engine.loadResource(Engine engine, string path, string resource_type)

  • void Engine.unloadResource(Engine engine, Resource resource)

  • Entity Engine.createEntity(Universe universe)

  • void Engine.destroyEntity(Universe universe, Entity entity)

  • Entity Engine.getEntityByName(Universe universe, string name)

  • void Engine.loadUniverse(Engine engine, Universe universe, string path, function callback)

  • void Engine.logError(string text)

  • void Engine.logInfo(string text)

  • Vec3 Engine.multVecQuat(Vec3 vec, Vec3 axis, number angle)

  • Vec3 Engine.multVecQuat(Vec3 vec, Quat quat)

  • void Engine.setEntityPosition(Universe univ, Entity entity, Vec3 position)

  • Vec3 Engine.getEntityPosition(Universe univ, Entity entity)

  • void Engine.setEntityRotation(Universe univ, Entity entity, Vec3 axis, number angle)

  • void Engine.setEntityRotation(Universe univ, Entity entity, Quat rot)

  • Quat Engine.getEntityRotation(Universe univ, Entity entity)

  • Entity Engine.getParent(Universe univ, Entity child)

  • void Engine.setParent(Universe univ, Entity parent, Entity child)

  • void Engine.setEntityLocalRotation(Universe univ, Entity entity, Vec3 axis, number angle)

  • Component Engine.createComponent(Scene scene, Entity entity, string type)

  • void Engine.destroyComponent(Scene scene, string type, Component component)

  • Component Engine.getRenderable(Scene scene, Entity entity)

  • ComponentType Engine.getComponentType(string type_name)

  • Component Engine.getComponent(Universe universe, Entity entity, ComponentType)

  • Table Engine.instantiatePrefab(Engine engine, Universe universe, Vec3 position, PrefabResource prefab)

  • Entity Engine.createEntityEx(Engine engine, Universe universe, Table entity_description)

  • void Engine.nextFrame(Engine engine)

  • void Engine.pause(Engine engine, bool pause)

  • void Engine.setTimeMultiplier(Engine engine, number multiplier)

Usage:

Engine.createEntityEx(g_engine, g_universe, {
    renderable = { Source = "models/utils/cube/cube.msh" },
    position = {6, 5, 5}
})
local y = 0
local x = 0
local resources = Editor.getResources(Editor.editor, "model")
for _, res in ipairs(resources) do
    if res:find("dir\\") or res:find("dir/") then
        Engine.createEntityEx(g_engine, g_universe, {
            position = {x, 0, y},
            renderable = { Source = res }
        })
        x = x + 2
        if x > 20 then
            x = 0
            y = y + 2
        end
    end
end

Lua Script

Functions

  • number LuaScript.addScript(Scene scene, Component script)
  • void LuaScript.setScriptSource(Scene scene, Component script, number index, string path)
  • int LuaScript.getScriptCount(Scene scene, Component script)
  • Table LuaScript.getEnvironment(Scene scene, Entity entity, number script_index) #620
  • Timer LuaScript.setTimer(Scene scene, number time_seconds, function callback)
  • void LuaScript.cancelTimer(Scene scene, Timer timer)

Input system

Constants

  • Engine.INPUT_DEVICE_KEYBOARD
  • Engine.INPUT_DEVICE_MOUSE
  • Engine.INPUT_DEVICE_CONTROLLER
  • Engine.INPUT_EVENT_BUTTON
  • Engine.INPUT_EVENT_AXIS
  • Engine.INPUT_EVENT_DEVICE_ADDED
  • Engine.INPUT_EVENT_DEVICE_REMOVED
  • scancodes
  • keycodes

Callback

onInputEvent function is called on every input event.

-- event = {
--     device = {
--         type = ..., -- e.g. Engine.INPUT_DEVICE_KEYBOARD
--         index = ..., -- number
--     },
--     type = ..., -- e.g. Engine.INPUT_EVENT_BUTTON
--     scancode = ..., -- see the list of scancodes, only valid for keyboard events
--     key_id = ..., -- id of button/key
--     x = ..., -- number
--     y = ..., -- number
--     x_abs = ..., -- number
--     y_abs = ... -- number
-- }

function onInputEvent(event)
	if event.type == Engine.INPUT_EVENT_BUTTON then
		if event.device.type == Engine.INPUT_DEVICE_KEYBOARD then
			if event.scancode == Engine.INPUT_SCANCODE_0 then
				Engine.logInfo("keyboard button event, scancode 0")
			end
		end		
	elseif event.type == Engine.INPUT_EVENT_AXIS then
		if event.device.type == Engine.INPUT_DEVICE_MOUSE then
			yaw = yaw + event.x * -0.005;
			pitch = pitch + event.y * -0.005;
		end
	end
end

Physics

  • bool Physics.isControllerCollisionDown(Scene scene, Component cmp)
  • void Physics.moveController(Scene scene, Component cmp, Vec3 velocity, number time_delta)
  • number Physics.getActorSpeed(Scene scene, Component cmp)
  • Component Physics.getActorComponent(Scene scene, Entity entity)
  • void Physics.putToSleep(Scene scene, Component component)
  • void Physics.applyForceToActor(Scene scene, Component component, Vec3 force)
  • is_hit, hit_entity, hit_position Physics.raycast(Scene scene, Vec3 origin, Vec3 dir)

If two entities with a physical component collide, onContact function is called in script on both of them:

function onContact(entity)
    Engine.logError("not implemented - onContact(" .. entity .. ")")
end

If a physical entity enters a trigger, onTrigger function is called in script on both of them:

function onTrigger(other_entity, touch_lost)
    Engine.logError("not implemented - onTrigger(" .. other_entity .. ")")
end

Audio

  • SoundHandle Audio.playSound(Scene scene, Entity entity, string clip_name, bool is_3d)
  • void Audio.setSoundVolume(Scene scene, SoundHandle sound_id, number volume)
  • void Audio.setEcho(Scene scene, SoundHandle sound, number wet_dry_mix, number feedback, number left_delay, number right_delay)

Renderer

  • Pipeline Renderer.createPipeline(Engine engine, string pipeline_path)
  • void Renderer.destroyPipeline(Pipeline pipeline)
  • void Renderer.setPipelineScene(Pipeline pipeline, RenderScene scene)
  • void Renderer.pipelineRender(Pipeline pipeline, number width, number height)
  • Image Renderer.getRenderBuffer(Pipeline pipeline, string framebuffer_name, number renderbuffer_index)
  • string Renderer.getCameraSlot(Camera component)
  • Camera Renderer.getCameraComponent(Entity entity)
  • Renderable Renderer.getRenderableComponent(Entity entity)
  • void Renderer.addDebugLine(Vec3 from, Vec3 to, number rgba_color, number life)
  • void Renderer.addDebugCross(Vec3 center, number size, number rgba_color, number life)
  • void Renderer.addDebugSphere(Vec3 center, number size, number rgba_color, number life)
  • Material Renderer.getTerrainMaterial(Terrain component)
  • Texture Renderer.getMaterialTexture(Material material, number texture_index)
  • void Renderer.setRenderableMaterial(Scene scene, Renderable component, number material_index, string path)
  • void Renderer.setRenderablePath(Scene scene, Renderable component, string path)
  • GlobalLight Renderer.getActiveGlobalLight()
  • Entity Renderer.getGlobalLightEntity(GlobalLight light)
  • void Renderer.emitParticle(Scene scene, ParticleEmitter emitter)

Animation

  • void Animation.setControllerIntInput(Entity e, int input_index, int value)
  • void Animation.setControllerBoolInput(Entity e, int input_index, bool value)
  • void Animation.setControllerFloatInput(Entity e, int input_index, float value)
  • int Animation.getControllerInputIndex(Entity e, string name)
  • void Animation.setIK(Entity e, int index, number weight, Vec3 target, string bone0, string bone1, ...)

Navigation

  • bool Navigation.generateNavmesh()
  • void Navigation.navigate(Entity entity, Vec3 destination, number speed)
  • void Navigation.cancelNavigation(Entity entity)
  • void Navigation.debugDrawCompactHeightfield()
  • void Navigation.debugDrawNavmesh()
  • void Navigation.debugDrawHeightfield()
  • void Navigation.debugDrawPaths()
  • void Navigation.debugDrawContours()
  • number Navigation.getPolygonCount()
  • void Navigation.generateTile(number x, number y, bool keep_data)
  • number Navigation.getAgentSpeed(Entity entity)
  • bool Navigation.save(string path)
  • bool Navigation.load(string path)
  • void Navigation.setGeneratorParams(number cell_size, number cell_height, number agent_radius, number agent_height, number walkable_angle, number max_climb)
function onPathFinished() ...

Lua function onPathFinished is called on an entity which finished its path.

App

  • void App.setUniverse(Universe universe)
  • void App.loadUniverse(string path)
  • void App.frame()
  • void App.exit(int exit_code)
  • bool App.isFinished()

Editor

ImGui

Following dear imgui functions are available in lua:

  • bool, number ImGui.DragFloat(string label, number value)
  • bool ImGui.Button(string label)
  • bool, bool ImGui.Checkbox(string label, bool checked)
  • void ImGui.Text(string text)
  • void ImGui.SameLine()
  • bool ImGui.BeginPopup(string str_id)
  • void ImGui.EndPopup()
  • bool ImGui.OpenPopup(string str_id)
  • bool ImGui.BeginDock(string str_id)
  • void ImGui.EndDock()
  • bool ImGui.Begin(string str_id)
  • void ImGui.End()
  • void ImGui.Image(ImTextureID texture_id, number width, number height)

In case of imgui function changing one of its pointer parameters, Lua returns two values, one bool (true if the value is changed, false otherwise) and the new value. For example the return value from ImGui::Checbox(const char* label, bool* value) C function is the first return value from Lua function and the new *value is the second return value from Lua function.

Usage

x = 0
checked = true
function onGUI()
    -- button
    if ImGui.Button("Test") then
        Engine.logError("Test clicked.")
    end

    -- drag float
    local changed = false
    changed, x = ImGui.DragFloat("Drag float", x)
    if changed then
        Engine.logError("X changed, new value = " .. tostring(x))
    end

    -- checkbox
    changed, checked = ImGui.Checkbox("Checkbox", checked)
    if changed then
        if checked then
            Engine.logError("Checkbox is checked")
        else
            Engine.logError("Checkbox is unchecked")
        end
    end
end

Importing asset

	function fileExists(name)
	   local f=io.open(name,"r")
	   if f~=nil then io.close(f) return true else return false end
	end


	function getOutDir(src)
		if src:lower():find("tree") ~= nil then return "vegetation\\"       end
		if src:lower():find("birch") ~= nil then return "vegetation\\"      end
		if src:lower():find("pine") ~= nil then return "vegetation\\"       end
		if src:lower():find("bush") ~= nil then return "vegetation\\"       end
		if src:lower():find("rock") ~= nil then return "rocks\\"            end
		if src:lower():find("grass") ~= nil then return "vegetation\\"      end
		if src:lower():find("fern") ~= nil then return "vegetation\\"       end
		if src:lower():find("corn") ~= nil then return "vegetation\\"       end
		if src:lower():find("pumpkins") ~= nil then return "vegetation\\"   end
		if src:lower():find("wood_el") ~= nil then return "props\\"         end
		return "misc"
	end

	function hasBillboard(src)
		if src:lower():find("rock") ~= nil then return false end
		if src:lower():find("pumpkins") ~= nil then return false end
		if src:lower():find("grass") ~= nil then return false end
		if src:lower():find("corn") ~= nil then return false end
		if src:lower():find("bush") ~= nil then return false end
		if src:lower():find("fern") ~= nil then return false end
		return true
	end

	function import(dir, filename, out_dir)

		ImportAsset.clearSources()
		ImportAsset.addSource(dir .. filename)

		for i = 0, ImportAsset.getMeshesCount() - 1 do
			ImportAsset.setMeshParams(i, {lod = 0, import = true, import_physics = true})
		end
		for i = 0, ImportAsset.getMaterialsCount() - 1 do
			local args = {import = true, alpha_cutout = false}
			local name = ImportAsset.getMaterialName(i)
			if name:lower():find("branch") then args.alpha_cutout = true end
			ImportAsset.setMaterialParams(i, args)
			
			for j = 0, ImportAsset.getTexturesCount(i) - 1 do
				ImportAsset.setTextureParams(i, j, {import = false, to_dds = true})
			end
		end
		
		local basename = string.sub(filename, 1, -string.len(".FBX") - 1)
		local lod_count = 0
		for lod_idx = 1, 2 do
			local lod_path = dir .. basename .. "_LOD" .. tostring(lod_idx) .. ".fbx"
			if fileExists(lod_path) then
				lod_count = lod_count + 1
				local old_count = ImportAsset.getMeshesCount()
				local old_mat_count = ImportAsset.getMaterialsCount()
			
				ImportAsset.addSource(lod_path)

				for i = old_count, ImportAsset.getMeshesCount() - 1 do
					ImportAsset.setMeshParams(i, {lod = lod_idx, import = true, import_physics = false})
				end

				for i = old_mat_count, ImportAsset.getMaterialsCount() - 1 do
					local args = {import = true, alpha_cutout = false}
					local name = ImportAsset.getMaterialName(i)
					if name:lower():find("branch") then args.alpha_cutout = true end
					ImportAsset.setMaterialParams(i, args)
					
					for j = 0, ImportAsset.getTexturesCount(i) - 1 do
						ImportAsset.setTextureParams(i, j, {import = false, to_dds = true})
					end
				end
			end
		end
		
		local has_billboard = hasBillboard(filename)
		local _lods = {30, -1}
		if lod_count > 0 then 
			_lods = {30, 60, -1}
		end
		local out_dir = getOutDir(filename)
		ImportAsset.setParams({
			output_dir = [[C:\projects\Hunter\models\]] .. out_dir .. "\\",
			texture_output_dir = [[C:\projects\Hunter\textures\]] .. out_dir .. "\\",
			lods = _lods,
			create_billboard = has_billboard,
			scale = 0.01
		})
		ImportAsset.import()
	end

	function importDir(src_dir)
		for filename in io.popen([[dir "]] .. src_dir .. [[" /b]]):lines() do 
			if string.sub(filename,-string.len(".FBX")):upper() ==".FBX" and string.find(filename, "_LOD") == nil then
				import(src_dir, filename)
				return
			end
		end
	end

	importDir([[..\Hunter_work\3rdparty\Nature Pack\Objects\]])

Startup script

App.loadUniverse("universes/player.unv")
while Engine.hasFilesystemWork(g_engine) do Engine.processFilesystemWork(g_engine) end
Engine.startGame(g_engine, App.universe)

Clone this wiki locally