Getting Started

Getting Started

Welcome to the KFEngine documentation!

hello.lua
local a = 1
local function hello()
    print(HelloWorld)
end
 
hello()
print(a)

Subscribe Engine event

  • BeginFrame: signals the beginning of the new frame. Input and Network react to this to check for operating system window messages and arrived network packets.
  • Update: application-wide logic update event. By default each update-enabled Scene reacts to this and triggers the scene update (more on this below.)
    • SceneUpdate: variable timestep scene update. This is a good place to implement any scene logic that does not need to happen at a fixed step.
    • SceneSubsystemUpdate: update scene-wide subsystems. Currently only the PhysicsWorld component listens to this, which causes it to step the physics simulation and send the following two events for each simulation step:
    • PhysicsPreStep: called before the simulation iteration. Happens at a fixed rate (the physics FPS.) If fixed timestep logic updates are needed, this is a good event to listen to.
    • PhysicsPostStep: called after the simulation iteration. Happens at the same rate as PhysicsPreStep.
    • SmoothingUpdate: update SmoothedTransform components in network client scenes.
    • ScenePostUpdate: variable timestep scene post-update. ParticleEmitter and AnimationController update themselves as a response to this event.
  • PostUpdate: application-wide logic post-update event. The UI subsystem updates its logic here.
  • RenderUpdate: Renderer updates its viewports here to prepare for rendering, and the UI generates render commands necessary to render the user interface.
  • PostRenderUpdate: by default nothing hooks to this. This can be used to implement logic that requires the rendering views to be up-to-date, for example to do accurate raycasts. Scenes may not be modified at this point; especially scene objects may not be deleted or crashes may occur.
  • EndFrame: signals the end of the frame. Before this, rendering the frame and measuring the next frame's timestep will have occurred.
app.lua
--game logic
local mygame = {}
 
local function OnUpdate(eventType, eventData)
    print("----OnUpdate----")
    --mygame:OnUpdate(eventType, eventData)
end
 
local function OnSceneUpdate(eventType, eventData)
    print("----OnSceneUpdate----")
    --mygame:OnSceneUpdate(eventType, eventData)
end
 
local function OnPostUpdate(eventType, eventData)
    print("----OnPostUpdate----")
    --mygame:OnPostUpdate(eventType, eventData)
end
 
local function OnPostRenderUpdate(eventType, eventData)
    print("----OnPostRenderUpdate----")
    --mygame:OnPostRenderUpdate(eventType, eventData)
end
 
function Start()
    SubscribeToEvent("Update", OnUpdate)
    SubscribeToEvent("SceneUpdate", OnSceneUpdate)
    SubscribeToEvent("PostUpdate", OnPostUpdate)
    SubscribeToEvent("PostRenderUpdate", OnPostRenderUpdate)
end

Create Simple Scene

