Phodex Games Posted April 9, 2018 Share Posted April 9, 2018 Hi, I wonder how it is possible to create a more complex progress bar. Currently I do it with 2 images (one bar image & one fill image) and scale the fill image down propotionally to the fill status of the bar. But what if want a circular status bar for example, or if the structure of my fill texture gets malformed due to the scaling? How would I achieve something that overcomes that problem? Maybe you can help Quote Link to comment Share on other sites More sharing options...
GorzenDev Posted April 9, 2018 Share Posted April 9, 2018 You could take the ProgressBar.lua as an example. It just draws a rectangle the size of the progress value. That would eliminate your texture malforming. Using GUI for your progressbar allows you to use draw functions like DrawCircle(), DrawPolygon to make any shape you want. Quote Link to comment Share on other sites More sharing options...
Phodex Games Posted April 10, 2018 Author Share Posted April 10, 2018 @GrozenDev Thanks for the answer :). Hmm, I do not use the LW GUI, but if I draw a rectangle its a single color area isnt it? Because I want a textured fill for my progress bar. I cant find DrawCircle or DrawPolygon in the Documentation where can I find that and how to use? Quote Link to comment Share on other sites More sharing options...
GorzenDev Posted April 10, 2018 Share Posted April 10, 2018 correct drawrect will draw a single color or some gradient if you want. DrawCircle and DrawPolygon are unfortunately not documented, they can be found in some widget scripts and offcourse in the header files if you have the c++ version. but for textures i'm not certain, i dont think widget system would give you any benefits. you might want to look into uv scaling/animation that might solve your texture scaling probems. as for a circular progressbar. personally i would create a custom widget that would animate single images as to appear loading circular (fake progress). you could always do some math to render a dynamic filled circular shape based on progressValue. some references for circular shapes. Segment of a circle (bottle): https://www.mathopenref.com/segmentareaht.html Sector of a circle (piepiece): https://www.mathopenref.com/arcsector.html Quote Link to comment Share on other sites More sharing options...
Phodex Games Posted April 10, 2018 Author Share Posted April 10, 2018 1 hour ago, GorzenDev said: you might want to look into uv scaling/animation Hmm yeah that is what I am searching for, but does LW support this and if so where is the syntax for it? Is it documented somewhere? 1 hour ago, GorzenDev said: some references for circular shapes By circular I meant something diffrent, but still interesting. I mean I can work around my problem myself, but this just works for uncomplex rectangular healthbars. I would like to be able to implement something like this: And as you can imagine if I just scale the fill of the bar on the x-axis the whole structure gets malformed and does not fit info the bar anymore. I would need something like a mask moving from left to right hiding parts of the fill texture. I also though of kind of an animation, but then I would need like 100 images of the diffrent fill states, which I consider to be not optimal, I would like to do this via code... Quote Link to comment Share on other sites More sharing options...
Rick Posted April 11, 2018 Share Posted April 11, 2018 There is a shader somewhere that helps aid in this I think. I don't think it's part of LE but might be able to find it by searching on the forums. Your green part in the image above would obviously be a rectangle (since all images are rectangle) but the shader would use a mask to define where the green part should be visible. Shadmar might have created it. I've used it some time ago. That really should be part of the LE engine itself because this is pretty common task in games. Quote Link to comment Share on other sites More sharing options...
Rick Posted April 11, 2018 Share Posted April 11, 2018 OK, we used this in urWorld game. Below is the code you need to manipulate the images. It's an example. In this example we had a shape of a human body as the border of the progressbar and I think it had another image as the filler. I've also included the shaders you need to have too. if BodyTemp ~= nil then return end import "Scripts/UI/UI.lua" BodyTemp = {} function BodyTemp:Create(actor, OnFreezingTick) local obj = {} obj.thermBg = Texture:Load("Materials/UI/Gameplay/stat_background.tex") obj.thermMask = Texture:Load("Materials/UI/Gameplay/stat_fill_mask.tex") obj.thermFill = Texture:Load("Materials/UI/Gameplay/stat_fill.tex") obj.thermOl = Texture:Load("Materials/UI/Gameplay/stat_border.tex") obj.thermIc = Texture:Load("Materials/UI/Gameplay/stat_icon_bodyTemp.tex") obj.thermFill:SetClampMode(true, true) obj.maskShader = Shader:Load("Shaders/Drawing/gui.shader") obj.drawShader = Shader:Load("Shaders/Drawing/drawimage.shader") obj.name = "Body Temp Stat" obj.max = 100 --obj.bodyTemp = obj.max obj.bodyTemp = 10 obj.bodyTempCheckInterval = 2500 obj.bodyTempLastInterval = Time:GetCurrent() obj.owner = actor obj.OnFreezingTick = OnFreezingTick --obj.x = 5 --obj.y = 4 obj.x = 0.0100 obj.y = -0.177 obj.ratio = obj.thermMask:GetHeight() / obj.thermMask:GetWidth() --obj.width = 48 --obj.height = obj.width * obj.ratio obj.width = 0.0700 obj.height = 0.1650 obj.anchor = ui.Anchors.BottomLeft obj.freezingInterval = 5000 obj.lastFreezingTick = 0 local k,v for k,v in pairs(BodyTemp) do obj[k] = v end return obj end function BodyTemp:Increase(value, source) self.bodyTemp = self.bodyTemp + value self.bodyTemp = Math:Min(100, self.bodyTemp) self.bodyTemp = Math:Max(0, self.bodyTemp) end function BodyTemp:Decrease(value) self.bodyTemp = self.bodyTemp - value self.bodyTemp = Math:Min(100, self.bodyTemp) self.bodyTemp = Math:Max(0, self.bodyTemp) end function BodyTemp:Update() if self.bodyTemp > 0 then if Time:GetCurrent() >= self.bodyTempCheckInterval + self.bodyTempLastInterval then self.bodyTempLastInterval = Time:GetCurrent() self:Decrease(1) end self.lastFreezingTick = Time:GetCurrent() end if self.bodyTemp == 0 then if Time:GetCurrent() >= self.freezingInterval + self.lastFreezingTick then self.lastFreezingTick = Time:GetCurrent() self.OnFreezingTick(self.owner) end end -- for pos/scale --if Window:GetCurrent():MouseDown(2) then -- if ui.pointOverElement(self) then -- ui.selectElement(self) -- end --end end function BodyTemp:Draw(context) context:SetBlendMode(Blend.Alpha) local pos = ui.getPosition(self) local scale = ui.getScale(self) context:SetShader(self.drawShader) context:SetColor(1, 1, 1, 1) context:DrawImage(self.thermBg, pos.x, pos.y, scale.width, scale.height) context:SetColor(1, 1, 1, (self.bodyTemp / self.max)) context:SetShader(self.maskShader) self.thermFill:Bind(1) context:DrawImage(self.thermMask, pos.x, pos.y, scale.width, scale.height) context:SetColor(1, 1, 1, 1) context:DrawImage(self.thermOl, pos.x, pos.y, scale.width, scale.height) context:DrawImage(self.thermIc, pos.x, pos.y, scale.width, scale.height) --[[ context:SetShader(self.drawShader) context:SetColor(1, 1, 1, 1) context:DrawImage(self.thermBg, self.x, context:GetHeight() - self.height - self.y, self.width, self.height) context:SetColor(1, 1, 1, (self.bodyTemp / self.max)) context:SetShader(self.maskShader) self.thermFill:Bind(1) context:DrawImage(self.thermMask, self.x, context:GetHeight() - self.height- self.y, self.width, self.height) context:SetColor(1, 1, 1, 1) context:DrawImage(self.thermOl, self.x, context:GetHeight() - self.height - self.y, self.width, self.height) context:DrawImage(self.thermIc, self.x, context:GetHeight() - self.height - self.y, self.width, self.height) ]] context:SetColor(Vec4(1)) context:SetBlendMode(Blend.Solid) end Shader (drawimage shader might already be part of LE and I don't think it changed but including it anyway): gui.shader SHADER version 1 @OpenGL2.Vertex #version 400 uniform mat4 projectionmatrix; uniform mat4 drawmatrix; uniform vec2 offset; uniform vec2 position[4]; uniform vec2 texcoords[4]; in vec3 vertex_position; in vec2 vertex_texcoords0; out vec2 vTexCoords0; void main(void) { gl_Position = projectionmatrix * (drawmatrix * vec4(position[gl_VertexID], 0.0, 1.0)); vTexCoords0 = texcoords[gl_VertexID]; } @OpenGLES2.Vertex @OpenGLES2.Fragment @OpenGL4.Vertex #version 400 uniform mat4 projectionmatrix; uniform mat4 drawmatrix; uniform vec2 offset; uniform vec2 position[4]; uniform vec2 texcoords[4]; in vec3 vertex_position; in vec2 vertex_texcoords0; out vec2 vTexCoords0; void main(void) { gl_Position = projectionmatrix * (drawmatrix * vec4(position[gl_VertexID], 0.0, 1.0)); vTexCoords0 = texcoords[gl_VertexID]; } @OpenGL4.Fragment #version 400 uniform vec4 drawcolor; uniform sampler2D texture0; uniform sampler2D texture1; in vec2 vTexCoords0; out vec4 fragData0; void main(void) { vec4 gui=texture(texture0,vTexCoords0); if (gui.r == 0. && gui.a > 0.) { gui.a = 0; gui = texture(texture1,vTexCoords0); if (1-drawcolor.a > vTexCoords0.y) gui.a=0; } fragData0 = gui; } drawimage.shader SHADER version 1 @OpenGL2.Vertex #version 400 uniform mat4 projectionmatrix; uniform mat4 drawmatrix; uniform vec2 offset; uniform vec2 position[4]; uniform vec2 texcoords[4]; in vec3 vertex_position; in vec2 vertex_texcoords0; out vec2 vTexCoords0; void main(void) { gl_Position = projectionmatrix * (drawmatrix * vec4(position[gl_VertexID], 0.0, 1.0)); vTexCoords0 = texcoords[gl_VertexID]; } @OpenGLES2.Vertex @OpenGLES2.Fragment @OpenGL4.Vertex #version 400 uniform mat4 projectionmatrix; uniform mat4 drawmatrix; uniform vec2 offset; uniform vec2 position[4]; uniform vec2 texcoords[4]; in vec3 vertex_position; in vec2 vertex_texcoords0; out vec2 vTexCoords0; void main(void) { gl_Position = projectionmatrix * (drawmatrix * vec4(position[gl_VertexID], 0.0, 1.0)); vTexCoords0 = texcoords[gl_VertexID]; } @OpenGL4.Fragment #version 400 uniform vec4 drawcolor; uniform sampler2D texture0; in vec2 vTexCoords0; out vec4 fragData0; void main(void) { fragData0 = drawcolor * texture(texture0,vTexCoords0); } I should really add this to the workshop and get more formal with it for people to easily use it. Maybe I'll do that this weekend. 2 1 Quote Link to comment Share on other sites More sharing options...
Phodex Games Posted April 11, 2018 Author Share Posted April 11, 2018 @Rick very cool you took the time to gather this stuff together for me I really appreciate it :). I guess it will take a while until I understood all that, especially since I am not very much, into shaders yet... Quote Link to comment Share on other sites More sharing options...
GorzenDev Posted April 13, 2018 Share Posted April 13, 2018 I meant to answer sooner but i had some hicups with loading the image. I have created a simple widget for leadwerks GUI that will get the results you are looking for i think. Usage is in the description. Result: --[[ ----ImageProgressBar.lua---- Simulates a progress/health bar with images. Uses GUI:SetClipRegion() to clip the foreground image. Uses Widget:SetImage() as foreground image. Makes use of the widget text during creation to specify background image path. --Create widget and set images local HPBar = Widget:Create("Path/To/Background/Image.tex", x, y, width, height, parent, "Scripts/GUI/ImageProgressBar.lua") local imagefill = gui:LoadImage("Path/To/Foreground/Image.tex") HPBar:SetImage(imagefill) --Update progress/health local progress = (self.health / self.maxHealth) HPBar:SetFloat("health", self.health) HPBar:SetFloat("maxHealth", self.maxHealth) HPBar:SetProgress(progress) HPBar:Redraw() ]] Script.hovered = false Script.pushed = false Script.textToggle = false Script.bgImage = nil -- Script.health = 0.0 Script.maxHealth = 100.0 function Script:Start() local gui = self.widget:GetGUI() --Get background image path from widget text and load it local bgImagePath = self.widget:GetText() if bgImagePath ~= "" and bgImagePath ~= nil then self.bgImage = gui:LoadImage(bgImagePath) end end function Script:Draw(x,y,width,height) local gui = self.widget:GetGUI() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) local scale = self.widget:GetGUI():GetScale() --Get 0 to 1 progress value local widgetProgress = self.widget:GetProgress() --Draw background image if self.bgImage ~= nil then gui:SetColor(1.0, 1.0, 1.0, 1.0) -- gui:DrawImage(self.bgImage, pos.x, pos.y, sz.x, sz.y) end --Clip the drawing region based on progress gui:SetClipRegion(pos.x, pos.y, sz.width * widgetProgress, sz.height) --Draw fill image local fillimage = self.widget:GetImage() if fillimage~=nil then gui:SetColor(1.0, 1.0, 1.0, 1.0) -- gui:DrawImage(fillimage, pos.x, pos.y, sz.x, sz.y) end --Set drawing region back to widget size gui:SetClipRegion(pos.x, pos.y, sz.width, sz.height) --Check if we want to draw text local drawtext = false if self.textToggle == true then drawtext = true elseif self.hovered == true then drawtext = true end --Draw health/maxHealth if drawtext == true then local style = Text.Center + Text.VCenter local text = tostring(self.health).."/"..tostring(self.maxHealth)--.." = "..tostring(widgetProgress) -- gui:SetColor(1.0,1.0,1.0,1.0) if widgetProgress <= 0.2 then gui:SetColor(1.0,0.0,0.0,1.0) end gui:DrawText(text, pos.x, pos.y, sz.width, sz.height, style) end end function Script:MouseEnter(x,y) self.hovered = true self.pushed = false self.widget:Redraw() end function Script:MouseLeave(x,y) self.hovered = false self.pushed = false self.widget:Redraw() end function Script:MouseDown(button,x,y) if self.pushed == true then return end if button == Mouse.Left then self.pushed = true self.widget:Redraw() end end function Script:MouseUp(button,x,y) if button == Mouse.Left then if self.pushed == true then self.textToggle = not self.textToggle end self.pushed = false self.widget:Redraw() end end 4 Quote Link to comment Share on other sites More sharing options...
Phodex Games Posted April 14, 2018 Author Share Posted April 14, 2018 Thanks for the effort man unfortunately I have no idea of the Leadwerks GUI system. I use my very own system, because back in the day there was no GUI system for LW and I never had time or felt to learn it as my own was getting better and better. But I guess I would benefit to learn it, so that I understand how GUI in general works and what the conventions are. For now I guess I will stay with my old system, as the effort would be insane to first learn the whole GUI system and then rebuild it for my project, especially as I am in the final stage of the project. So I will just stay with my normal healthbars ^^ I guess. But next time I will definetly try it out! I saw you have a tutorial series about it maybe that helps, but the for GUI system there are not many good tutorials and information yet... Quote Link to comment Share on other sites More sharing options...
GorzenDev Posted April 14, 2018 Share Posted April 14, 2018 I understand and i agree. Although i do think that both can be combined. I have a small helper script to easily implement the ImageProgressBar widget so no rebuilding of your project would be needed. function CreateSimpleHealthBar() takes a Context to create a widget. (for those that are not bothered to know how) function AddSimpleHealthBar() takes a GUI to create a widget. (for those that like to add it to an existing GUI) function SimpleHealthBar() takes a Widget as a parent. (for those that like to add it to an existing Widget) --[[ ---- SimpleHealthBar.lua ---- ]] function CreateSimpleHealthBar(x, y, width, height, context, emptyimage_path, fillimage_path) --GUI local gui = GUI:Create(context) gui:GetBase():SetScript("Scripts/GUI/Panel.lua") gui:GetBase():SetObject("backgroundcolor",Vec4(0,0,0,0.0)) -- local HPBar = {} HPBar = AddSimpleHealthBar(x, y, width, height, gui, emptyimage_path, fillimage_path) HPBar.context = context -- return HPBar end function AddSimpleHealthBar(x, y, width, height, gui, emptyimage_path, fillimage_path) return SimpleHealthBar(x, y, width, height, gui:GetBase(), emptyimage_path, fillimage_path) end function SimpleHealthBar(x, y, width, height, parent, emptyimage_path, fillimage_path) local HPBar = {} local scale = 1 -- HPBar.gui = parent:GetGUI() HPBar.gui:SetScale(scale) HPBar.parent = parent HPBar.position = iVec2(x, y) HPBar.size = iVec2(width, height) -- HPBar.maxHealth = 100.0 HPBar.health = 100.0 HPBar.hpChanged = false -- HPBar.zeroHP_CALLBACK = {} HPBar.zeroHP_CALLBACK_script = nil HPBar.fullHP_CALLBACK = {} HPBar.fullHP_CALLBACK_script = nil -- HPBar.Fill = Widget:Create(emptyimage_path, HPBar.position.x, HPBar.position.y, HPBar.size.x, HPBar.size.y, parent, "Scripts/GUI/ImageProgressBar.lua") HPBar.Fill:SetAlignment(0,0,0,1) HPBar.Fill:SetBool("isBackground", false) local imagefill = HPBar.gui:LoadImage(fillimage_path) HPBar.Fill:SetImage(imagefill) --HPBar.Fill:Redraw() -- function HPBar:SetMaxHealth(maxHP) self.maxHealth = maxHP if self.health > self.maxHealth then self.health = self.maxHealth --call callback script function if self.fullHP_CALLBACK_script ~= nil then self.fullHP_CALLBACK(self.fullHP_CALLBACK_script) end end self.hpChanged = true end function HPBar:SetHealth(HP) self.health = HP if self.health > self.maxHealth then self.health = self.maxHealth --call callback script function if self.fullHP_CALLBACK_script ~= nil then self.fullHP_CALLBACK(self.fullHP_CALLBACK_script) end elseif self.health < 0.0 then self.health = 0.0 --call callback script function if self.zeroHP_CALLBACK_script ~= nil then self.zeroHP_CALLBACK(self.zeroHP_CALLBACK_script) end end self.hpChanged = true end -- function HPBar:AddHealth(amount) self.health = self.health + amount if self.health > self.maxHealth then self.health = self.maxHealth --call callback script function if self.fullHP_CALLBACK_script ~= nil then self.fullHP_CALLBACK(self.fullHP_CALLBACK_script) end end self.hpChanged = true end function HPBar:SubHealth(amount) self.health = self.health - amount if self.health < 0.0 then self.health = 0.0 --call callback script function if self.zeroHP_CALLBACK_script ~= nil then self.zeroHP_CALLBACK(self.zeroHP_CALLBACK_script) end end self.hpChanged = true end -- function HPBar:SetZeroHPCallBack(callback, script) self.zeroHP_CALLBACK = callback self.zeroHP_CALLBACK_script = script end function HPBar:SetFullHPCallBack(callback, script) self.fullHP_CALLBACK = callback self.fullHP_CALLBACK_script = script end -- function HPBar:Show() if self.Fill:Hidden() then self.Fill:Show() end end function HPBar:Hidden() return self.Fill:Hidden() end function HPBar:Hide() if self.Fill:Hidden() == false then self.Fill:Hide() end end function HPBar:ProcessEvent(event) if event.id == Event.WidgetAction then if event.source == self.Fill then -- end end return true end function HPBar:Update() if self.Fill:Hidden() then return true end --Update widget progress if self.hpChanged then local progress = (self.health / self.maxHealth) self.Fill:SetFloat("health", self.health) self.Fill:SetFloat("maxHealth", self.maxHealth) self.Fill:SetProgress(progress) self.Fill:Redraw() self.hpChanged = false end return true end -- return HPBar end Quote Link to comment Share on other sites More sharing options...
Phodex Games Posted April 14, 2018 Author Share Posted April 14, 2018 Actually i was too curios about the UI system that i started to play around with it . Its very interesting. Your tutorial is pretty helpful, as there is no other information, besides the documentation about it. You undestand that it will take a while till I understand all that code you send me, but its a great reference, really appreciate your help. Very nice of you :). Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.