Hey guys, back here with my grid code again. I’m looking for some advice on any optimizations I could make and improvements in style. My main efficiency problems were solved here, Optimizing 2d array generation for flow fields, but since this is the foundation for a lot of what I plan to work on, I’ve posted the entire grid code + the cell code and some additional questions.
I have two main questions:
- Is it worth having a separate “cell” class if realistically my only cell functions are :GetNeighbors() and a basic constructor? I’m positive I could put these two classes together and have a simple grid.GetNeighbors(cell) type function.
- If I wanted to compute Cell.Direction values at the same time they are being generated, how could I go about that? The problem with my current implementation (not shown) is that I compute values based on the neighbors’ distance to the goal cell, which would mean that I would need to know the goal position and all of the cell neighbors prior to computation.
--// Modules
local Enumerations = require(game.ReplicatedStorage.Enumerations)
--// Variables
local cellDirs = Enumerations.CellDirection
--// Module
local Cell = {}
Cell.__index = Cell
--// Public
function Cell.new(grid, gridIndex, worldPos)
local cell = setmetatable({}, Cell)
cell.ParentGrid = grid
cell.GridIndex = gridIndex
cell.Position = worldPos
cell.Grid = grid
cell.Direction = Vector2.new()
return cell
end
function Cell:GetNeighbors()
local cell_x, cell_y = self.GridIndex.X, self.GridIndex.Y
local grid = self.Grid
-- neighbors
local neighbors = {}
for _, dir in pairs(cellDirs) do
neighbors[#neighbors+1] = grid:GetCell(cell_x + dir.X, cell_y + dir.Y)
end
return neighbors
end
--// Return
return Cell
local Cell = require(script.Cell)
--// Module
local Grid = {}
Grid.__index = Grid
--// Public
local function getXY(n, rows)
return (n-1) % rows + 1, math.floor((n-1) / rows) + 1
end
local function getIndex(x, y, rows)
return (x + rows * (y - 1))
end
function Grid.new(size, cellSize, origin)
-- constructs and returns a new grid
-- size: Vector3
-- cellSize: Int
-- origin: Vector3
local area = size.X * size.Y
local grid = table.create(area)
grid.Origin = origin or Vector3.new()
grid.Size = size
grid.CellSize = cellSize
for cellNum = 1, area do
local x, y = getXY(cellNum, size.X)
grid[cellNum] = Cell.new(
grid,
Vector2.new(x, y),
Vector3.new(x * cellSize, 0, y * cellSize) + grid.Origin
)
end
setmetatable(grid, Grid)
return grid
end
function Grid:Traverse(toX, toY, call, ...)
-- gets cells between [toX][toY]
-- passes each cell into call function first argument
-- this cannot be used as a setter, only as a getter
if toX > self.Size.X then
warn("Tried to traverse beyond X reach.")
elseif toY > self.Size.Y then
warn("Tried to traverse beyond Y reach.")
end
local area = toX * toY
local rows = self.Size.X
for cellNum = 1, area do
local x, y = getXY(cellNum, rows)
local cell = self:GetCell(x, y)
if cell then
call(cell, ...)
end
end
end
function Grid:GetCell(x, y)
-- get a cell at x, y
-- may return nil
local rows = self.Size.X
if x > rows or x <= 0 then
return nil
end
return self[getIndex(x, y, rows)]
end
function Grid:RemoveCell(x, y)
-- remove a cell at x, y
local rows = self.Size.X
self[getIndex(x, y, rows)] = nil
end
function Grid:GetRandomCell()
-- recursively run :getCell() at random(x, y)
-- until a cell is found
local cellN = math.random(1, self.Size.X * self.Size.Y)
local x, y = getXY(cellN, self.Size.X)
local cell = self:GetCell(x, y)
if cell then
return cell
else
return self:GetRandomCell()
end
end
function Grid:GetWorldPosition(index)
-- converts cell index to world position
-- returns Vec3
local cellSize = self.CellSize
return Vector3.new(index.X * cellSize, 0, index.Y * cellSize)
end
function Grid:GetIndex(worldPos)
-- converts worldPos to cell index
-- returns Vec2
local cellSize = self.CellSize
local origin = self.Origin
if cellSize and worldPos then
return Vector2.new(
math.floor((worldPos-origin).X/cellSize),
math.floor((worldPos-origin).Z/cellSize)
)
end
end
--// Return
return Grid
Feel free to check out the post I linked above as it’s possible my implementation is not entirely in line with the solution.