app.lua
function mygame:CreateScene()
    local scene = Scene()
    scene:CreateComponent(Octree.id)
    --create pipeline
    local pipeline = scene:CreateComponent(RenderPipeline.id)
    pipeline:SetAttribute("Color Space", Variant(1))-- 0: GammaLDR, 1: LinearLDR 2: LinearHDR
    -- pipeline:SetAttribute("Specular Quality", Variant(2)) -- 0: Disabled 1: Simple, 2: Antialiased
    pipeline:SetAttribute("PCF Kernel Size", Variant(5))
    -- pipeline:SetAttribute("Bloom", Variant(true))
    pipeline:SetAttribute("Post Process Antialiasing", Variant(2)) -- 0: "None" 1: "FXAA2" 2: "FXAA3"
    --create zone
    local zone = scene:CreateComponent(Zone.id)
    zone:SetEnabled(true)
    zone.bounding_box = math3d.BoundingBox(-1000.0, 1000.0)
    zone.background_brightness = 0.5
    zone:SetZoneTextureAttr("Textures/DefaultSkybox.xml")
    --create sky
    local skyNode = scene:CreateChild("Sky")
    skyNode.rotation = math3d.Quaternion(1.0, 0.0, 0.0, 0.0)
    local skybox = skyNode:CreateComponent(Skybox.id)
    skybox:SetModel(cache:GetResource("Model", "Models/Box.mdl"))
    skybox:SetMaterial(cache:GetResource("Material","Materials/DefaultSkybox.xml"))
    --create light
    local lightNode = scene:CreateChild("DirectionalLight")
    lightNode.direction = math3d.Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
    local light = lightNode:CreateComponent(Light.id)
    light.light_type = LIGHT_DIRECTIONAL
    light.cast_shadows = true
    light.shadow_bias = BiasParameters(DEFAULT_CONSTANTBIAS, DEFAULT_SLOPESCALEDBIAS)
    light.shadow_cascade = CascadeParameters(5.0, 12.0, 30.0, 100.0, DEFAULT_SHADOWFADESTART)
    --create ground
    local planeNode = scene:CreateChild("Plane");
    planeNode.scale = math3d.Vector3(100.0, 1.0, 100.0)
    local planeObject = planeNode:CreateComponent(StaticModel.id)
    planeObject:SetModel(cache:GetResource("Model", "Models/Plane.mdl"))
    local mtl = cache:GetResource("Material", "Materials/GridTiled.xml")
    mtl:SetShaderParameter("UOffset", Variant(math3d.Vector4(100.0, 0.0, 0.0, 0.0)))
    mtl:SetShaderParameter("VOffset", Variant(math3d.Vector4(0.0, 100.0, 0.0, 0.0)))
    planeObject:SetMaterial(mtl)
    -- create camera
    local cameraNode = scene:CreateChild("Camera")
    cameraNode.position = math3d.Vector3(0.0, 10.0, -35.0)
    cameraNode:LookAt(math3d.Vector3(0.0, 0.0, 0.0))
    local camera = cameraNode:CreateComponent(Camera.id)
    camera.near_clip = 0.5
    camera.far_clip = 500.0
    --
    self.scene = scene
    self.camera_node = cameraNode
    self.camera = camera
end

Add UI

