--[[ Makes use of `Appkit` to provide a basic project implementation: - Editor Test Level support - Standalone mode level loading - Creates and stores a single `World`, `Level`, `Unit` (Camera Unit) - Creates a "freecam" player that can be used to fly around the level This project implementation will be used if `core/appkit/lua/main.lua` is specified as the boot script in the project's settings.ini file. It provides hooks for extending behavior via `SimpleProject.extension_project`. ]]-- require 'core/appkit/lua/app' require 'core/appkit/lua/player_util' local LoadingScreen = require 'core/appkit/lua/loading_screen' -- Cached Engine Modules local Application = stingray.Application local EntityManager = stingray.EntityManager local Keyboard = stingray.Keyboard local ResourcePackage = stingray.ResourcePackage local Quaternion = stingray.Quaternion local Unit = stingray.Unit local Vector3 = stingray.Vector3 local World = stingray.World Appkit.SimpleProject = Appkit.SimpleProject or {} local SimpleProject = Appkit.SimpleProject -- SimpleProject.config should be modified by the project-specific lua to override defaults. SimpleProject.config = SimpleProject.config or { standalone_init_level_name = nil, -- Set this to a level resource name. camera_unit = "core/appkit/units/camera/camera", camera_index = 1, shading_environment = nil, -- Will override levels that have env set in editor. create_free_cam_player = true, free_cam_tracks_listener = true, exit_standalone_with_esc_key = true, stop_world_sounds_on_level_change = true, dont_capture_mouse_at_startup = false, viewport = "default", loading_screen_materials = nil, -- Enables a loading screen and transitions between the given materials. loading_screen_start_package = nil, -- If specified in combination with loading_screen_materials, loading screen will flush load this package at init time. loading_screen_end_package = nil, -- If specified in combination with loading_screen_materials, loading screen will load this in the background. loading_screen_shading_env = "core/stingray_renderer/environments/midday/midday" -- Controls the shading environment used by default by the loading screen. } -- These high level engine objects are created during SimpleProject.init() and -- can be referenced by the user project to extend behavior. SimpleProject.world = SimpleProject.world or nil SimpleProject.level = SimpleProject.level or nil SimpleProject.level_name = SimpleProject.level_name or nil SimpleProject.loading_shading_environment = SimpleProject.loading_shading_environment or nil SimpleProject.loading_start_package_resource = SimpleProject.loading_start_package_resource or nil SimpleProject.loading_end_package_resource = SimpleProject.loading_end_package_resource or nil SimpleProject.loading_packages_are_pending = SimpleProject.loading_packages_are_pending or false SimpleProject.loading_material_index = SimpleProject.loading_material_index or 1 SimpleProject.camera_unit = SimpleProject.camera_unit or nil SimpleProject.player = SimpleProject.player or nil SimpleProject.script_component = SimpleProject.script_component or nil -- SimpleProject.extension_project is a lua object which can extend behavior of -- the SimpleProject. It should be set before SimpleProject.init is called. -- The following functions can be implemented to extend behavior: -- - function ExtensionProjectName.on_level_load_pre_flow() -- - function ExtensionProjectName.on_level_shutdown_post_flow() -- - function ExtensionProjectName.on_init_complete() -- - function ExtensionProjectName.update(dt) -- - function ExtensionProjectName.render() -- - function ExtensionProjectName.shutdown() SimpleProject.extension_project = SimpleProject.extension_project or nil function SimpleProject.unload_level(level) if not level then return end local player = SimpleProject.player if player then Appkit.PlayerUtil.despawn_freecam(player) end local config = SimpleProject.config if stingray.Wwise and (config.stop_world_sounds_on_level_change == nil or config.stop_world_sounds_on_level_change == true) then stingray.WwiseWorld.stop_all(stingray.Wwise.wwise_world(SimpleProject.world)) end local level_wrapper = Appkit.get_managed_level_wrapper(level) if not level_wrapper then print "Error in SimpleProject.unload_level: level is not managed by Appkit." return end level_wrapper:shutdown() local extension_project = SimpleProject.extension_project if extension_project and extension_project.on_level_shutdown_post_flow then extension_project.on_level_shutdown_post_flow() end Appkit.unmanage_level(level) local world = SimpleProject.world World.destroy_level(world, level) SimpleProject.level = nil SimpleProject.level_name = nil end -- level_name is optional. It is needed if the level_name is not the -- same as the resource_name, as is the case for the editor Test Level. function SimpleProject.load_level(resource_name, level_name) if not resource_name then print "Error in SimpleProject.load_level: no level resource name." return end local world = SimpleProject.world level = World.load_level(world, resource_name) if not level then print ("Error in SimpleProject.change_level. Failed to load level ", resource_name) return end SimpleProject.level = level SimpleProject.level_name = level_name or resource_name print ("Resource name: "..resource_name) print ("Level name: "..(level_name or "undefined")) local config = SimpleProject.config Appkit.manage_level(level, SimpleProject.level_name, config.shading_environment == nil) if config.shading_environment then SimpleProject.shading_environment_override = World.create_shading_environment(world, config.shading_environment) Appkit.set_shading_environment(world, SimpleProject.shading_environment_override, config.shading_environment) end if config.create_free_cam_player then SimpleProject.player = Appkit.PlayerUtil.spawn_free_cam_player( level, SimpleProject.camera_unit, config.camera_index, Appkit.get_editor_view_position() or Vector3(0, 0, 2), Appkit.get_editor_view_rotation() or Quaternion.identity(), Appkit.input_mapper, config.free_cam_tracks_listener ) end local extension_project = SimpleProject.extension_project if extension_project and extension_project.on_level_load_pre_flow then extension_project.on_level_load_pre_flow() end -- The typical use case is to trigger the intiial flow Level Loaded node -- after lua project initialization is complete. Appkit.trigger_level_loaded_flow(level) end -- convenience function which unloads current level and loads given level function SimpleProject.change_level(resource_name) if not resource_name then return end local level = SimpleProject.level SimpleProject.unload_level(level) SimpleProject.load_level(resource_name) end function SimpleProject.load_startup_level() -- Support Test Level and standalone initial level load. The Editor Test Level -- is saved as a temporary file named "__level_editor_test". The actual original -- level name is stored in lua Application sript data for reference. The -- Appkit stores the original name in Appkit.test_level_name and the temp resource -- name in Appkit.test_level_resource_name during Appkit.setup_application(). -- Note that an untitled/unsaved level will have a Appkit.test_level_name of "". local standalone_init_level_name = SimpleProject.config.standalone_init_level_name local level_resource_name = Appkit.test_level_resource_name or standalone_init_level_name if level_resource_name then SimpleProject.load_level(level_resource_name, LEVEL_EDITOR_TEST_LEVEL_NAME or Appkit.test_level_name) else print "SimpleProject.load_startup_level: No level to load." end end function SimpleProject.init() if LEVEL_EDITOR_TEST and not LEVEL_EDITOR_TEST_READY then print("Waiting for test level initialization...") return end local config = SimpleProject.config -- Set load_level and unload_level overrides. This is needed so that the -- Appkit Change Level flow node can properly init and shutdown the SimpleProject -- levels. Appkit.custom_unload_level_function = SimpleProject.unload_level Appkit.custom_load_level_function = SimpleProject.load_level Appkit.setup_application() local world = Application.new_world() SimpleProject.world = world local world_wrapper = Appkit.manage_world(world, config.viewport) SimpleProject.script_component = EntityManager.script_component(world) -- Create a default camera. This can be retrieved and manipulated -- by flow or lua. It is also given to the Player in change_level. local camera_unit = World.spawn_unit(world, config.camera_unit) SimpleProject.camera_unit = camera_unit -- We need the default camera to render the world to draw levels or 2d UI. world_wrapper:set_camera_enabled(Unit.camera(camera_unit, 1), camera_unit, true) if not SimpleProject.init_loading_screen() then SimpleProject.load_startup_level() end local extension_project = SimpleProject.extension_project if extension_project and extension_project.on_init_complete then extension_project.on_init_complete() end if stingray.Window and not SimpleProject.config.dont_capture_mouse_at_startup then stingray.Window.set_clip_cursor(true) stingray.Window.set_mouse_focus(true) end end -- Attempts to start the loading splash screen. Returns true if there is a loading -- screen to show, false if not. This may trigger the loading of packages associated -- with the loading screen. function SimpleProject.init_loading_screen() local config = SimpleProject.config -- Do not show loading screen in Editor Test Level mode or if we have no materials local materials = config.loading_screen_materials local has_materials = materials and #materials > 0 if Appkit.test_level_resource_name or not has_materials then -- But do load the loading screen end package, if specified. if config.loading_screen_end_package then local package = Application.resource_package(config.loading_screen_end_package) ResourcePackage.load(package) ResourcePackage.flush(package) SimpleProject.loading_end_package_resource = package end return false end -- Init shading environment for loading screen portion. local world = SimpleProject.world local shading_environment_name = config.loading_screen_shading_env if Application.can_get("shading_environment", shading_environment_name or "") then local shading_environment_override = World.create_shading_environment(world, shading_environment_name) Appkit.set_shading_environment(world, shading_environment_override, shading_environment_name) SimpleProject.loading_shading_environment = shading_environment_override else print ("Error. SimpleProject.init_loading_screen. Cannot get shading environment resource: ", shading_environment_name) return false end -- Fully load start package, if configured. This is blocking. local package_name = config.loading_screen_start_package if package_name then local package = Application.resource_package(package_name) SimpleProject.loading_start_package_resource = package ResourcePackage.load(package) ResourcePackage.flush(package) end local material_name = materials[SimpleProject.loading_material_index] if not Application.can_get("material", material_name) then print ("Error. SimpleProject.init_loading_screen. Cannot get material: ", material_name) return false end LoadingScreen.init(world, material_name, SimpleProject.on_loading_screen_finished) -- Start loading background package, if configured. package_name = config.loading_screen_end_package if package_name then local package = Application.resource_package(package_name) SimpleProject.loading_end_package_resource = package ResourcePackage.load(package) SimpleProject.loading_packages_are_pending = true else LoadingScreen.allow_fade_out() end return true end function SimpleProject.on_loading_screen_finished() LoadingScreen.destroy() -- Support multiple images local materials = SimpleProject.config.loading_screen_materials local material_index = SimpleProject.loading_material_index if material_index < #materials then material_index = material_index + 1 SimpleProject.loading_material_index = material_index LoadingScreen.init(SimpleProject.world, materials[material_index], SimpleProject.on_loading_screen_finished) if not SimpleProject.loading_packages_are_pending then LoadingScreen.allow_fade_out() end return end -- Done showing loading screens, load startup level SimpleProject.load_startup_level() -- And cleanup local shading_environment = SimpleProject.loading_shading_environment if shading_environment then World.destroy_shading_environment(SimpleProject.world, shading_environment) SimpleProject.loading_shading_environment = nil end if SimpleProject.destroy_package(SimpleProject.loading_start_package_resource) then SimpleProject.loading_start_package_resource = nil end end function SimpleProject.update_loading_screen(dt) if LoadingScreen.update(dt) then if Keyboard.pressed(Keyboard.button_id('enter')) then SimpleProject.finish_loading_screen() SimpleProject.load_startup_level() elseif SimpleProject.loading_packages_are_pending then local package = SimpleProject.loading_end_package_resource if package and ResourcePackage.has_loaded(package) then ResourcePackage.flush(package) LoadingScreen.allow_fade_out() SimpleProject.loading_packages_are_pending = false end end end end function SimpleProject.destroy_package(package) if not package then return false end if ResourcePackage.has_loaded(package) then ResourcePackage.unload(package) end Application.release_resource_package(package) return true; end function SimpleProject.finish_loading_screen() LoadingScreen.destroy() if SimpleProject.destroy_package(SimpleProject.loading_start_package_resource) then SimpleProject.loading_start_package_resource = nil end local package = SimpleProject.loading_end_package_resource if package then if not ResourcePackage.has_loaded(package) then ResourcePackage.flush(package) end SimpleProject.loading_packages_are_pending = false end local shading_environment = SimpleProject.loading_shading_environment if shading_environment then World.destroy_shading_environment(SimpleProject.world, shading_environment) SimpleProject.loading_shading_environment = nil end end function SimpleProject.destroy_loading_screen_packages() if SimpleProject.destroy_package(SimpleProject.loading_start_package_resource) then SimpleProject.loading_start_package_resource = nil end if SimpleProject.destroy_package(SimpleProject.loading_end_package_resource) then SimpleProject.config.loading_end_package_resource = nil end SimpleProject.loading_packages_are_pending = false end function SimpleProject.update(dt) if LEVEL_EDITOR_TEST and not LEVEL_EDITOR_TEST_READY then return end Appkit.update(dt) SimpleProject.update_loading_screen(dt) local extension_project = SimpleProject.extension_project if extension_project and extension_project.update then extension_project.update(dt) end local script_component = SimpleProject.script_component if script_component then script_component:broadcast("update", dt) end if SimpleProject.config.exit_standalone_with_esc_key and Keyboard.pressed(Keyboard.button_id('esc')) and Appkit.is_standalone() then Application.quit() end end function SimpleProject.render(window) if LEVEL_EDITOR_TEST and not LEVEL_EDITOR_TEST_READY then return end local extension_project = SimpleProject.extension_project -- don't call Appkit.render if Project.render() returns a non-nil, non-false value if extension_project and extension_project.render and extension_project.render(window) then return end Appkit.render(window) end function SimpleProject.shutdown() if LEVEL_EDITOR_TEST and not LEVEL_EDITOR_TEST_READY then return end local extension_project = SimpleProject.extension_project if extension_project and extension_project.shutdown then extension_project.shutdown() end local world = SimpleProject.world if world == nil then print "Error in SimpleProject.shutdown. No world." return end local level = SimpleProject.level if level then SimpleProject.unload_level(level) end Appkit.shutdown() -- Appkit shutdowns managed objects so call it first LoadingScreen.destroy() Application.release_world(world) -- destroying the world destroys its units as well SimpleProject.world = nil -- Destroy packages after releasing the world, to wait for destruction of -- non-level units that may be referencing package content unloaded in the -- process. SimpleProject.destroy_loading_screen_packages() SimpleProject.camera_unit = nil SimpleProject.script_component = nil -- extra project shutdown post world destruction if extension_project and extension_project.post_world_shutdown then extension_project.post_world_shutdown() end end return SimpleProject