From 6e754155dc296e2856eb59f65dfe7af5fda9163a Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 13 Aug 2022 20:37:25 +0000 Subject: [PATCH] improve button in main menu --- src/data/audio/switch-5.ogg | Bin 0 -> 7814 bytes src/data/audio/switch-7.ogg | Bin 0 -> 7921 bytes src/data/fonts/kenney-future.ttf | Bin 0 -> 34116 bytes src/data/init.lua | 10 + src/data/ui/panels/blue-pressed.9.png | Bin 0 -> 7552 bytes src/data/ui/panels/blue.9.png | Bin 0 -> 8149 bytes src/groups.lua | 13 + src/helpers/rgb.lua | 2 +- src/lib/nata.lua | 19 + src/lib/slicy.lua | 497 ++++++++++++++++++++++++++ src/main.lua | 2 +- src/states/main-menu.lua | 116 ++++-- src/states/main.lua | 33 +- src/systems/bolt.lua | 2 + src/systems/physics.lua | 14 +- src/systems/player.lua | 20 +- src/systems/screen-scaler.lua | 2 + src/systems/sprite.lua | 6 +- src/systems/ui.lua | 111 ++++++ 19 files changed, 775 insertions(+), 72 deletions(-) create mode 100644 src/data/audio/switch-5.ogg create mode 100644 src/data/audio/switch-7.ogg create mode 100644 src/data/fonts/kenney-future.ttf create mode 100644 src/data/ui/panels/blue-pressed.9.png create mode 100644 src/data/ui/panels/blue.9.png create mode 100644 src/groups.lua create mode 100644 src/lib/slicy.lua create mode 100644 src/systems/ui.lua diff --git a/src/data/audio/switch-5.ogg b/src/data/audio/switch-5.ogg new file mode 100644 index 0000000000000000000000000000000000000000..799e3277c0c8826589216ec5518801293e13d5d7 GIT binary patch literal 7814 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 a6v#clA4L35zdJgAkN6k{4T$9*<-Y*ZXBEW& literal 0 HcmV?d00001 diff --git a/src/data/fonts/kenney-future.ttf b/src/data/fonts/kenney-future.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1dbb2dd5f7ca220796c5fae5bcbd4cfe09aa5a77 GIT binary patch literal 34116 zcmeHQYm8l2bzXO7{LaJRyq(8!Vuz5%9>+<5xG7ACaS{>=#3oJKG%#b2X9jy7nHl4l zM|CP8#E(iK6``m_h@z;fN?P#)wfVshBcZA)`GLTt0V3g2K>R2OwN#1}@a^}lwb$PJ zJnq~(*n$epx!(8evma}H>sxC-&bfE4r6VGnrIcYQ-g#(r-^i!ee@|rV0hGS)$n5yS zKmFrVZ1KFv$Y-WboIdv9y~}Sy{Z~bvzGixI{OEhPc0Vby?jf{!!!!zp%I-&SUE+G@ z^z8C8pZLUo{{+|1i3}e-F@I!SI{))|kw1GG*VD7(&n$GmBY!QjX(QSfbK|pRVpL?~>%h}g4v3vw6ukV$ww{u(C?CB+g z|8n*W8Ct~Hpa*ez`Fe2$MD{e-z1j|KN<3y<@3YSA%BT{cw}?PUn=h!`B=zbCQpuhE9AGyW9#-= z9{9Je`{)Dn3#S)nrlyyRM<(ayCQldp_TG6{$R0eod~$K}vB{~EC&m{K&d)6$T3$SP zB(}8bO4}kdetdFqX=Z+|xO2~5tKV&nGa)le#dxv2IDT|;c6{-8F@LN$cW8iHD|_mlyVrjvfP!CB?Dl$o%Z6iT_3g#W#yn<4eWF$(a+& z#i^O)X_KmbHXoQ=nwgrLTr7@GE*)8%S)hXbwEgkPBg^xPh2h-Xt}N+BucgtY>G@Nm z_wK#>t~cH@dR*DteM~8PU~v-2KQLK5Fh4tsmc{+c%ZoD;C#kGBG(EmJS=@hO=J;gL zawIf|J#4*aesOB_#LSV&xuwaa(TUT$m&Ql$+Ov1`ASF)BOchU`96xe=W^StZ;N-&e z>BXhu{qwMN_B70XrkI#K`oO8Ktmo@seN=4C-n%c9K4luXOA6!M5nVpPoG{WPxj z$zHir?y8j@l#^(C68$FSF{CNvPheav%0Wn+L){^yMU)EP|J~4@aJ3_ufrJ7`3&l4sMHOdwh_l1 z`YZ=5{^DQG12 z3D`ITjr7$ip~4eX|?@Rx&mPyhL57y-Uzfk1s?E_M^s_B+K;KytdBy+`KBw z+Uz#ZEy3<3$eIWGQMngByc_ZLM!5%9$D!S0Z#TG$3?Nb&YsUEpkSjEHXTc)vCNK8` zHEo|!hTJy=d^xTDWuN=O+l*>qtS2v#k6gO>$7SzPHl~1&y^koyCA`y;35=56$c-a^ zmqz3$d@&=2aRRw1=zSWNjHBmqT+eCA2ho24$T;?h{{7HlKAqKZM{hra@(Hv%3R_R1 z?>mq?0iLF@nhXtnsPjo#hkNm`aM~G*=w0$6S2x=Wi+m2Z^<#YA5RQX~4nHPiQoOo; zsASP_*};3~Rpy6EEh~`a9Y2p_kk@vsW1+Sm#tOVeUW@fpI@JTqpcRaXbpI0VzlQTY zI3LTd|El|MxcUD+N^Ye<)c#SZKh-!-qE^uQKf3>Z{(iRoB@%k61;kD)2?gs)bU@jo{NWrloQERQ&WvHD z4W>P4;B{yPJ4#|b4>1XoS}h3&BcaA=Jqi>C-!x!*Mt^7!RtqIoovI4 z2U_&vkx@)(-U3|x5WRn@B`QbRO#GoS`dGZHyR~!4Qbol}6^MJ?QZDBOUcm;2v*g}L z-iuMK@-!l#r{Yt1-C9-U+Zc&jw7<8^B)EmBM=kB<*-OvosAwZv0RI)pWdoYl!jzh=T7V6Col)K**eYhr09HpJvLKU+N70B}yW;0@E1zV#o zj0_gR=U>BU{0__6hMogI7%4=+LNLJ!yi2 z0S77?@-J=C^FZ3a2H^+k7s`nE=#I4xX zHpPayLv6FSh!QeXN5Ie%OV9vpJRW}e1IX*}gN zMypVV_|+EYX%7yc*J4B&!%5c$*`9egX-hfEvAdx#B9bhxsf@#~LFac-6UQcl0rOnh z*4h!$M~1cHk87Gd7#ARC1v+|+fS%;6FcL@(4vh~o#UpW3xzcuc%jR?zRc6qfsc5kd zV4Dj9(~�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 literal 0 HcmV?d00001 diff --git a/src/data/init.lua b/src/data/init.lua index 0ce676d..a2a8941 100644 --- a/src/data/init.lua +++ b/src/data/init.lua @@ -1,6 +1,10 @@ local cargo = require("lib.cargo") local aseLoader = require("lib.ase-loader") local pprint = require("lib.pprint") +local slicy = require("lib.slicy") +local ripple = require("lib.ripple") + +love.graphics.setDefaultFilter("nearest", "nearest") -- TODO: Maybe add a texture atlas library for packing frame data -- TODO: For production maybe use another type of loader? (https://github.com/elloramir/packer, https://github.com/EngineerSmith/Runtime-TextureAtlas) @@ -84,5 +88,11 @@ return cargo.init{ loaders = { aseprite = loadAsepriteSprite, ase = loadAsepriteSprite, + ["9.png"] = function(filename) + return slicy.load(filename) + end, + ogg = function(filename) + return ripple.newSound(love.audio.newSource(filename, 'static')) + end } } diff --git a/src/data/ui/panels/blue-pressed.9.png b/src/data/ui/panels/blue-pressed.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6ea732747bc290d5b705e6f87ac569a97f897de7 GIT binary patch literal 7552 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 LJF`NQ0}=lMmL&U3 literal 0 HcmV?d00001 diff --git a/src/data/ui/panels/blue.9.png b/src/data/ui/panels/blue.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d199a46b2cab7c63c1766615f94b88d2b59b7a31 GIT binary patch literal 8149 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 literal 0 HcmV?d00001 diff --git a/src/groups.lua b/src/groups.lua new file mode 100644 index 0000000..6069bb5 --- /dev/null +++ b/src/groups.lua @@ -0,0 +1,13 @@ +return { + physical = {filter = {"pos", "vel"}}, + player = {filter = { + "pos", "acc", "speed", "bolts" + -- "bolt_count", + -- "bolt_cooldown", "bolt_speed", "bolt_friction" + }}, + controllable_player = {filter = {"controllable"}}, + sprite = {filter = {"pos", "sprite"}}, + bolt = {filter={"pos", "vel", "bolt"}}, + collider = {filter={"collider"}}, + ui_button = {filter={"pos", "size", "text"}} +} diff --git a/src/helpers/rgb.lua b/src/helpers/rgb.lua index ec9f6fc..d83b471 100644 --- a/src/helpers/rgb.lua +++ b/src/helpers/rgb.lua @@ -1,3 +1,3 @@ return function(r, g, b) - return {r/255, g/255, b/255} + return {r/255, g/255, b/255, 1} end diff --git a/src/lib/nata.lua b/src/lib/nata.lua index 17993ae..e16a713 100644 --- a/src/lib/nata.lua +++ b/src/lib/nata.lua @@ -259,6 +259,25 @@ function Pool:_init(options, ...) self.data = options.data or {} local groups = options.groups or {} local systems = options.systems or {nata.oop()} + + if options.available_groups then + for _, system in ipairs(systems) do + if type(system.required_groups) == "table" then + for _, group_name in ipairs(system.required_groups) do + local group = options.available_groups[group_name] + if group and not self.groups[group_name] then + self.groups[group_name] = { + filter = group.filter, + sort = group.sort, + entities = {}, + hasEntity = {}, + } + end + end + end + end + end + for groupName, groupOptions in pairs(groups) do self.groups[groupName] = { filter = groupOptions.filter, diff --git a/src/lib/slicy.lua b/src/lib/slicy.lua new file mode 100644 index 0000000..30f1bb1 --- /dev/null +++ b/src/lib/slicy.lua @@ -0,0 +1,497 @@ +local M = {} + +--[[ + file format is as follows: + * extension: *.9.png, same as patchy and (afaik) behaves the same + * original author never documented it anywhere + * actual image has a 1 pixel border on all sides + * pixels in the border can either be black with opacity > 0 ("set") or not + * set pixels serve as metadata for how to slice the image into 9 patches + * first and last set pixels on the top and left define the interval for the "edge" portions of the image + * image "edge" will be scaled in 1 dimension to accomodate variable size + * first and last set pixels on the bottom and right define the "content window" + * content window defines inner padding so that content doesn't touch the borders + * can be different from the "edge" definitions + * getContentRegion() returns the content region bounds for a given size +]] + +---@class PatchedImage +---@field patches table +---@field contentPadding table +---@field x number +---@field y number +---@field width number +---@field height number +local PatchedImage = {} + +local PatchedImageMt = {__index = PatchedImage} + +local debugDraw = false +local debugLog = false +local DEBUG_DRAW_SEP_WIDTH = 1 + +local function dbg(...) + if debugLog then + print(...) + end +end + +local function firstBlackPixel(imgData, axis, axisoffset, reversed) + local lim, getpixel + if axis == "row" then + lim = imgData:getWidth() - 1 + getpixel = function(idx) + return imgData:getPixel(idx, axisoffset) + end + elseif axis == "col" then + lim = imgData:getHeight() - 1 + getpixel = function(idx) + return imgData:getPixel(axisoffset, idx) + end + else + return nil, "argument 2: expected either 'row' or 'col', got " .. tostring(axis) + end + dbg("looking for black pixel in axis", axis, axisoffset) + + local startidx, endidx, step + if reversed then + -- start at last valid position, down to 0 + startidx, endidx, step = lim, 0, -1 + else + -- go upwards instead + startidx, endidx, step = 0, lim, 1 + end + + for idx = startidx, endidx, step do + local r, g, b, a = getpixel(idx) + -- black non-transparent pixel + if r + g + b == 0 and a > 0 then + dbg("black pixel found at idx", idx) + return idx + end + end + return nil, "no black pixel found" +end + +local function setCorners(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) + dbg("slicing corners") + + -- -1 from file format border, -1 from excluding the pixel itself + local brCornerWidth = rawdata:getWidth() - horizontalEdgeSegment[2] - 2 + local brCornerHeight = rawdata:getHeight() - verticalEdgeSegment[2] - 2 + + local tlCorner = love.image.newImageData(horizontalEdgeSegment[1] - 1, verticalEdgeSegment[1] - 1) + tlCorner:paste( + rawdata, + 0, 0, + -- skip metadata column and row + 1, 1, + tlCorner:getDimensions() + ) + p.patches[1][1] = love.graphics.newImage(tlCorner, {}) + tlCorner:release() + dbg("top left corner:", p.patches[1][1]:getDimensions()) + + local trCorner = love.image.newImageData(brCornerWidth, verticalEdgeSegment[1] - 1) + trCorner:paste( + rawdata, + 0, 0, + horizontalEdgeSegment[2] + 1, 1, + trCorner:getDimensions() + ) + p.patches[1][3] = love.graphics.newImage(trCorner, {}) + trCorner:release() + dbg("top right corner:", p.patches[1][3]:getDimensions()) + + local blCorner = love.image.newImageData(horizontalEdgeSegment[1] - 1, brCornerHeight) + blCorner:paste( + rawdata, + 0, 0, + 1, verticalEdgeSegment[2] + 1, + blCorner:getDimensions() + ) + p.patches[3][1] = love.graphics.newImage(blCorner, {}) + blCorner:release() + dbg("bottom left corner:", p.patches[3][1]:getDimensions()) + + local brCorner = love.image.newImageData(brCornerWidth, brCornerHeight) + brCorner:paste( + rawdata, + 0, 0, + horizontalEdgeSegment[2] + 1, verticalEdgeSegment[2] + 1, + brCorner:getDimensions() + ) + p.patches[3][3] = love.graphics.newImage(brCorner, {}) + brCorner:release() + dbg("bottom right corner:", p.patches[3][3]:getDimensions()) +end + +local function setMiddle(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) + dbg("slicing middle") + local w = horizontalEdgeSegment[2] - horizontalEdgeSegment[1] + 1 + local h = verticalEdgeSegment[2] - verticalEdgeSegment[1] + 1 + local middle = love.image.newImageData(w, h) + middle:paste( + rawdata, + 0, 0, + horizontalEdgeSegment[1], verticalEdgeSegment[1], + w, h + ) + p.patches[2][2] = love.graphics.newImage(middle, {}) + middle:release() + dbg("middle:", p.patches[2][2]:getDimensions()) +end + +local function setEdges(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) + dbg("slicing edges") + local hlen = horizontalEdgeSegment[2] - horizontalEdgeSegment[1] + 1 + local vlen = verticalEdgeSegment[2] - verticalEdgeSegment[1] + 1 + + local top = love.image.newImageData(hlen, verticalEdgeSegment[1] - 1) + top:paste( + rawdata, + 0, 0, + -- 1 to skip over metadata row + horizontalEdgeSegment[1], 1, + top:getDimensions() + ) + p.patches[1][2] = love.graphics.newImage(top, {}) + top:release() + dbg("top:", p.patches[1][2]:getDimensions()) + + -- -2 because of 2 distinct -1s, see comments in setCorners + local bottom = love.image.newImageData(hlen, rawdata:getHeight() - verticalEdgeSegment[2] - 2) + bottom:paste( + rawdata, + 0, 0, + horizontalEdgeSegment[1], verticalEdgeSegment[2] + 1, + bottom:getDimensions() + ) + p.patches[3][2] = love.graphics.newImage(bottom, {}) + bottom:release() + dbg("bottom:", p.patches[3][2]:getDimensions()) + + local left = love.image.newImageData(horizontalEdgeSegment[1] - 1, vlen) + left:paste( + rawdata, + 0, 0, + 1, verticalEdgeSegment[1], + left:getDimensions() + ) + p.patches[2][1] = love.graphics.newImage(left, {}) + left:release() + dbg("left:", p.patches[2][1]:getDimensions()) + + local right = love.image.newImageData(rawdata:getWidth() - horizontalEdgeSegment[2] - 2, vlen) + right:paste( + rawdata, + 0, 0, + horizontalEdgeSegment[2] + 1, verticalEdgeSegment[1], + right:getDimensions() + ) + p.patches[2][3] = love.graphics.newImage(right, {}) + dbg("right:", p.patches[2][3]:getDimensions()) +end + +---Load a 9-slice image +---@param arg string|love.ImageData filename or raw image data to use for 9-slicing +---@return nil +---@return string +function M.load(arg) + local rawdata + local release = false + local p = {} + + if type(arg) == "string" then + dbg("loading sliced image from:", arg) + + rawdata = love.image.newImageData(arg, {}) + release = true + elseif arg.type and arg:type() == "ImageData" then + rawdata = arg + else + return nil, "expected string or ImageData, got "..tostring(arg) + end + + local horizontalEdgeSegment = { + assert(firstBlackPixel(rawdata, "row", 0, false)), + assert(firstBlackPixel(rawdata, "row", 0, true)) + } + + local verticalEdgeSegment = { + assert(firstBlackPixel(rawdata, "col", 0, false)), + assert(firstBlackPixel(rawdata, "col", 0, true)) + } + + local horizontalContentPadding = { + assert(firstBlackPixel(rawdata, "row", rawdata:getHeight() - 1, false)) - 1, + rawdata:getWidth() - assert(firstBlackPixel(rawdata, "row", rawdata:getHeight() - 1, true)) - 1 + } + + local verticalContentPadding = { + assert(firstBlackPixel(rawdata, "col", rawdata:getWidth() - 1, false)) - 1, + rawdata:getHeight() - assert(firstBlackPixel(rawdata, "col", rawdata:getWidth() - 1, true)) - 1 + } + + -- TODO check for valid value ranges in content padding + + p.contentPadding = { + left = horizontalContentPadding[1], + right = horizontalContentPadding[2], + up = verticalContentPadding[1], + down = verticalContentPadding[2] + } + dbg("padding (u,d,l,r):", p.contentPadding.up, p.contentPadding.down, p.contentPadding.left, p.contentPadding.right) + + p.patches = {{}, {}, {}} + + setCorners(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) + setMiddle(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) + setEdges(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) + + p.x, p.y = 0, 0 + p.width = p.patches[1][1]:getWidth() + p.patches[1][3]:getWidth() + p.height = p.patches[1][1]:getHeight() + p.patches[3][1]:getHeight() + + if release then + rawdata:release() + end + + setmetatable(p, PatchedImageMt) + return p +end + +-- THIS REUSES THE IMAGE OBJECTS FROM THE ORIGINAL +function PatchedImage:clone() + local c = {} + + for i = 1, 3 do + c.patches[i] = {} + for j = 1, 3 do + c.patches[i][j] = self.patches[i][j] + end + end + + c.contentPadding = { + left = self.contentPadding.left, + right = self.contentPadding.right, + up = self.contentPadding.up, + down = self.contentPadding.down + } + c.x, c.y = self.x, self.y + c.width, c.height = self.width, self.height + + setmetatable(c, PatchedImageMt) + return c +end + +---Resize image to given dimensions +---@param w number new width +---@param h number new height +function PatchedImage:resize(w, h) + self.width = self:clampWidth(assert(w)) + self.height = self:clampHeight(assert(h)) +end + +---Move image to given position +---@param x number +---@param y number +function PatchedImage:move(x, y) + self.x = assert(x) + self.y = assert(y) +end + +function PatchedImage:getX() + return self.x +end + +function PatchedImage:getY() + return self.y +end + +function PatchedImage:getPosition() + return self.x, self.y +end + +function PatchedImage:getWidth() + return self.width +end + +function PatchedImage:getHeight() + return self.height +end + +function PatchedImage:getDimensions() + return self.width, self.height +end + +---Draws a patch with an optional background debug rect +---@param p love.Image +---@param x number +---@param y number +---@param sx number? +---@param sy number? +local function drawPatch(p, x, y, sx, sy) + sx = sx or 1 + sy = sy or 1 + if debugDraw then + love.graphics.rectangle("fill", x, y, p:getWidth() * sx, p:getHeight() * sy) + end + love.graphics.draw(p, x, y, 0, sx, sy) +end + +function PatchedImage:draw(x, y, w, h) + if x then + self.x = x + else + x = self.x + end + if y then + self.y = y + else + y = self.y + end + if w then + self.width = w + else + w = self.width + end + if h then + self.height = h + else + h = self.height + end + local debugSpacing = debugDraw and DEBUG_DRAW_SEP_WIDTH or 0 + local horizontalEdgeLen = w - self.patches[1][1]:getWidth() - self.patches[1][3]:getWidth() + local verticalEdgeLen = h - self.patches[1][1]:getHeight() - self.patches[3][1]:getHeight() + + horizontalEdgeLen = math.max(horizontalEdgeLen, 0) + verticalEdgeLen = math.max(verticalEdgeLen, 0) + + local horizontalEdgeScale = horizontalEdgeLen / self.patches[1][2]:getWidth() + local verticalEdgeScale = verticalEdgeLen / self.patches[2][1]:getHeight() + + -- middle + drawPatch( + self.patches[2][2], + x + debugSpacing + self.patches[1][1]:getWidth(), + y + debugSpacing + self.patches[1][1]:getHeight(), + horizontalEdgeScale, verticalEdgeScale + ) + + -- edges + -- top + drawPatch( + self.patches[1][2], + x + debugSpacing + self.patches[1][1]:getWidth(), + y, + horizontalEdgeScale, 1 + ) + -- left + drawPatch( + self.patches[2][1], + x, + y + debugSpacing + self.patches[1][1]:getHeight(), + 1, verticalEdgeScale + ) + -- right + drawPatch( + self.patches[2][3], + x + 2*debugSpacing + self.patches[1][1]:getWidth() + horizontalEdgeLen, + y + debugSpacing + self.patches[1][1]:getHeight(), + 1, verticalEdgeScale + ) + -- bottom + drawPatch( + self.patches[3][2], + x + debugSpacing + self.patches[1][1]:getWidth(), + y + 2*debugSpacing + self.patches[1][1]:getHeight() + verticalEdgeLen, + horizontalEdgeScale, 1 + ) + + -- corners + -- top left + drawPatch(self.patches[1][1], x, y) + -- top right + drawPatch( + self.patches[1][3], + x + 2*debugSpacing + self.patches[1][1]:getWidth() + horizontalEdgeLen, + y + ) + -- bottom left + drawPatch( + self.patches[3][1], + x, + y + 2*debugSpacing + self.patches[1][1]:getHeight() + verticalEdgeLen + ) + -- bottom right + drawPatch( + self.patches[3][3], + x + 2*debugSpacing + self.patches[1][1]:getWidth() + horizontalEdgeLen, + y + 2*debugSpacing + self.patches[1][1]:getHeight() + verticalEdgeLen + ) + + if debugDraw then + local rw = 2*debugSpacing + - self.contentPadding.left - self.contentPadding.right + + self.patches[1][1]:getWidth() + horizontalEdgeLen + self.patches[1][3]:getWidth() + local rh = 2*debugSpacing + - self.contentPadding.up - self.contentPadding.down + + self.patches[1][1]:getHeight() + verticalEdgeLen + self.patches[3][1]:getHeight() + + local r, g, b, a = love.graphics.getColor() + local lw = love.graphics.getLineWidth() + love.graphics.setColor(1, 0, 0) + love.graphics.setLineWidth(1) + love.graphics.rectangle( + "line", + x + self.contentPadding.left + 0.5, y + self.contentPadding.up + 0.5, + rw, rh + ) + love.graphics.setColor(r, g, b, a) + love.graphics.setLineWidth(lw) + end +end + +function PatchedImage:getContentWindow(x, y, w, h) + x = x or self.x + y = y or self.y + w = w or self.width + h = h or self.height + + x = x + self.contentPadding.left + y = y + self.contentPadding.up + + w = self:clampWidth(w) - self.contentPadding.left - self.contentPadding.right + h = self:clampHeight(h) - self.contentPadding.up - self.contentPadding.down + + if debugDraw then + w = w + 2*DEBUG_DRAW_SEP_WIDTH + h = h + 2*DEBUG_DRAW_SEP_WIDTH + end + + return x, y, w, h +end + +function PatchedImage:clampWidth(w) + return math.max(w, self.patches[1][1]:getWidth() + self.patches[1][3]:getWidth()) +end + +function PatchedImage:clampHeight(h) + return math.max(h, self.patches[1][1]:getHeight() + self.patches[3][1]:getHeight()) +end + +function M.setDebug(t) + debugDraw = t.draw + debugLog = t.log +end + +function M.isDebugLogging() + return debugLog +end + +function M.isDebugDrawing() + return debugDraw +end + +return M diff --git a/src/main.lua b/src/main.lua index 51fe1cc..257cc57 100644 --- a/src/main.lua +++ b/src/main.lua @@ -12,6 +12,6 @@ function love.load() love.keyboard.setKeyRepeat(true) math.randomseed(love.timer.getTime()) - Gamestate.switch(require("states.main")) + Gamestate.switch(require("states.main-menu")) Gamestate.registerEvents() end diff --git a/src/states/main-menu.lua b/src/states/main-menu.lua index db8829b..4458221 100644 --- a/src/states/main-menu.lua +++ b/src/states/main-menu.lua @@ -1,11 +1,10 @@ local MainMenu = {} -local UI = require("ui") -local rgb = require("helpers.rgb") -local darken = require("helpers.darken") -local lighten = require("helpers.lighten") local Gamestate = require("lib.hump.gamestate") local http = require("socket.http") local enet = require("enet") +local nata = require("lib.nata") +local Vec = require("lib.brinevector") +local data = require("data") local function getPublicIP() local ip = http.request("https://ipinfo.io/ip") @@ -18,53 +17,112 @@ local function splitat(str, char) end function MainMenu:init() - self.ui = UI.new{ - font = love.graphics.newFont(32), - text_color = rgb(255, 255, 255), - bg_color = rgb(60, 60, 60), - bg_hover_color = {lighten(rgb(60, 60, 60))}, - bg_pressed_color = {darken(rgb(60, 60, 60))}, + local systems = { + require("systems.physics"), + require("systems.bolt"), + require("systems.ui"), + require("systems.player"), + require("systems.sprite"), + require("systems.screen-scaler"), } + if not love.filesystem.isFused() then + table.insert(systems, require("systems.debug")) + end - self.addr_textbox = { text = "" } + self.ecs = nata.new{ + available_groups = require("groups"), + systems = systems + } self.host_socket = enet.host_create("*:0") self.peer_socket = nil self.hosting = false self.connecting = false + + do + local PlayerSystem = self.ecs:getSystem(require("systems.player")) + local player = PlayerSystem:spawnPlayer(Vec(192, 48)) + player.sprite.flip = true + player.controllable = true + end + + do + local tileSize = 16 + local screenW, screenH = love.graphics.getDimensions() + local w = 16 * tileSize + local h = 16 * screenH/screenW * tileSize + self.canvas = love.graphics.newCanvas(w, h) + + local border = 2.5 + self.ecs:queue{ collider = {0, 0, w, border} } + self.ecs:queue{ collider = {0, 0, border, h} } + self.ecs:queue{ collider = {w-border, 0, w, h} } + self.ecs:queue{ collider = {0, h-border, w, h} } + end + + do + local host_btn = self.ecs:queue{ + pos = Vec(16, 16), + size = Vec(96, 32), + text = "Host" + } + local join_btn = self.ecs:queue{ + pos = Vec(16, 48+8), + size = Vec(96, 32), + text = "Join" + } + local quit_btn = self.ecs:queue{ + pos = Vec(16, 96), + size = Vec(96, 32), + text = "Quit" + } + self.ecs:on("on_btn_pressed", function(btn) + if btn == quit_btn then + love.event.quit() + end + end) + end end -function MainMenu:update() +function MainMenu:update(dt) + self.ecs:flush() + self.ecs:emit("update", dt) local event = self.host_socket:service() if event and event.type == "connect" then Gamestate.switch(require("states.main"), self.host_socket) end end -function MainMenu:keypressed(...) - self.ui:keypressed(...) -end +function MainMenu:mousemoved(x, y, dx, dy, istouch) + local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler")) + if not ScreenScaler:isInBounds(x, y) then return end -function MainMenu:textinput(...) - self.ui:textinput(...) + x, y = ScreenScaler:getPosition(x, y) + dx = (ScreenScaler.scale or 1) * dx + dy = (ScreenScaler.scale or 1) * dy + self.ecs:emit("mousemoved", x, y, dx, dy, istouch) end function MainMenu:draw() - local w, h = love.graphics.getDimensions() + local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler")) - self.ui:textbox(self.addr_textbox, w/2-250, h/2-30, 280, 60) - if self.ui:button("Copy my address", w/2-50, h/2-100, 300, 60) then - local _, port = splitat(self.host_socket:get_socket_address(), ":") - local address = getPublicIP() .. ":" .. port - love.system.setClipboardText(address) - end - if self.ui:button("Connect", w/2+50, h/2-30, 200, 60) then - self.connecting = true - self.host_socket:connect(self.addr_textbox.text) - end + ScreenScaler:start(self.canvas) + self.ecs:emit("draw") + ScreenScaler:finish() - self.ui:postDraw() + -- self.ui:textbox(self.addr_textbox, w/2-250, h/2-30, 280, 60) + -- if self.ui:button("Copy my address", w/2-50, h/2-100, 300, 60) then + -- local _, port = splitat(self.host_socket:get_socket_address(), ":") + -- local address = getPublicIP() .. ":" .. port + -- love.system.setClipboardText(address) + -- end + -- if self.ui:button("Connect", w/2+50, h/2-30, 200, 60) then + -- self.connecting = true + -- self.host_socket:connect(self.addr_textbox.text) + -- end + + -- self.ui:postDraw() end return MainMenu diff --git a/src/states/main.lua b/src/states/main.lua index 6253e13..570af7f 100644 --- a/src/states/main.lua +++ b/src/states/main.lua @@ -4,19 +4,6 @@ local nata = require("lib.nata") local data = require("data") function MainState:enter(_, host_socket) - local groups = { - physical = {filter = {"pos", "vel"}}, - player = {filter = { - "pos", "acc", "speed", "bolts" - -- "bolt_count", - -- "bolt_cooldown", "bolt_speed", "bolt_friction" - }}, - controllable_player = {filter = {"controllable"}}, - sprite = {filter = {"pos", "sprite"}}, - bolt = {filter={"pos", "vel", "bolt"}}, - collider = {filter={"pos", "collider"}} - } - local systems = { require("systems.physics"), require("systems.map"), @@ -35,7 +22,7 @@ function MainState:enter(_, host_socket) end self.ecs = nata.new{ - groups = groups, + available_groups = require("groups"), systems = systems, data = { host_socket = host_socket @@ -47,7 +34,12 @@ function MainState:enter(_, host_socket) self.ecs:on("onMapSwitch", function(map) self:refreshDownscaledCanvas(map) end) - love.graphics.setNewFont(48) + + + -- Spawn in the entity that the local player will be using + local PlayerSystem = self.ecs:getSystem(require("systems.player")) + local player = PlayerSystem:spawnPlayer() + player.controllable = true end function MainState:refreshDownscaledCanvas(map) @@ -104,20 +96,9 @@ end function MainState:draw() local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler")) - -- Draw the game ScreenScaler:start(self.downscaled_canvas) self.ecs:emit("draw") ScreenScaler:finish() - - -- Draw UI on top - -- local w, h = self.downscaled_canvas:getDimensions() - -- ScreenScaler:start(1000, h/w * 1000) - -- love.graphics.setColor(1, 1, 1) - -- love.graphics.print("Hello World!") - -- ScreenScaler:finish() - - -- Override scaling factors from UI, with scalers for the game - ScreenScaler:overrideScaling(self.downscaled_canvas:getDimensions()) end return MainState diff --git a/src/systems/bolt.lua b/src/systems/bolt.lua index 1a4b165..2f85fcb 100644 --- a/src/systems/bolt.lua +++ b/src/systems/bolt.lua @@ -5,6 +5,8 @@ local Vec = require("lib.brinevector") local BOLT_COLLIDER = {-3, -3, 3, 3} +Bolt.required_groups = {"bolt"} + function Bolt:update(dt) for _, bolt in ipairs(self.pool.groups.bolt.entities) do if bolt.vel.length < 20 and not bolt.pickupable then diff --git a/src/systems/physics.lua b/src/systems/physics.lua index 55fbfd7..782e2c4 100644 --- a/src/systems/physics.lua +++ b/src/systems/physics.lua @@ -5,6 +5,8 @@ local Vec = require("lib.brinevector") -- TODO: Tweak bump world `cellSize` at runtime, when the map switches +Physics.required_groups = {"physical", "collider"} + function Physics:init() self.bump = bump.newWorld() self.boundCollisionFilter = function(...) @@ -17,10 +19,14 @@ function Physics:onMapSwitch(map) end local function getColliderBounds(e) - local x = e.pos.x + e.collider[1] - local y = e.pos.y + e.collider[2] + local x = e.collider[1] + local y = e.collider[2] local w = math.abs(e.collider[3] - e.collider[1]) local h = math.abs(e.collider[4] - e.collider[2]) + if e.pos then + x = x + e.pos.x + y = y + e.pos.y + end return x, y, w, h end @@ -163,4 +169,8 @@ function Physics:project(from, direction, distance) return key_points end +function Physics:queryRect(pos, size, filter) + return self.bump:queryRect(pos.x, pos.y, size.x, size.y, filter) +end + return Physics diff --git a/src/systems/player.lua b/src/systems/player.lua index 01811b3..7126fef 100644 --- a/src/systems/player.lua +++ b/src/systems/player.lua @@ -16,11 +16,7 @@ local function getDirection(up_key, down_key, left_key, right_key) return Vec(dx, dy).normalized end -function Player:init() - -- Spawn in the entity that the local player will be using - local player = self:spawnPlayer() - player.controllable = true -end +Player.required_groups = {"player", "controllable_player", "bolt"} function Player:getMoveDirection() return getDirection( @@ -201,14 +197,16 @@ function Player:resetMap() end end -function Player:spawnPlayer() - local map = self.pool:getSystem(require("systems.map")) - local spawnpoints = map:listSpawnpoints() - local players = self.pool.groups.player.entities - local spawnpoint = spawnpoints[#players % #spawnpoints + 1] +function Player:spawnPlayer(pos) + if not pos then + local map = self.pool:getSystem(require("systems.map")) + local spawnpoints = map:listSpawnpoints() + local players = self.pool.groups.player.entities + pos = spawnpoints[#players % #spawnpoints + 1] + end local player = self.pool:queue{ - pos = spawnpoint, + pos = pos, vel = Vec(0, 0), acc = Vec(), diff --git a/src/systems/screen-scaler.lua b/src/systems/screen-scaler.lua index 635b01a..9a8290c 100644 --- a/src/systems/screen-scaler.lua +++ b/src/systems/screen-scaler.lua @@ -78,6 +78,7 @@ function ScreenScaler:start(p1, p2) self.scale = math.min(sw / width, sh / height) self.offset_x = (sw - width * self.scale)/2 self.offset_y = (sh - height * self.scale)/2 + self.active = true love.graphics.push() if self.canvas then @@ -99,6 +100,7 @@ function ScreenScaler:finish() self:hideBorders() end self.canvas = nil + self.active = false end return ScreenScaler diff --git a/src/systems/sprite.lua b/src/systems/sprite.lua index 79686a2..f49cfc9 100644 --- a/src/systems/sprite.lua +++ b/src/systems/sprite.lua @@ -2,10 +2,12 @@ local data = require("data") local pprint = require("lib.pprint") local Sprite = {} -function Sprite:addToWorld(group, e) +Sprite.required_groups = {"sprite"} + +function Sprite:addToGroup(group, e) if group ~= "sprite" then return end - local sprite = data.sprites[e.sprite.variant] + local sprite = data.sprites[e.sprite.name] assert(sprite, ("Attempt to draw unknown sprite: %s"):format(e.sprite.name)) end diff --git a/src/systems/ui.lua b/src/systems/ui.lua new file mode 100644 index 0000000..5eaf4f2 --- /dev/null +++ b/src/systems/ui.lua @@ -0,0 +1,111 @@ +local UI = {} +local data = require("data") +local rgb = require("helpers.rgb") + +local font = data.fonts["kenney-future"](16) +local on_press_sound = data.audio["switch-5"] +local on_release_sound = data.audio["switch-7"] + +UI.required_groups = {"ui_button", "player"} + +local BUTTON_TIME = 1.2 + +function UI:addToGroup(group, e) + if group == "ui_button" then + e.pressed_timer = 0 + end +end + +function UI:update(dt) + local physics = self.pool:getSystem(require("systems.physics")) + + for _, btn in ipairs(self.pool.groups.ui_button.entities) do + local collisions = physics:queryRect(btn.pos, btn.size) + local pressed = #collisions > 0 + if btn.pressed then + btn.pressed_timer = math.min(btn.pressed_timer + dt, BUTTON_TIME) + elseif btn.pressed_timer and btn.pressed_timer > 0 then + btn.pressed_timer = math.max(0, btn.pressed_timer - dt * 3) + end + + if btn.pressed_timer == BUTTON_TIME and not btn.event_emitted then + btn.event_emitted = true + self.pool:emit("on_btn_pressed", btn) + elseif btn.pressed_timer < BUTTON_TIME and btn.event_emitted then + btn.event_emitted = nil + self.pool:emit("on_btn_released", btn) + end + + if not btn.pressed and pressed then + on_press_sound:play() + elseif btn.pressed and not pressed then + on_release_sound:play() + end + btn.pressed = pressed + end +end + +local mask_shader = love.graphics.newShader[[ + extern number offset_x; + extern number scale; + extern number threshold; + extern vec4 mask_color; + vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) + { + vec4 texturecolor = Texel(tex, texture_coords); + if (screen_coords.x > threshold * scale + offset_x) { + return texturecolor * color; + } else { + return texturecolor * mask_color; + } + } +]] + +function UI:draw() + love.graphics.setFont(font) + local ScreenScaler = self.pool:getSystem(require("systems.screen-scaler")) + + mask_shader:send("mask_color", rgb(10, 250, 20)) + if ScreenScaler.active and not ScreenScaler.canvas then + mask_shader:send("scale", ScreenScaler.scale) + mask_shader:send("offset_x", ScreenScaler.offset_x) + else + mask_shader:send("scale", 1) + mask_shader:send("offset_x", 0) + end + + for _, btn in ipairs(self.pool.groups.ui_button.entities) do + local panel = data.ui.panels["blue"] + local oy = 0 + local r = 0 + if btn.pressed then + panel = data.ui.panels["blue-pressed"] + oy = 2 + r = math.sin(love.timer.getTime()*20)*0.15 + end + + love.graphics.setColor(1, 1, 1) + panel:draw(btn.pos.x, btn.pos.y, btn.size.x, btn.size.y) + local cx, cy, cw, ch = panel:getContentWindow() + + local text_width = font:getWidth(btn.text) + local text_height = font:getHeight(btn.text) + local threshold = math.min(1, (btn.pressed_timer or 0) / BUTTON_TIME) + mask_shader:send("threshold", cx+threshold*text_width + (cw-text_width)/2) + love.graphics.setShader(mask_shader) + love.graphics.printf( + btn.text, + cx+cw/2, + cy+ch/2+oy, + cw, + "left", + r, + 1, 1, + text_width/2, + text_height/2 + ) + love.graphics.setShader() + end +end + +return UI