Jump to content

Animated Progressionbar


Phodex Games
 Share

Recommended Posts

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 :)

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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:

2-G_filled.png.6abdc210fd0943393a4d5a23f443e822.png

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...

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

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:
hpbarA.thumb.JPG.3b6a8c54841e308e158f25e1d8d4a92f.JPGhpbarB.thumb.JPG.de7acb0bab8144d8779f7000a6ad12fa.JPG

 

--[[

	----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

 

 

  • Like 4
Link to comment
Share on other sites

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...

Link to comment
Share on other sites

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

 

Link to comment
Share on other sites

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 :).

Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   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.

 Share

×
×
  • Create New...