Created
October 31, 2020 19:06
-
-
Save Saarth-Jain/65346bc219122fbdbdc62bb561924bdf to your computer and use it in GitHub Desktop.
CS50 Solution pset8 tracks games : mario
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Holds a collection of frames that switch depending on how much time has | |
passed. | |
]] | |
Animation = Class{} | |
function Animation:init(params) | |
self.texture = params.texture | |
-- quads defining this animation | |
self.frames = params.frames or {} | |
-- time in seconds each frame takes (1/20 by default) | |
self.interval = params.interval or 0.05 | |
-- stores amount of time that has elapsed | |
self.timer = 0 | |
self.currentFrame = 1 | |
end | |
function Animation:getCurrentFrame() | |
return self.frames[self.currentFrame] | |
end | |
function Animation:restart() | |
self.timer = 0 | |
self.currentFrame = 1 | |
end | |
function Animation:update(dt) | |
self.timer = self.timer + dt | |
-- iteratively subtract interval from timer to proceed in the animation, | |
-- in case we skipped more than one frame | |
while self.timer > self.interval do | |
self.timer = self.timer - self.interval | |
self.currentFrame = (self.currentFrame + 1) % #self.frames | |
if self.currentFrame == 0 then self.currentFrame = 1 end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Copyright (c) 2010-2013 Matthias Richter | |
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. | |
Except as contained in this notice, the name(s) of the above copyright holders | |
shall not be used in advertising or otherwise to promote the sale, use or | |
other dealings in this Software without prior written authorization. | |
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 function include_helper(to, from, seen) | |
if from == nil then | |
return to | |
elseif type(from) ~= 'table' then | |
return from | |
elseif seen[from] then | |
return seen[from] | |
end | |
seen[from] = to | |
for k,v in pairs(from) do | |
k = include_helper({}, k, seen) -- keys might also be tables | |
if to[k] == nil then | |
to[k] = include_helper({}, v, seen) | |
end | |
end | |
return to | |
end | |
-- deeply copies `other' into `class'. keys in `other' that are already | |
-- defined in `class' are omitted | |
local function include(class, other) | |
return include_helper(class, other, {}) | |
end | |
-- returns a deep copy of `other' | |
local function clone(other) | |
return setmetatable(include({}, other), getmetatable(other)) | |
end | |
local function new(class) | |
-- mixins | |
class = class or {} -- class can be nil | |
local inc = class.__includes or {} | |
if getmetatable(inc) then inc = {inc} end | |
for _, other in ipairs(inc) do | |
if type(other) == "string" then | |
other = _G[other] | |
end | |
include(class, other) | |
end | |
-- class implementation | |
class.__index = class | |
class.init = class.init or class[1] or function() end | |
class.include = class.include or include | |
class.clone = class.clone or clone | |
-- constructor call | |
return setmetatable(class, {__call = function(c, ...) | |
local o = setmetatable({}, c) | |
o:init(...) | |
return o | |
end}) | |
end | |
-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons). | |
if class_commons ~= false and not common then | |
common = {} | |
function common.class(name, prototype, parent) | |
return new{__includes = {prototype, parent}} | |
end | |
function common.instance(class, ...) | |
return class(...) | |
end | |
end | |
-- the module | |
return setmetatable({new = new, include = include, clone = clone}, | |
{__call = function(_,...) return new(...) end}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Super Mario Bros. Demo | |
Author: Colton Ogden | |
Original Credit: Nintendo | |
Demonstrates rendering a screen of tiles. | |
]] | |
Class = require 'class' | |
push = require 'push' | |
require 'Animation' | |
require 'Map' | |
require 'Player' | |
-- close resolution to NES but 16:9 | |
VIRTUAL_WIDTH = 432 | |
VIRTUAL_HEIGHT = 243 | |
-- actual window resolution | |
WINDOW_WIDTH = 1280 | |
WINDOW_HEIGHT = 720 | |
-- seed RNG | |
math.randomseed(os.time()) | |
-- makes upscaling look pixel-y instead of blurry | |
love.graphics.setDefaultFilter('nearest', 'nearest') | |
-- an object to contain our map data | |
map = Map() | |
-- performs initialization of all objects and data needed by program | |
function love.load() | |
-- sets up virtual screen resolution for an authentic retro feel | |
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, { | |
fullscreen = false, | |
resizable = true | |
}) | |
gamestate = 'start' | |
love.window.setTitle('Super Mario 50') | |
love.keyboard.keysPressed = {} | |
love.keyboard.keysReleased = {} | |
end | |
-- called whenever window is resized | |
function love.resize(w, h) | |
push:resize(w, h) | |
end | |
-- global key pressed function | |
function love.keyboard.wasPressed(key) | |
if (love.keyboard.keysPressed[key]) then | |
return true | |
else | |
return false | |
end | |
end | |
-- global key released function | |
function love.keyboard.wasReleased(key) | |
if (love.keyboard.keysReleased[key]) then | |
return true | |
else | |
return false | |
end | |
end | |
-- called whenever a key is pressed | |
function love.keypressed(key) | |
if key == 'escape' then | |
love.event.quit() | |
end | |
love.keyboard.keysPressed[key] = true | |
end | |
-- called whenever a key is released | |
function love.keyreleased(key) | |
love.keyboard.keysReleased[key] = true | |
end | |
-- called every frame, with dt passed in as delta in time since last frame | |
function love.update(dt) | |
map:update(dt) | |
-- reset all keys pressed and released this frame | |
love.keyboard.keysPressed = {} | |
love.keyboard.keysReleased = {} | |
end | |
-- called each frame, used to render to the screen | |
function love.draw() | |
-- begin virtual resolution drawing | |
push:apply('start') | |
love.graphics.clear(108/255, 140/255, 255/255, 255/255) | |
-- renders our map object onto the screen | |
love.graphics.translate(math.floor(-map.camX + 0.5), math.floor(-map.camY + 0.5)) | |
map:render() | |
-- end virtual resolution | |
push:apply('end') | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Contains tile data and necessary code for rendering a tile map to the | |
screen. | |
]] | |
require 'Util' | |
require 'Player' | |
Map = Class{} | |
TILE_BRICK = 1 | |
TILE_EMPTY = -1 | |
-- cloud tiles | |
CLOUD_LEFT = 6 | |
CLOUD_RIGHT = 7 | |
-- bush tiles | |
BUSH_LEFT = 2 | |
BUSH_RIGHT = 3 | |
-- flag tiles | |
FLAG_1 = 8 | |
FLAG_2 = 12 | |
FLAG_3 = 16 | |
FLAG_4 = 14 | |
-- mushroom tiles | |
MUSHROOM_TOP = 10 | |
MUSHROOM_BOTTOM = 11 | |
-- jump block | |
JUMP_BLOCK = 5 | |
JUMP_BLOCK_HIT = 9 | |
-- constructor for our map object | |
function Map:init() | |
Font = love.graphics.newFont('font.ttf', 64) | |
self.spritesheet = love.graphics.newImage('graphics/spritesheet.png') | |
self.sprites = generateQuads(self.spritesheet, 16, 16) | |
self.music = love.audio.newSource('sounds/music.wav', 'static') | |
self.tileWidth = 16 | |
self.tileHeight = 16 | |
self.mapWidth = 250 | |
self.mapHeight = 28 | |
self.tiles = {} | |
-- applies positive Y influence on anything affected | |
self.gravity = 15 | |
-- associate player with map | |
self.player = Player(self) | |
-- camera offsets | |
self.camX = 0 | |
self.camY = -3 | |
-- cache width and height of map in pixels | |
self.mapWidthPixels = self.mapWidth * self.tileWidth | |
self.mapHeightPixels = self.mapHeight * self.tileHeight | |
-- first, fill map with empty tiles | |
for y = 1, self.mapHeight do | |
for x = 1, self.mapWidth do | |
-- support for multiple sheets per tile; storing tiles as tables | |
self:setTile(x, y, TILE_EMPTY) | |
end | |
end | |
-- begin generating the terrain using vertical scan lines | |
local x = 1 | |
while x < self.mapWidth do | |
for y = self.mapHeight / 2, self.mapHeight do | |
self:setTile(x + x, y, TILE_BRICK) | |
end | |
-- 2% chance to generate a cloud | |
-- make sure we're 2 tiles from edge at least | |
if x < self.mapWidth - 2 then | |
if math.random(20) == 1 then | |
-- choose a random vertical spot above where blocks/pipes generate | |
local cloudStart = math.random(self.mapHeight / 2 - 6) | |
self:setTile(x, cloudStart, CLOUD_LEFT) | |
self:setTile(x + 1, cloudStart, CLOUD_RIGHT) | |
end | |
end | |
-- 5% chance to generate a mushroom | |
if math.random(20) == 1 then | |
-- left side of pipe | |
self:setTile(x, self.mapHeight / 2 - 2, MUSHROOM_TOP) | |
self:setTile(x, self.mapHeight / 2 - 1, MUSHROOM_BOTTOM) | |
-- creates column of tiles going to bottom of map | |
for y = self.mapHeight / 2, self.mapHeight do | |
self:setTile(x, y, TILE_BRICK) | |
end | |
-- next vertical scan line | |
x = x + 1 | |
-- 10% chance to generate bush, being sure to generate away from edge | |
elseif math.random(10) == 1 and x < self.mapWidth - 3 then | |
local bushLevel = self.mapHeight / 2 - 1 | |
-- place bush component and then column of bricks | |
self:setTile(x, bushLevel, BUSH_LEFT) | |
for y = self.mapHeight / 2, self.mapHeight do | |
self:setTile(x, y, TILE_BRICK) | |
end | |
x = x + 1 | |
self:setTile(x, bushLevel, BUSH_RIGHT) | |
for y = self.mapHeight / 2, self.mapHeight do | |
self:setTile(x, y, TILE_BRICK) | |
end | |
x = x + 1 | |
-- 10% chance to not generate anything, creating a gap | |
elseif math.random(10) ~= 1 then | |
-- creates column of tiles going to bottom of map | |
for y = self.mapHeight / 2, self.mapHeight do | |
self:setTile(x, y, TILE_BRICK) | |
end | |
-- chance to create a block for Mario to hit | |
if math.random(15) == 1 then | |
self:setTile(x, self.mapHeight / 2 - 4, JUMP_BLOCK) | |
end | |
-- next vertical scan line | |
x = x + 1 | |
else | |
-- increment X so we skip two scanlines, creating a 2-tile gap | |
x = x + 2 | |
end | |
m = 1 | |
end | |
m = 1 | |
for l = 0 , 9 do | |
for y = self.mapHeight / 2 - m + 6, self.mapHeight do | |
self:setTile(l + 100, y, TILE_BRICK) | |
end | |
m = m + 1 | |
end | |
self:setTile(x + 245, self.mapHeight / 2 - 8, FLAG_1) | |
self:setTile(x + 246, self.mapHeight / 2 - 8, FLAG_4) | |
m = 1 | |
for l = 0 , 9 do | |
for y = self.mapHeight / 2 - m + 4, self.mapHeight do | |
self:setTile(l + 160, y, TILE_BRICK) | |
end | |
m = m + 1 | |
end | |
for qq = 3, 7 do | |
self:setTile(x + 245, self.mapHeight / 2 - qq, FLAG_2) | |
end | |
self:setTile(x + 245, self.mapHeight / 2 - 2, FLAG_3) | |
m = 1 | |
for l = 0 , 9 do | |
for y = self.mapHeight / 2 - m, self.mapHeight do | |
self:setTile(l + 232, y, TILE_BRICK) | |
end | |
m = m + 1 | |
end | |
-- start the background music | |
self.music:setLooping(true) | |
self.music:play() | |
end | |
-- return whether a given tile is collidable | |
function Map:collides(tile) | |
-- define our collidable tiles | |
local collidables = { | |
TILE_BRICK, JUMP_BLOCK, JUMP_BLOCK_HIT, | |
MUSHROOM_TOP, MUSHROOM_BOTTOM, FLAG_1, FLAG_2, FLAG_3, FLAG_4 | |
} | |
-- iterate and return true if our tile type matches | |
for _, v in ipairs(collidables) do | |
if tile.id == v then | |
return true | |
end | |
end | |
return false | |
end | |
-- function to update camera offset with delta time | |
function Map:update(dt) | |
self.player:update(dt) | |
-- keep camera's X coordinate following the player, preventing camera from | |
-- scrolling past 0 to the left and the map''s width | |
self.camX = math.max(0, math.min(self.player.x - VIRTUAL_WIDTH / 2, | |
math.min(self.mapWidthPixels, self.player.x))) | |
end | |
-- gets the tile type at a given pixel coordinate | |
function Map:tileAt(x, y) | |
return { | |
x = math.floor(x / self.tileWidth) + 1, | |
y = math.floor(y / self.tileHeight) + 1, | |
id = self:getTile(math.floor(x / self.tileWidth) + 1, math.floor(y / self.tileHeight) + 1) | |
} | |
end | |
-- returns an integer value for the tile at a given x-y coordinate | |
function Map:getTile(x, y) | |
return self.tiles[(y - 1) * self.mapWidth + x] | |
end | |
-- sets a tile at a given x-y coordinate to an integer value | |
function Map:setTile(x, y, id) | |
self.tiles[(y - 1) * 250 + x] = id | |
end | |
-- renders our map to the screen, to be called by main's render | |
function Map:render() | |
for y = 1, self.mapHeight do | |
for x = 1, self.mapWidth do | |
local tile = self:getTile(x, y) | |
if tile ~= TILE_EMPTY then | |
love.graphics.draw(self.spritesheet, self.sprites[tile], | |
(x - 1) * self.tileWidth, (y - 1) * self.tileHeight) | |
end | |
end | |
end | |
self.player:render() | |
if gamestate == 'done' then | |
love.graphics.setDefaultFilter('nearest' ,'nearest') | |
love.graphics.setFont(Font) | |
love.graphics.clear(1, 10/255, 1 ,1) | |
love.graphics.printf('YOU WON!', 3736, VIRTUAL_HEIGHT / 2 - 1.6, VIRTUAL_WIDTH, 'center') | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Represents our player in the game, with its own sprite. | |
]] | |
Player = Class{} | |
local WALKING_SPEED = 140 | |
local JUMP_VELOCITY = 400 | |
function Player:init(map) | |
self.x = 0 | |
self.y = 0 | |
self.width = 16 | |
self.height = 20 | |
-- offset from top left to center to support sprite flipping | |
self.xOffset = 8 | |
self.yOffset = 10 | |
-- reference to map for checking tiles | |
self.map = map | |
self.texture = love.graphics.newImage('graphics/blue_alien.png') | |
-- sound effects | |
self.sounds = { | |
['jump'] = love.audio.newSource('sounds/jump.wav', 'static'), | |
['hit'] = love.audio.newSource('sounds/hit.wav', 'static'), | |
['coin'] = love.audio.newSource('sounds/coin.wav', 'static') | |
} | |
-- animation frames | |
self.frames = {} | |
-- current animation frame | |
self.currentFrame = nil | |
-- used to determine behavior and animations | |
self.state = 'idle' | |
-- determines sprite flipping | |
self.direction = 'left' | |
-- x and y velocity | |
self.dx = 0 | |
self.dy = 0 | |
-- position on top of map tiles | |
self.y = map.tileHeight * ((map.mapHeight - 2) / 2) - self.height | |
self.x = map.tileWidth * 10 | |
-- initialize all player animations | |
self.animations = { | |
['idle'] = Animation({ | |
texture = self.texture, | |
frames = { | |
love.graphics.newQuad(0, 0, 16, 20, self.texture:getDimensions()) | |
} | |
}), | |
['walking'] = Animation({ | |
texture = self.texture, | |
frames = { | |
love.graphics.newQuad(128, 0, 16, 20, self.texture:getDimensions()), | |
love.graphics.newQuad(144, 0, 16, 20, self.texture:getDimensions()), | |
love.graphics.newQuad(160, 0, 16, 20, self.texture:getDimensions()), | |
love.graphics.newQuad(144, 0, 16, 20, self.texture:getDimensions()), | |
}, | |
interval = 0.15 | |
}), | |
['jumping'] = Animation({ | |
texture = self.texture, | |
frames = { | |
love.graphics.newQuad(32, 0, 16, 20, self.texture:getDimensions()) | |
} | |
}) | |
} | |
-- initialize animation and current frame we should render | |
self.animation = self.animations['idle'] | |
self.currentFrame = self.animation:getCurrentFrame() | |
-- behavior map we can call based on player state | |
self.behaviors = { | |
['idle'] = function(dt) | |
-- add spacebar functionality to trigger jump state | |
if love.keyboard.wasPressed('space') then | |
self.dy = -JUMP_VELOCITY | |
self.state = 'jumping' | |
self.animation = self.animations['jumping'] | |
self.sounds['jump']:play() | |
elseif love.keyboard.isDown('left') then | |
self.direction = 'left' | |
self.dx = -WALKING_SPEED | |
self.state = 'walking' | |
self.animations['walking']:restart() | |
self.animation = self.animations['walking'] | |
elseif love.keyboard.isDown('right') then | |
self.direction = 'right' | |
self.dx = WALKING_SPEED | |
self.state = 'walking' | |
self.animations['walking']:restart() | |
self.animation = self.animations['walking'] | |
else | |
self.dx = 0 | |
end | |
end, | |
['walking'] = function(dt) | |
-- keep track of input to switch movement while walking, or reset | |
-- to idle if we're not moving | |
if love.keyboard.wasPressed('space') then | |
self.dy = -JUMP_VELOCITY | |
self.state = 'jumping' | |
self.animation = self.animations['jumping'] | |
self.sounds['jump']:play() | |
elseif love.keyboard.isDown('left') then | |
self.direction = 'left' | |
self.dx = -WALKING_SPEED | |
elseif love.keyboard.isDown('right') then | |
self.direction = 'right' | |
self.dx = WALKING_SPEED | |
else | |
self.dx = 0 | |
self.state = 'idle' | |
self.animation = self.animations['idle'] | |
end | |
-- check for collisions moving left and right | |
self:checkRightCollision() | |
self:checkLeftCollision() | |
-- check if there's a tile directly beneath us | |
if not self.map:collides(self.map:tileAt(self.x, self.y + self.height)) and | |
not self.map:collides(self.map:tileAt(self.x + self.width - 1, self.y + self.height)) then | |
-- if so, reset velocity and position and change state | |
self.state = 'jumping' | |
self.animation = self.animations['jumping'] | |
end | |
end, | |
['jumping'] = function(dt) | |
-- break if we go below the surface | |
if self.y > 300 then | |
return | |
end | |
if love.keyboard.isDown('left') then | |
self.direction = 'left' | |
self.dx = -WALKING_SPEED | |
elseif love.keyboard.isDown('right') then | |
self.direction = 'right' | |
self.dx = WALKING_SPEED | |
end | |
-- apply map's gravity before y velocity | |
self.dy = self.dy + self.map.gravity | |
-- check if there's a tile directly beneath us | |
if self.map:collides(self.map:tileAt(self.x, self.y + self.height)) or | |
self.map:collides(self.map:tileAt(self.x + self.width - 1, self.y + self.height)) then | |
-- if so, reset velocity and position and change state | |
self.dy = 0 | |
self.state = 'idle' | |
self.animation = self.animations['idle'] | |
self.y = (self.map:tileAt(self.x, self.y + self.height).y - 1) * self.map.tileHeight - self.height | |
end | |
-- check for collisions moving left and right | |
self:checkRightCollision() | |
self:checkLeftCollision() | |
end | |
} | |
end | |
function Player:update(dt) | |
self.behaviors[self.state](dt) | |
self.animation:update(dt) | |
self.currentFrame = self.animation:getCurrentFrame() | |
self.x = self.x + self.dx * dt | |
self:calculateJumps() | |
-- apply velocity | |
self.y = self.y + self.dy * dt | |
if self.map:tileAt(self.x, self.y + 1).id == Flag_4 or self.map:tileAt(self.x, self.y - 1).id == Flag_4 then | |
gamestate = 'done' | |
end | |
end | |
-- jumping and block hitting logic | |
function Player:calculateJumps() | |
-- if we have negative y velocity (jumping), check if we collide | |
-- with any blocks above us | |
if self.dy < 0 then | |
if self.map:tileAt(self.x, self.y).id ~= TILE_EMPTY or | |
self.map:tileAt(self.x + self.width - 1, self.y).id ~= TILE_EMPTY then | |
-- reset y velocity | |
self.dy = 0 | |
-- change block to different block | |
local playCoin = false | |
local playHit = false | |
if self.map:tileAt(self.x, self.y).id == JUMP_BLOCK then | |
self.map:setTile(math.floor(self.x / self.map.tileWidth) + 1, | |
math.floor(self.y / self.map.tileHeight) + 1, JUMP_BLOCK_HIT) | |
playCoin = true | |
else | |
playHit = true | |
end | |
if self.map:tileAt(self.x + self.width - 1, self.y).id == JUMP_BLOCK then | |
self.map:setTile(math.floor((self.x + self.width - 1) / self.map.tileWidth) + 1, | |
math.floor(self.y / self.map.tileHeight) + 1, JUMP_BLOCK_HIT) | |
playCoin = true | |
else | |
playHit = true | |
end | |
if playCoin then | |
self.sounds['coin']:play() | |
elseif playHit then | |
self.sounds['hit']:play() | |
end | |
end | |
end | |
end | |
-- checks two tiles to our left to see if a collision occurred | |
function Player:checkLeftCollision() | |
if self.dx < 0 then | |
-- check if there's a tile directly beneath us | |
if self.map:collides(self.map:tileAt(self.x - 1, self.y)) or | |
self.map:collides(self.map:tileAt(self.x - 1, self.y + self.height - 1)) then | |
-- if so, reset velocity and position and change state | |
self.dx = 0 | |
self.x = self.map:tileAt(self.x - 1, self.y).x * self.map.tileWidth | |
end | |
end | |
end | |
-- checks two tiles to our right to see if a collision occurred | |
function Player:checkRightCollision() | |
if self.dx ~= nil then | |
if self.dx > 0 then | |
-- check if there's a tile directly beneath us | |
if self.map:collides(self.map:tileAt(self.x + self.width, self.y)) or | |
self.map:collides(self.map:tileAt(self.x + self.width, self.y + self.height - 1)) then | |
-- if so, reset velocity and position and change state | |
self.dx = 0 | |
self.x = (self.map:tileAt(self.x + self.width, self.y).x - 1) * self.map.tileWidth - self.width | |
end | |
end | |
end | |
end | |
function Player:render() | |
local scaleX | |
-- set negative x scale factor if facing left, which will flip the sprite | |
-- when applied | |
if self.direction == 'right' then | |
scaleX = 1 | |
else | |
scaleX = -1 | |
end | |
-- draw sprite with scale factor and offsets | |
love.graphics.draw(self.texture, self.currentFrame, math.floor(self.x + self.xOffset), | |
math.floor(self.y + self.yOffset), 0, scaleX, 1, self.xOffset, self.yOffset) | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- push.lua v0.3 | |
-- Copyright (c) 2018 Ulysse Ramage | |
-- 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 love11 = love.getVersion() == 11 | |
local getDPI = love11 and love.window.getDPIScale or love.window.getPixelScale | |
local windowUpdateMode = love11 and love.window.updateMode or function(width, height, settings) | |
local _, _, flags = love.window.getMode() | |
for k, v in pairs(settings) do flags[k] = v end | |
love.window.setMode(width, height, flags) | |
end | |
local push = { | |
defaults = { | |
fullscreen = false, | |
resizable = false, | |
pixelperfect = false, | |
highdpi = true, | |
canvas = true, | |
stencil = true | |
} | |
} | |
setmetatable(push, push) | |
function push:applySettings(settings) | |
for k, v in pairs(settings) do | |
self["_" .. k] = v | |
end | |
end | |
function push:resetSettings() return self:applySettings(self.defaults) end | |
function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, settings) | |
settings = settings or {} | |
self._WWIDTH, self._WHEIGHT = WWIDTH, WHEIGHT | |
self._RWIDTH, self._RHEIGHT = RWIDTH, RHEIGHT | |
self:applySettings(self.defaults) --set defaults first | |
self:applySettings(settings) --then fill with custom settings | |
windowUpdateMode(self._RWIDTH, self._RHEIGHT, { | |
fullscreen = self._fullscreen, | |
resizable = self._resizable, | |
highdpi = self._highdpi | |
}) | |
self:initValues() | |
if self._canvas then | |
self:setupCanvas({ "default" }) --setup canvas | |
end | |
self._borderColor = {0, 0, 0} | |
self._drawFunctions = { | |
["start"] = self.start, | |
["end"] = self.finish | |
} | |
return self | |
end | |
function push:setupCanvas(canvases) | |
table.insert(canvases, { name = "_render", private = true }) --final render | |
self._canvas = true | |
self.canvases = {} | |
for i = 1, #canvases do | |
push:addCanvas(canvases[i]) | |
end | |
return self | |
end | |
function push:addCanvas(params) | |
table.insert(self.canvases, { | |
name = params.name, | |
private = params.private, | |
shader = params.shader, | |
canvas = love.graphics.newCanvas(self._WWIDTH, self._WHEIGHT), | |
stencil = params.stencil or self._stencil | |
}) | |
end | |
function push:setCanvas(name) | |
if not self._canvas then return true end | |
return love.graphics.setCanvas(self:getCanvasTable(name).canvas) | |
end | |
function push:getCanvasTable(name) | |
for i = 1, #self.canvases do | |
if self.canvases[i].name == name then | |
return self.canvases[i] | |
end | |
end | |
end | |
function push:setShader(name, shader) | |
if not shader then | |
self:getCanvasTable("_render").shader = name | |
else | |
self:getCanvasTable(name).shader = shader | |
end | |
end | |
function push:initValues() | |
self._PSCALE = (not love11 and self._highdpi) and getDPI() or 1 | |
self._SCALE = { | |
x = self._RWIDTH/self._WWIDTH * self._PSCALE, | |
y = self._RHEIGHT/self._WHEIGHT * self._PSCALE | |
} | |
if self._stretched then --if stretched, no need to apply offset | |
self._OFFSET = {x = 0, y = 0} | |
else | |
local scale = math.min(self._SCALE.x, self._SCALE.y) | |
if self._pixelperfect then scale = math.floor(scale) end | |
self._OFFSET = {x = (self._SCALE.x - scale) * (self._WWIDTH/2), y = (self._SCALE.y - scale) * (self._WHEIGHT/2)} | |
self._SCALE.x, self._SCALE.y = scale, scale --apply same scale to X and Y | |
end | |
self._GWIDTH = self._RWIDTH * self._PSCALE - self._OFFSET.x * 2 | |
self._GHEIGHT = self._RHEIGHT * self._PSCALE - self._OFFSET.y * 2 | |
end | |
function push:apply(operation, shader) | |
self._drawFunctions[operation](self, shader) | |
end | |
function push:start() | |
if self._canvas then | |
love.graphics.push() | |
love.graphics.setCanvas({ self.canvases[1].canvas, stencil = self.canvases[1].stencil }) | |
else | |
love.graphics.translate(self._OFFSET.x, self._OFFSET.y) | |
love.graphics.setScissor(self._OFFSET.x, self._OFFSET.y, self._WWIDTH*self._SCALE.x, self._WHEIGHT*self._SCALE.y) | |
love.graphics.push() | |
love.graphics.scale(self._SCALE.x, self._SCALE.y) | |
end | |
end | |
function push:applyShaders(canvas, shaders) | |
local _shader = love.graphics.getShader() | |
if #shaders <= 1 then | |
love.graphics.setShader(shaders[1]) | |
love.graphics.draw(canvas) | |
else | |
local _canvas = love.graphics.getCanvas() | |
local _tmp = self:getCanvasTable("_tmp") | |
if not _tmp then --create temp canvas only if needed | |
self:addCanvas({ name = "_tmp", private = true, shader = nil }) | |
_tmp = self:getCanvasTable("_tmp") | |
end | |
love.graphics.push() | |
love.graphics.origin() | |
local outputCanvas | |
for i = 1, #shaders do | |
local inputCanvas = i % 2 == 1 and canvas or _tmp.canvas | |
outputCanvas = i % 2 == 0 and canvas or _tmp.canvas | |
love.graphics.setCanvas(outputCanvas) | |
love.graphics.clear() | |
love.graphics.setShader(shaders[i]) | |
love.graphics.draw(inputCanvas) | |
love.graphics.setCanvas(inputCanvas) | |
end | |
love.graphics.pop() | |
love.graphics.setCanvas(_canvas) | |
love.graphics.draw(outputCanvas) | |
end | |
love.graphics.setShader(_shader) | |
end | |
function push:finish(shader) | |
love.graphics.setBackgroundColor(unpack(self._borderColor)) | |
if self._canvas then | |
local _render = self:getCanvasTable("_render") | |
love.graphics.pop() | |
local white = love11 and 1 or 255 | |
love.graphics.setColor(white, white, white) | |
--draw canvas | |
love.graphics.setCanvas(_render.canvas) | |
for i = 1, #self.canvases do --do not draw _render yet | |
local _table = self.canvases[i] | |
if not _table.private then | |
local _canvas = _table.canvas | |
local _shader = _table.shader | |
self:applyShaders(_canvas, type(_shader) == "table" and _shader or { _shader }) | |
end | |
end | |
love.graphics.setCanvas() | |
--draw render | |
love.graphics.translate(self._OFFSET.x, self._OFFSET.y) | |
local shader = shader or _render.shader | |
love.graphics.push() | |
love.graphics.scale(self._SCALE.x, self._SCALE.y) | |
self:applyShaders(_render.canvas, type(shader) == "table" and shader or { shader }) | |
love.graphics.pop() | |
--clear canvas | |
for i = 1, #self.canvases do | |
love.graphics.setCanvas(self.canvases[i].canvas) | |
love.graphics.clear() | |
end | |
love.graphics.setCanvas() | |
love.graphics.setShader() | |
else | |
love.graphics.pop() | |
love.graphics.setScissor() | |
end | |
end | |
function push:setBorderColor(color, g, b) | |
self._borderColor = g and {color, g, b} or color | |
end | |
function push:toGame(x, y) | |
x, y = x - self._OFFSET.x, y - self._OFFSET.y | |
local normalX, normalY = x / self._GWIDTH, y / self._GHEIGHT | |
x = (x >= 0 and x <= self._WWIDTH * self._SCALE.x) and normalX * self._WWIDTH or nil | |
y = (y >= 0 and y <= self._WHEIGHT * self._SCALE.y) and normalY * self._WHEIGHT or nil | |
return x, y | |
end | |
--doesn't work - TODO | |
function push:toReal(x, y) | |
return x + self._OFFSET.x, y + self._OFFSET.y | |
end | |
function push:switchFullscreen(winw, winh) | |
self._fullscreen = not self._fullscreen | |
local windowWidth, windowHeight = love.window.getDesktopDimensions() | |
if self._fullscreen then --save windowed dimensions for later | |
self._WINWIDTH, self._WINHEIGHT = self._RWIDTH, self._RHEIGHT | |
elseif not self._WINWIDTH or not self._WINHEIGHT then | |
self._WINWIDTH, self._WINHEIGHT = windowWidth * .5, windowHeight * .5 | |
end | |
self._RWIDTH = self._fullscreen and windowWidth or winw or self._WINWIDTH | |
self._RHEIGHT = self._fullscreen and windowHeight or winh or self._WINHEIGHT | |
self:initValues() | |
love.window.setFullscreen(self._fullscreen, "desktop") | |
if not self._fullscreen and (winw or winh) then | |
windowUpdateMode(self._RWIDTH, self._RHEIGHT) --set window dimensions | |
end | |
end | |
function push:resize(w, h) | |
if self._highdpi then w, h = w / self._PSCALE, h / self._PSCALE end | |
self._RWIDTH = w | |
self._RHEIGHT = h | |
self:initValues() | |
end | |
function push:getWidth() return self._WWIDTH end | |
function push:getHeight() return self._WHEIGHT end | |
function push:getDimensions() return self._WWIDTH, self._WHEIGHT end | |
return push |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Stores utility functions used by our game engine. | |
]] | |
-- takes a texture, width, and height of tiles and splits it into quads | |
-- that can be individually drawn | |
function generateQuads(atlas, tilewidth, tileheight) | |
local sheetWidth = atlas:getWidth() / tilewidth | |
local sheetHeight = atlas:getHeight() / tileheight | |
local sheetCounter = 1 | |
local quads = {} | |
for y = 0, sheetHeight - 1 do | |
for x = 0, sheetWidth - 1 do | |
-- this quad represents a square cutout of our atlas that we can | |
-- individually draw instead of the whole atlas | |
quads[sheetCounter] = | |
love.graphics.newQuad(x * tilewidth, y * tileheight, tilewidth, | |
tileheight, atlas:getDimensions()) | |
sheetCounter = sheetCounter + 1 | |
end | |
end | |
return quads | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment