├── 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 |
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 |
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 |
--------------------------------------------------------------------------------