From bc730ef48cc411159ff8a033fd5be69deccca237 Mon Sep 17 00:00:00 2001 From: PeaAshMeter Date: Sun, 7 Dec 2025 20:08:07 +0300 Subject: [PATCH] implement hp and mana bars --- lib/simple_ui/color.lua | 20 +++ lib/simple_ui/element.lua | 2 +- lib/simple_ui/level/layout.lua | 216 ++++++++++++++++++++++++------ lib/simple_ui/level/skill_row.lua | 47 +------ 4 files changed, 198 insertions(+), 87 deletions(-) create mode 100644 lib/simple_ui/color.lua diff --git a/lib/simple_ui/color.lua b/lib/simple_ui/color.lua new file mode 100644 index 0000000..f97788d --- /dev/null +++ b/lib/simple_ui/color.lua @@ -0,0 +1,20 @@ +--- @class Color +--- @field r number +--- @field g number +--- @field b number +--- @field a number +local color = { + r = 1, + g = 1, + b = 1, + a = 1 +} +color.__index = color + +--- @param rgba {r?: number, g?: number, b?: number, a?: number} +--- @return Color +function color.new(rgba) + return setmetatable(rgba, color) +end + +return color.new diff --git a/lib/simple_ui/element.lua b/lib/simple_ui/element.lua index 708fd34..d73e462 100644 --- a/lib/simple_ui/element.lua +++ b/lib/simple_ui/element.lua @@ -33,7 +33,7 @@ end --- @return T function uiElement.new(self, values) values.bounds = values.bounds or Rect {} - values.overlayGradientMesh = values.overlayGradientMesh or uiElement.bounds; + values.overlayGradientMesh = values.overlayGradientMesh or uiElement.overlayGradientMesh; return setmetatable(values, self) end diff --git a/lib/simple_ui/level/layout.lua b/lib/simple_ui/level/layout.lua index a385f8b..2f2fe8a 100644 --- a/lib/simple_ui/level/layout.lua +++ b/lib/simple_ui/level/layout.lua @@ -3,13 +3,19 @@ local AnimationNode = require "lib.animation_node" local Element = require "lib.simple_ui.element" local Rect = require "lib.simple_ui.rect" local SkillRow = require "lib.simple_ui.level.skill_row" +local Color = require "lib.simple_ui.color" --- @class BarElement : UIElement --- @field getter fun() : number --- @field value number --- @field maxValue number +--- @field color Color +--- @field useDividers boolean +--- @field drawText boolean local barElement = setmetatable({}, Element) barElement.__index = barElement +barElement.useDividers = false +barElement.drawText = false function barElement:update(dt) local val = self.getter() @@ -17,20 +23,54 @@ function barElement:update(dt) end function barElement:draw() - love.graphics.push() - love.graphics.applyTransform(self.transform) + local valueWidth = self.bounds.width * self.value / self.maxValue + local emptyWidth = self.bounds.width - valueWidth - love.graphics.setColor(1, 1, 1) + --- шум + love.graphics.setShader(Tree.assets.files.shaders.soft_uniform_noise) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + love.graphics.setShader() - love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width * self.value / self.maxValue, + --- закраска пустой части + love.graphics.setColor(0.05, 0.05, 0.05) + love.graphics.setBlendMode("multiply", "premultiplied") + love.graphics.rectangle("fill", self.bounds.x + valueWidth, self.bounds.y, emptyWidth, self.bounds.height) + love.graphics.setBlendMode("alpha") + + --- закраска значимой части её цветом + love.graphics.setColor(self.color.r, self.color.g, self.color.b) + love.graphics.setBlendMode("multiply", "premultiplied") + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, valueWidth, + self.bounds.height) + love.graphics.setBlendMode("alpha") + + --- мерки + love.graphics.setColor(38 / 255, 50 / 255, 56 / 255) + if self.useDividers then + local count = self.maxValue - 1 + local measureWidth = self.bounds.width / self.maxValue + + for i = 1, count, 1 do + love.graphics.line(self.bounds.x + i * measureWidth, self.bounds.y, self.bounds.x + i * measureWidth, + self.bounds.y + self.bounds.height) + end + end + love.graphics.setColor(1, 1, 1) - love.graphics.pop() + --- текст поверх + if self.drawText then + love.graphics.printf(tostring(self.value) .. "/" .. tostring(self.maxValue), self.bounds.x, + self.bounds.y, self.bounds.width, "center") + end + + self:drawGradientOverlay() end --- @class BottomBars : UIElement ---- @field children BarElement[] +--- @field hpBar BarElement +--- @field manaBar BarElement local bottomBars = setmetatable({}, Element) bottomBars.__index = bottomBars; @@ -38,7 +78,7 @@ bottomBars.__index = bottomBars; function bottomBars.new(cid) local t = setmetatable({}, bottomBars) - t.children = { + t.hpBar = barElement:new { getter = function() local char = Tree.level.characters[cid] @@ -46,9 +86,12 @@ function bottomBars.new(cid) return stats.hp or 0 end) end, + color = Color { r = 130 / 255, g = 8 / 255, b = 8 / 255 }, + drawText = true, maxValue = 20 - }, + } + t.manaBar = barElement:new { getter = function() local char = Tree.level.characters[cid] @@ -56,63 +99,154 @@ function bottomBars.new(cid) return stats.mana or 0 end) end, + color = Color { r = 51 / 255, g = 105 / 255, b = 30 / 255 }, + useDividers = true, maxValue = 10 } - } + return t end function bottomBars:update(dt) - local padding = 8 - local screenW, screenH = love.graphics.getDimensions() - self.bounds = Rect { - height = 40 + 2 * padding, - width = screenW * 0.25, - y = screenH - 64 - 1 - 2 * padding - 40 + local height = 14 + local margin = 2 + + self.bounds.height = height + self.bounds.y = self.bounds.y - height + + self.hpBar.bounds = Rect { + width = -2 * margin + self.bounds.width / 2, + height = height, + x = self.bounds.x + margin, + y = self.bounds.y } - self.transform = love.math.newTransform():translate(-self.bounds.width / 2 + screenW / 2, self.bounds.y) + self.manaBar.bounds = Rect { + width = -2 * margin + self.bounds.width / 2, + height = height, + x = self.bounds.x + margin + self.bounds.width / 2, + y = self.bounds.y + } - for i = 1, #self.children do - self.children[i].bounds = Rect { - width = screenW * 0.25, - height = 20 - } - self.children[i].transform = self.transform:clone():translate(0, - self.children[i].bounds.height * (i - 1) + (i - 1) * padding) - end - - - for _, el in ipairs(self.children) do - el:update(dt) - end + self.hpBar:update(dt) + self.manaBar:update(dt) end function bottomBars:draw() - for _, el in ipairs(self.children) do - el:draw() - end + -- шум + love.graphics.setShader(Tree.assets.files.shaders.soft_uniform_noise) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + love.graphics.setShader() + + love.graphics.setColor(38 / 255, 50 / 255, 56 / 255) + love.graphics.setBlendMode("multiply", "premultiplied") + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + love.graphics.setBlendMode("alpha") + + self.hpBar:draw() + self.manaBar:draw() end +local c = love.graphics.newCanvas(1280, 720) --- @TODO: выставлять канвасу правильный размер в зависимости от окна + +--- @class CharacterPanel : UIElement +--- @field animationNode AnimationNode +--- @field state "show" | "idle" | "hide" +--- @field skillRow SkillRow +--- @field bars BottomBars +local characterPanel = setmetatable({}, Element) +characterPanel.__index = characterPanel + +function characterPanel.new(characterId) + local t = {} + t.state = "show" + t.skillRow = SkillRow(characterId) + t.bars = bottomBars.new(characterId) + return setmetatable(t, characterPanel) +end + +function characterPanel:show() + AnimationNode { + function(animationNode) + if self.animationNode then self.animationNode:finish() end + self.animationNode = animationNode + self.state = "show" + end, + duration = 300, + onEnd = function() + self.state = "idle" + end, + easing = easing.easeOutCubic + }:run() +end + +function characterPanel:hide() + AnimationNode { + function(animationNode) + if self.animationNode then self.animationNode:finish() end + self.animationNode = animationNode + self.state = "hide" + end, + duration = 300, + easing = easing.easeOutCubic + }:run() +end + +function characterPanel:update(dt) + if self.animationNode then self.animationNode:update(dt) end + self.skillRow:update(dt) + self.bars.bounds = Rect { + width = self.skillRow.bounds.width, + x = self.skillRow.bounds.x, + y = self.skillRow.bounds.y + } + self.bars:update(dt) + + self.bounds = Rect { + x = self.bars.bounds.x, + y = self.bars.bounds.y, + width = self.bars.bounds.width, + height = self.bars.bounds.height + self.skillRow.bounds.height + } +end + +function characterPanel:draw() + love.graphics.setCanvas(c) + love.graphics.clear() + self.skillRow:draw() + self.bars:draw() + + self:drawBorder("outer") + + local alpha = 1 + if self.state == "show" then + alpha = self.animationNode:getValue() + elseif self.state == "hide" then + alpha = 1 - self.animationNode:getValue() + end + + love.graphics.setCanvas() + love.graphics.setColor(1, 1, 1, alpha) + love.graphics.draw(c) + love.graphics.setColor(1, 1, 1) +end + +----------------------------------- local layout = {} function layout:update(dt) local cid = Tree.level.selector:selected() if cid then - self.skillRow = SkillRow(cid) - self.skillRow:show() - -- self.bottomBars = bottomBars.new(cid) + self.characterPanel = characterPanel.new(cid) + self.characterPanel:show() elseif Tree.level.selector:deselected() then - self.skillRow:hide() - -- self.bottomBars = nil + self.characterPanel:hide() end - if self.skillRow then self.skillRow:update(dt) end - -- if self.bottomBars then self.bottomBars:update(dt) end + if self.characterPanel then self.characterPanel:update(dt) end end function layout:draw() - if self.skillRow then self.skillRow:draw() end - -- if self.bottomBars then self.bottomBars:draw() end + if self.characterPanel then self.characterPanel:draw() end end return layout diff --git a/lib/simple_ui/level/skill_row.lua b/lib/simple_ui/level/skill_row.lua index 65ebda3..fc51b67 100644 --- a/lib/simple_ui/level/skill_row.lua +++ b/lib/simple_ui/level/skill_row.lua @@ -63,8 +63,6 @@ end --- @class SkillRow : UIElement --- @field characterId Id --- @field selected SkillButton? ---- @field animationNode AnimationNode ---- @field state "show" | "idle" | "hide" --- @field children SkillButton[] local skillRow = setmetatable({}, Element) skillRow.__index = skillRow @@ -74,7 +72,6 @@ skillRow.__index = skillRow function skillRow.new(characterId) local t = { characterId = characterId, - state = "show", children = {} } @@ -85,7 +82,6 @@ function skillRow.new(characterId) for i, spell in ipairs(behavior.spellbook) do local skb = skillButton:new { icon = spell.tag } skb.onClick = function() - if t.state ~= "idle" then return end skb.selected = not skb.selected if t.selected then t.selected.selected = false end t.selected = skb @@ -109,36 +105,7 @@ function skillRow.new(characterId) return t end -function skillRow:show() - AnimationNode { - function(animationNode) - if self.animationNode then self.animationNode:finish() end - self.animationNode = animationNode - self.state = "show" - end, - duration = 300, - onEnd = function() - self.state = "idle" - end, - easing = easing.easeOutCubic - }:run() -end - -function skillRow:hide() - AnimationNode { - function(animationNode) - if self.animationNode then self.animationNode:finish() end - self.animationNode = animationNode - self.state = "hide" - end, - duration = 300, - easing = easing.easeOutCubic - }:run() -end - function skillRow:update(dt) - if self.animationNode then self.animationNode:update(dt) end - local iconSize = 64 * UI_SCALE local screenW, screenH = love.graphics.getDimensions() local padding, margin = 8, 4 @@ -161,6 +128,7 @@ end local c = love.graphics.newCanvas(1280, 720) --- @TODO: выставлять канвасу правильный размер в зависимости от окна function skillRow:draw() + local oldCanvas = love.graphics.getCanvas() love.graphics.setCanvas({ c, stencil = true }) love.graphics.clear() love.graphics.setColor(1, 1, 1) @@ -195,24 +163,13 @@ function skillRow:draw() love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) love.graphics.setBlendMode("alpha") - --граница - self:drawBorder("outer") - love.graphics.setStencilTest() --затенение self:drawGradientOverlay() love.graphics.setColor(1, 1, 1) - love.graphics.setCanvas() - - local alpha = 1 - if self.state == "show" then - alpha = self.animationNode:getValue() - elseif self.state == "hide" then - alpha = 1 - self.animationNode:getValue() - end - love.graphics.setColor(1, 1, 1, alpha) + love.graphics.setCanvas(oldCanvas) love.graphics.draw(c) end