From 6ae7e3b1a40262587d28f6e80226480e9a07ad5a Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Tue, 27 Dec 2022 19:11:43 +0200 Subject: [PATCH] created better map and tweaked player movement --- src/conf.lua | 5 +- src/lib/center.lua | 138 +++++++++++++++++++++++ src/main.lua | 6 +- src/screen-scaler.lua | 232 ++++++++++++++++++++++++++++++++++++++ src/states/main.lua | 54 +++++---- src/states/map-editor.lua | 95 ++++++++++++++++ 6 files changed, 506 insertions(+), 24 deletions(-) create mode 100644 src/lib/center.lua create mode 100644 src/screen-scaler.lua create mode 100644 src/states/map-editor.lua diff --git a/src/conf.lua b/src/conf.lua index 1389b6e..4e1e90a 100644 --- a/src/conf.lua +++ b/src/conf.lua @@ -3,6 +3,7 @@ function love.conf(t) t.console = true - t.window.width = 854 - t.window.height = 480 + t.window.resizable = true + t.window.width = 640 * 2 + t.window.height = 360 * 2 end diff --git a/src/lib/center.lua b/src/lib/center.lua new file mode 100644 index 0000000..75b2e7d --- /dev/null +++ b/src/lib/center.lua @@ -0,0 +1,138 @@ +--[[ + 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 3d32f3c..cd811a8 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,7 +1,11 @@ local Gamestate = require("lib.hump.gamestate") +local ScreenScaler = require("screen-scaler") pprint = require("lib.pprint") -PLAYER_SIZE = 20 +PLAYER_SIZE = 10 + +-- 640 x 360 +ScreenScaler.setVirtualDimensions(16 * 40, 9 * 40) function love.load() love.math.setRandomSeed(love.timer.getTime()) diff --git a/src/screen-scaler.lua b/src/screen-scaler.lua new file mode 100644 index 0000000..475eaaa --- /dev/null +++ b/src/screen-scaler.lua @@ -0,0 +1,232 @@ +--- Module resposible for scaling screen and centering contents +-- while respecting ratios. +-- @module ScreenScaler +local ScreenScaler = {} + +-- This "Center" library will do most of the heavy lifting +-- TODO: Removed this depedency, embed needed functionality +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) + + 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() + love.graphics.clear(love.graphics.getBackgroundColor()) + + if love.draw then + if ScreenScaler.isEnabled() then + Center:start() + love.draw() + Center:finish() + else + love.draw() + 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 e83719a..5028aac 100644 --- a/src/states/main.lua +++ b/src/states/main.lua @@ -6,7 +6,7 @@ local rgb = require("helpers.rgb") local lerp = require("lib.lume").lerp local BoltMods = require("bolt-mods") -local BOLT_DIST_FROM_PLAYER = 40 +local BOLT_DIST_FROM_PLAYER = PLAYER_SIZE*2 local BOLT_SIZE = 4 local DRAW_COLLIDERS = false @@ -31,14 +31,24 @@ function MainState:enter() self.bolts = {} self.obstacles = {} - table.insert(self.obstacles, {{90, 90}, {100, 200}, {300, 300}, {200, 100}}) - table.insert(self.obstacles, {{390, 90}, {500, 200}, {500, 300}}) - table.insert(self.obstacles, {{450, 390}, {500, 200}, {500, 300}}) - table.insert(self.obstacles, {{750, 390}, {500, 200}, {500, 300}}) - table.insert(self.obstacles, {{750, 290}, {600, 200}, {600, 100}}) - table.insert(self.obstacles, {{550, 400}, {670, 500}, {500, 500}}) - table.insert(self.obstacles, {{700, 0}, {850, 200}, {850, 0}}) - table.insert(self.obstacles, {{250, 350}, {200, 500}, {300, 500}}) + do + table.insert(self.obstacles, {{77, 2}, {6, 60}, {5, 5}}) + table.insert(self.obstacles, {{576, 357}, {639, 314}, {639, 357}}) + table.insert(self.obstacles, {{275, 101}, {395, 101}, {392, 55}, {281, 60}}) + table.insert(self.obstacles, {{271, 280}, {392, 277}, {392, 322}, {252, 320}}) + table.insert(self.obstacles, {{222, 115}, {248, 145}, {221, 176}, {192, 151}}) + table.insert(self.obstacles, {{435, 112}, {401, 142}, {426, 174}, {457, 142}}) + table.insert(self.obstacles, {{194, 229}, {246, 260}, {192, 262}}) + table.insert(self.obstacles, {{417, 260}, {466, 258}, {465, 226}}) + table.insert(self.obstacles, {{141, 148}, {136, 249}, {94, 246}, {105, 149}}) + table.insert(self.obstacles, {{216, 318}, {216, 359}, {134, 358}}) + table.insert(self.obstacles, {{435, 0}, {434, 52}, {541, 5}}) + table.insert(self.obstacles, {{507, 142}, {505, 247}, {554, 246}, {553, 138}}) + table.insert(self.obstacles, {{124, 104}, {219, 34}, {230, 64}, {166, 111}}) + table.insert(self.obstacles, {{429, 337}, {527, 282}, {504, 274}, {432, 312}}) + table.insert(self.obstacles, {{63, 292}, {78, 314}, {40, 315}}) + table.insert(self.obstacles, {{588, 92}, {565, 65}, {616, 66}}) + end self.obstacle_colliders = {} for _, obstacle in ipairs(self.obstacles) do @@ -83,10 +93,9 @@ end function MainState:start_match() self:stop_match() - local winw, winh = love.graphics.getDimensions() - self:create_player("keyboard", Vec(50, 200)) + self:create_player("keyboard", Vec(60, 80)) -- self:create_player("joystick", Vec(winw-50, 200)) - self:create_player("joystick", Vec(50, 300)) + self:create_player("joystick", Vec(600, 300)) end function MainState:update(dt) @@ -121,9 +130,9 @@ function MainState:update(dt) end local move_dir = player:get_move_dir() - local acc = move_dir * 300 + local acc = move_dir * 1700 player.vel = player.vel + acc * dt - player.vel = player.vel * 0.98 + player.vel = player.vel * 0.8 player.collider:setLinearVelocity(player.vel.x, player.vel.y) if not player.has_bolt then @@ -158,8 +167,13 @@ function MainState:update(dt) end for _, player in ipairs(self.players) do - player.pos.x = player.collider:getX() - player.pos.y = player.collider:getY() + local collider = player.collider + player.pos.x = collider:getX() + player.pos.y = collider:getY() + + local velx, vely = collider:getLinearVelocity() + player.vel.x = velx + player.vel.y = vely end else self.victory_timer = (self.victory_timer or 0) + dt @@ -183,9 +197,9 @@ end function MainState:create_player(controls, pos) local player = Player:new(controls, self.world, pos) player.bolt_mods = { - BoltMods.mods.speed_bounce, + -- BoltMods.mods.speed_bounce, BoltMods.mods.bouncy, - BoltMods.mods.retain_speed, + -- BoltMods.mods.retain_speed, } sort_bolt_mods(player.bolt_mods) player.collider.fixture:setUserData("player") @@ -340,8 +354,6 @@ function MainState:draw() love.graphics.setColor(1, 1, 1) love.graphics.line(mx-cursor_size, my, mx+cursor_size, my) love.graphics.line(mx, my-cursor_size, mx, my+cursor_size) - -- love.graphics.print(mx, 200, 10) - -- love.graphics.print(my, 200, 40) if DRAW_COLLIDERS then love.graphics.setColor(0.8, 0.1, 0.8) @@ -350,7 +362,7 @@ function MainState:draw() if #self.players == 1 then love.graphics.setColor(0.2, 1, 0.2) - love.graphics.print("Victory!", 300, 200) + love.graphics.print("Victory!", 150, 100) end end diff --git a/src/states/map-editor.lua b/src/states/map-editor.lua new file mode 100644 index 0000000..4f192ee --- /dev/null +++ b/src/states/map-editor.lua @@ -0,0 +1,95 @@ +local MapEditor = {} + +function MapEditor:enter() + self.obstacles = {} + self.current_obstacle = {} + self:from_clipboard() +end + +function MapEditor:keypressed(key) + if key == "escape" then + love.event.quit() + self:refresh_clipboard() + end +end + +function MapEditor:mousepressed(mx, my) + mx = math.floor(mx) + my = math.floor(my) + if self.current_obstacle[1] then + local origin_x, origin_y = self.current_obstacle[1][1], self.current_obstacle[1][2] + local dist = ((origin_x-mx)^2 + (origin_y-my)^2)^0.5 + if dist < 10 then + table.insert(self.obstacles, self.current_obstacle) + self.current_obstacle = {} + self:update_clipboard() + return + end + end + + table.insert(self.current_obstacle, {mx, my}) +end + +function MapEditor:update_clipboard() + local lines = {} + for _, obstacle in ipairs(self.obstacles) do + local points = {} + for _, point in ipairs(obstacle) do + table.insert(points, ("{%d, %d}"):format(point[1], point[2])) + end + table.insert(lines, ("table.insert(self.obstacles, {%s})"):format(table.concat(points, ", "))) + end + + local text = table.concat(lines, "\n") + love.system.setClipboardText(text) +end + +function MapEditor:from_clipboard() + self.obstacles = {} + self.current_obstacle = {} + + local text = love.system.getClipboardText() + for line in text:gmatch("%s*([^\n]+)%s*") do + local points = line:match("^table%.insert%(self%.obstacles, {(.+)}%)$") + if points then + local obstacle = {} + table.insert(self.obstacles, obstacle) + for x, y in points:gmatch("{(%d+),%s*(%d+)}") do + table.insert(obstacle, {tonumber(x), tonumber(y)}) + end + end + end +end + +function MapEditor:draw_obstacle(obstacle) + local n = #obstacle + if n >= 2 then + love.graphics.line(obstacle[1][1], obstacle[1][2], obstacle[n][1], obstacle[n][2]) + for i=1, n-1 do + love.graphics.line(obstacle[i][1], obstacle[i][2], obstacle[i+1][1], obstacle[i+1][2]) + end + end +end + +function MapEditor:draw() + for _, obstacle in ipairs(self.obstacles) do + self:draw_obstacle(obstacle) + end + + do + local obstacle = self.current_obstacle + local n = #self.current_obstacle + for i=1, n-1 do + love.graphics.line(obstacle[i][1], obstacle[i][2], obstacle[i+1][1], obstacle[i+1][2]) + end + if n > 0 then + local mx, my = love.mouse.getPosition() + mx = math.floor(mx) + my = math.floor(my) + local last_point = self.current_obstacle[n] + love.graphics.line(last_point[1], last_point[2], mx, my) + end + end +end + +return MapEditor