├── love2d ├── shaders │ ├── map │ │ ├── tiles.png │ │ └── map.tmx │ ├── images │ │ ├── parrot.png │ │ └── background.png │ ├── main.lua │ └── shaders.lua ├── parrot │ ├── images │ │ ├── parrot.png │ │ └── background.png │ └── main.lua ├── 5_camera │ ├── maps │ │ ├── tileset.png │ │ ├── testMap.tmx │ │ └── testMap.lua │ ├── sprites │ │ ├── background.png │ │ └── player-sheet.png │ ├── libraries │ │ ├── sti │ │ │ ├── graphics.lua │ │ │ ├── utils.lua │ │ │ ├── plugins │ │ │ │ ├── bump.lua │ │ │ │ └── box2d.lua │ │ │ └── init.lua │ │ ├── camera.lua │ │ └── anim8.lua │ └── main.lua ├── 3_animations │ ├── sprites │ │ ├── background.png │ │ └── player-sheet.png │ ├── main.lua │ └── libraries │ │ └── anim8.lua ├── 1_player-movement │ └── main.lua └── blast │ └── blast.lua ├── mobile ├── necrozone-support │ └── README.md ├── necrozone-ios │ └── README.md └── necrozone-google-play │ └── README.md └── LICENSE /love2d/shaders/map/tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/shaders/map/tiles.png -------------------------------------------------------------------------------- /love2d/parrot/images/parrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/parrot/images/parrot.png -------------------------------------------------------------------------------- /love2d/5_camera/maps/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/5_camera/maps/tileset.png -------------------------------------------------------------------------------- /love2d/shaders/images/parrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/shaders/images/parrot.png -------------------------------------------------------------------------------- /love2d/parrot/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/parrot/images/background.png -------------------------------------------------------------------------------- /love2d/shaders/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/shaders/images/background.png -------------------------------------------------------------------------------- /love2d/5_camera/sprites/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/5_camera/sprites/background.png -------------------------------------------------------------------------------- /love2d/5_camera/sprites/player-sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/5_camera/sprites/player-sheet.png -------------------------------------------------------------------------------- /love2d/3_animations/sprites/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/3_animations/sprites/background.png -------------------------------------------------------------------------------- /love2d/3_animations/sprites/player-sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/challacade/snippets/HEAD/love2d/3_animations/sprites/player-sheet.png -------------------------------------------------------------------------------- /mobile/necrozone-support/README.md: -------------------------------------------------------------------------------- 1 | # Necrozone Support 2 | 3 | Necrozone was created by sole-developer Kyle Schaub under Challacade LLC. For any questions or concerns, please reach out to my email: 4 | 5 | kylejschaub@gmail.com 6 | -------------------------------------------------------------------------------- /mobile/necrozone-ios/README.md: -------------------------------------------------------------------------------- 1 | # Necrozone Privacy Policy 2 | 3 | The developer of Necrozone (Challacade LLC) does not collect user data. Alongside all applications from the App Store, usage metrics are tracked by Apple and are visible by the developer. Progress made throughout the game is saved in a local file and is not collected. 4 | -------------------------------------------------------------------------------- /mobile/necrozone-google-play/README.md: -------------------------------------------------------------------------------- 1 | # Necrozone Privacy Policy 2 | 3 | The developer of Necrozone (Challacade LLC) does not collect user data. Alongside all applications from Google Play, usage metrics are tracked by Google and are visible by the developer. Progress made throughout the game is saved in a local file and is not collected. 4 | -------------------------------------------------------------------------------- /love2d/1_player-movement/main.lua: -------------------------------------------------------------------------------- 1 | function love.load() 2 | player = {} 3 | player.x = 400 4 | player.y = 200 5 | player.speed = 5 6 | end 7 | 8 | function love.update(dt) 9 | if love.keyboard.isDown("right") then 10 | player.x = player.x + player.speed 11 | end 12 | 13 | if love.keyboard.isDown("left") then 14 | player.x = player.x - player.speed 15 | end 16 | 17 | if love.keyboard.isDown("down") then 18 | player.y = player.y + player.speed 19 | end 20 | 21 | if love.keyboard.isDown("up") then 22 | player.y = player.y - player.speed 23 | end 24 | end 25 | 26 | function love.draw() 27 | love.graphics.circle("fill", player.x, player.y, 100) 28 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kyle Schaub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /love2d/parrot/main.lua: -------------------------------------------------------------------------------- 1 | function love.load() 2 | sprites = { 3 | parrot = love.graphics.newImage("images/parrot.png"), 4 | background = love.graphics.newImage("images/background.png"), 5 | } 6 | 7 | player = { 8 | x = 400, 9 | y = 300, 10 | speed = 5, 11 | sprite = sprites.parrot, 12 | scale = 1.5 13 | } 14 | 15 | player.ox = player.sprite:getWidth() / 2 16 | player.oy = player.sprite:getHeight() / 2 17 | end 18 | 19 | function love.update(dt) 20 | if love.keyboard.isDown("right", "d") then 21 | player.x = player.x + player.speed 22 | end 23 | 24 | if love.keyboard.isDown("left", "a") then 25 | player.x = player.x - player.speed 26 | end 27 | 28 | if love.keyboard.isDown("down", "s") then 29 | player.y = player.y + player.speed 30 | end 31 | 32 | if love.keyboard.isDown("up", "w") then 33 | player.y = player.y - player.speed 34 | end 35 | end 36 | 37 | function love.draw() 38 | love.graphics.setColor(1, 1, 1, 1) 39 | love.graphics.draw(sprites.background, 0, 0) 40 | love.graphics.draw(player.sprite, player.x, player.y, nil, player.scale, nil, player.ox, player.oy) 41 | end -------------------------------------------------------------------------------- /love2d/blast/blast.lua: -------------------------------------------------------------------------------- 1 | blasts = {} 2 | 3 | function spawnBlast(x, y, size, color, time) 4 | blast = {} 5 | blast.x = x 6 | blast.y = y 7 | blast.color = color 8 | blast.radius = 1 9 | blast.max_radius = size 10 | blast.time = time 11 | blast.timer = 0.25 12 | blast.timer2 = time + 1 13 | blast.dead = false 14 | blast.alpha = 180 15 | blast.state = 0 16 | 17 | function blast:update(dt, i) 18 | self.timer2 = self.timer2 - dt 19 | if self.timer2 < 0 then 20 | self.dead = true 21 | end 22 | 23 | if self.state == 0 then 24 | local max = self.max_radius 25 | 26 | 27 | -- Tweens utilize Flux, an open-source Love2D library 28 | -- https://github.com/rxi/flux 29 | flux.to(self, self.time, { radius = max }) 30 | flux.to(self, self.time, { alpha = 0 }) 31 | 32 | self.state = 1 33 | end 34 | end 35 | table.insert(blasts, blast) 36 | end 37 | 38 | function blasts:update(dt) 39 | for i,w in ipairs(blasts) do 40 | w:update(dt, i) 41 | end 42 | 43 | local i = table.getn(blasts) 44 | while i > 0 do 45 | if blasts[i].dead == true then 46 | table.remove(blasts, i) 47 | end 48 | i = i - 1 49 | end 50 | end 51 | 52 | function blasts:draw() 53 | for _,e in ipairs(blasts) do 54 | love.graphics.setColor(200/255, 200/255, 200/255, e.alpha/255) 55 | if e.color == "dark" then 56 | love.graphics.setColor(0, 0, 0, e.alpha/255) 57 | end 58 | love.graphics.circle("fill", e.x, e.y, e.radius, math.ceil(e.radius/2)) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /love2d/shaders/main.lua: -------------------------------------------------------------------------------- 1 | function love.load() 2 | shaders = require("shaders") 3 | 4 | sprites = { 5 | parrot = love.graphics.newImage("images/parrot.png"), 6 | background = love.graphics.newImage("images/background.png"), 7 | } 8 | 9 | player = { 10 | x = 400, 11 | y = 300, 12 | speed = 5, 13 | sprite = sprites.parrot, 14 | scale = 1.5 15 | } 16 | 17 | player.ox = player.sprite:getWidth() / 2 18 | player.oy = player.sprite:getHeight() / 2 19 | end 20 | 21 | function love.update(dt) 22 | if love.keyboard.isDown("right", "d") then 23 | player.x = player.x + player.speed 24 | end 25 | 26 | if love.keyboard.isDown("left", "a") then 27 | player.x = player.x - player.speed 28 | end 29 | 30 | if love.keyboard.isDown("down", "s") then 31 | player.y = player.y + player.speed 32 | end 33 | 34 | if love.keyboard.isDown("up", "w") then 35 | player.y = player.y - player.speed 36 | end 37 | 38 | shaders.light:send("playerPosition", {player.x, player.y}) 39 | 40 | -- Set up multiple light sources 41 | local lights = { 42 | {player.x, player.y, 250}, -- Player light 43 | {100, 150, 150}, -- Static light 1 44 | {600, 400, 200}, -- Static light 2 45 | } 46 | 47 | shaders.multiLight:send("lightPositions", unpack(lights)) 48 | shaders.multiLight:send("numLights", #lights) 49 | end 50 | 51 | function love.draw() 52 | love.graphics.setColor(1, 1, 1, 1) 53 | love.graphics.draw(sprites.background, 0, 0) 54 | 55 | love.graphics.setShader(shaders.greyscale) 56 | love.graphics.draw(player.sprite, player.x, player.y, nil, player.scale, nil, player.ox, player.oy) 57 | love.graphics.setShader() 58 | 59 | love.graphics.setShader(shaders.multiLight) 60 | love.graphics.setColor(0, 0, 0, 0.75) 61 | love.graphics.rectangle("fill", 0, 0, 1920, 1080) 62 | love.graphics.setShader() 63 | end -------------------------------------------------------------------------------- /love2d/3_animations/main.lua: -------------------------------------------------------------------------------- 1 | function love.load() 2 | anim8 = require 'libraries/anim8' 3 | love.graphics.setDefaultFilter("nearest", "nearest") 4 | 5 | player = {} 6 | player.x = 400 7 | player.y = 200 8 | player.speed = 5 9 | player.sprite = love.graphics.newImage('sprites/parrot.png') 10 | player.spriteSheet = love.graphics.newImage('sprites/player-sheet.png') 11 | player.grid = anim8.newGrid( 12, 18, player.spriteSheet:getWidth(), player.spriteSheet:getHeight() ) 12 | 13 | player.animations = {} 14 | player.animations.down = anim8.newAnimation( player.grid('1-4', 1), 0.2 ) 15 | player.animations.left = anim8.newAnimation( player.grid('1-4', 2), 0.2 ) 16 | player.animations.right = anim8.newAnimation( player.grid('1-4', 3), 0.2 ) 17 | player.animations.up = anim8.newAnimation( player.grid('1-4', 4), 0.2 ) 18 | 19 | player.anim = player.animations.left 20 | 21 | background = love.graphics.newImage('sprites/background.png') 22 | end 23 | 24 | function love.update(dt) 25 | local isMoving = false 26 | 27 | if love.keyboard.isDown("right") then 28 | player.x = player.x + player.speed 29 | player.anim = player.animations.right 30 | isMoving = true 31 | end 32 | 33 | if love.keyboard.isDown("left") then 34 | player.x = player.x - player.speed 35 | player.anim = player.animations.left 36 | isMoving = true 37 | end 38 | 39 | if love.keyboard.isDown("down") then 40 | player.y = player.y + player.speed 41 | player.anim = player.animations.down 42 | isMoving = true 43 | end 44 | 45 | if love.keyboard.isDown("up") then 46 | player.y = player.y - player.speed 47 | player.anim = player.animations.up 48 | isMoving = true 49 | end 50 | 51 | if isMoving == false then 52 | player.anim:gotoFrame(2) 53 | end 54 | 55 | player.anim:update(dt) 56 | end 57 | 58 | function love.draw() 59 | love.graphics.draw(background, 0, 0) 60 | player.anim:draw(player.spriteSheet, player.x, player.y, nil, 10) 61 | end -------------------------------------------------------------------------------- /love2d/shaders/map/map.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 27 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 28 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 29 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 30 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 31 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 32 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 33 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 34 | 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 35 | 36 | 37 | 38 | 39 | 0,0,11,12,12,12,12,12,12,4,22,5,12,13,0,0, 40 | 0,1,15,12,12,4,22,22,22,23,0,21,5,14,3,0, 41 | 0,11,12,12,4,23,0,0,0,0,0,0,11,12,13,0, 42 | 2,15,12,12,13,0,0,0,0,0,0,1,15,12,14,2, 43 | 12,12,12,4,23,0,0,0,0,1,2,15,12,12,12,12, 44 | 12,12,12,13,0,0,0,0,0,11,12,12,12,12,12,12, 45 | 22,22,5,14,3,0,0,1,2,15,12,4,22,5,12,12, 46 | 0,0,21,5,14,2,2,15,12,12,4,23,0,21,22,5, 47 | 0,0,0,11,12,12,12,12,12,12,13,0,0,0,0,11 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /love2d/5_camera/libraries/sti/graphics.lua: -------------------------------------------------------------------------------- 1 | local lg = _G.love.graphics 2 | local graphics = { isCreated = lg and true or false } 3 | 4 | function graphics.newSpriteBatch(...) 5 | if graphics.isCreated then 6 | return lg.newSpriteBatch(...) 7 | end 8 | end 9 | 10 | function graphics.newCanvas(...) 11 | if graphics.isCreated then 12 | return lg.newCanvas(...) 13 | end 14 | end 15 | 16 | function graphics.newImage(...) 17 | if graphics.isCreated then 18 | return lg.newImage(...) 19 | end 20 | end 21 | 22 | function graphics.newQuad(...) 23 | if graphics.isCreated then 24 | return lg.newQuad(...) 25 | end 26 | end 27 | 28 | function graphics.getCanvas(...) 29 | if graphics.isCreated then 30 | return lg.getCanvas(...) 31 | end 32 | end 33 | 34 | function graphics.setCanvas(...) 35 | if graphics.isCreated then 36 | return lg.setCanvas(...) 37 | end 38 | end 39 | 40 | function graphics.clear(...) 41 | if graphics.isCreated then 42 | return lg.clear(...) 43 | end 44 | end 45 | 46 | function graphics.push(...) 47 | if graphics.isCreated then 48 | return lg.push(...) 49 | end 50 | end 51 | 52 | function graphics.origin(...) 53 | if graphics.isCreated then 54 | return lg.origin(...) 55 | end 56 | end 57 | 58 | function graphics.scale(...) 59 | if graphics.isCreated then 60 | return lg.scale(...) 61 | end 62 | end 63 | 64 | function graphics.translate(...) 65 | if graphics.isCreated then 66 | return lg.translate(...) 67 | end 68 | end 69 | 70 | function graphics.pop(...) 71 | if graphics.isCreated then 72 | return lg.pop(...) 73 | end 74 | end 75 | 76 | function graphics.draw(...) 77 | if graphics.isCreated then 78 | return lg.draw(...) 79 | end 80 | end 81 | 82 | function graphics.rectangle(...) 83 | if graphics.isCreated then 84 | return lg.rectangle(...) 85 | end 86 | end 87 | 88 | function graphics.getColor(...) 89 | if graphics.isCreated then 90 | return lg.getColor(...) 91 | end 92 | end 93 | 94 | function graphics.setColor(...) 95 | if graphics.isCreated then 96 | return lg.setColor(...) 97 | end 98 | end 99 | 100 | function graphics.line(...) 101 | if graphics.isCreated then 102 | return lg.line(...) 103 | end 104 | end 105 | 106 | function graphics.polygon(...) 107 | if graphics.isCreated then 108 | return lg.polygon(...) 109 | end 110 | end 111 | 112 | function graphics.points(...) 113 | if graphics.isCreated then 114 | return lg.points(...) 115 | end 116 | end 117 | 118 | function graphics.getWidth() 119 | if graphics.isCreated then 120 | return lg.getWidth() 121 | end 122 | return 0 123 | end 124 | 125 | function graphics.getHeight() 126 | if graphics.isCreated then 127 | return lg.getHeight() 128 | end 129 | return 0 130 | end 131 | 132 | return graphics 133 | -------------------------------------------------------------------------------- /love2d/shaders/shaders.lua: -------------------------------------------------------------------------------- 1 | local shaders = {} 2 | 3 | shaders.whiteout = love.graphics.newShader[[ 4 | vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { 5 | vec4 pixel = Texel(texture, texture_coords); 6 | 7 | if (screen_coords.x < 400.0) { 8 | return vec4(0, 0, 1, pixel.a); 9 | } 10 | 11 | return pixel * color; 12 | } 13 | ]] 14 | 15 | shaders.greyscale = love.graphics.newShader[[ 16 | vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { 17 | vec4 pixel = Texel(texture, texture_coords); 18 | 19 | // Calculate average of RGB channels 20 | float grey = (pixel.r + pixel.g + pixel.b) / 3.0; 21 | 22 | return vec4(grey, grey, grey, pixel.a) * color; 23 | } 24 | ]] 25 | 26 | shaders.sepia = love.graphics.newShader[[ 27 | vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { 28 | vec4 pixel = Texel(texture, texture_coords); 29 | 30 | // Apply sepia tone transformation matrix 31 | // These values create the warm, brownish vintage photo effect 32 | float r = (pixel.r * 0.393) + (pixel.g * 0.769) + (pixel.b * 0.189); 33 | float g = (pixel.r * 0.349) + (pixel.g * 0.686) + (pixel.b * 0.168); 34 | float b = (pixel.r * 0.272) + (pixel.g * 0.534) + (pixel.b * 0.131); 35 | 36 | // Clamp values to prevent overflow 37 | r = min(r, 1.0); 38 | g = min(g, 1.0); 39 | b = min(b, 1.0); 40 | 41 | return vec4(r, g, b, pixel.a) * color; 42 | } 43 | ]] 44 | 45 | shaders.light = love.graphics.newShader[[ 46 | extern vec2 playerPosition; 47 | 48 | vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { 49 | vec4 pixel = Texel(texture, texture_coords); 50 | 51 | float distance = length(screen_coords - playerPosition); 52 | float fade = clamp(distance / 250, 0.0, 1.0); 53 | 54 | pixel.a = pixel.a * fade; 55 | 56 | return pixel * color; 57 | } 58 | ]] 59 | 60 | shaders.multiLight = love.graphics.newShader[[ 61 | extern vec3 lightPositions[16]; // x, y, radius 62 | extern int numLights; // total number of lights 63 | 64 | vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { 65 | vec4 pixel = Texel(texture, texture_coords); 66 | 67 | float totalLight = 0.0; 68 | 69 | for (int i = 0; i < numLights; i++) { 70 | vec2 lightPos = lightPositions[i].xy; 71 | float radius = lightPositions[i].z; 72 | 73 | float distance = length(screen_coords - lightPos); 74 | float fade = clamp(distance / radius, 0.0, 1.0); 75 | 76 | // Invert fade so light is 1.0 at center, 0.0 at edge 77 | float lightAmount = 1.0 - fade; 78 | totalLight += lightAmount; 79 | } 80 | 81 | // Clamp total light and use it to reduce darkness 82 | totalLight = clamp(totalLight, 0.0, 1.0); 83 | pixel.a = pixel.a * (1.0 - totalLight); 84 | 85 | return pixel * color; 86 | } 87 | ]] 88 | 89 | return shaders -------------------------------------------------------------------------------- /love2d/5_camera/main.lua: -------------------------------------------------------------------------------- 1 | function love.load() 2 | camera = require 'libraries/camera' 3 | cam = camera() 4 | 5 | anim8 = require 'libraries/anim8' 6 | love.graphics.setDefaultFilter("nearest", "nearest") 7 | 8 | sti = require 'libraries/sti' 9 | gameMap = sti('maps/testMap.lua') 10 | 11 | player = {} 12 | player.x = 400 13 | player.y = 200 14 | player.speed = 5 15 | player.spriteSheet = love.graphics.newImage('sprites/player-sheet.png') 16 | player.grid = anim8.newGrid( 12, 18, player.spriteSheet:getWidth(), player.spriteSheet:getHeight() ) 17 | 18 | player.animations = {} 19 | player.animations.down = anim8.newAnimation( player.grid('1-4', 1), 0.2 ) 20 | player.animations.left = anim8.newAnimation( player.grid('1-4', 2), 0.2 ) 21 | player.animations.right = anim8.newAnimation( player.grid('1-4', 3), 0.2 ) 22 | player.animations.up = anim8.newAnimation( player.grid('1-4', 4), 0.2 ) 23 | 24 | player.anim = player.animations.left 25 | 26 | background = love.graphics.newImage('sprites/background.png') 27 | end 28 | 29 | function love.update(dt) 30 | local isMoving = false 31 | 32 | if love.keyboard.isDown("right") then 33 | player.x = player.x + player.speed 34 | player.anim = player.animations.right 35 | isMoving = true 36 | end 37 | 38 | if love.keyboard.isDown("left") then 39 | player.x = player.x - player.speed 40 | player.anim = player.animations.left 41 | isMoving = true 42 | end 43 | 44 | if love.keyboard.isDown("down") then 45 | player.y = player.y + player.speed 46 | player.anim = player.animations.down 47 | isMoving = true 48 | end 49 | 50 | if love.keyboard.isDown("up") then 51 | player.y = player.y - player.speed 52 | player.anim = player.animations.up 53 | isMoving = true 54 | end 55 | 56 | if isMoving == false then 57 | player.anim:gotoFrame(2) 58 | end 59 | 60 | player.anim:update(dt) 61 | 62 | -- Update camera position 63 | cam:lookAt(player.x, player.y) 64 | 65 | -- This section prevents the camera from viewing outside the background 66 | -- First, get width/height of the game window 67 | local w = love.graphics.getWidth() 68 | local h = love.graphics.getHeight() 69 | 70 | -- Left border 71 | if cam.x < w/2 then 72 | cam.x = w/2 73 | end 74 | 75 | -- Right border 76 | if cam.y < h/2 then 77 | cam.y = h/2 78 | end 79 | 80 | -- Get width/height of background 81 | local mapW = gameMap.width * gameMap.tilewidth 82 | local mapH = gameMap.height * gameMap.tileheight 83 | 84 | -- Right border 85 | if cam.x > (mapW - w/2) then 86 | cam.x = (mapW - w/2) 87 | end 88 | -- Bottom border 89 | if cam.y > (mapH - h/2) then 90 | cam.y = (mapH - h/2) 91 | end 92 | end 93 | 94 | function love.draw() 95 | cam:attach() 96 | gameMap:drawLayer(gameMap.layers["Ground"]) 97 | gameMap:drawLayer(gameMap.layers["Trees"]) 98 | player.anim:draw(player.spriteSheet, player.x, player.y, nil, 6, nil, 6, 9) 99 | cam:detach() 100 | end -------------------------------------------------------------------------------- /love2d/5_camera/maps/testMap.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, 9 | 11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, 10 | 11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, 11 | 11,11,11,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, 12 | 11,11,11,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, 13 | 11,11,11,14,14,11,11,11,11,11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, 14 | 11,11,11,14,14,11,11,11,11,11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, 15 | 11,11,11,14,14,11,11,11,11,11,11,11,14,14,11,11,43,43,43,43,43,43,43,43,43,43,43,43,43,43, 16 | 11,11,11,14,14,11,37,38,38,38,39,11,14,14,11,11,43,43,43,43,43,43,43,43,43,43,43,43,43,43, 17 | 11,11,11,14,14,11,46,47,47,47,48,11,14,14,11,11,43,43,43,43,43,43,43,43,43,43,43,43,43,43, 18 | 11,11,11,14,14,11,11,11,11,11,11,11,14,14,11,11,43,43,43,43,43,43,43,43,43,43,43,43,43,43, 19 | 11,11,11,14,14,11,11,11,11,11,11,11,14,14,11,11,43,43,43,43,43,43,43,43,43,43,43,43,43,43, 20 | 11,11,11,14,14,11,14,14,14,14,14,11,14,14,11,11,43,43,43,43,43,43,43,43,43,43,43,43,43,43, 21 | 11,11,11,14,14,11,14,14,14,14,14,11,14,14,11,11,43,43,43,43,43,43,43,43,43,43,43,43,43,43, 22 | 11,11,11,14,14,11,14,14,14,14,14,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, 23 | 11,11,11,14,14,11,11,11,11,11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, 24 | 11,11,11,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, 25 | 11,11,11,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, 26 | 11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11, 27 | 11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11, 28 | 11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11, 29 | 11,11,11,14,14,11,37,38,38,38,39,11,11,11,11,11,11,37,38,38,38,39,11,11,14,14,11,11,11,11, 30 | 11,11,11,14,14,11,46,47,47,47,48,11,11,11,11,11,11,46,47,47,47,48,11,11,14,14,11,11,11,11, 31 | 11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11, 32 | 11,11,11,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,11,11,11,11, 33 | 11,11,11,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,11,11,11,11, 34 | 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11, 35 | 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11, 36 | 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11, 37 | 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11 38 | 39 | 40 | 41 | 42 | 43,43,44,0,0,0,37,38,38,38,39,0,0,0,0,42,43,43,43,43,43,43,43,43,43,43,43,43,43,43, 43 | 43,43,44,0,0,0,46,47,47,47,48,0,0,0,0,51,52,52,52,52,52,52,52,52,52,52,52,52,52,52, 44 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 45 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 46 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 47 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 48 | 43,43,44,0,0,0,7,8,8,8,9,0,0,0,0,33,34,34,34,34,34,34,34,34,34,34,34,34,34,34, 49 | 43,43,44,0,0,0,25,26,26,26,27,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 50 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 51 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 52 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 53 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 54 | 43,43,44,0,0,0,0,31,0,31,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 55 | 43,43,44,0,0,0,0,49,0,49,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 56 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,51,52,52,52,52,52,52,52,52,52,52,52,52,52,52, 57 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 58 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 59 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 60 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 61 | 43,43,44,0,0,0,7,8,8,8,9,0,0,0,0,0,0,7,8,8,8,9,0,0,0,0,0,0,0,0, 62 | 43,43,44,0,0,0,25,26,26,26,27,0,0,0,0,0,0,25,26,26,26,27,0,0,0,0,0,0,0,0, 63 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 64 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 65 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 66 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 67 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 68 | 43,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 69 | 43,43,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,35,0,0,33,34,34,34, 70 | 43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,44,0,0,42,43,43,43, 71 | 43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,44,0,0,42,43,43,43 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /love2d/5_camera/libraries/sti/utils.lua: -------------------------------------------------------------------------------- 1 | -- Some utility functions that shouldn't be exposed. 2 | local utils = {} 3 | 4 | -- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286 5 | function utils.format_path(path) 6 | local np_gen1,np_gen2 = '[^SEP]+SEP%.%.SEP?','SEP+%.?SEP' 7 | local np_pat1, np_pat2 = np_gen1:gsub('SEP','/'), np_gen2:gsub('SEP','/') 8 | local k 9 | 10 | repeat -- /./ -> / 11 | path,k = path:gsub(np_pat2,'/',1) 12 | until k == 0 13 | 14 | repeat -- A/../ -> (empty) 15 | path,k = path:gsub(np_pat1,'',1) 16 | until k == 0 17 | 18 | if path == '' then path = '.' end 19 | 20 | return path 21 | end 22 | 23 | -- Compensation for scale/rotation shift 24 | function utils.compensate(tile, tileX, tileY, tileW, tileH) 25 | local compx = 0 26 | local compy = 0 27 | 28 | if tile.sx < 0 then compx = tileW end 29 | if tile.sy < 0 then compy = tileH end 30 | 31 | if tile.r > 0 then 32 | tileX = tileX + tileH - compy 33 | tileY = tileY + tileH + compx - tileW 34 | elseif tile.r < 0 then 35 | tileX = tileX + compy 36 | tileY = tileY - compx + tileH 37 | else 38 | tileX = tileX + compx 39 | tileY = tileY + compy 40 | end 41 | 42 | return tileX, tileY 43 | end 44 | 45 | -- Cache images in main STI module 46 | function utils.cache_image(sti, path, image) 47 | image = image or love.graphics.newImage(path) 48 | image:setFilter("nearest", "nearest") 49 | sti.cache[path] = image 50 | end 51 | 52 | -- We just don't know. 53 | function utils.get_tiles(imageW, tileW, margin, spacing) 54 | imageW = imageW - margin 55 | local n = 0 56 | 57 | while imageW >= tileW do 58 | imageW = imageW - tileW 59 | if n ~= 0 then imageW = imageW - spacing end 60 | if imageW >= 0 then n = n + 1 end 61 | end 62 | 63 | return n 64 | end 65 | 66 | -- Decompress tile layer data 67 | function utils.get_decompressed_data(data) 68 | local ffi = require "ffi" 69 | local d = {} 70 | local decoded = ffi.cast("uint32_t*", data) 71 | 72 | for i = 0, data:len() / ffi.sizeof("uint32_t") do 73 | table.insert(d, tonumber(decoded[i])) 74 | end 75 | 76 | return d 77 | end 78 | 79 | -- Convert a Tiled ellipse object to a LOVE polygon 80 | function utils.convert_ellipse_to_polygon(x, y, w, h, max_segments) 81 | local ceil = math.ceil 82 | local cos = math.cos 83 | local sin = math.sin 84 | 85 | local function calc_segments(segments) 86 | local function vdist(a, b) 87 | local c = { 88 | x = a.x - b.x, 89 | y = a.y - b.y, 90 | } 91 | 92 | return c.x * c.x + c.y * c.y 93 | end 94 | 95 | segments = segments or 64 96 | local vertices = {} 97 | 98 | local v = { 1, 2, ceil(segments/4-1), ceil(segments/4) } 99 | 100 | local m 101 | if love and love.physics then 102 | m = love.physics.getMeter() 103 | else 104 | m = 32 105 | end 106 | 107 | for _, i in ipairs(v) do 108 | local angle = (i / segments) * math.pi * 2 109 | local px = x + w / 2 + cos(angle) * w / 2 110 | local py = y + h / 2 + sin(angle) * h / 2 111 | 112 | table.insert(vertices, { x = px / m, y = py / m }) 113 | end 114 | 115 | local dist1 = vdist(vertices[1], vertices[2]) 116 | local dist2 = vdist(vertices[3], vertices[4]) 117 | 118 | -- Box2D threshold 119 | if dist1 < 0.0025 or dist2 < 0.0025 then 120 | return calc_segments(segments-2) 121 | end 122 | 123 | return segments 124 | end 125 | 126 | local segments = calc_segments(max_segments) 127 | local vertices = {} 128 | 129 | table.insert(vertices, { x = x + w / 2, y = y + h / 2 }) 130 | 131 | for i = 0, segments do 132 | local angle = (i / segments) * math.pi * 2 133 | local px = x + w / 2 + cos(angle) * w / 2 134 | local py = y + h / 2 + sin(angle) * h / 2 135 | 136 | table.insert(vertices, { x = px, y = py }) 137 | end 138 | 139 | return vertices 140 | end 141 | 142 | function utils.rotate_vertex(map, vertex, x, y, cos, sin, oy) 143 | if map.orientation == "isometric" then 144 | x, y = utils.convert_isometric_to_screen(map, x, y) 145 | vertex.x, vertex.y = utils.convert_isometric_to_screen(map, vertex.x, vertex.y) 146 | end 147 | 148 | vertex.x = vertex.x - x 149 | vertex.y = vertex.y - y 150 | 151 | return 152 | x + cos * vertex.x - sin * vertex.y, 153 | y + sin * vertex.x + cos * vertex.y - (oy or 0) 154 | end 155 | 156 | --- Project isometric position to cartesian position 157 | function utils.convert_isometric_to_screen(map, x, y) 158 | local mapW = map.width 159 | local tileW = map.tilewidth 160 | local tileH = map.tileheight 161 | local tileX = x / tileH 162 | local tileY = y / tileH 163 | local offsetX = mapW * tileW / 2 164 | 165 | return 166 | (tileX - tileY) * tileW / 2 + offsetX, 167 | (tileX + tileY) * tileH / 2 168 | end 169 | 170 | function utils.hex_to_color(hex) 171 | if hex:sub(1, 1) == "#" then 172 | hex = hex:sub(2) 173 | end 174 | 175 | return { 176 | r = tonumber(hex:sub(1, 2), 16) / 255, 177 | g = tonumber(hex:sub(3, 4), 16) / 255, 178 | b = tonumber(hex:sub(5, 6), 16) / 255 179 | } 180 | end 181 | 182 | function utils.pixel_function(_, _, r, g, b, a) 183 | local mask = utils._TC 184 | 185 | if r == mask.r and 186 | g == mask.g and 187 | b == mask.b then 188 | return r, g, b, 0 189 | end 190 | 191 | return r, g, b, a 192 | end 193 | 194 | function utils.fix_transparent_color(tileset, path) 195 | local image_data = love.image.newImageData(path) 196 | tileset.image = love.graphics.newImage(image_data) 197 | 198 | if tileset.transparentcolor then 199 | utils._TC = utils.hex_to_color(tileset.transparentcolor) 200 | 201 | image_data:mapPixel(utils.pixel_function) 202 | tileset.image = love.graphics.newImage(image_data) 203 | end 204 | end 205 | 206 | function utils.deepCopy(t) 207 | local copy = {} 208 | for k,v in pairs(t) do 209 | if type(v) == "table" then 210 | v = utils.deepCopy(v) 211 | end 212 | copy[k] = v 213 | end 214 | return copy 215 | end 216 | 217 | return utils 218 | -------------------------------------------------------------------------------- /love2d/5_camera/libraries/sti/plugins/bump.lua: -------------------------------------------------------------------------------- 1 | --- Bump.lua plugin for STI 2 | -- @module bump.lua 3 | -- @author David Serrano (BobbyJones|FrenchFryLord) 4 | -- @copyright 2019 5 | -- @license MIT/X11 6 | 7 | local lg = require((...):gsub('plugins.bump', 'graphics')) 8 | 9 | return { 10 | bump_LICENSE = "MIT/X11", 11 | bump_URL = "https://github.com/karai17/Simple-Tiled-Implementation", 12 | bump_VERSION = "3.1.7.1", 13 | bump_DESCRIPTION = "Bump hooks for STI.", 14 | 15 | --- Adds each collidable tile to the Bump world. 16 | -- @param world The Bump world to add objects to. 17 | -- @return collidables table containing the handles to the objects in the Bump world. 18 | bump_init = function(map, world) 19 | local collidables = {} 20 | 21 | for _, tileset in ipairs(map.tilesets) do 22 | for _, tile in ipairs(tileset.tiles) do 23 | local gid = tileset.firstgid + tile.id 24 | 25 | if map.tileInstances[gid] then 26 | for _, instance in ipairs(map.tileInstances[gid]) do 27 | -- Every object in every instance of a tile 28 | if tile.objectGroup then 29 | for _, object in ipairs(tile.objectGroup.objects) do 30 | if object.properties.collidable == true then 31 | local t = { 32 | name = object.name, 33 | type = object.type, 34 | x = instance.x + map.offsetx + object.x, 35 | y = instance.y + map.offsety + object.y, 36 | width = object.width, 37 | height = object.height, 38 | layer = instance.layer, 39 | properties = object.properties 40 | 41 | } 42 | 43 | world:add(t, t.x, t.y, t.width, t.height) 44 | table.insert(collidables, t) 45 | end 46 | end 47 | end 48 | 49 | -- Every instance of a tile 50 | if tile.properties and tile.properties.collidable == true then 51 | local t = { 52 | x = instance.x + map.offsetx, 53 | y = instance.y + map.offsety, 54 | width = map.tilewidth, 55 | height = map.tileheight, 56 | layer = instance.layer, 57 | type = tile.type, 58 | properties = tile.properties 59 | } 60 | 61 | world:add(t, t.x, t.y, t.width, t.height) 62 | table.insert(collidables, t) 63 | end 64 | end 65 | end 66 | end 67 | end 68 | 69 | for _, layer in ipairs(map.layers) do 70 | -- Entire layer 71 | if layer.properties.collidable == true then 72 | if layer.type == "tilelayer" then 73 | for y, tiles in ipairs(layer.data) do 74 | for x, tile in pairs(tiles) do 75 | 76 | if tile.objectGroup then 77 | for _, object in ipairs(tile.objectGroup.objects) do 78 | if object.properties.collidable == true then 79 | local t = { 80 | name = object.name, 81 | type = object.type, 82 | x = ((x-1) * map.tilewidth + tile.offset.x + map.offsetx) + object.x, 83 | y = ((y-1) * map.tileheight + tile.offset.y + map.offsety) + object.y, 84 | width = object.width, 85 | height = object.height, 86 | layer = layer, 87 | properties = object.properties 88 | } 89 | 90 | world:add(t, t.x, t.y, t.width, t.height) 91 | table.insert(collidables, t) 92 | end 93 | end 94 | end 95 | 96 | 97 | local t = { 98 | x = (x-1) * map.tilewidth + tile.offset.x + map.offsetx, 99 | y = (y-1) * map.tileheight + tile.offset.y + map.offsety, 100 | width = tile.width, 101 | height = tile.height, 102 | layer = layer, 103 | type = tile.type, 104 | properties = tile.properties 105 | } 106 | 107 | world:add(t, t.x, t.y, t.width, t.height) 108 | table.insert(collidables, t) 109 | end 110 | end 111 | elseif layer.type == "imagelayer" then 112 | world:add(layer, layer.x, layer.y, layer.width, layer.height) 113 | table.insert(collidables, layer) 114 | end 115 | end 116 | 117 | -- individual collidable objects in a layer that is not "collidable" 118 | -- or whole collidable objects layer 119 | if layer.type == "objectgroup" then 120 | for _, obj in ipairs(layer.objects) do 121 | if layer.properties.collidable == true or obj.properties.collidable == true then 122 | if obj.shape == "rectangle" then 123 | local t = { 124 | name = obj.name, 125 | type = obj.type, 126 | x = obj.x + map.offsetx, 127 | y = obj.y + map.offsety, 128 | width = obj.width, 129 | height = obj.height, 130 | layer = layer, 131 | properties = obj.properties 132 | } 133 | 134 | if obj.gid then 135 | t.y = t.y - obj.height 136 | end 137 | 138 | world:add(t, t.x, t.y, t.width, t.height) 139 | table.insert(collidables, t) 140 | end -- TODO implement other object shapes? 141 | end 142 | end 143 | end 144 | end 145 | 146 | map.bump_world = world 147 | map.bump_collidables = collidables 148 | end, 149 | 150 | --- Remove layer 151 | -- @param index to layer to be removed 152 | bump_removeLayer = function(map, index) 153 | local layer = assert(map.layers[index], "Layer not found: " .. index) 154 | local collidables = map.bump_collidables 155 | 156 | -- Remove collision objects 157 | for i = #collidables, 1, -1 do 158 | local obj = collidables[i] 159 | 160 | if obj.layer == layer 161 | and ( 162 | layer.properties.collidable == true 163 | or obj.properties.collidable == true 164 | ) then 165 | map.bump_world:remove(obj) 166 | table.remove(collidables, i) 167 | end 168 | end 169 | end, 170 | 171 | --- Draw bump collisions world. 172 | -- @param world bump world holding the tiles geometry 173 | -- @param tx Translate on X 174 | -- @param ty Translate on Y 175 | -- @param sx Scale on X 176 | -- @param sy Scale on Y 177 | bump_draw = function(map, tx, ty, sx, sy) 178 | lg.push() 179 | lg.scale(sx or 1, sy or sx or 1) 180 | lg.translate(math.floor(tx or 0), math.floor(ty or 0)) 181 | 182 | local items = map.bump_world:getItems() 183 | for _, item in ipairs(items) do 184 | lg.rectangle("line", map.bump_world:getRect(item)) 185 | end 186 | 187 | lg.pop() 188 | end 189 | } 190 | 191 | --- Custom Properties in Tiled are used to tell this plugin what to do. 192 | -- @table Properties 193 | -- @field collidable set to true, can be used on any Layer, Tile, or Object 194 | -------------------------------------------------------------------------------- /love2d/5_camera/libraries/camera.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2015 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local _PATH = (...):match('^(.*[%./])[^%.%/]+$') or '' 28 | local cos, sin = math.cos, math.sin 29 | 30 | local camera = {} 31 | camera.__index = camera 32 | 33 | -- Movement interpolators (for camera locking/windowing) 34 | camera.smooth = {} 35 | 36 | function camera.smooth.none() 37 | return function(dx,dy) return dx,dy end 38 | end 39 | 40 | function camera.smooth.linear(speed) 41 | assert(type(speed) == "number", "Invalid parameter: speed = "..tostring(speed)) 42 | return function(dx,dy, s) 43 | -- normalize direction 44 | local d = math.sqrt(dx*dx+dy*dy) 45 | local dts = math.min((s or speed) * love.timer.getDelta(), d) -- prevent overshooting the goal 46 | if d > 0 then 47 | dx,dy = dx/d, dy/d 48 | end 49 | 50 | return dx*dts, dy*dts 51 | end 52 | end 53 | 54 | function camera.smooth.damped(stiffness) 55 | assert(type(stiffness) == "number", "Invalid parameter: stiffness = "..tostring(stiffness)) 56 | return function(dx,dy, s) 57 | local dts = love.timer.getDelta() * (s or stiffness) 58 | return dx*dts, dy*dts 59 | end 60 | end 61 | 62 | 63 | local function new(x,y, zoom, rot, smoother) 64 | x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2 65 | zoom = zoom or 1 66 | rot = rot or 0 67 | smoother = smoother or camera.smooth.none() -- for locking, see below 68 | return setmetatable({x = x, y = y, scale = zoom, rot = rot, smoother = smoother}, camera) 69 | end 70 | 71 | function camera:lookAt(x,y) 72 | self.x, self.y = x, y 73 | return self 74 | end 75 | 76 | function camera:move(dx,dy) 77 | self.x, self.y = self.x + dx, self.y + dy 78 | return self 79 | end 80 | 81 | function camera:position() 82 | return self.x, self.y 83 | end 84 | 85 | function camera:rotate(phi) 86 | self.rot = self.rot + phi 87 | return self 88 | end 89 | 90 | function camera:rotateTo(phi) 91 | self.rot = phi 92 | return self 93 | end 94 | 95 | function camera:zoom(mul) 96 | self.scale = self.scale * mul 97 | return self 98 | end 99 | 100 | function camera:zoomTo(zoom) 101 | self.scale = zoom 102 | return self 103 | end 104 | 105 | function camera:attach(x,y,w,h, noclip) 106 | x,y = x or 0, y or 0 107 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 108 | 109 | self._sx,self._sy,self._sw,self._sh = love.graphics.getScissor() 110 | if not noclip then 111 | love.graphics.setScissor(x,y,w,h) 112 | end 113 | 114 | local cx,cy = x+w/2, y+h/2 115 | love.graphics.push() 116 | love.graphics.translate(cx, cy) 117 | love.graphics.scale(self.scale) 118 | love.graphics.rotate(self.rot) 119 | love.graphics.translate(-self.x, -self.y) 120 | end 121 | 122 | function camera:detach() 123 | love.graphics.pop() 124 | love.graphics.setScissor(self._sx,self._sy,self._sw,self._sh) 125 | end 126 | 127 | function camera:draw(...) 128 | local x,y,w,h,noclip,func 129 | local nargs = select("#", ...) 130 | if nargs == 1 then 131 | func = ... 132 | elseif nargs == 5 then 133 | x,y,w,h,func = ... 134 | elseif nargs == 6 then 135 | x,y,w,h,noclip,func = ... 136 | else 137 | error("Invalid arguments to camera:draw()") 138 | end 139 | 140 | self:attach(x,y,w,h,noclip) 141 | func() 142 | self:detach() 143 | end 144 | 145 | -- world coordinates to camera coordinates 146 | function camera:cameraCoords(x,y, ox,oy,w,h) 147 | ox, oy = ox or 0, oy or 0 148 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 149 | 150 | -- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center 151 | local c,s = cos(self.rot), sin(self.rot) 152 | x,y = x - self.x, y - self.y 153 | x,y = c*x - s*y, s*x + c*y 154 | return x*self.scale + w/2 + ox, y*self.scale + h/2 + oy 155 | end 156 | 157 | -- camera coordinates to world coordinates 158 | function camera:worldCoords(x,y, ox,oy,w,h) 159 | ox, oy = ox or 0, oy or 0 160 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 161 | 162 | -- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y) 163 | local c,s = cos(-self.rot), sin(-self.rot) 164 | x,y = (x - w/2 - ox) / self.scale, (y - h/2 - oy) / self.scale 165 | x,y = c*x - s*y, s*x + c*y 166 | return x+self.x, y+self.y 167 | end 168 | 169 | function camera:mousePosition(ox,oy,w,h) 170 | local mx,my = love.mouse.getPosition() 171 | return self:worldCoords(mx,my, ox,oy,w,h) 172 | end 173 | 174 | -- camera scrolling utilities 175 | function camera:lockX(x, smoother, ...) 176 | local dx, dy = (smoother or self.smoother)(x - self.x, self.y, ...) 177 | self.x = self.x + dx 178 | return self 179 | end 180 | 181 | function camera:lockY(y, smoother, ...) 182 | local dx, dy = (smoother or self.smoother)(self.x, y - self.y, ...) 183 | self.y = self.y + dy 184 | return self 185 | end 186 | 187 | function camera:lockPosition(x,y, smoother, ...) 188 | return self:move((smoother or self.smoother)(x - self.x, y - self.y, ...)) 189 | end 190 | 191 | function camera:lockWindow(x, y, x_min, x_max, y_min, y_max, smoother, ...) 192 | -- figure out displacement in camera coordinates 193 | x,y = self:cameraCoords(x,y) 194 | local dx, dy = 0,0 195 | if x < x_min then 196 | dx = x - x_min 197 | elseif x > x_max then 198 | dx = x - x_max 199 | end 200 | if y < y_min then 201 | dy = y - y_min 202 | elseif y > y_max then 203 | dy = y - y_max 204 | end 205 | 206 | -- transform displacement to movement in world coordinates 207 | local c,s = cos(-self.rot), sin(-self.rot) 208 | dx,dy = (c*dx - s*dy) / self.scale, (s*dx + c*dy) / self.scale 209 | 210 | -- move 211 | self:move((smoother or self.smoother)(dx,dy,...)) 212 | end 213 | 214 | -- the module 215 | return setmetatable({new = new, smooth = camera.smooth}, 216 | {__call = function(_, ...) return new(...) end}) 217 | -------------------------------------------------------------------------------- /love2d/5_camera/maps/testMap.lua: -------------------------------------------------------------------------------- 1 | return { 2 | version = "1.5", 3 | luaversion = "5.1", 4 | tiledversion = "1.5.0", 5 | orientation = "orthogonal", 6 | renderorder = "right-down", 7 | width = 30, 8 | height = 30, 9 | tilewidth = 64, 10 | tileheight = 64, 11 | nextlayerid = 3, 12 | nextobjectid = 1, 13 | properties = {}, 14 | tilesets = { 15 | { 16 | name = "test-tiles", 17 | firstgid = 1, 18 | tilewidth = 64, 19 | tileheight = 64, 20 | spacing = 0, 21 | margin = 0, 22 | columns = 9, 23 | image = "tileset.png", 24 | imagewidth = 576, 25 | imageheight = 384, 26 | objectalignment = "unspecified", 27 | tileoffset = { 28 | x = 0, 29 | y = 0 30 | }, 31 | grid = { 32 | orientation = "orthogonal", 33 | width = 64, 34 | height = 64 35 | }, 36 | properties = {}, 37 | wangsets = {}, 38 | tilecount = 54, 39 | tiles = {} 40 | } 41 | }, 42 | layers = { 43 | { 44 | type = "tilelayer", 45 | x = 0, 46 | y = 0, 47 | width = 30, 48 | height = 30, 49 | id = 1, 50 | name = "Ground", 51 | visible = true, 52 | opacity = 1, 53 | offsetx = 0, 54 | offsety = 0, 55 | parallaxx = 1, 56 | parallaxy = 1, 57 | properties = {}, 58 | encoding = "lua", 59 | data = { 60 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 61 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 62 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 63 | 11, 11, 11, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 64 | 11, 11, 11, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 65 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 66 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 67 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 68 | 11, 11, 11, 14, 14, 11, 37, 38, 38, 38, 39, 11, 14, 14, 11, 11, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 69 | 11, 11, 11, 14, 14, 11, 46, 47, 47, 47, 48, 11, 14, 14, 11, 11, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 70 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 71 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 72 | 11, 11, 11, 14, 14, 11, 14, 14, 14, 14, 14, 11, 14, 14, 11, 11, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 73 | 11, 11, 11, 14, 14, 11, 14, 14, 14, 14, 14, 11, 14, 14, 11, 11, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 74 | 11, 11, 11, 14, 14, 11, 14, 14, 14, 14, 14, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 75 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 76 | 11, 11, 11, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 77 | 11, 11, 11, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 78 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 79 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 80 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 81 | 11, 11, 11, 14, 14, 11, 37, 38, 38, 38, 39, 11, 11, 11, 11, 11, 11, 37, 38, 38, 38, 39, 11, 11, 14, 14, 11, 11, 11, 11, 82 | 11, 11, 11, 14, 14, 11, 46, 47, 47, 47, 48, 11, 11, 11, 11, 11, 11, 46, 47, 47, 47, 48, 11, 11, 14, 14, 11, 11, 11, 11, 83 | 11, 11, 11, 14, 14, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 84 | 11, 11, 11, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 11, 11, 11, 11, 85 | 11, 11, 11, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 11, 11, 11, 11, 86 | 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 87 | 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 88 | 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11, 89 | 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 11, 11, 11, 11 90 | } 91 | }, 92 | { 93 | type = "tilelayer", 94 | x = 0, 95 | y = 0, 96 | width = 30, 97 | height = 30, 98 | id = 2, 99 | name = "Trees", 100 | visible = true, 101 | opacity = 1, 102 | offsetx = 0, 103 | offsety = 0, 104 | parallaxx = 1, 105 | parallaxy = 1, 106 | properties = {}, 107 | encoding = "lua", 108 | data = { 109 | 43, 43, 44, 0, 0, 0, 37, 38, 38, 38, 39, 0, 0, 0, 0, 42, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 110 | 43, 43, 44, 0, 0, 0, 46, 47, 47, 47, 48, 0, 0, 0, 0, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 111 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115 | 43, 43, 44, 0, 0, 0, 7, 8, 8, 8, 9, 0, 0, 0, 0, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 116 | 43, 43, 44, 0, 0, 0, 25, 26, 26, 26, 27, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 118 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121 | 43, 43, 44, 0, 0, 0, 0, 31, 0, 31, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122 | 43, 43, 44, 0, 0, 0, 0, 49, 0, 49, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 124 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128 | 43, 43, 44, 0, 0, 0, 7, 8, 8, 8, 9, 0, 0, 0, 0, 0, 0, 7, 8, 8, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 129 | 43, 43, 44, 0, 0, 0, 25, 26, 26, 26, 27, 0, 0, 0, 0, 0, 0, 25, 26, 26, 26, 27, 0, 0, 0, 0, 0, 0, 0, 0, 130 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135 | 43, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136 | 43, 43, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 0, 0, 33, 34, 34, 34, 137 | 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 44, 0, 0, 42, 43, 43, 43, 138 | 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 44, 0, 0, 42, 43, 43, 43 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /love2d/3_animations/libraries/anim8.lua: -------------------------------------------------------------------------------- 1 | local anim8 = { 2 | _VERSION = 'anim8 v2.3.1', 3 | _DESCRIPTION = 'An animation library for LÖVE', 4 | _URL = 'https://github.com/kikito/anim8', 5 | _LICENSE = [[ 6 | MIT LICENSE 7 | 8 | Copyright (c) 2011 Enrique García Cota 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a 11 | copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included 19 | in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | ]] 29 | } 30 | 31 | local Grid = {} 32 | 33 | local _frames = {} 34 | 35 | local function assertPositiveInteger(value, name) 36 | if type(value) ~= 'number' then error(("%s should be a number, was %q"):format(name, tostring(value))) end 37 | if value < 1 then error(("%s should be a positive number, was %d"):format(name, value)) end 38 | if value ~= math.floor(value) then error(("%s should be an integer, was %d"):format(name, value)) end 39 | end 40 | 41 | local function createFrame(self, x, y) 42 | local fw, fh = self.frameWidth, self.frameHeight 43 | return love.graphics.newQuad( 44 | self.left + (x-1) * fw + x * self.border, 45 | self.top + (y-1) * fh + y * self.border, 46 | fw, 47 | fh, 48 | self.imageWidth, 49 | self.imageHeight 50 | ) 51 | end 52 | 53 | local function getGridKey(...) 54 | return table.concat( {...} ,'-' ) 55 | end 56 | 57 | local function getOrCreateFrame(self, x, y) 58 | if x < 1 or x > self.width or y < 1 or y > self.height then 59 | error(("There is no frame for x=%d, y=%d"):format(x, y)) 60 | end 61 | local key = self._key 62 | _frames[key] = _frames[key] or {} 63 | _frames[key][x] = _frames[key][x] or {} 64 | _frames[key][x][y] = _frames[key][x][y] or createFrame(self, x, y) 65 | return _frames[key][x][y] 66 | end 67 | 68 | local function parseInterval(str) 69 | if type(str) == "number" then return str,str,1 end 70 | str = str:gsub('%s', '') -- remove spaces 71 | local min, max = str:match("^(%d+)-(%d+)$") 72 | assert(min and max, ("Could not parse interval from %q"):format(str)) 73 | min, max = tonumber(min), tonumber(max) 74 | local step = min <= max and 1 or -1 75 | return min, max, step 76 | end 77 | 78 | function Grid:getFrames(...) 79 | local result, args = {}, {...} 80 | local minx, maxx, stepx, miny, maxy, stepy 81 | 82 | for i=1, #args, 2 do 83 | minx, maxx, stepx = parseInterval(args[i]) 84 | miny, maxy, stepy = parseInterval(args[i+1]) 85 | for y = miny, maxy, stepy do 86 | for x = minx, maxx, stepx do 87 | result[#result+1] = getOrCreateFrame(self,x,y) 88 | end 89 | end 90 | end 91 | 92 | return result 93 | end 94 | 95 | local Gridmt = { 96 | __index = Grid, 97 | __call = Grid.getFrames 98 | } 99 | 100 | local function newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) 101 | assertPositiveInteger(frameWidth, "frameWidth") 102 | assertPositiveInteger(frameHeight, "frameHeight") 103 | assertPositiveInteger(imageWidth, "imageWidth") 104 | assertPositiveInteger(imageHeight, "imageHeight") 105 | 106 | left = left or 0 107 | top = top or 0 108 | border = border or 0 109 | 110 | local key = getGridKey(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) 111 | 112 | local grid = setmetatable( 113 | { frameWidth = frameWidth, 114 | frameHeight = frameHeight, 115 | imageWidth = imageWidth, 116 | imageHeight = imageHeight, 117 | left = left, 118 | top = top, 119 | border = border, 120 | width = math.floor(imageWidth/frameWidth), 121 | height = math.floor(imageHeight/frameHeight), 122 | _key = key 123 | }, 124 | Gridmt 125 | ) 126 | return grid 127 | end 128 | 129 | ----------------------------------------------------------- 130 | 131 | local Animation = {} 132 | 133 | local function cloneArray(arr) 134 | local result = {} 135 | for i=1,#arr do result[i] = arr[i] end 136 | return result 137 | end 138 | 139 | local function parseDurations(durations, frameCount) 140 | local result = {} 141 | if type(durations) == 'number' then 142 | for i=1,frameCount do result[i] = durations end 143 | else 144 | local min, max, step 145 | for key,duration in pairs(durations) do 146 | assert(type(duration) == 'number', "The value [" .. tostring(duration) .. "] should be a number") 147 | min, max, step = parseInterval(key) 148 | for i = min,max,step do result[i] = duration end 149 | end 150 | end 151 | 152 | if #result < frameCount then 153 | error("The durations table has length of " .. tostring(#result) .. ", but it should be >= " .. tostring(frameCount)) 154 | end 155 | 156 | return result 157 | end 158 | 159 | local function parseIntervals(durations) 160 | local result, time = {0},0 161 | for i=1,#durations do 162 | time = time + durations[i] 163 | result[i+1] = time 164 | end 165 | return result, time 166 | end 167 | 168 | local Animationmt = { __index = Animation } 169 | local nop = function() end 170 | 171 | local function newAnimation(frames, durations, onLoop) 172 | local td = type(durations); 173 | if (td ~= 'number' or durations <= 0) and td ~= 'table' then 174 | error("durations must be a positive number. Was " .. tostring(durations) ) 175 | end 176 | onLoop = onLoop or nop 177 | durations = parseDurations(durations, #frames) 178 | local intervals, totalDuration = parseIntervals(durations) 179 | return setmetatable({ 180 | frames = cloneArray(frames), 181 | durations = durations, 182 | intervals = intervals, 183 | totalDuration = totalDuration, 184 | onLoop = onLoop, 185 | timer = 0, 186 | position = 1, 187 | status = "playing", 188 | flippedH = false, 189 | flippedV = false 190 | }, 191 | Animationmt 192 | ) 193 | end 194 | 195 | function Animation:clone() 196 | local newAnim = newAnimation(self.frames, self.durations, self.onLoop) 197 | newAnim.flippedH, newAnim.flippedV = self.flippedH, self.flippedV 198 | return newAnim 199 | end 200 | 201 | function Animation:flipH() 202 | self.flippedH = not self.flippedH 203 | return self 204 | end 205 | 206 | function Animation:flipV() 207 | self.flippedV = not self.flippedV 208 | return self 209 | end 210 | 211 | local function seekFrameIndex(intervals, timer) 212 | local high, low, i = #intervals-1, 1, 1 213 | 214 | while(low <= high) do 215 | i = math.floor((low + high) / 2) 216 | if timer >= intervals[i+1] then low = i + 1 217 | elseif timer < intervals[i] then high = i - 1 218 | else 219 | return i 220 | end 221 | end 222 | 223 | return i 224 | end 225 | 226 | function Animation:update(dt) 227 | if self.status ~= "playing" then return end 228 | 229 | self.timer = self.timer + dt 230 | local loops = math.floor(self.timer / self.totalDuration) 231 | if loops ~= 0 then 232 | self.timer = self.timer - self.totalDuration * loops 233 | local f = type(self.onLoop) == 'function' and self.onLoop or self[self.onLoop] 234 | f(self, loops) 235 | end 236 | 237 | self.position = seekFrameIndex(self.intervals, self.timer) 238 | end 239 | 240 | function Animation:pause() 241 | self.status = "paused" 242 | end 243 | 244 | function Animation:gotoFrame(position) 245 | self.position = position 246 | self.timer = self.intervals[self.position] 247 | end 248 | 249 | function Animation:pauseAtEnd() 250 | self.position = #self.frames 251 | self.timer = self.totalDuration 252 | self:pause() 253 | end 254 | 255 | function Animation:pauseAtStart() 256 | self.position = 1 257 | self.timer = 0 258 | self:pause() 259 | end 260 | 261 | function Animation:resume() 262 | self.status = "playing" 263 | end 264 | 265 | function Animation:draw(image, x, y, r, sx, sy, ox, oy, kx, ky) 266 | love.graphics.draw(image, self:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky)) 267 | end 268 | 269 | function Animation:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky) 270 | local frame = self.frames[self.position] 271 | if self.flippedH or self.flippedV then 272 | r,sx,sy,ox,oy,kx,ky = r or 0, sx or 1, sy or 1, ox or 0, oy or 0, kx or 0, ky or 0 273 | local _,_,w,h = frame:getViewport() 274 | 275 | if self.flippedH then 276 | sx = sx * -1 277 | ox = w - ox 278 | kx = kx * -1 279 | ky = ky * -1 280 | end 281 | 282 | if self.flippedV then 283 | sy = sy * -1 284 | oy = h - oy 285 | kx = kx * -1 286 | ky = ky * -1 287 | end 288 | end 289 | return frame, x, y, r, sx, sy, ox, oy, kx, ky 290 | end 291 | 292 | function Animation:getDimensions() 293 | local _,_,w,h = self.frames[self.position]:getViewport() 294 | return w,h 295 | end 296 | 297 | ----------------------------------------------------------- 298 | 299 | anim8.newGrid = newGrid 300 | anim8.newAnimation = newAnimation 301 | 302 | return anim8 303 | -------------------------------------------------------------------------------- /love2d/5_camera/libraries/anim8.lua: -------------------------------------------------------------------------------- 1 | local anim8 = { 2 | _VERSION = 'anim8 v2.3.1', 3 | _DESCRIPTION = 'An animation library for LÖVE', 4 | _URL = 'https://github.com/kikito/anim8', 5 | _LICENSE = [[ 6 | MIT LICENSE 7 | 8 | Copyright (c) 2011 Enrique García Cota 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a 11 | copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included 19 | in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | ]] 29 | } 30 | 31 | local Grid = {} 32 | 33 | local _frames = {} 34 | 35 | local function assertPositiveInteger(value, name) 36 | if type(value) ~= 'number' then error(("%s should be a number, was %q"):format(name, tostring(value))) end 37 | if value < 1 then error(("%s should be a positive number, was %d"):format(name, value)) end 38 | if value ~= math.floor(value) then error(("%s should be an integer, was %d"):format(name, value)) end 39 | end 40 | 41 | local function createFrame(self, x, y) 42 | local fw, fh = self.frameWidth, self.frameHeight 43 | return love.graphics.newQuad( 44 | self.left + (x-1) * fw + x * self.border, 45 | self.top + (y-1) * fh + y * self.border, 46 | fw, 47 | fh, 48 | self.imageWidth, 49 | self.imageHeight 50 | ) 51 | end 52 | 53 | local function getGridKey(...) 54 | return table.concat( {...} ,'-' ) 55 | end 56 | 57 | local function getOrCreateFrame(self, x, y) 58 | if x < 1 or x > self.width or y < 1 or y > self.height then 59 | error(("There is no frame for x=%d, y=%d"):format(x, y)) 60 | end 61 | local key = self._key 62 | _frames[key] = _frames[key] or {} 63 | _frames[key][x] = _frames[key][x] or {} 64 | _frames[key][x][y] = _frames[key][x][y] or createFrame(self, x, y) 65 | return _frames[key][x][y] 66 | end 67 | 68 | local function parseInterval(str) 69 | if type(str) == "number" then return str,str,1 end 70 | str = str:gsub('%s', '') -- remove spaces 71 | local min, max = str:match("^(%d+)-(%d+)$") 72 | assert(min and max, ("Could not parse interval from %q"):format(str)) 73 | min, max = tonumber(min), tonumber(max) 74 | local step = min <= max and 1 or -1 75 | return min, max, step 76 | end 77 | 78 | function Grid:getFrames(...) 79 | local result, args = {}, {...} 80 | local minx, maxx, stepx, miny, maxy, stepy 81 | 82 | for i=1, #args, 2 do 83 | minx, maxx, stepx = parseInterval(args[i]) 84 | miny, maxy, stepy = parseInterval(args[i+1]) 85 | for y = miny, maxy, stepy do 86 | for x = minx, maxx, stepx do 87 | result[#result+1] = getOrCreateFrame(self,x,y) 88 | end 89 | end 90 | end 91 | 92 | return result 93 | end 94 | 95 | local Gridmt = { 96 | __index = Grid, 97 | __call = Grid.getFrames 98 | } 99 | 100 | local function newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) 101 | assertPositiveInteger(frameWidth, "frameWidth") 102 | assertPositiveInteger(frameHeight, "frameHeight") 103 | assertPositiveInteger(imageWidth, "imageWidth") 104 | assertPositiveInteger(imageHeight, "imageHeight") 105 | 106 | left = left or 0 107 | top = top or 0 108 | border = border or 0 109 | 110 | local key = getGridKey(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) 111 | 112 | local grid = setmetatable( 113 | { frameWidth = frameWidth, 114 | frameHeight = frameHeight, 115 | imageWidth = imageWidth, 116 | imageHeight = imageHeight, 117 | left = left, 118 | top = top, 119 | border = border, 120 | width = math.floor(imageWidth/frameWidth), 121 | height = math.floor(imageHeight/frameHeight), 122 | _key = key 123 | }, 124 | Gridmt 125 | ) 126 | return grid 127 | end 128 | 129 | ----------------------------------------------------------- 130 | 131 | local Animation = {} 132 | 133 | local function cloneArray(arr) 134 | local result = {} 135 | for i=1,#arr do result[i] = arr[i] end 136 | return result 137 | end 138 | 139 | local function parseDurations(durations, frameCount) 140 | local result = {} 141 | if type(durations) == 'number' then 142 | for i=1,frameCount do result[i] = durations end 143 | else 144 | local min, max, step 145 | for key,duration in pairs(durations) do 146 | assert(type(duration) == 'number', "The value [" .. tostring(duration) .. "] should be a number") 147 | min, max, step = parseInterval(key) 148 | for i = min,max,step do result[i] = duration end 149 | end 150 | end 151 | 152 | if #result < frameCount then 153 | error("The durations table has length of " .. tostring(#result) .. ", but it should be >= " .. tostring(frameCount)) 154 | end 155 | 156 | return result 157 | end 158 | 159 | local function parseIntervals(durations) 160 | local result, time = {0},0 161 | for i=1,#durations do 162 | time = time + durations[i] 163 | result[i+1] = time 164 | end 165 | return result, time 166 | end 167 | 168 | local Animationmt = { __index = Animation } 169 | local nop = function() end 170 | 171 | local function newAnimation(frames, durations, onLoop) 172 | local td = type(durations); 173 | if (td ~= 'number' or durations <= 0) and td ~= 'table' then 174 | error("durations must be a positive number. Was " .. tostring(durations) ) 175 | end 176 | onLoop = onLoop or nop 177 | durations = parseDurations(durations, #frames) 178 | local intervals, totalDuration = parseIntervals(durations) 179 | return setmetatable({ 180 | frames = cloneArray(frames), 181 | durations = durations, 182 | intervals = intervals, 183 | totalDuration = totalDuration, 184 | onLoop = onLoop, 185 | timer = 0, 186 | position = 1, 187 | status = "playing", 188 | flippedH = false, 189 | flippedV = false 190 | }, 191 | Animationmt 192 | ) 193 | end 194 | 195 | function Animation:clone() 196 | local newAnim = newAnimation(self.frames, self.durations, self.onLoop) 197 | newAnim.flippedH, newAnim.flippedV = self.flippedH, self.flippedV 198 | return newAnim 199 | end 200 | 201 | function Animation:flipH() 202 | self.flippedH = not self.flippedH 203 | return self 204 | end 205 | 206 | function Animation:flipV() 207 | self.flippedV = not self.flippedV 208 | return self 209 | end 210 | 211 | local function seekFrameIndex(intervals, timer) 212 | local high, low, i = #intervals-1, 1, 1 213 | 214 | while(low <= high) do 215 | i = math.floor((low + high) / 2) 216 | if timer >= intervals[i+1] then low = i + 1 217 | elseif timer < intervals[i] then high = i - 1 218 | else 219 | return i 220 | end 221 | end 222 | 223 | return i 224 | end 225 | 226 | function Animation:update(dt) 227 | if self.status ~= "playing" then return end 228 | 229 | self.timer = self.timer + dt 230 | local loops = math.floor(self.timer / self.totalDuration) 231 | if loops ~= 0 then 232 | self.timer = self.timer - self.totalDuration * loops 233 | local f = type(self.onLoop) == 'function' and self.onLoop or self[self.onLoop] 234 | f(self, loops) 235 | end 236 | 237 | self.position = seekFrameIndex(self.intervals, self.timer) 238 | end 239 | 240 | function Animation:pause() 241 | self.status = "paused" 242 | end 243 | 244 | function Animation:gotoFrame(position) 245 | self.position = position 246 | self.timer = self.intervals[self.position] 247 | end 248 | 249 | function Animation:pauseAtEnd() 250 | self.position = #self.frames 251 | self.timer = self.totalDuration 252 | self:pause() 253 | end 254 | 255 | function Animation:pauseAtStart() 256 | self.position = 1 257 | self.timer = 0 258 | self:pause() 259 | end 260 | 261 | function Animation:resume() 262 | self.status = "playing" 263 | end 264 | 265 | function Animation:draw(image, x, y, r, sx, sy, ox, oy, kx, ky) 266 | love.graphics.draw(image, self:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky)) 267 | end 268 | 269 | function Animation:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky) 270 | local frame = self.frames[self.position] 271 | if self.flippedH or self.flippedV then 272 | r,sx,sy,ox,oy,kx,ky = r or 0, sx or 1, sy or 1, ox or 0, oy or 0, kx or 0, ky or 0 273 | local _,_,w,h = frame:getViewport() 274 | 275 | if self.flippedH then 276 | sx = sx * -1 277 | ox = w - ox 278 | kx = kx * -1 279 | ky = ky * -1 280 | end 281 | 282 | if self.flippedV then 283 | sy = sy * -1 284 | oy = h - oy 285 | kx = kx * -1 286 | ky = ky * -1 287 | end 288 | end 289 | return frame, x, y, r, sx, sy, ox, oy, kx, ky 290 | end 291 | 292 | function Animation:getDimensions() 293 | local _,_,w,h = self.frames[self.position]:getViewport() 294 | return w,h 295 | end 296 | 297 | ----------------------------------------------------------- 298 | 299 | anim8.newGrid = newGrid 300 | anim8.newAnimation = newAnimation 301 | 302 | return anim8 303 | -------------------------------------------------------------------------------- /love2d/5_camera/libraries/sti/plugins/box2d.lua: -------------------------------------------------------------------------------- 1 | --- Box2D plugin for STI 2 | -- @module box2d 3 | -- @author Landon Manning 4 | -- @copyright 2019 5 | -- @license MIT/X11 6 | 7 | local love = _G.love 8 | local utils = require((...):gsub('plugins.box2d', 'utils')) 9 | local lg = require((...):gsub('plugins.box2d', 'graphics')) 10 | 11 | return { 12 | box2d_LICENSE = "MIT/X11", 13 | box2d_URL = "https://github.com/karai17/Simple-Tiled-Implementation", 14 | box2d_VERSION = "2.3.2.7", 15 | box2d_DESCRIPTION = "Box2D hooks for STI.", 16 | 17 | --- Initialize Box2D physics world. 18 | -- @param world The Box2D world to add objects to. 19 | box2d_init = function(map, world) 20 | assert(love.physics, "To use the Box2D plugin, please enable the love.physics module.") 21 | 22 | local body = love.physics.newBody(world, map.offsetx, map.offsety) 23 | local collision = { 24 | body = body, 25 | } 26 | 27 | local function addObjectToWorld(objshape, vertices, userdata, object) 28 | local shape 29 | 30 | if objshape == "polyline" then 31 | if #vertices == 4 then 32 | shape = love.physics.newEdgeShape(unpack(vertices)) 33 | else 34 | shape = love.physics.newChainShape(false, unpack(vertices)) 35 | end 36 | else 37 | shape = love.physics.newPolygonShape(unpack(vertices)) 38 | end 39 | 40 | local currentBody = body 41 | --dynamic are objects/players etc. 42 | if userdata.properties.dynamic == true then 43 | currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'dynamic') 44 | -- static means it shouldn't move. Things like walls/ground. 45 | elseif userdata.properties.static == true then 46 | currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'static') 47 | -- kinematic means that the object is static in the game world but effects other bodies 48 | elseif userdata.properties.kinematic == true then 49 | currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'kinematic') 50 | end 51 | 52 | local fixture = love.physics.newFixture(currentBody, shape) 53 | fixture:setUserData(userdata) 54 | 55 | -- Set some custom properties from userdata (or use default set by box2d) 56 | fixture:setFriction(userdata.properties.friction or 0.2) 57 | fixture:setRestitution(userdata.properties.restitution or 0.0) 58 | fixture:setSensor(userdata.properties.sensor or false) 59 | fixture:setFilterData( 60 | userdata.properties.categories or 1, 61 | userdata.properties.mask or 65535, 62 | userdata.properties.group or 0 63 | ) 64 | 65 | local obj = { 66 | object = object, 67 | body = currentBody, 68 | shape = shape, 69 | fixture = fixture, 70 | } 71 | 72 | table.insert(collision, obj) 73 | end 74 | 75 | local function getPolygonVertices(object) 76 | local vertices = {} 77 | for _, vertex in ipairs(object.polygon) do 78 | table.insert(vertices, vertex.x) 79 | table.insert(vertices, vertex.y) 80 | end 81 | 82 | return vertices 83 | end 84 | 85 | local function calculateObjectPosition(object, tile) 86 | local o = { 87 | shape = object.shape, 88 | x = (object.dx or object.x) + map.offsetx, 89 | y = (object.dy or object.y) + map.offsety, 90 | w = object.width, 91 | h = object.height, 92 | polygon = object.polygon or object.polyline or object.ellipse or object.rectangle 93 | } 94 | 95 | local userdata = { 96 | object = o, 97 | properties = object.properties 98 | } 99 | 100 | o.r = object.rotation or 0 101 | if o.shape == "rectangle" then 102 | local cos = math.cos(math.rad(o.r)) 103 | local sin = math.sin(math.rad(o.r)) 104 | local oy = 0 105 | 106 | if object.gid then 107 | local tileset = map.tilesets[map.tiles[object.gid].tileset] 108 | local lid = object.gid - tileset.firstgid 109 | local t = {} 110 | 111 | -- This fixes a height issue 112 | o.y = o.y + map.tiles[object.gid].offset.y 113 | oy = o.h 114 | 115 | for _, tt in ipairs(tileset.tiles) do 116 | if tt.id == lid then 117 | t = tt 118 | break 119 | end 120 | end 121 | 122 | if t.objectGroup then 123 | for _, obj in ipairs(t.objectGroup.objects) do 124 | -- Every object in the tile 125 | calculateObjectPosition(obj, object) 126 | end 127 | 128 | return 129 | else 130 | o.w = map.tiles[object.gid].width 131 | o.h = map.tiles[object.gid].height 132 | end 133 | end 134 | 135 | o.polygon = { 136 | { x=o.x+0, y=o.y+0 }, 137 | { x=o.x+o.w, y=o.y+0 }, 138 | { x=o.x+o.w, y=o.y+o.h }, 139 | { x=o.x+0, y=o.y+o.h } 140 | } 141 | 142 | for _, vertex in ipairs(o.polygon) do 143 | vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin, oy) 144 | end 145 | 146 | local vertices = getPolygonVertices(o) 147 | addObjectToWorld(o.shape, vertices, userdata, tile or object) 148 | elseif o.shape == "ellipse" then 149 | if not o.polygon then 150 | o.polygon = utils.convert_ellipse_to_polygon(o.x, o.y, o.w, o.h) 151 | end 152 | local vertices = getPolygonVertices(o) 153 | local triangles = love.math.triangulate(vertices) 154 | 155 | for _, triangle in ipairs(triangles) do 156 | addObjectToWorld(o.shape, triangle, userdata, tile or object) 157 | end 158 | elseif o.shape == "polygon" then 159 | -- Recalculate collision polygons inside tiles 160 | if tile then 161 | local cos = math.cos(math.rad(o.r)) 162 | local sin = math.sin(math.rad(o.r)) 163 | for _, vertex in ipairs(o.polygon) do 164 | vertex.x = vertex.x + o.x 165 | vertex.y = vertex.y + o.y 166 | vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin) 167 | end 168 | end 169 | 170 | local vertices = getPolygonVertices(o) 171 | local triangles = love.math.triangulate(vertices) 172 | 173 | for _, triangle in ipairs(triangles) do 174 | addObjectToWorld(o.shape, triangle, userdata, tile or object) 175 | end 176 | elseif o.shape == "polyline" then 177 | local vertices = getPolygonVertices(o) 178 | addObjectToWorld(o.shape, vertices, userdata, tile or object) 179 | end 180 | end 181 | 182 | for _, tile in pairs(map.tiles) do 183 | if map.tileInstances[tile.gid] then 184 | for _, instance in ipairs(map.tileInstances[tile.gid]) do 185 | -- Every object in every instance of a tile 186 | if tile.objectGroup then 187 | for _, object in ipairs(tile.objectGroup.objects) do 188 | if object.properties.collidable == true then 189 | object = utils.deepCopy(object) 190 | object.dx = instance.x + object.x 191 | object.dy = instance.y + object.y 192 | calculateObjectPosition(object, instance) 193 | end 194 | end 195 | end 196 | 197 | -- Every instance of a tile 198 | if tile.properties.collidable == true then 199 | local object = { 200 | shape = "rectangle", 201 | x = instance.x, 202 | y = instance.y, 203 | width = map.tilewidth, 204 | height = map.tileheight, 205 | properties = tile.properties 206 | } 207 | 208 | calculateObjectPosition(object, instance) 209 | end 210 | end 211 | end 212 | end 213 | 214 | for _, layer in ipairs(map.layers) do 215 | -- Entire layer 216 | if layer.properties.collidable == true then 217 | if layer.type == "tilelayer" then 218 | for gid, tiles in pairs(map.tileInstances) do 219 | local tile = map.tiles[gid] 220 | local tileset = map.tilesets[tile.tileset] 221 | 222 | for _, instance in ipairs(tiles) do 223 | if instance.layer == layer then 224 | local object = { 225 | shape = "rectangle", 226 | x = instance.x, 227 | y = instance.y, 228 | width = tileset.tilewidth, 229 | height = tileset.tileheight, 230 | properties = tile.properties 231 | } 232 | 233 | calculateObjectPosition(object, instance) 234 | end 235 | end 236 | end 237 | elseif layer.type == "objectgroup" then 238 | for _, object in ipairs(layer.objects) do 239 | calculateObjectPosition(object) 240 | end 241 | elseif layer.type == "imagelayer" then 242 | local object = { 243 | shape = "rectangle", 244 | x = layer.x or 0, 245 | y = layer.y or 0, 246 | width = layer.width, 247 | height = layer.height, 248 | properties = layer.properties 249 | } 250 | 251 | calculateObjectPosition(object) 252 | end 253 | end 254 | 255 | -- Individual objects 256 | if layer.type == "objectgroup" then 257 | for _, object in ipairs(layer.objects) do 258 | if object.properties.collidable == true then 259 | calculateObjectPosition(object) 260 | end 261 | end 262 | end 263 | end 264 | 265 | map.box2d_collision = collision 266 | end, 267 | 268 | --- Remove Box2D fixtures and shapes from world. 269 | -- @param index The index or name of the layer being removed 270 | box2d_removeLayer = function(map, index) 271 | local layer = assert(map.layers[index], "Layer not found: " .. index) 272 | local collision = map.box2d_collision 273 | 274 | -- Remove collision objects 275 | for i = #collision, 1, -1 do 276 | local obj = collision[i] 277 | 278 | if obj.object.layer == layer then 279 | obj.fixture:destroy() 280 | table.remove(collision, i) 281 | end 282 | end 283 | end, 284 | 285 | --- Draw Box2D physics world. 286 | -- @param tx Translate on X 287 | -- @param ty Translate on Y 288 | -- @param sx Scale on X 289 | -- @param sy Scale on Y 290 | box2d_draw = function(map, tx, ty, sx, sy) 291 | local collision = map.box2d_collision 292 | 293 | lg.push() 294 | lg.scale(sx or 1, sy or sx or 1) 295 | lg.translate(math.floor(tx or 0), math.floor(ty or 0)) 296 | 297 | for _, obj in ipairs(collision) do 298 | local points = {obj.body:getWorldPoints(obj.shape:getPoints())} 299 | local shape_type = obj.shape:getType() 300 | 301 | if shape_type == "edge" or shape_type == "chain" then 302 | love.graphics.line(points) 303 | elseif shape_type == "polygon" then 304 | love.graphics.polygon("line", points) 305 | else 306 | error("sti box2d plugin does not support "..shape_type.." shapes") 307 | end 308 | end 309 | 310 | lg.pop() 311 | end 312 | } 313 | 314 | --- Custom Properties in Tiled are used to tell this plugin what to do. 315 | -- @table Properties 316 | -- @field collidable set to true, can be used on any Layer, Tile, or Object 317 | -- @field sensor set to true, can be used on any Tile or Object that is also collidable 318 | -- @field dynamic set to true, can be used on any Tile or Object 319 | -- @field friction can be used to define the friction of any Object 320 | -- @field restitution can be used to define the restitution of any Object 321 | -- @field categories can be used to set the filter Category of any Object 322 | -- @field mask can be used to set the filter Mask of any Object 323 | -- @field group can be used to set the filter Group of any Object 324 | -------------------------------------------------------------------------------- /love2d/5_camera/libraries/sti/init.lua: -------------------------------------------------------------------------------- 1 | --- Simple and fast Tiled map loader and renderer. 2 | -- @module sti 3 | -- @author Landon Manning 4 | -- @copyright 2019 5 | -- @license MIT/X11 6 | 7 | local STI = { 8 | _LICENSE = "MIT/X11", 9 | _URL = "https://github.com/karai17/Simple-Tiled-Implementation", 10 | _VERSION = "1.2.3.0", 11 | _DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.", 12 | cache = {} 13 | } 14 | STI.__index = STI 15 | 16 | local love = _G.love 17 | local cwd = (...):gsub('%.init$', '') .. "." 18 | local utils = require(cwd .. "utils") 19 | local ceil = math.ceil 20 | local floor = math.floor 21 | local lg = require(cwd .. "graphics") 22 | local Map = {} 23 | Map.__index = Map 24 | 25 | local function new(map, plugins, ox, oy) 26 | local dir = "" 27 | 28 | if type(map) == "table" then 29 | map = setmetatable(map, Map) 30 | else 31 | -- Check for valid map type 32 | local ext = map:sub(-4, -1) 33 | assert(ext == ".lua", string.format( 34 | "Invalid file type: %s. File must be of type: lua.", 35 | ext 36 | )) 37 | 38 | -- Get directory of map 39 | dir = map:reverse():find("[/\\]") or "" 40 | if dir ~= "" then 41 | dir = map:sub(1, 1 + (#map - dir)) 42 | end 43 | 44 | -- Load map 45 | map = setmetatable(assert(love.filesystem.load(map))(), Map) 46 | end 47 | 48 | map:init(dir, plugins, ox, oy) 49 | 50 | return map 51 | end 52 | 53 | --- Instance a new map. 54 | -- @param map Path to the map file or the map table itself 55 | -- @param plugins A list of plugins to load 56 | -- @param ox Offset of map on the X axis (in pixels) 57 | -- @param oy Offset of map on the Y axis (in pixels) 58 | -- @return table The loaded Map 59 | function STI.__call(_, map, plugins, ox, oy) 60 | return new(map, plugins, ox, oy) 61 | end 62 | 63 | --- Flush image cache. 64 | function STI:flush() 65 | self.cache = {} 66 | end 67 | 68 | --- Map object 69 | 70 | --- Instance a new map 71 | -- @param path Path to the map file 72 | -- @param plugins A list of plugins to load 73 | -- @param ox Offset of map on the X axis (in pixels) 74 | -- @param oy Offset of map on the Y axis (in pixels) 75 | function Map:init(path, plugins, ox, oy) 76 | if type(plugins) == "table" then 77 | self:loadPlugins(plugins) 78 | end 79 | 80 | self:resize() 81 | self.objects = {} 82 | self.tiles = {} 83 | self.tileInstances = {} 84 | self.drawRange = { 85 | sx = 1, 86 | sy = 1, 87 | ex = self.width, 88 | ey = self.height, 89 | } 90 | self.offsetx = ox or 0 91 | self.offsety = oy or 0 92 | 93 | self.freeBatchSprites = {} 94 | setmetatable(self.freeBatchSprites, { __mode = 'k' }) 95 | 96 | -- Set tiles, images 97 | local gid = 1 98 | for i, tileset in ipairs(self.tilesets) do 99 | assert(tileset.image, "STI does not support Tile Collections.\nYou need to create a Texture Atlas.") 100 | 101 | -- Cache images 102 | if lg.isCreated then 103 | local formatted_path = utils.format_path(path .. tileset.image) 104 | 105 | if not STI.cache[formatted_path] then 106 | utils.fix_transparent_color(tileset, formatted_path) 107 | utils.cache_image(STI, formatted_path, tileset.image) 108 | else 109 | tileset.image = STI.cache[formatted_path] 110 | end 111 | end 112 | 113 | gid = self:setTiles(i, tileset, gid) 114 | end 115 | 116 | local layers = {} 117 | for _, layer in ipairs(self.layers) do 118 | self:groupAppendToList(layers, layer) 119 | end 120 | self.layers = layers 121 | 122 | -- Set layers 123 | for _, layer in ipairs(self.layers) do 124 | self:setLayer(layer, path) 125 | end 126 | end 127 | 128 | --- Layers from the group are added to the list 129 | -- @param layers List of layers 130 | -- @param layer Layer data 131 | function Map:groupAppendToList(layers, layer) 132 | if layer.type == "group" then 133 | for _, groupLayer in pairs(layer.layers) do 134 | groupLayer.name = layer.name .. "." .. groupLayer.name 135 | groupLayer.visible = layer.visible 136 | groupLayer.opacity = layer.opacity * groupLayer.opacity 137 | groupLayer.offsetx = layer.offsetx + groupLayer.offsetx 138 | groupLayer.offsety = layer.offsety + groupLayer.offsety 139 | 140 | for key, property in pairs(layer.properties) do 141 | if groupLayer.properties[key] == nil then 142 | groupLayer.properties[key] = property 143 | end 144 | end 145 | 146 | self:groupAppendToList(layers, groupLayer) 147 | end 148 | else 149 | table.insert(layers, layer) 150 | end 151 | end 152 | 153 | --- Load plugins 154 | -- @param plugins A list of plugins to load 155 | function Map:loadPlugins(plugins) 156 | for _, plugin in ipairs(plugins) do 157 | local pluginModulePath = cwd .. 'plugins.' .. plugin 158 | local ok, pluginModule = pcall(require, pluginModulePath) 159 | if ok then 160 | for k, func in pairs(pluginModule) do 161 | if not self[k] then 162 | self[k] = func 163 | end 164 | end 165 | end 166 | end 167 | end 168 | 169 | --- Create Tiles 170 | -- @param index Index of the Tileset 171 | -- @param tileset Tileset data 172 | -- @param gid First Global ID in Tileset 173 | -- @return number Next Tileset's first Global ID 174 | function Map:setTiles(index, tileset, gid) 175 | local quad = lg.newQuad 176 | local imageW = tileset.imagewidth 177 | local imageH = tileset.imageheight 178 | local tileW = tileset.tilewidth 179 | local tileH = tileset.tileheight 180 | local margin = tileset.margin 181 | local spacing = tileset.spacing 182 | local w = utils.get_tiles(imageW, tileW, margin, spacing) 183 | local h = utils.get_tiles(imageH, tileH, margin, spacing) 184 | 185 | for y = 1, h do 186 | for x = 1, w do 187 | local id = gid - tileset.firstgid 188 | local quadX = (x - 1) * tileW + margin + (x - 1) * spacing 189 | local quadY = (y - 1) * tileH + margin + (y - 1) * spacing 190 | local type = "" 191 | local properties, terrain, animation, objectGroup 192 | 193 | for _, tile in pairs(tileset.tiles) do 194 | if tile.id == id then 195 | properties = tile.properties 196 | animation = tile.animation 197 | objectGroup = tile.objectGroup 198 | type = tile.type 199 | 200 | if tile.terrain then 201 | terrain = {} 202 | 203 | for i = 1, #tile.terrain do 204 | terrain[i] = tileset.terrains[tile.terrain[i] + 1] 205 | end 206 | end 207 | end 208 | end 209 | 210 | local tile = { 211 | id = id, 212 | gid = gid, 213 | tileset = index, 214 | type = type, 215 | quad = quad( 216 | quadX, quadY, 217 | tileW, tileH, 218 | imageW, imageH 219 | ), 220 | properties = properties or {}, 221 | terrain = terrain, 222 | animation = animation, 223 | objectGroup = objectGroup, 224 | frame = 1, 225 | time = 0, 226 | width = tileW, 227 | height = tileH, 228 | sx = 1, 229 | sy = 1, 230 | r = 0, 231 | offset = tileset.tileoffset, 232 | } 233 | 234 | self.tiles[gid] = tile 235 | gid = gid + 1 236 | end 237 | end 238 | 239 | return gid 240 | end 241 | 242 | --- Create Layers 243 | -- @param layer Layer data 244 | -- @param path (Optional) Path to an Image Layer's image 245 | function Map:setLayer(layer, path) 246 | if layer.encoding then 247 | if layer.encoding == "base64" then 248 | assert(require "ffi", "Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to \"CSV\".") 249 | local fd = love.data.decode("string", "base64", layer.data) 250 | 251 | if not layer.compression then 252 | layer.data = utils.get_decompressed_data(fd) 253 | else 254 | assert(love.data.decompress, "zlib and gzip compression require LOVE 11.0+.\nPlease set your Tile Layer Format to \"Base64 (uncompressed)\" or \"CSV\".") 255 | 256 | if layer.compression == "zlib" then 257 | local data = love.data.decompress("string", "zlib", fd) 258 | layer.data = utils.get_decompressed_data(data) 259 | end 260 | 261 | if layer.compression == "gzip" then 262 | local data = love.data.decompress("string", "gzip", fd) 263 | layer.data = utils.get_decompressed_data(data) 264 | end 265 | end 266 | end 267 | end 268 | 269 | layer.x = (layer.x or 0) + layer.offsetx + self.offsetx 270 | layer.y = (layer.y or 0) + layer.offsety + self.offsety 271 | layer.update = function() end 272 | 273 | if layer.type == "tilelayer" then 274 | self:setTileData(layer) 275 | self:setSpriteBatches(layer) 276 | layer.draw = function() self:drawTileLayer(layer) end 277 | elseif layer.type == "objectgroup" then 278 | self:setObjectData(layer) 279 | self:setObjectCoordinates(layer) 280 | self:setObjectSpriteBatches(layer) 281 | layer.draw = function() self:drawObjectLayer(layer) end 282 | elseif layer.type == "imagelayer" then 283 | layer.draw = function() self:drawImageLayer(layer) end 284 | 285 | if layer.image ~= "" then 286 | local formatted_path = utils.format_path(path .. layer.image) 287 | if not STI.cache[formatted_path] then 288 | utils.cache_image(STI, formatted_path) 289 | end 290 | 291 | layer.image = STI.cache[formatted_path] 292 | layer.width = layer.image:getWidth() 293 | layer.height = layer.image:getHeight() 294 | end 295 | end 296 | 297 | self.layers[layer.name] = layer 298 | end 299 | 300 | --- Add Tiles to Tile Layer 301 | -- @param layer The Tile Layer 302 | function Map:setTileData(layer) 303 | if layer.chunks then 304 | for _, chunk in ipairs(layer.chunks) do 305 | self:setTileData(chunk) 306 | end 307 | return 308 | end 309 | 310 | local i = 1 311 | local map = {} 312 | 313 | for y = 1, layer.height do 314 | map[y] = {} 315 | for x = 1, layer.width do 316 | local gid = layer.data[i] 317 | 318 | -- NOTE: Empty tiles have a GID of 0 319 | if gid > 0 then 320 | map[y][x] = self.tiles[gid] or self:setFlippedGID(gid) 321 | end 322 | 323 | i = i + 1 324 | end 325 | end 326 | 327 | layer.data = map 328 | end 329 | 330 | --- Add Objects to Layer 331 | -- @param layer The Object Layer 332 | function Map:setObjectData(layer) 333 | for _, object in ipairs(layer.objects) do 334 | object.layer = layer 335 | self.objects[object.id] = object 336 | end 337 | end 338 | 339 | --- Correct position and orientation of Objects in an Object Layer 340 | -- @param layer The Object Layer 341 | function Map:setObjectCoordinates(layer) 342 | for _, object in ipairs(layer.objects) do 343 | local x = layer.x + object.x 344 | local y = layer.y + object.y 345 | local w = object.width 346 | local h = object.height 347 | local cos = math.cos(math.rad(object.rotation)) 348 | local sin = math.sin(math.rad(object.rotation)) 349 | 350 | if object.shape == "rectangle" and not object.gid then 351 | object.rectangle = {} 352 | 353 | local vertices = { 354 | { x=x, y=y }, 355 | { x=x + w, y=y }, 356 | { x=x + w, y=y + h }, 357 | { x=x, y=y + h }, 358 | } 359 | 360 | for _, vertex in ipairs(vertices) do 361 | vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) 362 | table.insert(object.rectangle, { x = vertex.x, y = vertex.y }) 363 | end 364 | elseif object.shape == "ellipse" then 365 | object.ellipse = {} 366 | local vertices = utils.convert_ellipse_to_polygon(x, y, w, h) 367 | 368 | for _, vertex in ipairs(vertices) do 369 | vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) 370 | table.insert(object.ellipse, { x = vertex.x, y = vertex.y }) 371 | end 372 | elseif object.shape == "polygon" then 373 | for _, vertex in ipairs(object.polygon) do 374 | vertex.x = vertex.x + x 375 | vertex.y = vertex.y + y 376 | vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) 377 | end 378 | elseif object.shape == "polyline" then 379 | for _, vertex in ipairs(object.polyline) do 380 | vertex.x = vertex.x + x 381 | vertex.y = vertex.y + y 382 | vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) 383 | end 384 | end 385 | end 386 | end 387 | 388 | --- Convert tile location to tile instance location 389 | -- @param layer Tile layer 390 | -- @param tile Tile 391 | -- @param x Tile location on X axis (in tiles) 392 | -- @param y Tile location on Y axis (in tiles) 393 | -- @return number Tile instance location on X axis (in pixels) 394 | -- @return number Tile instance location on Y axis (in pixels) 395 | function Map:getLayerTilePosition(layer, tile, x, y) 396 | local tileW = self.tilewidth 397 | local tileH = self.tileheight 398 | local tileX, tileY 399 | 400 | if self.orientation == "orthogonal" then 401 | local tileset = self.tilesets[tile.tileset] 402 | tileX = (x - 1) * tileW + tile.offset.x 403 | tileY = (y - 0) * tileH + tile.offset.y - tileset.tileheight 404 | tileX, tileY = utils.compensate(tile, tileX, tileY, tileW, tileH) 405 | elseif self.orientation == "isometric" then 406 | tileX = (x - y) * (tileW / 2) + tile.offset.x + layer.width * tileW / 2 - self.tilewidth / 2 407 | tileY = (x + y - 2) * (tileH / 2) + tile.offset.y 408 | else 409 | local sideLen = self.hexsidelength or 0 410 | if self.staggeraxis == "y" then 411 | if self.staggerindex == "odd" then 412 | if y % 2 == 0 then 413 | tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x 414 | else 415 | tileX = (x - 1) * tileW + tile.offset.x 416 | end 417 | else 418 | if y % 2 == 0 then 419 | tileX = (x - 1) * tileW + tile.offset.x 420 | else 421 | tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x 422 | end 423 | end 424 | 425 | local rowH = tileH - (tileH - sideLen) / 2 426 | tileY = (y - 1) * rowH + tile.offset.y 427 | else 428 | if self.staggerindex == "odd" then 429 | if x % 2 == 0 then 430 | tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y 431 | else 432 | tileY = (y - 1) * tileH + tile.offset.y 433 | end 434 | else 435 | if x % 2 == 0 then 436 | tileY = (y - 1) * tileH + tile.offset.y 437 | else 438 | tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y 439 | end 440 | end 441 | 442 | local colW = tileW - (tileW - sideLen) / 2 443 | tileX = (x - 1) * colW + tile.offset.x 444 | end 445 | end 446 | 447 | return tileX, tileY 448 | end 449 | 450 | --- Place new tile instance 451 | -- @param layer Tile layer 452 | -- @param chunk Layer chunk 453 | -- @param tile Tile 454 | -- @param number Tile location on X axis (in tiles) 455 | -- @param number Tile location on Y axis (in tiles) 456 | function Map:addNewLayerTile(layer, chunk, tile, x, y) 457 | local tileset = tile.tileset 458 | local image = self.tilesets[tile.tileset].image 459 | local batches 460 | local size 461 | 462 | if chunk then 463 | batches = chunk.batches 464 | size = chunk.width * chunk.height 465 | else 466 | batches = layer.batches 467 | size = layer.width * layer.height 468 | end 469 | 470 | batches[tileset] = batches[tileset] or lg.newSpriteBatch(image, size) 471 | 472 | local batch = batches[tileset] 473 | local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y) 474 | 475 | local instance = { 476 | layer = layer, 477 | chunk = chunk, 478 | gid = tile.gid, 479 | x = tileX, 480 | y = tileY, 481 | r = tile.r, 482 | oy = 0 483 | } 484 | 485 | -- NOTE: STI can run headless so it is not guaranteed that a batch exists. 486 | if batch then 487 | instance.batch = batch 488 | instance.id = batch:add(tile.quad, tileX, tileY, tile.r, tile.sx, tile.sy) 489 | end 490 | 491 | self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} 492 | table.insert(self.tileInstances[tile.gid], instance) 493 | end 494 | 495 | function Map:set_batches(layer, chunk) 496 | if chunk then 497 | chunk.batches = {} 498 | else 499 | layer.batches = {} 500 | end 501 | 502 | if self.orientation == "orthogonal" or self.orientation == "isometric" then 503 | local offsetX = chunk and chunk.x or 0 504 | local offsetY = chunk and chunk.y or 0 505 | 506 | local startX = 1 507 | local startY = 1 508 | local endX = chunk and chunk.width or layer.width 509 | local endY = chunk and chunk.height or layer.height 510 | local incrementX = 1 511 | local incrementY = 1 512 | 513 | -- Determine order to add tiles to sprite batch 514 | -- Defaults to right-down 515 | if self.renderorder == "right-up" then 516 | startY, endY, incrementY = endY, startY, -1 517 | elseif self.renderorder == "left-down" then 518 | startX, endX, incrementX = endX, startX, -1 519 | elseif self.renderorder == "left-up" then 520 | startX, endX, incrementX = endX, startX, -1 521 | startY, endY, incrementY = endY, startY, -1 522 | end 523 | 524 | for y = startY, endY, incrementY do 525 | for x = startX, endX, incrementX do 526 | -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil 527 | local tile 528 | if chunk then 529 | tile = chunk.data[y][x] 530 | else 531 | tile = layer.data[y][x] 532 | end 533 | 534 | if tile then 535 | self:addNewLayerTile(layer, chunk, tile, x + offsetX, y + offsetY) 536 | end 537 | end 538 | end 539 | else 540 | if self.staggeraxis == "y" then 541 | for y = 1, (chunk and chunk.height or layer.height) do 542 | for x = 1, (chunk and chunk.width or layer.width) do 543 | -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil 544 | local tile 545 | if chunk then 546 | tile = chunk.data[y][x] 547 | else 548 | tile = layer.data[y][x] 549 | end 550 | 551 | if tile then 552 | self:addNewLayerTile(layer, chunk, tile, x, y) 553 | end 554 | end 555 | end 556 | else 557 | local i = 0 558 | local _x 559 | 560 | if self.staggerindex == "odd" then 561 | _x = 1 562 | else 563 | _x = 2 564 | end 565 | 566 | while i < (chunk and chunk.width * chunk.height or layer.width * layer.height) do 567 | for _y = 1, (chunk and chunk.height or layer.height) + 0.5, 0.5 do 568 | local y = floor(_y) 569 | 570 | for x = _x, (chunk and chunk.width or layer.width), 2 do 571 | i = i + 1 572 | 573 | -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil 574 | local tile 575 | if chunk then 576 | tile = chunk.data[y][x] 577 | else 578 | tile = layer.data[y][x] 579 | end 580 | 581 | if tile then 582 | self:addNewLayerTile(layer, chunk, tile, x, y) 583 | end 584 | end 585 | 586 | if _x == 1 then 587 | _x = 2 588 | else 589 | _x = 1 590 | end 591 | end 592 | end 593 | end 594 | end 595 | end 596 | 597 | --- Batch Tiles in Tile Layer for improved draw speed 598 | -- @param layer The Tile Layer 599 | function Map:setSpriteBatches(layer) 600 | if layer.chunks then 601 | for _, chunk in ipairs(layer.chunks) do 602 | self:set_batches(layer, chunk) 603 | end 604 | return 605 | end 606 | 607 | self:set_batches(layer) 608 | end 609 | 610 | --- Batch Tiles in Object Layer for improved draw speed 611 | -- @param layer The Object Layer 612 | function Map:setObjectSpriteBatches(layer) 613 | local newBatch = lg.newSpriteBatch 614 | local batches = {} 615 | 616 | if layer.draworder == "topdown" then 617 | table.sort(layer.objects, function(a, b) 618 | return a.y + a.height < b.y + b.height 619 | end) 620 | end 621 | 622 | for _, object in ipairs(layer.objects) do 623 | if object.gid then 624 | local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid) 625 | local tileset = tile.tileset 626 | local image = self.tilesets[tileset].image 627 | 628 | batches[tileset] = batches[tileset] or newBatch(image) 629 | 630 | local sx = object.width / tile.width 631 | local sy = object.height / tile.height 632 | 633 | -- Tiled rotates around bottom left corner, where love2D rotates around top left corner 634 | local ox = 0 635 | local oy = tile.height 636 | 637 | local batch = batches[tileset] 638 | local tileX = object.x + tile.offset.x 639 | local tileY = object.y + tile.offset.y 640 | local tileR = math.rad(object.rotation) 641 | 642 | -- Compensation for scale/rotation shift 643 | if tile.sx == -1 then 644 | tileX = tileX + object.width 645 | 646 | if tileR ~= 0 then 647 | tileX = tileX - object.width 648 | ox = ox + tile.width 649 | end 650 | end 651 | 652 | if tile.sy == -1 then 653 | tileY = tileY - object.height 654 | 655 | if tileR ~= 0 then 656 | tileY = tileY + object.width 657 | oy = oy - tile.width 658 | end 659 | end 660 | 661 | local instance = { 662 | id = batch:add(tile.quad, tileX, tileY, tileR, tile.sx * sx, tile.sy * sy, ox, oy), 663 | batch = batch, 664 | layer = layer, 665 | gid = tile.gid, 666 | x = tileX, 667 | y = tileY - oy, 668 | r = tileR, 669 | oy = oy 670 | } 671 | 672 | self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} 673 | table.insert(self.tileInstances[tile.gid], instance) 674 | end 675 | end 676 | 677 | layer.batches = batches 678 | end 679 | 680 | --- Create a Custom Layer to place userdata in (such as player sprites) 681 | -- @param name Name of Custom Layer 682 | -- @param index Draw order within Layer stack 683 | -- @return table Custom Layer 684 | function Map:addCustomLayer(name, index) 685 | index = index or #self.layers + 1 686 | local layer = { 687 | type = "customlayer", 688 | name = name, 689 | visible = true, 690 | opacity = 1, 691 | properties = {}, 692 | } 693 | 694 | function layer.draw() end 695 | function layer.update() end 696 | 697 | table.insert(self.layers, index, layer) 698 | self.layers[name] = self.layers[index] 699 | 700 | return layer 701 | end 702 | 703 | --- Convert another Layer into a Custom Layer 704 | -- @param index Index or name of Layer to convert 705 | -- @return table Custom Layer 706 | function Map:convertToCustomLayer(index) 707 | local layer = assert(self.layers[index], "Layer not found: " .. index) 708 | 709 | layer.type = "customlayer" 710 | layer.x = nil 711 | layer.y = nil 712 | layer.width = nil 713 | layer.height = nil 714 | layer.encoding = nil 715 | layer.data = nil 716 | layer.chunks = nil 717 | layer.objects = nil 718 | layer.image = nil 719 | 720 | function layer.draw() end 721 | function layer.update() end 722 | 723 | return layer 724 | end 725 | 726 | --- Remove a Layer from the Layer stack 727 | -- @param index Index or name of Layer to remove 728 | function Map:removeLayer(index) 729 | local layer = assert(self.layers[index], "Layer not found: " .. index) 730 | 731 | if type(index) == "string" then 732 | for i, l in ipairs(self.layers) do 733 | if l.name == index then 734 | table.remove(self.layers, i) 735 | self.layers[index] = nil 736 | break 737 | end 738 | end 739 | else 740 | local name = self.layers[index].name 741 | table.remove(self.layers, index) 742 | self.layers[name] = nil 743 | end 744 | 745 | -- Remove layer batches 746 | if layer.batches then 747 | for _, batch in pairs(layer.batches) do 748 | self.freeBatchSprites[batch] = nil 749 | end 750 | end 751 | 752 | -- Remove chunk batches 753 | if layer.chunks then 754 | for _, chunk in ipairs(layer.chunks) do 755 | for _, batch in pairs(chunk.batches) do 756 | self.freeBatchSprites[batch] = nil 757 | end 758 | end 759 | end 760 | 761 | -- Remove tile instances 762 | if layer.type == "tilelayer" then 763 | for _, tiles in pairs(self.tileInstances) do 764 | for i = #tiles, 1, -1 do 765 | local tile = tiles[i] 766 | if tile.layer == layer then 767 | table.remove(tiles, i) 768 | end 769 | end 770 | end 771 | end 772 | 773 | -- Remove objects 774 | if layer.objects then 775 | for i, object in pairs(self.objects) do 776 | if object.layer == layer then 777 | self.objects[i] = nil 778 | end 779 | end 780 | end 781 | end 782 | 783 | --- Animate Tiles and update every Layer 784 | -- @param dt Delta Time 785 | function Map:update(dt) 786 | for _, tile in pairs(self.tiles) do 787 | local update = false 788 | 789 | if tile.animation then 790 | tile.time = tile.time + dt * 1000 791 | 792 | while tile.time > tonumber(tile.animation[tile.frame].duration) do 793 | update = true 794 | tile.time = tile.time - tonumber(tile.animation[tile.frame].duration) 795 | tile.frame = tile.frame + 1 796 | 797 | if tile.frame > #tile.animation then tile.frame = 1 end 798 | end 799 | 800 | if update and self.tileInstances[tile.gid] then 801 | for _, j in pairs(self.tileInstances[tile.gid]) do 802 | local t = self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid] 803 | j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy) 804 | end 805 | end 806 | end 807 | end 808 | 809 | for _, layer in ipairs(self.layers) do 810 | layer:update(dt) 811 | end 812 | end 813 | 814 | --- Draw every Layer 815 | -- @param tx Translate on X 816 | -- @param ty Translate on Y 817 | -- @param sx Scale on X 818 | -- @param sy Scale on Y 819 | function Map:draw(tx, ty, sx, sy) 820 | local current_canvas = lg.getCanvas() 821 | lg.setCanvas(self.canvas) 822 | lg.clear() 823 | 824 | -- Scale map to 1.0 to draw onto canvas, this fixes tearing issues 825 | -- Map is translated to correct position so the right section is drawn 826 | lg.push() 827 | lg.origin() 828 | lg.translate(math.floor(tx or 0), math.floor(ty or 0)) 829 | 830 | for _, layer in ipairs(self.layers) do 831 | if layer.visible and layer.opacity > 0 then 832 | self:drawLayer(layer) 833 | end 834 | end 835 | 836 | lg.pop() 837 | 838 | -- Draw canvas at 0,0; this fixes scissoring issues 839 | -- Map is scaled to correct scale so the right section is shown 840 | lg.push() 841 | lg.origin() 842 | lg.scale(sx or 1, sy or sx or 1) 843 | 844 | lg.setCanvas(current_canvas) 845 | lg.draw(self.canvas) 846 | 847 | lg.pop() 848 | end 849 | 850 | --- Draw an individual Layer 851 | -- @param layer The Layer to draw 852 | function Map.drawLayer(_, layer) 853 | local r,g,b,a = lg.getColor() 854 | lg.setColor(r, g, b, a * layer.opacity) 855 | layer:draw() 856 | lg.setColor(r,g,b,a) 857 | end 858 | 859 | --- Default draw function for Tile Layers 860 | -- @param layer The Tile Layer to draw 861 | function Map:drawTileLayer(layer) 862 | if type(layer) == "string" or type(layer) == "number" then 863 | layer = self.layers[layer] 864 | end 865 | 866 | assert(layer.type == "tilelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: tilelayer") 867 | 868 | -- NOTE: This does not take into account any sort of draw range clipping and will always draw every chunk 869 | if layer.chunks then 870 | for _, chunk in ipairs(layer.chunks) do 871 | for _, batch in pairs(chunk.batches) do 872 | lg.draw(batch, 0, 0) 873 | end 874 | end 875 | 876 | return 877 | end 878 | 879 | for _, batch in pairs(layer.batches) do 880 | lg.draw(batch, floor(layer.x), floor(layer.y)) 881 | end 882 | end 883 | 884 | --- Default draw function for Object Layers 885 | -- @param layer The Object Layer to draw 886 | function Map:drawObjectLayer(layer) 887 | if type(layer) == "string" or type(layer) == "number" then 888 | layer = self.layers[layer] 889 | end 890 | 891 | assert(layer.type == "objectgroup", "Invalid layer type: " .. layer.type .. ". Layer must be of type: objectgroup") 892 | 893 | local line = { 160, 160, 160, 255 * layer.opacity } 894 | local fill = { 160, 160, 160, 255 * layer.opacity * 0.5 } 895 | local r,g,b,a = lg.getColor() 896 | local reset = { r, g, b, a * layer.opacity } 897 | 898 | local function sortVertices(obj) 899 | local vertex = {} 900 | 901 | for _, v in ipairs(obj) do 902 | table.insert(vertex, v.x) 903 | table.insert(vertex, v.y) 904 | end 905 | 906 | return vertex 907 | end 908 | 909 | local function drawShape(obj, shape) 910 | local vertex = sortVertices(obj) 911 | 912 | if shape == "polyline" then 913 | lg.setColor(line) 914 | lg.line(vertex) 915 | return 916 | elseif shape == "polygon" then 917 | lg.setColor(fill) 918 | if not love.math.isConvex(vertex) then 919 | local triangles = love.math.triangulate(vertex) 920 | for _, triangle in ipairs(triangles) do 921 | lg.polygon("fill", triangle) 922 | end 923 | else 924 | lg.polygon("fill", vertex) 925 | end 926 | else 927 | lg.setColor(fill) 928 | lg.polygon("fill", vertex) 929 | end 930 | 931 | lg.setColor(line) 932 | lg.polygon("line", vertex) 933 | end 934 | 935 | for _, object in ipairs(layer.objects) do 936 | if object.visible then 937 | if object.shape == "rectangle" and not object.gid then 938 | drawShape(object.rectangle, "rectangle") 939 | elseif object.shape == "ellipse" then 940 | drawShape(object.ellipse, "ellipse") 941 | elseif object.shape == "polygon" then 942 | drawShape(object.polygon, "polygon") 943 | elseif object.shape == "polyline" then 944 | drawShape(object.polyline, "polyline") 945 | elseif object.shape == "point" then 946 | lg.points(object.x, object.y) 947 | end 948 | end 949 | end 950 | 951 | lg.setColor(reset) 952 | for _, batch in pairs(layer.batches) do 953 | lg.draw(batch, 0, 0) 954 | end 955 | lg.setColor(r,g,b,a) 956 | end 957 | 958 | --- Default draw function for Image Layers 959 | -- @param layer The Image Layer to draw 960 | function Map:drawImageLayer(layer) 961 | if type(layer) == "string" or type(layer) == "number" then 962 | layer = self.layers[layer] 963 | end 964 | 965 | assert(layer.type == "imagelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: imagelayer") 966 | 967 | if layer.image ~= "" then 968 | lg.draw(layer.image, layer.x, layer.y) 969 | end 970 | end 971 | 972 | --- Resize the drawable area of the Map 973 | -- @param w The new width of the drawable area (in pixels) 974 | -- @param h The new Height of the drawable area (in pixels) 975 | function Map:resize(w, h) 976 | if lg.isCreated then 977 | w = w or lg.getWidth() 978 | h = h or lg.getHeight() 979 | 980 | self.canvas = lg.newCanvas(w, h) 981 | self.canvas:setFilter("nearest", "nearest") 982 | end 983 | end 984 | 985 | --- Create flipped or rotated Tiles based on bitop flags 986 | -- @param gid The flagged Global ID 987 | -- @return table Flipped Tile 988 | function Map:setFlippedGID(gid) 989 | local bit31 = 2147483648 990 | local bit30 = 1073741824 991 | local bit29 = 536870912 992 | local flipX = false 993 | local flipY = false 994 | local flipD = false 995 | local realgid = gid 996 | 997 | if realgid >= bit31 then 998 | realgid = realgid - bit31 999 | flipX = not flipX 1000 | end 1001 | 1002 | if realgid >= bit30 then 1003 | realgid = realgid - bit30 1004 | flipY = not flipY 1005 | end 1006 | 1007 | if realgid >= bit29 then 1008 | realgid = realgid - bit29 1009 | flipD = not flipD 1010 | end 1011 | 1012 | local tile = self.tiles[realgid] 1013 | local data = { 1014 | id = tile.id, 1015 | gid = gid, 1016 | tileset = tile.tileset, 1017 | frame = tile.frame, 1018 | time = tile.time, 1019 | width = tile.width, 1020 | height = tile.height, 1021 | offset = tile.offset, 1022 | quad = tile.quad, 1023 | properties = tile.properties, 1024 | terrain = tile.terrain, 1025 | animation = tile.animation, 1026 | sx = tile.sx, 1027 | sy = tile.sy, 1028 | r = tile.r, 1029 | } 1030 | 1031 | if flipX then 1032 | if flipY and flipD then 1033 | data.r = math.rad(-90) 1034 | data.sy = -1 1035 | elseif flipY then 1036 | data.sx = -1 1037 | data.sy = -1 1038 | elseif flipD then 1039 | data.r = math.rad(90) 1040 | else 1041 | data.sx = -1 1042 | end 1043 | elseif flipY then 1044 | if flipD then 1045 | data.r = math.rad(-90) 1046 | else 1047 | data.sy = -1 1048 | end 1049 | elseif flipD then 1050 | data.r = math.rad(90) 1051 | data.sy = -1 1052 | end 1053 | 1054 | self.tiles[gid] = data 1055 | 1056 | return self.tiles[gid] 1057 | end 1058 | 1059 | --- Get custom properties from Layer 1060 | -- @param layer The Layer 1061 | -- @return table List of properties 1062 | function Map:getLayerProperties(layer) 1063 | local l = self.layers[layer] 1064 | 1065 | if not l then 1066 | return {} 1067 | end 1068 | 1069 | return l.properties 1070 | end 1071 | 1072 | --- Get custom properties from Tile 1073 | -- @param layer The Layer that the Tile belongs to 1074 | -- @param x The X axis location of the Tile (in tiles) 1075 | -- @param y The Y axis location of the Tile (in tiles) 1076 | -- @return table List of properties 1077 | function Map:getTileProperties(layer, x, y) 1078 | local tile = self.layers[layer].data[y][x] 1079 | 1080 | if not tile then 1081 | return {} 1082 | end 1083 | 1084 | return tile.properties 1085 | end 1086 | 1087 | --- Get custom properties from Object 1088 | -- @param layer The Layer that the Object belongs to 1089 | -- @param object The index or name of the Object 1090 | -- @return table List of properties 1091 | function Map:getObjectProperties(layer, object) 1092 | local o = self.layers[layer].objects 1093 | 1094 | if type(object) == "number" then 1095 | o = o[object] 1096 | else 1097 | for _, v in ipairs(o) do 1098 | if v.name == object then 1099 | o = v 1100 | break 1101 | end 1102 | end 1103 | end 1104 | 1105 | if not o then 1106 | return {} 1107 | end 1108 | 1109 | return o.properties 1110 | end 1111 | 1112 | --- Change a tile in a layer to another tile 1113 | -- @param layer The Layer that the Tile belongs to 1114 | -- @param x The X axis location of the Tile (in tiles) 1115 | -- @param y The Y axis location of the Tile (in tiles) 1116 | -- @param gid The gid of the new tile 1117 | function Map:setLayerTile(layer, x, y, gid) 1118 | layer = self.layers[layer] 1119 | 1120 | layer.data[y] = layer.data[y] or {} 1121 | local tile = layer.data[y][x] 1122 | local instance 1123 | if tile then 1124 | local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y) 1125 | for _, inst in pairs(self.tileInstances[tile.gid]) do 1126 | if inst.x == tileX and inst.y == tileY then 1127 | instance = inst 1128 | break 1129 | end 1130 | end 1131 | end 1132 | 1133 | if tile == self.tiles[gid] then 1134 | return 1135 | end 1136 | 1137 | tile = self.tiles[gid] 1138 | 1139 | if instance then 1140 | self:swapTile(instance, tile) 1141 | else 1142 | self:addNewLayerTile(layer, tile, x, y) 1143 | end 1144 | layer.data[y][x] = tile 1145 | end 1146 | 1147 | --- Swap a tile in a spritebatch 1148 | -- @param instance The current Instance object we want to replace 1149 | -- @param tile The Tile object we want to use 1150 | -- @return none 1151 | function Map:swapTile(instance, tile) 1152 | -- Update sprite batch 1153 | if instance.batch then 1154 | if tile then 1155 | instance.batch:set( 1156 | instance.id, 1157 | tile.quad, 1158 | instance.x, 1159 | instance.y, 1160 | tile.r, 1161 | tile.sx, 1162 | tile.sy 1163 | ) 1164 | else 1165 | instance.batch:set( 1166 | instance.id, 1167 | instance.x, 1168 | instance.y, 1169 | 0, 1170 | 0) 1171 | 1172 | self.freeBatchSprites[instance.batch] = self.freeBatchSprites[instance.batch] or {} 1173 | table.insert(self.freeBatchSprites[instance.batch], instance) 1174 | end 1175 | end 1176 | 1177 | -- Remove old tile instance 1178 | for i, ins in ipairs(self.tileInstances[instance.gid]) do 1179 | if ins.batch == instance.batch and ins.id == instance.id then 1180 | table.remove(self.tileInstances[instance.gid], i) 1181 | break 1182 | end 1183 | end 1184 | 1185 | -- Add new tile instance 1186 | if tile then 1187 | self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} 1188 | 1189 | local freeBatchSprites = self.freeBatchSprites[instance.batch] 1190 | local newInstance 1191 | if freeBatchSprites and #freeBatchSprites > 0 then 1192 | newInstance = freeBatchSprites[#freeBatchSprites] 1193 | freeBatchSprites[#freeBatchSprites] = nil 1194 | else 1195 | newInstance = {} 1196 | end 1197 | 1198 | newInstance.layer = instance.layer 1199 | newInstance.batch = instance.batch 1200 | newInstance.id = instance.id 1201 | newInstance.gid = tile.gid or 0 1202 | newInstance.x = instance.x 1203 | newInstance.y = instance.y 1204 | newInstance.r = tile.r or 0 1205 | newInstance.oy = tile.r ~= 0 and tile.height or 0 1206 | table.insert(self.tileInstances[tile.gid], newInstance) 1207 | end 1208 | end 1209 | 1210 | --- Convert tile location to pixel location 1211 | -- @param x The X axis location of the point (in tiles) 1212 | -- @param y The Y axis location of the point (in tiles) 1213 | -- @return number The X axis location of the point (in pixels) 1214 | -- @return number The Y axis location of the point (in pixels) 1215 | function Map:convertTileToPixel(x,y) 1216 | if self.orientation == "orthogonal" then 1217 | local tileW = self.tilewidth 1218 | local tileH = self.tileheight 1219 | return 1220 | x * tileW, 1221 | y * tileH 1222 | elseif self.orientation == "isometric" then 1223 | local mapH = self.height 1224 | local tileW = self.tilewidth 1225 | local tileH = self.tileheight 1226 | local offsetX = mapH * tileW / 2 1227 | return 1228 | (x - y) * tileW / 2 + offsetX, 1229 | (x + y) * tileH / 2 1230 | elseif self.orientation == "staggered" or 1231 | self.orientation == "hexagonal" then 1232 | local tileW = self.tilewidth 1233 | local tileH = self.tileheight 1234 | local sideLen = self.hexsidelength or 0 1235 | 1236 | if self.staggeraxis == "x" then 1237 | return 1238 | x * tileW, 1239 | ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0) 1240 | else 1241 | return 1242 | ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0), 1243 | y * tileH 1244 | end 1245 | end 1246 | end 1247 | 1248 | --- Convert pixel location to tile location 1249 | -- @param x The X axis location of the point (in pixels) 1250 | -- @param y The Y axis location of the point (in pixels) 1251 | -- @return number The X axis location of the point (in tiles) 1252 | -- @return number The Y axis location of the point (in tiles) 1253 | function Map:convertPixelToTile(x, y) 1254 | if self.orientation == "orthogonal" then 1255 | local tileW = self.tilewidth 1256 | local tileH = self.tileheight 1257 | return 1258 | x / tileW, 1259 | y / tileH 1260 | elseif self.orientation == "isometric" then 1261 | local mapH = self.height 1262 | local tileW = self.tilewidth 1263 | local tileH = self.tileheight 1264 | local offsetX = mapH * tileW / 2 1265 | return 1266 | y / tileH + (x - offsetX) / tileW, 1267 | y / tileH - (x - offsetX) / tileW 1268 | elseif self.orientation == "staggered" then 1269 | local staggerX = self.staggeraxis == "x" 1270 | local even = self.staggerindex == "even" 1271 | 1272 | local function topLeft(x, y) 1273 | if staggerX then 1274 | if ceil(x) % 2 == 1 and even then 1275 | return x - 1, y 1276 | else 1277 | return x - 1, y - 1 1278 | end 1279 | else 1280 | if ceil(y) % 2 == 1 and even then 1281 | return x, y - 1 1282 | else 1283 | return x - 1, y - 1 1284 | end 1285 | end 1286 | end 1287 | 1288 | local function topRight(x, y) 1289 | if staggerX then 1290 | if ceil(x) % 2 == 1 and even then 1291 | return x + 1, y 1292 | else 1293 | return x + 1, y - 1 1294 | end 1295 | else 1296 | if ceil(y) % 2 == 1 and even then 1297 | return x + 1, y - 1 1298 | else 1299 | return x, y - 1 1300 | end 1301 | end 1302 | end 1303 | 1304 | local function bottomLeft(x, y) 1305 | if staggerX then 1306 | if ceil(x) % 2 == 1 and even then 1307 | return x - 1, y + 1 1308 | else 1309 | return x - 1, y 1310 | end 1311 | else 1312 | if ceil(y) % 2 == 1 and even then 1313 | return x, y + 1 1314 | else 1315 | return x - 1, y + 1 1316 | end 1317 | end 1318 | end 1319 | 1320 | local function bottomRight(x, y) 1321 | if staggerX then 1322 | if ceil(x) % 2 == 1 and even then 1323 | return x + 1, y + 1 1324 | else 1325 | return x + 1, y 1326 | end 1327 | else 1328 | if ceil(y) % 2 == 1 and even then 1329 | return x + 1, y + 1 1330 | else 1331 | return x, y + 1 1332 | end 1333 | end 1334 | end 1335 | 1336 | local tileW = self.tilewidth 1337 | local tileH = self.tileheight 1338 | 1339 | if staggerX then 1340 | x = x - (even and tileW / 2 or 0) 1341 | else 1342 | y = y - (even and tileH / 2 or 0) 1343 | end 1344 | 1345 | local halfH = tileH / 2 1346 | local ratio = tileH / tileW 1347 | local referenceX = ceil(x / tileW) 1348 | local referenceY = ceil(y / tileH) 1349 | local relativeX = x - referenceX * tileW 1350 | local relativeY = y - referenceY * tileH 1351 | 1352 | if (halfH - relativeX * ratio > relativeY) then 1353 | return topLeft(referenceX, referenceY) 1354 | elseif (-halfH + relativeX * ratio > relativeY) then 1355 | return topRight(referenceX, referenceY) 1356 | elseif (halfH + relativeX * ratio < relativeY) then 1357 | return bottomLeft(referenceX, referenceY) 1358 | elseif (halfH * 3 - relativeX * ratio < relativeY) then 1359 | return bottomRight(referenceX, referenceY) 1360 | end 1361 | 1362 | return referenceX, referenceY 1363 | elseif self.orientation == "hexagonal" then 1364 | local staggerX = self.staggeraxis == "x" 1365 | local even = self.staggerindex == "even" 1366 | local tileW = self.tilewidth 1367 | local tileH = self.tileheight 1368 | local sideLenX = 0 1369 | local sideLenY = 0 1370 | 1371 | local colW = tileW / 2 1372 | local rowH = tileH / 2 1373 | if staggerX then 1374 | sideLenX = self.hexsidelength 1375 | x = x - (even and tileW or (tileW - sideLenX) / 2) 1376 | colW = colW - (colW - sideLenX / 2) / 2 1377 | else 1378 | sideLenY = self.hexsidelength 1379 | y = y - (even and tileH or (tileH - sideLenY) / 2) 1380 | rowH = rowH - (rowH - sideLenY / 2) / 2 1381 | end 1382 | 1383 | local referenceX = ceil(x) / (colW * 2) 1384 | local referenceY = ceil(y) / (rowH * 2) 1385 | 1386 | -- If in staggered line, then shift reference by 0.5 of other axes 1387 | if staggerX then 1388 | if (floor(referenceX) % 2 == 0) == even then 1389 | referenceY = referenceY - 0.5 1390 | end 1391 | else 1392 | if (floor(referenceY) % 2 == 0) == even then 1393 | referenceX = referenceX - 0.5 1394 | end 1395 | end 1396 | 1397 | local relativeX = x - referenceX * colW * 2 1398 | local relativeY = y - referenceY * rowH * 2 1399 | local centers 1400 | 1401 | if staggerX then 1402 | local left = sideLenX / 2 1403 | local centerX = left + colW 1404 | local centerY = tileH / 2 1405 | 1406 | centers = { 1407 | { x = left, y = centerY }, 1408 | { x = centerX, y = centerY - rowH }, 1409 | { x = centerX, y = centerY + rowH }, 1410 | { x = centerX + colW, y = centerY }, 1411 | } 1412 | else 1413 | local top = sideLenY / 2 1414 | local centerX = tileW / 2 1415 | local centerY = top + rowH 1416 | 1417 | centers = { 1418 | { x = centerX, y = top }, 1419 | { x = centerX - colW, y = centerY }, 1420 | { x = centerX + colW, y = centerY }, 1421 | { x = centerX, y = centerY + rowH } 1422 | } 1423 | end 1424 | 1425 | local nearest = 0 1426 | local minDist = math.huge 1427 | 1428 | local function len2(ax, ay) 1429 | return ax * ax + ay * ay 1430 | end 1431 | 1432 | for i = 1, 4 do 1433 | local dc = len2(centers[i].x - relativeX, centers[i].y - relativeY) 1434 | 1435 | if dc < minDist then 1436 | minDist = dc 1437 | nearest = i 1438 | end 1439 | end 1440 | 1441 | local offsetsStaggerX = { 1442 | { x = 1, y = 1 }, 1443 | { x = 2, y = 0 }, 1444 | { x = 2, y = 1 }, 1445 | { x = 3, y = 1 }, 1446 | } 1447 | 1448 | local offsetsStaggerY = { 1449 | { x = 1, y = 1 }, 1450 | { x = 0, y = 2 }, 1451 | { x = 1, y = 2 }, 1452 | { x = 1, y = 3 }, 1453 | } 1454 | 1455 | local offsets = staggerX and offsetsStaggerX or offsetsStaggerY 1456 | 1457 | return 1458 | referenceX + offsets[nearest].x, 1459 | referenceY + offsets[nearest].y 1460 | end 1461 | end 1462 | 1463 | --- A list of individual layers indexed both by draw order and name 1464 | -- @table Map.layers 1465 | -- @see TileLayer 1466 | -- @see ObjectLayer 1467 | -- @see ImageLayer 1468 | -- @see CustomLayer 1469 | 1470 | --- A list of individual tiles indexed by Global ID 1471 | -- @table Map.tiles 1472 | -- @see Tile 1473 | -- @see Map.tileInstances 1474 | 1475 | --- A list of tile instances indexed by Global ID 1476 | -- @table Map.tileInstances 1477 | -- @see TileInstance 1478 | -- @see Tile 1479 | -- @see Map.tiles 1480 | 1481 | --- A list of no-longer-used batch sprites, indexed by batch 1482 | --@table Map.freeBatchSprites 1483 | 1484 | --- A list of individual objects indexed by Global ID 1485 | -- @table Map.objects 1486 | -- @see Object 1487 | 1488 | --- @table TileLayer 1489 | -- @field name The name of the layer 1490 | -- @field x Position on the X axis (in pixels) 1491 | -- @field y Position on the Y axis (in pixels) 1492 | -- @field width Width of layer (in tiles) 1493 | -- @field height Height of layer (in tiles) 1494 | -- @field visible Toggle if layer is visible or hidden 1495 | -- @field opacity Opacity of layer 1496 | -- @field properties Custom properties 1497 | -- @field data A tileWo dimensional table filled with individual tiles indexed by [y][x] (in tiles) 1498 | -- @field update Update function 1499 | -- @field draw Draw function 1500 | -- @see Map.layers 1501 | -- @see Tile 1502 | 1503 | --- @table ObjectLayer 1504 | -- @field name The name of the layer 1505 | -- @field x Position on the X axis (in pixels) 1506 | -- @field y Position on the Y axis (in pixels) 1507 | -- @field visible Toggle if layer is visible or hidden 1508 | -- @field opacity Opacity of layer 1509 | -- @field properties Custom properties 1510 | -- @field objects List of objects indexed by draw order 1511 | -- @field update Update function 1512 | -- @field draw Draw function 1513 | -- @see Map.layers 1514 | -- @see Object 1515 | 1516 | --- @table ImageLayer 1517 | -- @field name The name of the layer 1518 | -- @field x Position on the X axis (in pixels) 1519 | -- @field y Position on the Y axis (in pixels) 1520 | -- @field visible Toggle if layer is visible or hidden 1521 | -- @field opacity Opacity of layer 1522 | -- @field properties Custom properties 1523 | -- @field image Image to be drawn 1524 | -- @field update Update function 1525 | -- @field draw Draw function 1526 | -- @see Map.layers 1527 | 1528 | --- Custom Layers are used to place userdata such as sprites within the draw order of the map. 1529 | -- @table CustomLayer 1530 | -- @field name The name of the layer 1531 | -- @field x Position on the X axis (in pixels) 1532 | -- @field y Position on the Y axis (in pixels) 1533 | -- @field visible Toggle if layer is visible or hidden 1534 | -- @field opacity Opacity of layer 1535 | -- @field properties Custom properties 1536 | -- @field update Update function 1537 | -- @field draw Draw function 1538 | -- @see Map.layers 1539 | -- @usage 1540 | -- -- Create a Custom Layer 1541 | -- local spriteLayer = map:addCustomLayer("Sprite Layer", 3) 1542 | -- 1543 | -- -- Add data to Custom Layer 1544 | -- spriteLayer.sprites = { 1545 | -- player = { 1546 | -- image = lg.newImage("assets/sprites/player.png"), 1547 | -- x = 64, 1548 | -- y = 64, 1549 | -- r = 0, 1550 | -- } 1551 | -- } 1552 | -- 1553 | -- -- Update callback for Custom Layer 1554 | -- function spriteLayer:update(dt) 1555 | -- for _, sprite in pairs(self.sprites) do 1556 | -- sprite.r = sprite.r + math.rad(90 * dt) 1557 | -- end 1558 | -- end 1559 | -- 1560 | -- -- Draw callback for Custom Layer 1561 | -- function spriteLayer:draw() 1562 | -- for _, sprite in pairs(self.sprites) do 1563 | -- local x = math.floor(sprite.x) 1564 | -- local y = math.floor(sprite.y) 1565 | -- local r = sprite.r 1566 | -- lg.draw(sprite.image, x, y, r) 1567 | -- end 1568 | -- end 1569 | 1570 | --- @table Tile 1571 | -- @field id Local ID within Tileset 1572 | -- @field gid Global ID 1573 | -- @field tileset Tileset ID 1574 | -- @field quad Quad object 1575 | -- @field properties Custom properties 1576 | -- @field terrain Terrain data 1577 | -- @field animation Animation data 1578 | -- @field frame Current animation frame 1579 | -- @field time Time spent on current animation frame 1580 | -- @field width Width of tile 1581 | -- @field height Height of tile 1582 | -- @field sx Scale value on the X axis 1583 | -- @field sy Scale value on the Y axis 1584 | -- @field r Rotation of tile (in radians) 1585 | -- @field offset Offset drawing position 1586 | -- @field offset.x Offset value on the X axis 1587 | -- @field offset.y Offset value on the Y axis 1588 | -- @see Map.tiles 1589 | 1590 | --- @table TileInstance 1591 | -- @field batch Spritebatch the Tile Instance belongs to 1592 | -- @field id ID within the spritebatch 1593 | -- @field gid Global ID 1594 | -- @field x Position on the X axis (in pixels) 1595 | -- @field y Position on the Y axis (in pixels) 1596 | -- @see Map.tileInstances 1597 | -- @see Tile 1598 | 1599 | --- @table Object 1600 | -- @field id Global ID 1601 | -- @field name Name of object (non-unique) 1602 | -- @field shape Shape of object 1603 | -- @field x Position of object on X axis (in pixels) 1604 | -- @field y Position of object on Y axis (in pixels) 1605 | -- @field width Width of object (in pixels) 1606 | -- @field height Heigh tof object (in pixels) 1607 | -- @field rotation Rotation of object (in radians) 1608 | -- @field visible Toggle if object is visible or hidden 1609 | -- @field properties Custom properties 1610 | -- @field ellipse List of verticies of specific shape 1611 | -- @field rectangle List of verticies of specific shape 1612 | -- @field polygon List of verticies of specific shape 1613 | -- @field polyline List of verticies of specific shape 1614 | -- @see Map.objects 1615 | 1616 | return setmetatable({}, STI) 1617 | --------------------------------------------------------------------------------