Jump to content

Lua Beginner's Guide - first map, Main Menu, Loading Screen and GUI


Dreikblack

433 views

 Share

Prerequisites

https://www.ultraengine.com/learn/luasetup?lang=lua

Also Lua extension is quite useful for VS Code, if you don't mind Tencent.

image.png.c1df8c2f14b92c583ecac476f603cf41.png

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:

image.thumb.png.d531001fa979287483c5e369

You can add skybox in Edit > World Settings (at top buttons in the Editor) by choosing skybox.dds from Materials\Environment\Default
image.png.bd8059101bd1a69116f0f7bc73951a

 

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:

image.thumb.png.cd71500a6807d7c17912c70b

 

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:

image.thumb.png.754ac879af57796db1f32a58

 

All created and modified classes:

LuaMenuGameLoad.zip

Repository: https://github.com/Dreikblack/LuaTutorialProject/tree/menu-loading-screen-and-gui

  • Like 2
  • Thanks 1
 Share

2 Comments


Recommended Comments

Nice Tutorial. Some parts need editing.

"Now in Visual Studio (VS) you can compile and run main.cpp to see how it’s looks in game:"

Should be replaced with " Press "F5" in the editor, or select "Game" then "run" to run your game. 

Your "menu" map YouTube dosen't show the save process. Minor I know but newbies might miss this.

Where does the user find "main.lua" and how do they edit it?

Solution: In the editor select "Projrect" ( top right of screen). Then select "Source". Then double click the main.lua file. This will open Visual Studio Code and show the contents of main.lua. If this fails make sure you have setup Visual Studio Code correctly https://www.ultraengine.com/learn/luasetup?lang=cpp 

Select all and delete it from Studio Code. Now paste the contents of the file from the tutorial.

Save file as main.lua.

How do you create the MainMenu.lua file?

Solution: 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 contents from the tutorial for this file and save.

Where do we find the FPS component?

Solution: Select "Project" ( Top right of the editor screen). select "Source", select "Components", select "Player". Double click "FPSPlayer.lua".

You also ask for this code snipit to be added at the top but it should be added to line 24.

For the mouse position reset code your tutorial says FPSPlayer.cpp should read FPSPlayer.lua.

Again a line number for where this code needs to be put would be helpful. (line 286). I know you give a link to a Github for all these files but if a newbie is only following the tutorial they will struggle.

There is no mention of where game.lua should be created. ( in the source folder).

Add new game include and global variable mentions main.cpp should be main.lua.

No mention of where the function call backs should be placed in main.lua. Should be Line 24.

Sorry if this sounds picky but you have put alot of work into this tutorial and it needs to be understood by as many new users to UltraEngine as we can.

Keep up the good work.

 

 

 

 

 

  • Like 1
Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...