From 467c450349e2aeb999b95d3e1a8e1a6f4ce3ab72 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Wed, 6 Jul 2022 02:00:44 +0300 Subject: [PATCH] refactor sreen scalar to support multiple resolutions --- src/lib/center.lua | 138 ------------------ src/main.lua | 1 - src/screen-scaler.lua | 254 ---------------------------------- src/states/main.lua | 49 ++++++- src/systems/map.lua | 7 +- src/systems/player.lua | 8 +- src/systems/screen-scaler.lua | 100 +++++++++++++ 7 files changed, 153 insertions(+), 404 deletions(-) delete mode 100644 src/lib/center.lua delete mode 100644 src/screen-scaler.lua create mode 100644 src/systems/screen-scaler.lua diff --git a/src/lib/center.lua b/src/lib/center.lua deleted file mode 100644 index 75b2e7d..0000000 --- a/src/lib/center.lua +++ /dev/null @@ -1,138 +0,0 @@ ---[[ - MIT License - - Copyright (c) 2019 Semyon Entsov - - 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 center = {} - -function center:setupScreen(width, height) - self._WIDTH = width - self._HEIGHT = height - self._MAX_WIDTH = 0 - self._MAX_HEIGHT = 0 - self._MAX_RELATIVE_WIDTH = 0 - self._MAX_RELATIVE_HEIGHT = 0 - self._SCREEN_WIDTH = love.graphics.getWidth() - self._SCREEN_HEIGHT = love.graphics.getHeight() - self._BORDERS = { - ['t'] = 0, - ['r'] = 0, - ['b'] = 0, - ['l'] = 0 - } - self:apply() - return self -end - -function center:setBorders(top, right, bottom, left) - self._BORDERS.t = top - self._BORDERS.r = right - self._BORDERS.b = bottom - self._BORDERS.l = left -end - -function center:getScale() - return self._SCALE -end - -function center:getOffsetX() - return self._OFFSET_X -end - -function center:getOffsetY() - return self._OFFSET_Y -end - -function center:setMaxWidth(width) - self._MAX_WIDTH = width -end - -function center:setMaxHeight(height) - self._MAX_HEIGHT = height -end - -function center:setMaxRelativeWidth(width) - self._MAX_RELATIVE_WIDTH = width -end - -function center:setMaxRelativeHeight(height) - self._MAX_RELATIVE_HEIGHT = height -end - -function center:resize(width, height) - self._SCREEN_WIDTH = width - self._SCREEN_HEIGHT = height - self:apply() -end - -function center:apply() - local available_width = self._SCREEN_WIDTH - self._BORDERS.l - self._BORDERS.r - local available_height = self._SCREEN_HEIGHT - self._BORDERS.t - self._BORDERS.b - local max_width = available_width - local max_height = available_height - if self._MAX_RELATIVE_WIDTH > 0 and available_width * self._MAX_RELATIVE_WIDTH < max_width then - max_width = available_width * self._MAX_RELATIVE_WIDTH - end - if self._MAX_RELATIVE_HEIGHT > 0 and available_height * self._MAX_RELATIVE_HEIGHT < max_height then - max_height = available_height * self._MAX_RELATIVE_HEIGHT - end - if self._MAX_WIDTH > 0 and self._MAX_WIDTH < max_width then - max_width = self._MAX_WIDTH - end - if self._MAX_HEIGHT > 0 and self._MAX_HEIGHT < max_height then - max_height = self._MAX_HEIGHT - end - if max_height / max_width > self._HEIGHT / self._WIDTH then - self._CANVAS_WIDTH = max_width - self._CANVAS_HEIGHT = self._CANVAS_WIDTH * (self._HEIGHT / self._WIDTH) - else - self._CANVAS_HEIGHT = max_height - self._CANVAS_WIDTH = self._CANVAS_HEIGHT * (self._WIDTH / self._HEIGHT) - end - self._SCALE = self._CANVAS_HEIGHT / self._HEIGHT - self._OFFSET_X = self._BORDERS.l + (available_width - self._CANVAS_WIDTH) / 2 - self._OFFSET_Y = self._BORDERS.t + (available_height - self._CANVAS_HEIGHT) / 2 -end - -function center:start() - love.graphics.push() - love.graphics.translate(self._OFFSET_X, self._OFFSET_Y) - love.graphics.scale(self._SCALE, self._SCALE) -end - -function center:finish() - love.graphics.pop() -end - -function center:toGame(x, y, w, h) - if not (self._OFFSET_X and self._OFFSET_Y and self._SCALE) then - return x, y, w, h - end - - return (x - self._OFFSET_X) / self._SCALE, - (y - self._OFFSET_Y) / self._SCALE, - w and w / self._SCALE, - h and h / self._SCALE -end - -return center - diff --git a/src/main.lua b/src/main.lua index fe212ba..51fe1cc 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,7 +1,6 @@ local Gamestate = require("lib.hump.gamestate") local binser = require("lib.binser") local Vec = require("lib.brinevector") -require("screen-scaler") binser.registerStruct("brinevector", function(v) return v.x, v.y end, diff --git a/src/screen-scaler.lua b/src/screen-scaler.lua deleted file mode 100644 index 81301e2..0000000 --- a/src/screen-scaler.lua +++ /dev/null @@ -1,254 +0,0 @@ ---- Module resposible for scaling screen and centering contents --- while respecting ratios. --- @module ScreenScaler -local ScreenScaler = {} - -local canvas - --- This "Center" library will do most of the heavy lifting -local Center = require("lib.center") - -local getRealDimensions = love.graphics.getDimensions - ---- Set the "ideal" dimensions from which everything else will be scaled. --- @tparam number w virtual width --- @tparam number h virtual height -function ScreenScaler.setVirtualDimensions(w, h) - assert(type(w) == "number", "Expected width to be number") - assert(type(h) == "number", "Expected height to be number") - ScreenScaler.width, ScreenScaler.height = nil, nil - - -- Setup library resposible for scaling the screen - Center:setupScreen(w, h) - canvas = love.graphics.newCanvas(w, h) - canvas:setFilter("nearest", "nearest") - - ScreenScaler.width, ScreenScaler.height = w, h -end - ---- Unsets virtual dimensions. Effectively disables scaler. -function ScreenScaler.unsetVirtualDimensions() - ScreenScaler.width = nil - ScreenScaler.height = nil -end - ---- Get virtual dimensions. --- @return width, height -function ScreenScaler.getVirtualDimensions() - return ScreenScaler.width, ScreenScaler.height -end - ---- Returns true if scaler is enabled. --- @treturn boolean -function ScreenScaler.isEnabled() - return ScreenScaler.width ~= nil -end - --- Overwrite default love.mouse functions to return position --- relative to scaled window -do - local getX = love.mouse.getX - function love.mouse.getX() - local x = getX() - if not ScreenScaler.isEnabled() then - return x - end - return (x - Center:getOffsetX()) / Center:getScale() - end - - local getY = love.mouse.getY - function love.mouse.getY() - local y = getY() - if not ScreenScaler.isEnabled() then - return y - end - return (y - Center:getOffsetY()) / Center:getScale() - end - - local getPosition = love.mouse.getPosition - function love.mouse.getPosition() - if ScreenScaler.isEnabled() then - return Center:toGame(getPosition()) - else - return getPosition() - end - end - - -- TODO: add replacements for setX, setY, setPosition -end - --- Overwrite default getDimensions, getWidth, getHeight --- to return virtual width and height if scaler is enabled -do - local getWidth = love.graphics.getWidth - function love.graphics.getWidth() - return ScreenScaler.width or getWidth() - end - - local getHeight = love.graphics.getHeight - function love.graphics.getHeight() - return ScreenScaler.height or getHeight() - end - - function love.graphics.getDimensions() - return love.graphics.getWidth(), love.graphics.getHeight() - end -end - --- Adjust setScissor and intersectScissor function, so that they are relative --- to the scaled screen. By default these functions are unaffected by transformations -do - local setScissor = love.graphics.setScissor - function love.graphics.setScissor(x, y, w, h) - if x and ScreenScaler.isEnabled() then - setScissor(Center:toGame(x, y, w, h)) - else - setScissor(x, y, w, h) - end - end - - local intersectScissor = love.graphics.intersectScissor - function love.graphics.intersectScissor(x, y, w, h) - if x and ScreenScaler.isEnabled() then - intersectScissor(Center:toGame(x, y, w, h)) - else - intersectScissor(x, y, w, h) - end - end -end - -local function isInBounds(x, y) - if not ScreenScaler.isEnabled() then return true end - local w, h = ScreenScaler.getVirtualDimensions() - return x >= 0 and x < w and y >= 0 and y < h -end - --- Create event proccessors for converting normal screen coordinates --- to scaled screen coordinates --- If the user clicked out of bounds, it will not handled -local eventPreProccessor = {} -function eventPreProccessor.mousepressed(x, y, button, istouch, presses) - x, y = Center:toGame(x, y) - return isInBounds(x, y), x, y, button, istouch, presses -end - -function eventPreProccessor.mousereleased(x, y, button, istouch, presses) - x, y = Center:toGame(x, y) - return isInBounds(x, y), x, y, button, istouch, presses -end - -function eventPreProccessor.mousemoved(x, y, dx, dy, istouch) - local scale = Center:getScale() - x, y = Center:toGame(x, y) - dx, dy = dx / scale, dy / scale - return isInBounds(x, y), x, y, dx, dy, istouch, istouch -end - -function eventPreProccessor.wheelmoved(x, y) - return isInBounds(love.mouse.getPosition()), x, y -end - -local function hideOutOfBounds() - local r, g, b, a = love.graphics.getColor() - love.graphics.setColor(0, 0, 0, 1) - local w, h = getRealDimensions() - - if Center._OFFSET_X ~= 0 then - love.graphics.rectangle("fill", 0, 0, Center._OFFSET_X, h) - love.graphics.rectangle("fill", Center._WIDTH*Center._SCALE+Center._OFFSET_X, 0, Center._OFFSET_X, h) - end - - if Center._OFFSET_Y ~= 0 then - love.graphics.rectangle("fill", 0, 0, w, Center._OFFSET_Y) - love.graphics.rectangle("fill", 0, Center._HEIGHT*Center._SCALE+Center._OFFSET_Y, w, Center._OFFSET_Y) - end - - love.graphics.setColor(r, g, b, a) -end - --- Modify core game loop so that if scaler is enabled: --- * resize events are not handled --- * out of bounds mouse events are not handled --- * all drawing operations are centered -function love.run() - if love.load then love.load(love.arg.parseGameArguments(arg), arg) end - - -- We don't want the first frame's dt to include time taken by love.load. - if love.timer then love.timer.step() end - - local dt = 0 - - -- Main loop time. - return function() - -- Process events. - if love.event then - love.event.pump() - for name, a,b,c,d,e,f in love.event.poll() do - if name == "quit" then - if not love.quit or not love.quit() then - return a or 0 - end - end - - if ScreenScaler.isEnabled() then - if name == "resize" then - Center:resize(a, b) - goto continue - elseif eventPreProccessor[name] then - local success - success, a, b, c, d, e, f = eventPreProccessor[name](a, b, c, d, e, f) - if not success then goto continue end - end - end - love.handlers[name](a, b, c, d, e, f) - ::continue:: - end - end - - -- Update dt, as we'll be passing it to update - if love.timer then dt = love.timer.step() end - - -- Call update and draw - if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled - - if love.graphics and love.graphics.isActive() then - love.graphics.origin() - - if canvas then - love.graphics.clear(love.graphics.getBackgroundColor()) - - love.graphics.setCanvas(canvas) - love.graphics.clear(love.graphics.getBackgroundColor()) - love.draw() - love.graphics.setCanvas() - - Center:start() - love.graphics.draw(canvas) - Center:finish() - else - love.graphics.clear(love.graphics.getBackgroundColor()) - - if love.draw then - if ScreenScaler.isEnabled() then - love.graphics.setCanvas(canvas) - love.draw() - love.graphics.setCanvas() - - Center:start() - love.graphics.draw(canvas) - Center:finish() - hideOutOfBounds() - else - love.draw() - end - end - end - - love.graphics.present() - end - - if love.timer then love.timer.sleep(0.001) end - end -end - -return ScreenScaler diff --git a/src/states/main.lua b/src/states/main.lua index 7c817db..a037c9c 100644 --- a/src/states/main.lua +++ b/src/states/main.lua @@ -21,6 +21,7 @@ function MainState:enter(_, host_socket) require("systems.map"), require("systems.player"), require("systems.sprite"), + require("systems.screen-scaler"), } if host_socket then @@ -34,6 +35,30 @@ function MainState:enter(_, host_socket) host_socket = host_socket } } + + self.downscaled_canvas = nil + self:refreshDownscaledCanvas() + self.ecs:on("onMapSwitch", function(map) + self:refreshDownscaledCanvas(map) + end) + love.graphics.setNewFont(48) +end + +function MainState:refreshDownscaledCanvas(map) + if not map then + local Map = self.ecs:getSystem(require("systems.map")) + map = Map.map + if not map then + self.downscaled_canvas = nil + return + end + end + + self.downscaled_canvas = love.graphics.newCanvas( + map.width * map.tilewidth, + map.height * map.tileheight + ) + self.downscaled_canvas:setFilter("nearest", "nearest") end function MainState:update(dt) @@ -56,8 +81,14 @@ function MainState:quit() self:cleanup() end -function MainState:mousemoved(...) - self.ecs:emit("mousemoved", ...) +function MainState:mousemoved(x, y, dx, dy, istouch) + local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler")) + if not ScreenScaler:isInBounds(x, y) then return end + + 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 MainState:keypressed(...) @@ -65,7 +96,21 @@ function MainState:keypressed(...) 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.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/map.lua b/src/systems/map.lua index 3949719..febc0ea 100644 --- a/src/systems/map.lua +++ b/src/systems/map.lua @@ -1,6 +1,5 @@ local rgb = require("helpers.rgb") local sti = require("lib.sti") -local ScreenScaler = require("screen-scaler") local Map = {} local DEBUG_GRID = true @@ -8,11 +7,7 @@ local DEBUG_GRID_COLOR = rgb(30, 30, 30) function Map:init() self.map = sti("data/maps/test.lua") - - ScreenScaler.setVirtualDimensions( - self.map.width * self.map.tilewidth, - self.map.height * self.map.tileheight - ) + self.pool:emit("onMapSwitch", self.map) end function Map:update(dt) diff --git a/src/systems/player.lua b/src/systems/player.lua index 0dc9c55..a69a74d 100644 --- a/src/systems/player.lua +++ b/src/systems/player.lua @@ -29,9 +29,11 @@ end function Player:getAimDirection(player) if self.use_mouse_aim then + local ScreenScaler = self.pool:getSystem(require("systems.screen-scaler")) + local MAX_DIRECTIONS = 8 local angle_segment = math.pi*2/MAX_DIRECTIONS - local angle = (Vec(love.mouse.getPosition()) - player.pos).angle + local angle = (Vec(ScreenScaler:getMousePosition()) - player.pos).angle local new_angle = math.floor(angle/angle_segment+0.5)*angle_segment return Vec(math.cos(new_angle), math.sin(new_angle)) else @@ -92,7 +94,7 @@ function Player:update(dt) -- If the player is nearby a non-moving bolt, pick it up for _, bolt in ipairs(self.pool.groups.bolt.entities) do - if (e.pos-bolt.pos).length < 30 and bolt.vel.length < 1 then + if (e.pos-bolt.pos).length < 20 and bolt.vel.length < 3 then self.pool:removeEntity(bolt) e.bolt_count = e.bolt_count + 1 end @@ -132,7 +134,7 @@ function Player:spawnPlayer() friction = 0.998, speed = 800, bolt_count = 1, - bolt_speed = 1000, + bolt_speed = 500, bolt_cooldown = 0.2, bolt_friction = 0.9 } diff --git a/src/systems/screen-scaler.lua b/src/systems/screen-scaler.lua new file mode 100644 index 0000000..3044fd8 --- /dev/null +++ b/src/systems/screen-scaler.lua @@ -0,0 +1,100 @@ +local ScreenScaler = {} + +function ScreenScaler:hideBorders() + local r, g, b, a = love.graphics.getColor() + love.graphics.setColor(love.graphics.getBackgroundColor()) + local w, h = love.graphics.getDimensions() + + if self.offset_x ~= 0 then + love.graphics.rectangle("fill", 0, 0, self.offset_x, h) + love.graphics.rectangle("fill", w-self.offset_x, 0, self.offset_x, h) + end + + if self.offset_y ~= 0 then + love.graphics.rectangle("fill", 0, 0, w, self.offset_y) + love.graphics.rectangle("fill", 0, h-self.offset_y, w, self.offset_y) + end + + love.graphics.setColor(r, g, b, a) +end + +function ScreenScaler:getPosition(x, y) + return (x - self.offset_x) / self.scale, + (y - self.offset_y) / self.scale +end + +function ScreenScaler:isInBounds(x, y) + if self.scale then + local w, h = love.graphics.getDimensions() + return x >= self.offset_x and + x < w-self.offset_x and + y >= self.offset_y and + y < h-self.offset_y + else + return true + end +end + +function ScreenScaler:getMousePosition() + if self.scale then + return self:getPosition(love.mouse.getPosition()) + else + return love.mouse.getPosition() + end +end + +function ScreenScaler:getDimensions() + if self.scale then + local w, h = love.graphics.getDimensions() + return w*self.scale, h*self.scale + else + return love.graphics.getDimensions() + end +end + +function ScreenScaler:overrideScaling(width, height) + local sw, sh = love.graphics.getDimensions() + self.scale = math.min(sw / width, sh / height) + self.offset_x = (sw - width * self.scale)/2 + self.offset_y = (sh - height * self.scale)/2 +end + +function ScreenScaler:start(p1, p2) + local width, height + if type(p1) == "number" and type(p2) == "number" then + width, height = p1, p2 + self.canvas = nil + elseif p1:typeOf("Canvas") then + self.canvas = p1 + width, height = self.canvas:getDimensions() + else + return + end + + local sw, sh = love.graphics.getDimensions() + self.scale = math.min(sw / width, sh / height) + self.offset_x = (sw - width * self.scale)/2 + self.offset_y = (sh - height * self.scale)/2 + + love.graphics.push() + if self.canvas then + love.graphics.setCanvas(self.canvas) + love.graphics.clear() + else + love.graphics.translate(self.offset_x, self.offset_y) + love.graphics.scale(self.scale) + end +end + +function ScreenScaler:finish() + love.graphics.pop() + if self.canvas then + love.graphics.setCanvas() + love.graphics.draw(self.canvas, self.offset_x, self.offset_y, 0, self.scale) + else + self:hideBorders() + end + self.canvas = nil +end + +return ScreenScaler