Update "literary" parts.

This commit is contained in:
Gonzalo Delgado 2024-09-10 07:45:47 -03:00
parent b9e1de7e67
commit d1952ef33e
4 changed files with 50 additions and 37 deletions

View File

@ -244,13 +244,14 @@ But ~map~ would feel too lonely being the only functional function around, let's
end
#+end_src
To achieve Flicky's screen wrap-around trick (or part of it at least), we need a ~normalize~ function that given a number ~n~, it returns a new ~n~ that's within a range offset the original ~n~, so if the screen's width is 100, and ~n~ is ~110~, normalizing it would result in ~10~, if that makes sense:
To achieve Flicky's screen wrap-around trick (or part of it at least), we'll start with a ~normalize~ function that given a number ~n~, it returns a new ~n~ that's within a range offset the original ~n~, so if the screen's width is 100, and ~n~ is ~110~, normalizing it would result in ~10~, if that makes sense:
#+begin_src lua :tangle helpers.lua
function helpers.normalize(n, min, max)
local range = max - min
return ((n - min) % range + range) % range + min
end
#+end_src
[[Try implementing wrap-around with polar coordinates and cosine][I'm planning on later replacing that with polar coordinates]] instead, think of levels being drawn on a cylinder, and the player seamlessly moving around it, so ~normalize~ will hopefully go away in the future.
We'll need a function to tell if two rectangles overlap for basic collision detection, with rectangles being Lua tables with these field names:
- x :: horizontal pixel position
@ -275,7 +276,9 @@ Finally return the helpers object containing all those functions, and load it fr
#+end_src
* Swept AABB
#+begin_notes
This is not being used yet, but I'm keeping it for now since I believe it'll be useful for better collision handling at high speeds, like falls, or enemies going directly at the player.
#+end_notes
This is what I ended up learning with more difficulty than I expected. I could probably get away with something simpler for this game, but I want to learn as much as I can.
In any case, the idea here is to detect two rectangles colliding and avoid the "tunneling" problem
@ -349,6 +352,9 @@ Lua:
#+end_src
* Broad-Phasing collisions with solid tiles
#+begin_notes
This is also not wired into the game just yet, although I'm not sure this will be needed, so I may end up removing it.
#+end_notes
This basically comes down to finding the (solid) tiles within the rectangle determined by the
initial and final positions of an entity (the player for now).
@ -567,11 +573,16 @@ Let's add a function to draw some debug stuff, starting with just the player's p
#+end_src
* Map class
I'm using the venerable [[https://www.mapeditor.org/][Tiled]] to create the level(s). Tiled is so nice it exports levels as .lua files containing a big table with all the information one can need.
Go through each tile in the map and query its value to draw solid tiles (value > 0) in color (this will later be replaced by drawing the actual tile graphics form the tilesheet.)
Tile values deserve a quick explanation: Tiled provides a single-dimension array representing each tile on the map. Each element in the array is simply a number representing the tile's index (to be mapped to a spritesheet), with 0 meaning "no tile here".
I'll need an interface for that though, to make it more manageable and fit my style of coding, so let's create a ~Map~ class that can provide useful functionality, such as:
- load the .lua file generated by Tiled
- spit out [[Tile class][~Tile~ objects]] that provide information such as their location and whether their solid
tiles or not
- draw the whole level to the screen
- query tiles by pixel position
Of course, a ~Map~ will be an ~Object subclass:
#+begin_src lua :tangle map.lua
local Object = require("object")
local Tile = require("tile")
@ -579,10 +590,13 @@ Tile values deserve a quick explanation: Tiled provides a single-dimension array
local inspect = require("inspect/inspect")
local Map = Object:extend()
#+end_src
The map will be initialized with just the path to the Tiled Lua file, which it'll load and get some metadata from. Mainly, it'll look up a "platforms" layer, which is where I'm laying out the blocks that make up a level's platforms.
#+begin_src lua :tangle map.lua
function Map:init(filename)
Map.__super.init(self)
local map = dofile(filename) -- love.filesystem.load(filename)
local map = love.filesystem.load(filename)()
local platform_layers = helpers.filter(map.layers, function (layer) return layer.name == "platforms" end)
self.width = map.width
self.height = map.height
@ -591,7 +605,14 @@ Tile values deserve a quick explanation: Tiled provides a single-dimension array
self.data = platform_layers[1].data
self.max_pixel = {x=map.width*map.tilewidth, y=map.height*map.tileheight}
end
#+end_src
We'll need a way to iterate through each of the platform tiles of a level. Tiled provides a single-dimension array representing each tile on the map. Each element in the array is simply a number representing the tile's index (to be mapped to a spritesheet), with 0 meaning "no tile here".
We'll translate that to instances of our [[Tile class][~Tile~ class]] which will use that value for its ~solid~ attribute.
I tried using the ~__ipairs~ metamethod to get a nice ~for i, tile in ipairs(map) do~ bit of code, but turns out ~__ipairs~ was deprecated and removed from Lua, bummer. Instead, we'll simply have a ~itertiles~ method that will do the same:
#+begin_src lua :tangle map.lua
function Map:itertiles()
local function iterator(map, index)
index = index + 1
@ -605,7 +626,10 @@ Tile values deserve a quick explanation: Tiled provides a single-dimension array
end
return iterator, self, 0
end
#+end_src
Having ~Map:itertiles~ makes drawing the map to the screen super simple:
#+begin_src lua :tangle map.lua
function Map:draw()
for i, tile in self:itertiles() do
love.graphics.setColor(0.9, 0.8, 0.7)
@ -617,7 +641,10 @@ Tile values deserve a quick explanation: Tiled provides a single-dimension array
love.graphics.rectangle("line", tile.pixel_rect.x, tile.pixel_rect.y, tile.w, tile.h)
end
end
#+end_src
Finally, we'll need the map to provide the tile for a given pixel position on the screen. This will be useful for how I'm doing collision detection.
#+begin_src lua :tangle map.lua
function Map:get_tile_from_pixel(pixel)
local tile_x = math.floor(pixel.x/self.tilewidth) + 1
local tile_y = math.floor(pixel.y/self.tileheight) + 1
@ -630,15 +657,21 @@ Tile values deserve a quick explanation: Tiled provides a single-dimension array
* Tile class
#+begin_src lua :tangle tile.lua
local inspect = require("inspect/inspect")
local Vector = require("vector")
local helpers = require("helpers")
Tiles will be represented as very simple objects consisting of basic attributes such as:
- width and height
- tile-space position (not pixel)
- whether their solid or not
local default_w = 8
local default_h = 8
I'm making tiles be vectors in order to easily find adjacent tiles by adding or subtracting unit vectors.
#+begin_src lua :tangle tile.lua
local Vector = require("vector")
local Tile = Vector:extend()
#+end_src
The initializer is otherwise simple, storing the arguments and creating a ~pixel_rect~ table to easily map a tile to its pixels on the screen.
#+begin_src lua :tangle tile.lua
function Tile:init(x, y, w, h, value)
Tile.__super.init(self, x, y)
@ -654,19 +687,9 @@ Tile values deserve a quick explanation: Tiled provides a single-dimension array
self.solid = not (value == 0 or value == nil)
end
function Tile:from_pixel(pixel, w, h)
w = w or default_w
h = h or default_h
return Tile:new(math.floor(pixel.x/w) + 1, math.floor(pixel.y/h) + 1, w, h)
end
return Tile
#+end_src
#+begin_src lua
local Tile = require("tile")
#+end_src
* Entity class
If my mind's bandwidth allows, I may also learn some ECS, so let's start with an entity class that has position and velocity (which should later become components?).
@ -1086,8 +1109,11 @@ Explain ~game.map:box2d_draw~ call for debugging purposes.
** DONE Bugfix level wrap-around
Can't move past right side of the level, but going to the left works (?)
** TODO Update "literary" parts
** DONE Update "literary" parts
DEADLINE: <2024-09-11 mié>
:LOGBOOK:
CLOCK: [2024-09-10 mar 07:08]--[2024-09-10 mar 07:45] => 0:37
:END:
** TODO Player slide
Tweak friction values

View File

@ -121,8 +121,6 @@ end
local states = require("states")
local Tile = require("tile")
local Entity = require("entity")
local scale_width, scale_height, current_state

View File

@ -7,7 +7,7 @@ local Map = Object:extend()
function Map:init(filename)
Map.__super.init(self)
local map = dofile(filename) -- love.filesystem.load(filename)
local map = love.filesystem.load(filename)()
local platform_layers = helpers.filter(map.layers, function (layer) return layer.name == "platforms" end)
self.width = map.width
self.height = map.height

View File

@ -1,9 +1,4 @@
local inspect = require("inspect/inspect")
local Vector = require("vector")
local helpers = require("helpers")
local default_w = 8
local default_h = 8
local Tile = Vector:extend()
@ -21,10 +16,4 @@ function Tile:init(x, y, w, h, value)
self.solid = not (value == 0 or value == nil)
end
function Tile:from_pixel(pixel, w, h)
w = w or default_w
h = h or default_h
return Tile:new(math.floor(pixel.x/w) + 1, math.floor(pixel.y/h) + 1, w, h)
end
return Tile