app.lua
function mygame:CreateFairyGUI()
    if not self.fairygui_scene then
        self.fairygui_scene = FairyGUI.FairyGUIScene()
    end
    local uiroot = self.fairygui_scene.groot
    FairyGUI.RegisterFont("default", "Fonts/FZY3JW.TTF")
    FairyGUI.SetDesignResolutionSize(1280, 720)
    FairyGUI.UIPackage.AddPackage("UI/VirtualList")
    local view = FairyGUI.UIPackage.CreateObject("VirtualList", "Main")
    uiroot:AddChild(view)
    self.ui_view = view
    self.gamelist = {
        {author = "Author1", name = "Game1"},
        {author = "Author2", name = "Game2"},
    }
    local list = self.ui_view:GetChild("mailList")
    list:SetVirtual()
    list:SetItemRenderer(function (index, obj)
        --obj:GetController("c1"):SetSelectedIndex((index % 3 == 0) and 1 or 0)
        --obj:GetController("IsRead"):SetSelectedIndex((index % 2 == 0) and 1 or 0);
        obj:GetChild("timeText"):SetText("Author: " .. self.gamelist[index + 1].author)
        obj:SetText(self.gamelist[index + 1].name)
    end)
    -- init items
    list:SetNumItems(#self.gamelist)
    local numItems = #self.gamelist
    for i = 1, numItems do
        local obj = list:GetChildAt(i - 1)
        obj:AddClickListener(function (context)
            local sender = context:GetSender()
            -- context:GetData()
            -- context:GetDataAsString()
            print("click :", sender:GetText())
        end)
    end
    -- dynamic append item
    view:GetChild("play"):AddClickListener(function (context)
        local list = self.ui_view:GetChild("mailList")
        local num = #self.gamelist + 1
        self.gamelist[num] = {author = "Author"..num, name = "Game"..num}
        list:SetNumItems(num)
        local obj = list:GetChildAt(num - 1)
        obj:AddClickListener(function (context)
            local sender = context:GetSender()
            -- context:GetData()
            -- context:GetDataAsString()
            print("click :", sender:GetText())
        end)
    end)
    FairyGUI.ReplaceScene(self.fairygui_scene)
end

Add Effect

app.lua
function mygame:CreateEffect()
    local emitter = self.scene:CreateChild("effect")
    emitter.position = math3d.Vector3(0.0, 2.0, 0.0)
    local effect = emitter:CreateComponent(EffekseerEmitter.id)
    effect:SetEffect("Effekseer/01_Suzuki01/001_magma_effect/aura.efk")
    effect:SetLooping(true)
    effect:Play()
    self.emitter = emitter
    self.effect = effect
end

Add Sound

app.lua
function mygame:CreateSound()
    local bankname = "Sounds/Master.bank"
    local ret = Audio.LoadBank(bankname)
    if not ret then
        print("LoadBank Faied. :", bankname)
    end
    local bankname = "Sounds/Master.strings.bank"
    ret = Audio.LoadBank(bankname)
    if not ret then
        print("LoadBank Faied. :", bankname)
    end
    self.sound_attack = Audio.GetEvent("event:/Scene/attack")
end

Input handle

Full code

app.lua
local function GetGameDir(userid)
    local platform = GetPlatformName()
    if platform == "Android" or platform == "Web" or platform == "iOS" then
        return filesystem:GetAppPreferencesDir("KFEngine", "KFPlayer") .. "Games/"..userid
    elseif platform == "Windows" then
        return filesystem:GetProgramDir() .. "Assets/Games/"..userid
    end
end
 
--game logic
local mygame = {
    yaw = 0,
    pitch = 0,
    MOVE_SPEED = 5.0
}
function mygame:Load(viewport, fairygui_scene)
    self.game_dir = GetGameDir(10002)
    virtual_filesystem:MountDir(self.game_dir)
    local pkg = self.game_dir.."/data.pak"
    if filesystem:FileExists(pkg) then
        virtual_filesystem:MountPackage(pkg)
    end
    self.fairygui_scene = fairygui_scene
    -- create level
    self:CreateScene()
    self:CreateFairyGUI()
    self:CreateEffect()
    self:CreateSound()
    -- setup viewport
    if not viewport then
        self.viewport = Viewport(self.scene, self.camera)
    else
        viewport:SetScene(self.scene)
        viewport:SetCamera(self.camera)
        self.viewport = viewport
    end
    Effekseer.SetCamera(self.camera)
    renderer_system:SetViewport(0, self.viewport)
    if touchEnabled then
        input_system.CreateJoystick(math3d.IntVector2(512, 512), 1.0)
    end
end
 
function mygame:UnLoad()
    -- cleanup resource
    virtual_filesystem:Unmount(self.game_dir)
    local pkg = self.game_dir.."/data.pak"
    if filesystem:FileExists(pkg) then
        virtual_filesystem:Unmount(pkg)
    end
end
 
function mygame:CreateScene()
    local scene = Scene()
    scene:CreateComponent(Octree.id)
    --create pipeline
    local pipeline = scene:CreateComponent(RenderPipeline.id)
    pipeline:SetAttribute("Color Space", Variant(1))-- 0: GammaLDR, 1: LinearLDR 2: LinearHDR
    -- pipeline:SetAttribute("Specular Quality", Variant(2)) -- 0: Disabled 1: Simple, 2: Antialiased
    pipeline:SetAttribute("PCF Kernel Size", Variant(5))
    -- pipeline:SetAttribute("Bloom", Variant(true))
    pipeline:SetAttribute("Post Process Antialiasing", Variant(2)) -- 0: "None" 1: "FXAA2" 2: "FXAA3"
    --create zone
    local zone = scene:CreateComponent(Zone.id)
    zone:SetEnabled(true)
    zone.bounding_box = math3d.BoundingBox(-1000.0, 1000.0)
    zone.background_brightness = 0.5
    zone:SetZoneTextureAttr("Textures/DefaultSkybox.xml")
    --create sky
    local skyNode = scene:CreateChild("Sky")
    skyNode.rotation = math3d.Quaternion(1.0, 0.0, 0.0, 0.0)
    local skybox = skyNode:CreateComponent(Skybox.id)
    skybox:SetModel(cache:GetResource("Model", "Models/Box.mdl"))
    skybox:SetMaterial(cache:GetResource("Material","Materials/DefaultSkybox.xml"))
    --create light
    local lightNode = scene:CreateChild("DirectionalLight")
    lightNode.direction = math3d.Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
    local light = lightNode:CreateComponent(Light.id)
    light.light_type = LIGHT_DIRECTIONAL
    light.cast_shadows = true
    light.shadow_bias = BiasParameters(DEFAULT_CONSTANTBIAS, DEFAULT_SLOPESCALEDBIAS)
    light.shadow_cascade = CascadeParameters(5.0, 12.0, 30.0, 100.0, DEFAULT_SHADOWFADESTART)
    --create ground
    local planeNode = scene:CreateChild("Plane");
    planeNode.scale = math3d.Vector3(100.0, 1.0, 100.0)
    local planeObject = planeNode:CreateComponent(StaticModel.id)
    planeObject:SetModel(cache:GetResource("Model", "Models/Plane.mdl"))
    local mtl = cache:GetResource("Material", "Materials/GridTiled.xml")
    mtl:SetShaderParameter("UOffset", Variant(math3d.Vector4(100.0, 0.0, 0.0, 0.0)))
    mtl:SetShaderParameter("VOffset", Variant(math3d.Vector4(0.0, 100.0, 0.0, 0.0)))
    planeObject:SetMaterial(mtl)
    -- create camera
    local cameraNode = scene:CreateChild("Camera")
    cameraNode.position = math3d.Vector3(0.0, 10.0, -35.0)
    cameraNode:LookAt(math3d.Vector3(0.0, 0.0, 0.0))
    local camera = cameraNode:CreateComponent(Camera.id)
    camera.near_clip = 0.5
    camera.far_clip = 500.0
    --
    self.scene          = scene
    self.camera_node    = cameraNode
    self.camera         = camera
    self.yaw            = cameraNode.rotation:YawAngle()
    self.pitch          = cameraNode.rotation:PitchAngle()
end
 
function mygame:CreateFairyGUI()
    if not self.fairygui_scene then
        self.fairygui_scene = FairyGUI.FairyGUIScene()
    end
    local uiroot = self.fairygui_scene.groot
    FairyGUI.RegisterFont("default", "Fonts/FZY3JW.TTF")
    FairyGUI.SetDesignResolutionSize(1280, 720)
    FairyGUI.UIPackage.AddPackage("UI/VirtualList")
    local view = FairyGUI.UIPackage.CreateObject("VirtualList", "Main")
    uiroot:AddChild(view)
    self.ui_view = view
    self.gamelist = {
        {author = "Author1", name = "Game1"},
        {author = "Author2", name = "Game2"},
    }
    local list = self.ui_view:GetChild("mailList")
    list:SetVirtual()
    list:SetItemRenderer(function (index, obj)
        --obj:GetController("c1"):SetSelectedIndex((index % 3 == 0) and 1 or 0)
        --obj:GetController("IsRead"):SetSelectedIndex((index % 2 == 0) and 1 or 0);
        obj:GetChild("timeText"):SetText("Author: " .. self.gamelist[index + 1].author)
        obj:SetText(self.gamelist[index + 1].name)
    end)
    -- init items
    list:SetNumItems(#self.gamelist)
    local numItems = #self.gamelist
    for i = 1, numItems do
        local obj = list:GetChildAt(i - 1)
        obj:AddClickListener(function (context)
            local sender = context:GetSender()
            -- context:GetData()
            -- context:GetDataAsString()
            print("click :", sender:GetText())
        end)
    end
    -- dynamic append item
    view:GetChild("play"):AddClickListener(function (context)
        local list = self.ui_view:GetChild("mailList")
        local num = #self.gamelist + 1
        self.gamelist[num] = {author = "Author"..num, name = "Game"..num}
        list:SetNumItems(num)
        local obj = list:GetChildAt(num - 1)
        obj:AddClickListener(function (context)
            local sender = context:GetSender()
            -- context:GetData()
            -- context:GetDataAsString()
            print("click :", sender:GetText())
        end)
    end)
    FairyGUI.ReplaceScene(self.fairygui_scene)
end
 
function mygame:CreateEffect()
    local emitter = self.scene:CreateChild("effect")
    emitter.position = math3d.Vector3(0.0, 2.0, 0.0)
    local effect = emitter:CreateComponent(EffekseerEmitter.id)
    effect:SetEffect("Effekseer/01_Suzuki01/001_magma_effect/aura.efk")
    effect:SetLooping(true)
    effect:Play()
    self.emitter = emitter
    self.effect = effect
end
 
function mygame:CreateSound()
    local bankname = "Sounds/Master.bank"
    local ret = Audio.LoadBank(bankname)
    if not ret then
        print("LoadBank Faied. :", bankname)
    end
    local bankname = "Sounds/Master.strings.bank"
    ret = Audio.LoadBank(bankname)
    if not ret then
        print("LoadBank Faied. :", bankname)
    end
    self.sound_attack = Audio.GetEvent("event:/Scene/attack")
end
 
local sound_play_time = 0
function mygame:OnUpdate(eventType, eventData)
    -- print("----mygame OnUpdate----")
    local timeStep = eventData[ParamType.P_TIMESTEP]:GetFloat()
    sound_play_time = sound_play_time + timeStep
    if sound_play_time > 3.0 then
        sound_play_time = 0
        self.sound_attack:Start()
    end
end
 
function mygame:OnSceneUpdate(eventType, eventData)
    -- print("----mygame OnSceneUpdate----")
    local timeStep = eventData[ParamType.P_TIMESTEP]:GetFloat()
    if not FairyGUI.IsFocusUI() then
        local rotate = false
        if touchEnabled then
            local TOUCH_SENSITIVITY = 2
            for i = 0, input_system:GetNumTouches() - 1 do
                if input_system.GetJoystickTouchID() ~= i then
                    local state = input_system:GetTouch(i)
                    if state.delta.x or state.delta.y then
                        local camera = self.camera_node:GetComponent(Camera.id)
                        if not camera then
                            return
                        end
                        self.yaw = self.yaw + TOUCH_SENSITIVITY * camera.fov / graphics_system.height * state.delta.x
                        self.pitch = math3d.ClampF(self.pitch + TOUCH_SENSITIVITY * camera.fov / graphics_system.height * state.delta.y, -90.0, 90.0)
                        rotate = true
                    end
                    break
                end
            end
        elseif input_system:GetMouseButtonDown(input.MOUSEB_RIGHT) then
            -- Mouse sensitivity as degrees per pixel
            local MOUSE_SENSITIVITY = 0.1
            -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
            local mouseMove = input_system.mouseMove
            self.yaw = self.yaw + MOUSE_SENSITIVITY * mouseMove.x
            self.pitch = math3d.ClampF(self.pitch + MOUSE_SENSITIVITY * mouseMove.y, -90.0, 90.0)
            rotate = true
        end
        if rotate then
            -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
            self.camera_node.rotation = math3d.Quaternion(self.pitch, self.yaw, 0.0)
        end
    end
    local move = false
    local controlDirection = math3d.Vector3(0.0, 0.0, 0.0)
    if input_system.IsJoystickCapture() then
        controlDirection = math3d.Quaternion(0.0, input_system.GetJoystickDegree() - 90, 0.0) * math3d.Vector3.BACK
        controlDirection:Normalize()
        move = true
    elseif not FairyGUI.IsInputing() then
        if input_system:GetKeyDown(input.KEY_W) then
            controlDirection = math3d.Vector3.FORWARD
            move = true
        end
        if input_system:GetKeyDown(input.KEY_S) then
            controlDirection = math3d.Vector3.BACK
            move = true
        end
        if input_system:GetKeyDown(input.KEY_A) then
            controlDirection = math3d.Vector3.LEFT
            move = true
        end
        if input_system:GetKeyDown(input.KEY_D) then
            controlDirection = math3d.Vector3.RIGHT
            move = true
        end
    end
    if move then
        self.camera_node:Translate(controlDirection * self.MOVE_SPEED * timeStep)
    end
end
 
local function OnUpdate(eventType, eventData)
    --print("----OnUpdate----")
    mygame:OnUpdate(eventType, eventData)
end
 
local function OnSceneUpdate(eventType, eventData)
    --print("----OnSceneUpdate----")
    --mygame:OnSceneUpdate(eventType, eventData)
end
 
local function OnPostUpdate(eventType, eventData)
    --print("----OnPostUpdate----")
    --mygame:OnPostUpdate(eventType, eventData)
end
 
local function OnPostRenderUpdate(eventType, eventData)
    --print("----OnPostRenderUpdate----")
    --mygame:OnPostRenderUpdate(eventType, eventData)
end
 
function Start()
    mygame:Load()
    SubscribeToEvent("Update", OnUpdate)
    SubscribeToEvent("SceneUpdate", OnSceneUpdate)
    SubscribeToEvent("PostUpdate", OnPostUpdate)
    SubscribeToEvent("PostRenderUpdate", OnPostRenderUpdate)
end