From 1e299dfa8430bdfbfc9ce1fc9f30bb81498595f6 Mon Sep 17 00:00:00 2001 From: Patrick Schwarzer <patrickschwarzer2000@gmail.com> Date: Mon, 14 Apr 2025 03:58:35 +0200 Subject: [PATCH] classes, better debugging and spritesheets --- classes.lua | 160 +++++++++++++++-- games/map.json | 0 games/test.lua | 30 ++-- games/testSound.mp3 | Bin 0 -> 5504 bytes games/testSprites.json | 18 ++ games/tileset.png | Bin 0 -> 2375 bytes libraries/json.lua | 400 +++++++++++++++++++++++++++++++++++++++++ main.lua | 114 ++++++++---- 8 files changed, 660 insertions(+), 62 deletions(-) create mode 100644 games/map.json create mode 100644 games/testSound.mp3 create mode 100644 games/testSprites.json create mode 100644 games/tileset.png create mode 100644 libraries/json.lua diff --git a/classes.lua b/classes.lua index 5c4152f..01721ec 100644 --- a/classes.lua +++ b/classes.lua @@ -1,4 +1,27 @@ +local function newImage(path, alphaColor) + if (not alphaColor) then + return love.graphics.newImage(path) + end + + local imageData = love.image.newImageData(path) + + imageData:mapPixel(function(x, y, r, g, b, a) + if (r == alphaColor.r and + g == alphaColor.g and + b == alphaColor.b and + a == alphaColor.a) then + return 0, 0, 0, 0 + end + + return r, g, b, a + end) + + local image = love.graphics.newImage(imageData) + + return image +end + --[[ Sprite Class This is essentially just a Wrapper for LÖVEs Image @@ -7,10 +30,10 @@ Sprite = {} Sprite.__index = Sprite -function Sprite.new(path) +function Sprite.new(path, alphaColor) local self = setmetatable({}, Sprite) - self.image = love.graphics.newImage(path) + self.image = newImage(path, alphaColor) return self end @@ -21,41 +44,150 @@ end --[[ - Audio Class + Spritesheet Class + This is essentially just a Wrapper for LÖVEs Image, specifically made for Spritesheets +]]-- + +Spritesheet = {} +Spritesheet.__index = Spritesheet + +function Spritesheet.new(path, width, height, alphaColor) + local self = setmetatable({}, Spritesheet) + + self.sprites = {} + + if (string.find(path, ".json")) then + local contents, size = love.filesystem.read(path) + local sliceData = json.decode(contents) + + print("games/" .. sliceData.image) + self.image = newImage("games/" .. sliceData.image, alphaColor) + + -- Create the slices according to the sliceData + local imageWidth, imageHeight = self.image:getWidth(), self.image:getHeight() + + local counter = 0 + for _, data in pairs(sliceData) do + table.insert(self.sprites, love.graphics.newQuad( + data.x, + data.y, + data.width, + data.height, + imageWidth, + imageHeight + )) + counter = counter + 1 + end + + self.Count = counter + + return self + end + + self.image = newImage(path, alphaColor) + + -- Generate slices + local imageWidth, imageHeight = self.image:getWidth(), self.image:getHeight() + local columns = math.floor(imageWidth / width) + local rows = math.floor(imageHeight / height) + + local counter = 0 + for x = 0, columns - 1 do + for y = 0, rows - 1 do + table.insert(self.sprites, love.graphics.newQuad( + x * width, + y * height, + width, + height, + imageWidth, + imageHeight + )) + counter = counter + 1 + end + end + + self.Count = counter + + return self +end + +function Spritesheet:Draw(slice, x, y) + local sprite = self.sprites[slice] + if sprite then + love.graphics.draw(self.image, sprite, x or 0, y or 0) + end +end + + +--[[ + Sound Class This is essentially just a Wrapper for LÖVEs Source ]]-- -Audio = {} -Audio.__index = Audio +Sound = {} +Sound.__index = Sound -function Audio.new(path, soundType) - local self = setmetatable({}, Audio) +function Sound.new(path, soundType) + local self = setmetatable({}, Sound) self.source = love.audio.newSource(path, soundType or "static") return self end -function Audio:Play() +function Sound:Play() self.source:play() end -function Audio:Stop() +function Sound:Stop() self.source:stop() end -function Audio:Pause() +function Sound:Pause() self.source:pause() end -function Audio:Resume() +function Sound:Resume() self.source:play() end -function Audio:SetLooping(bool) +function Sound:SetLooping(bool) self.source:setLooping(bool) end -function Audio:SetVolume(volume) +function Sound:SetVolume(volume) self.source:setVolume(volume) -end \ No newline at end of file +end + + +--[[ + Color Class + A convenient Color class that works with any love2D function +]]-- + +function Color(r, g, b, a) + r = (r or 255) / 255 + g = (g or 255) / 255 + b = (b or 255) / 255 + a = (a or 255) / 255 + + local color = { + r = r, g = g, b = b, a = a, + [1] = r, [2] = g, [3] = b, [4] = a -- love2D expects a sequential array, so heres the fix for that + } + + -- Return it as a class, that way we can use it freely anywhere we wish + return setmetatable(color, { + __index = function(tbl, key) + if key == 1 then return tbl.r + elseif key == 2 then return tbl.g + elseif key == 3 then return tbl.b + elseif key == 4 then return tbl.a + else return rawget(tbl, key) + end + end, + __tostring = function(tbl) + return tbl + end + }) +end diff --git a/games/map.json b/games/map.json new file mode 100644 index 0000000..e69de29 diff --git a/games/test.lua b/games/test.lua index 66af9ad..ea70599 100644 --- a/games/test.lua +++ b/games/test.lua @@ -3,26 +3,36 @@ NAME = "Test" AUTHOR = "Tarion" VERSION = 0 -local test = 0 -local delay = 0 - -local last = "" - +local backgroundColor = Color(48, 104, 20, 255) function Load() + SetBackgroundColor(backgroundColor) end function Update(dt, curTime) end -local sprite = Sprite("testSprites.png") +local sprite = Spritesheet("testSprites.json", 16, 16, Color(48, 104, 80, 255)) +local testSound = Sound("testSound.mp3", "static") +local test = 1 function KeyPressed(key) - if (key == "f5") then + if (key == "f3") then + test = test - 1 + + if (test == 0) then + test = sprite.Count + end + elseif (key == "f4") then test = test + 1 - sprite = Sprite("testSprites.png") + + if (test == sprite.Count + 1) then + test = 1 + end + elseif (key == "f2") then + testSound:Play() end end function Draw() - print("Loaded Sprites: " .. test, 10, 10) - sprite:Draw(100, 0) + DrawText(test .. "/" .. sprite.Count, 25, 25) + sprite:Draw(test, 50, 0) end diff --git a/games/testSound.mp3 b/games/testSound.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8e64435ac5c63491e4cb272974d1a366c50bdb50 GIT binary patch literal 5504 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 L|4;sZ(}Dj1SBJz) literal 0 HcmV?d00001 diff --git a/games/testSprites.json b/games/testSprites.json new file mode 100644 index 0000000..e806425 --- /dev/null +++ b/games/testSprites.json @@ -0,0 +1,18 @@ +[ + { + "image": "testSprites.png" + }, + {"x":0,"y":0,"width":64,"height":24}, + {"x":64,"y":0,"width":16,"height":24}, + {"x":64,"y":0,"width":16,"height":24}, + {"x":0,"y":24,"width":64,"height":24}, + {"x":80,"y":52,"width":8,"height":12}, + {"x":80,"y":68,"width":8,"height":12}, + {"x":0,"y":72,"width":80,"height":24}, + {"x":90,"y":48,"width":14,"height":48}, + {"x":72,"y":96,"width":32,"height":15}, + {"x":32,"y":96,"width":40,"height":15}, + {"x":24,"y":96,"width":8,"height":8}, + {"x":16,"y":96,"width":8,"height":8}, + {"x":16,"y":104,"width":8,"height":8}, + {"x":0,"y":96,"width":16,"height":16},{"x":0,"y":112,"width":32,"height":24},{"x":32,"y":111,"width":32,"height":24},{"x":72,"y":136,"width":32,"height":24}] \ No newline at end of file diff --git a/games/tileset.png b/games/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..5b21c11a8e151c4b29a85acb53e14d5c5b1cab8a GIT binary patch literal 2375 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 literal 0 HcmV?d00001 diff --git a/libraries/json.lua b/libraries/json.lua new file mode 100644 index 0000000..098e7b2 --- /dev/null +++ b/libraries/json.lua @@ -0,0 +1,400 @@ +-- +-- json.lua +-- +-- Copyright (c) 2019 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/main.lua b/main.lua index d3dc6a8..08a522b 100644 --- a/main.lua +++ b/main.lua @@ -1,8 +1,15 @@ +if arg[2] == "debug" then + require("lldebugger").start() +end + + +json = require("libraries/json") + require("classes") local LoadObject = { - ["sprite"] = function(name) + ["sprite"] = function(name, alphaColor) name = "games/" .. name local info = love.filesystem.getInfo(name) @@ -15,10 +22,10 @@ local LoadObject = { return end - local newObject = Sprite.new(name) + local newObject = Sprite.new(name, alphaColor) virtual.loadedObjects[newObject] = { - object = newSprite, + object = newObject, size = info.size, } virtual.currentMemory = virtual.currentMemory + info.size @@ -26,7 +33,32 @@ local LoadObject = { return newObject end, - ["audio"] = function(name, soundType) + ["spritesheet"] = function(name, width, height, alphaColor) + name = "games/" .. name + height = height or width + + local info = love.filesystem.getInfo(name) + if (not info) then + error("Failed to load Object (FILE NOT FOUND) ("..name..")") + return + end + if ((info.size + virtual.currentMemory) > virtual.maxMemory) then + error("Failed to load Object (MAX MEMORY) ("..name..")") + return + end + + local newObject = Spritesheet.new(name, width, height, alphaColor) + + virtual.loadedObjects[newObject] = { + object = newObject, + size = info.size, + } + virtual.currentMemory = virtual.currentMemory + info.size + virtual.loadedCount = virtual.loadedCount + 1 + + return newObject + end, + ["sound"] = function(name, soundType) name = "games/" .. name soundType = soundType or "static" @@ -40,10 +72,10 @@ local LoadObject = { return end - local newObject = Audio.new(name, soundType) + local newObject = Sound.new(name, soundType) virtual.loadedObjects[newObject] = { - object = newSprite, + object = newObject, size = info.size, } virtual.currentMemory = virtual.currentMemory + info.size @@ -53,6 +85,10 @@ local LoadObject = { end, } +local function SetBackgroundColor(color) + love.graphics.setBackgroundColor(color) +end + -- Establish Sandbox -- Everything in the table is what the Sandbox has access to local consoleEnv = { @@ -65,17 +101,18 @@ local consoleEnv = { Draw, KeyPressed, - Sprite2 = LoadSprite, Sprite = LoadObject["sprite"], + Spritesheet = LoadObject["spritesheet"], + Sound = LoadObject["sound"], - print = love.graphics.print, + Color = Color, + + SetBackgroundColor = SetBackgroundColor, + DrawText = love.graphics.print, + + print = print, } consoleEnv._G = consoleEnv -setmetatable(consoleEnv, { - __index = function(_, key) - return function(...) end -- Return an empty function to avoid erroring, not perfect, but works most of the time - end -}) function loadGame(gameName) local fn, err = loadfile("games/" .. gameName .. ".lua", "t", consoleEnv) @@ -83,9 +120,9 @@ function loadGame(gameName) error("Failed to load sandbox file (1): " .. err) end - local ok, execErr = pcall(fn) + local ok, execErr = xpcall(fn, debug.traceback) if not ok then - error("Error running sandbox file (2): " .. execErr) + error("Error running sandbox file (2):\n" .. execErr) end love.window.setTitle(consoleEnv.NAME or "NULL") @@ -100,7 +137,6 @@ function love.load() -- Console Variables curTime = 0 scaling = 4 - keysPressed = {} -- Setup Window love.graphics.setDefaultFilter("nearest", "nearest") @@ -118,7 +154,6 @@ function love.load() loadGame("test") - if (consoleEnv.Load) then consoleEnv.Load() end @@ -128,26 +163,7 @@ function love.keypressed(key) if key == "escape" then love.event.quit() end - - keysPressed[key] = true - - if (consoleEnv.KeyPressed) then - consoleEnv.KeyPressed(key) - end -end - -function love.keyboard.wasPressed(key) - return keysPressed[key] -end - -function love.window.updateWindow() - love.window.setMode(scaling*240, scaling*160, {resizable=false, vsync=0}) -end - -function love.update(dt) - curTime = curTime + dt - - if (love.keyboard.wasPressed("f1")) then + if (key == "f1") then scaling = scaling + 1 if (scaling == maxScaling+1) then @@ -157,7 +173,17 @@ function love.update(dt) love.window.updateWindow() end - keysPressed = {} + if (consoleEnv.KeyPressed) then + consoleEnv.KeyPressed(key) + end +end + +function love.window.updateWindow() + love.window.setMode(scaling*240, scaling*160, {resizable=false, vsync=0}) +end + +function love.update(dt) + curTime = curTime + dt if (consoleEnv.Update) then consoleEnv.Update(dt, curTime) @@ -176,3 +202,15 @@ function love.draw() consoleEnv.Draw() end end + + + +local love_errorhandler = love.errhand + +function love.errorhandler(msg) + if lldebugger then + error(msg, 2) + else + return love_errorhandler(msg) + end +end \ No newline at end of file