Prerequisites
https://www.ultraengine.com/learn/luasetup?lang=lua
Also Lua extension is quite useful for VS Code, if you don't mind Tencent.
Map
Create a simple map call start.ultra with a brush as floor and a Empty aka pivot with FPSPlayer component
How to do it:
Now you can press "F5" in the editor, or select "Game" then "run" to run your game to just made map:
You can add skybox in Edit > World Settings (at top buttons in the Editor) by choosing skybox.dds from Materials\Environment\Default
Main menu, loading screen and GUI
Create a simple menu with couple brushes and camera and name this map menu.ultra
Now open main.lua. For that open Project tab in top right corner, select root project folder and double click on Open the VSCode.bat file. If this fails make sure you have setup Visual Studio Code correctly https://www.ultraengine.com/learn/luasetup?lang=lua
Select all i nfile and delete it from VS Code. Now paste there folllow code with just a Loading screen, almost without anything else and save:
-- local before variable means it will be avaiable only in current scope (inside script, funtion, cycle or condition body) -- loading screen vars local loadingWorld = nil local loadingCamera = nil local loadingText = nil local loadingBackground = nil -- for using in main loop local currentWorld = nil local currentUi = nil local displays = GetDisplays() -- You can WINDOW_FULLSCREEN to styles to make game fullscreen, 3rd and 4th are resolution size window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[1], WINDOW_CENTER | WINDOW_TITLEBAR) -- Create a framebuffer, needed for rendering framebuffer = CreateFramebuffer(window) font = LoadFont("Fonts/arial.ttf"); loadingWorld = CreateWorld(); local centerX = framebuffer:GetSize().x * 0.5 local centerY = framebuffer:GetSize().y * 0.5 local labelHeight = framebuffer:GetSize().y * 0.2 loadingBackground = CreateSprite(loadingWorld, framebuffer.size.x, framebuffer.size.y) loadingBackground:SetColor(0.2, 0.2, 0.2); loadingBackground:SetRenderLayers(2); loadingText = CreateSprite(loadingWorld, font, "LOADING", labelHeight, TEXT_CENTER | TEXT_MIDDLE) loadingText:SetPosition(centerX, centerY + labelHeight * 0.5, 0) -- 0 layer - no render, 1 - default render, we will use 2 for UI and sprites loadingText:SetRenderLayers(2) -- Creating camera for sprites, which needs to be orthographic (2D) for UI and sprites if they used as UI loadingCamera = CreateCamera(loadingWorld, PROJECTION_ORTHOGRAPHIC); loadingCamera:SetPosition(centerX, centerY, 0); -- camera render layer should match with stuff that you want to be visible for this camera. RenderLayers is a bit mask, so you can combine few layers, but probably you don't need it in most cases loadingCamera:SetRenderLayers(2) currentWorld = loadingWorld -- simple minimum game loop while window:Closed() == false do -- Garbage collection step collectgarbage() -- getting all events from queue - input, UI etc. while (PeekEvent()) do local event = WaitEvent() -- You need to do it for UI in 3D scene if (currentUi) then currentUi:ProcessEvent(event) end end if (currentWorld) then -- Update game logic (positions, components etc.). By default 60 HZ and not depends on framerate if you have 60+ FPS currentWorld:Update() -- 2nd param is VSync (true by default), 3rd is fps limit. Can by changed dynamically. currentWorld:Render(framebuffer) end end
Now if we start a game we will see this loading screen:
And once we have a screen we can create a menu to load in.
Create MainMenu.lua in Source folder. To do it in Visual Studio Code select "File" then select "New file". Type in "MainMenu.lua" and hit return.
A dialog box will open asking where you want to save this file. It should open in the source folder by default but if it dosen't navigate to the source folder and hit return.
Paste in the following content for this file and save:
local function NewGameButtonCallback(Event, Extra) --this overload currently is not working --EmitEvent(EVENT_GAME_START, nil, 0, 0, 0, 0, 0, nil, "start.ultra") EmitEvent(EVENT_GAME_START) return true end local function ExitButtonCallback(Event, Extra) --to close application os.exit() return true end local function InitMainMenu(mainMenu) mainMenu.world = CreateWorld(); mainMenu.scene = LoadMap(mainMenu.world, "Maps/menu.ultra") --Create user interface local frameSize = framebuffer:GetSize(); mainMenu.ui = CreateInterface(mainMenu.world, font, frameSize) mainMenu.ui:SetRenderLayers(2); --to make backgrount transparent mainMenu.ui.background:SetColor(0.0, 0.0, 0.0, 0.0) --Create camera for GUI mainMenu.uiCamera = CreateCamera(mainMenu.world, PROJECTION_ORTHOGRAPHIC) mainMenu.uiCamera:SetPosition(frameSize.x * 0.5, frameSize.y * 0.5, 0); mainMenu.uiCamera:SetRenderLayers(2); --for correct rendering above 3D scene mainMenu.uiCamera:SetClearMode(CLEAR_DEPTH); --Menu buttons local newGameButton = CreateButton("New game", frameSize.x / 2 - 100, 125, 200, 50, mainMenu.ui.background); ListenEvent(EVENT_WIDGETACTION, newGameButton, NewGameButtonCallback); local exitButton = CreateButton("Exit", frameSize.x / 2 - 100, 200, 200, 50, mainMenu.ui.background); ListenEvent(EVENT_WIDGETACTION, exitButton, ExitButtonCallback); end --functions should be declared after another function that this fucntion uses function CreateMainMenu() local mainMenu = {} InitMainMenu(mainMenu) return mainMenu end
Let's get back to main.lua. We need to import main menu script and custom event ids to top:
import "Source/MainMenu.lua" --custom events ids EVENT_GAME_START = 1001 EVENT_MAIN_MENU = 1002
Now add after "local currentUi = nil" menu var and callback fuctions for events:
framebuffer = nil local menu = nil local function StartGameEventCallback(Event, Extra) --nothing just for now return true; end --function should be declared after vars that this function uses local function MainMenuEventCallback(Event, Extra) menu = CreateMainMenu(); --switching current render and update targets for loop currentWorld = menu.world; currentUi = menu.ui; return true; end
Put this above main loop:
--to show Loading screen before Main Menu loadingWorld:Render(framebuffer); --ListenEvent are needed to do something in callback function when specific even from specfic source (or not, if 2nd param is nil) emitted ListenEvent(EVENT_GAME_START, nil, StartGameEventCallback); ListenEvent(EVENT_MAIN_MENU, nil, MainMenuEventCallback); --let's try it out! EmitEvent(EVENT_MAIN_MENU)
Just in case if something one wrong, current main.lua main.lua
We need to modify FPSPlayer component just a little bit. Via Editor you can find it by selecting "Project" ( Top right of the editor screen), select "Source", select "Components", select "Player". Double click "FPSPlayer.lua".
Add to its top to other vars a new one (24th line to be precisely)
FPSPlayer.doResetMousePosition = true
And new condition for mouse position reset in FPSPlayer.lua in Update() at 286 line or find thine line with Ctrl-F and past to search field "window:SetMousePosition(cx, cy)"
if doResetMousePosition then window:SetMousePosition(cx, cy) end
One big thing left - a game to play. Add Game.lua to project at Source folder:
local function GameMenuButtonCallback(Event, Extra) if (KEY_ESCAPE == Event.data and Extra) then local game = Extra local isHidden = game.menuPanel:GetHidden() game.menuPanel:SetHidden(not isHidden) if isHidden then window:SetCursor(CURSOR_DEFAULT) else window:SetCursor(CURSOR_NONE) end if (game.player ~= nil) then --to stop cursor reset to center when menu on game.player.doResetMousePosition = not isHidden end end return false end local function MainMenuButtonCallback(Event, Extra) EmitEvent(EVENT_MAIN_MENU) return true end local function ExitButtonCallback(Event, Extra) os.exit() return true end local function InitGame(game, mapPath) game.world = CreateWorld(); game.scene = LoadScene(game.world, mapPath) for k, entity in pairs(game.scene.entities) do local foundPlayer = entity:GetComponent("FPSPlayer") if (foundPlayer ~= nil) then game.player = foundPlayer; break; end end --Create user interface for game menu local frameSize = framebuffer:GetSize() game.ui = CreateInterface(game.world, font, frameSize) game.ui:SetRenderLayers(2) game.ui.background:SetColor(0.0, 0.0, 0.0, 0.0) game.uiCamera = CreateCamera(game.world, PROJECTION_ORTHOGRAPHIC) game.uiCamera:SetPosition(frameSize.x * 0.5, frameSize.y * 0.5, 0) game.uiCamera:SetRenderLayers(2); game.uiCamera:SetClearMode(CLEAR_DEPTH); --widgets are stays without extra pointers because parent widet, game.ui.background in this case, keep them --to remove widget you should do widget:SetParent(nil) game.menuPanel = CreatePanel(frameSize.x / 2 - 150, frameSize.y / 2 - 125 / 2, 300, 250, game.ui.background) local menuButton = CreateButton("Main menu", 50, 50, 200, 50, game.menuPanel); ListenEvent(EVENT_WIDGETACTION, menuButton, MainMenuButtonCallback); local exitButton = CreateButton("Exit", 50, 150, 200, 50, game.menuPanel); ListenEvent(EVENT_WIDGETACTION, exitButton, ExitButtonCallback); --we don't need game menu on screen while playing game.menuPanel:SetHidden(true); --and we will need it once hitting Esc button ListenEvent(EVENT_KEYUP, window, GameMenuButtonCallback, game); end --functions should be declared after another function that this fucntion uses function CreateGame(mapPath) local game = {} InitGame(game, mapPath) return game end
Add new Game include and global var to main.lua:
import "Source/Game.lua" local game = nil
Update function callbacks at 24 line:
local function StartGameEventCallback(Event, Extra) --destroying a main menu menu = nil --to show loading screen loadingWorld:Render(framebuffer) if Event.text ~= nil and Event.text ~= "" then --in lua .. used for string concatenation game = CreateGame("Maps/" .. Event.text); else game = CreateGame("Maps/start.ultra"); end --switching current render and update targets for loop currentWorld = game.world currentUi = game.ui return true; end --function should be declared after vars that this function uses local function MainMenuEventCallback(Event, Extra) --destroying a game instance if one existed game = nil --to show loading screen loadingWorld:Render(framebuffer) menu = CreateMainMenu() --switching current render and update targets for loop currentWorld = menu.world; currentUi = menu.ui; return true; end
And in the end we have a game with own menu:
All created and modified classes:
Repository: https://github.com/Dreikblack/LuaTutorialProject/tree/menu-loading-screen-and-gui
- 2
- 1
2 Comments
Recommended Comments