improve button in main menu
This commit is contained in:
parent
7f26575942
commit
6e754155dc
BIN
src/data/audio/switch-5.ogg
Normal file
BIN
src/data/audio/switch-5.ogg
Normal file
Binary file not shown.
BIN
src/data/audio/switch-7.ogg
Normal file
BIN
src/data/audio/switch-7.ogg
Normal file
Binary file not shown.
BIN
src/data/fonts/kenney-future.ttf
Normal file
BIN
src/data/fonts/kenney-future.ttf
Normal file
Binary file not shown.
@ -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
|
||||
}
|
||||
}
|
||||
|
BIN
src/data/ui/panels/blue-pressed.9.png
Normal file
BIN
src/data/ui/panels/blue-pressed.9.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
BIN
src/data/ui/panels/blue.9.png
Normal file
BIN
src/data/ui/panels/blue.9.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
13
src/groups.lua
Normal file
13
src/groups.lua
Normal file
@ -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"}}
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
|
497
src/lib/slicy.lua
Normal file
497
src/lib/slicy.lua
Normal file
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
111
src/systems/ui.lua
Normal file
111
src/systems/ui.lua
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user