Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Terrain is now generated as needed in a separate thread

  • Loading branch information...
commit 4219d7cec0db44445401a5302af6f09cfde9b974 1 parent 2f4bb06
@Middlerun authored
View
35 TSerial.lua
@@ -0,0 +1,35 @@
+-- TSerial v1.2, a simple table serializer which turns tables into Lua script
+-- by Taehl (SelfMadeSpirit@gmail.com)
+
+-- Usage: table = TSerial.unpack( TSerial.pack(table) )
+TSerial = {}
+function TSerial.pack(t)
+ assert(type(t) == "table", "Can only TSerial.pack tables.")
+ if not t then return nil end
+ local s = "{"
+ for k, v in pairs(t) do
+ local tk, tv = type(k), type(v)
+ if tk == "boolean" then k = k and "[true]" or "[false]"
+ elseif tk == "number" then k = "["..k.."]"
+ elseif tk == "string" then -- no transform needed
+ elseif tk == "table" then k = "["..TSerial.pack(k).."]"
+ else error("Attempted to Tserialize a table with an invalid key: "..tostring(k))
+ end
+ if tv == "boolean" then v = v and "true" or "false"
+ elseif tv == "number" then -- no transform needed
+ elseif tv == "string" then v = string.format("%q", v)
+ elseif tv == "table" then v = TSerial.pack(v)
+ else error("Attempted to Tserialize a table with an invalid value: "..tostring(v))
+ end
+ s = s..k.."="..v..","
+ end
+ return s.."}"
+end
+
+function TSerial.unpack(s)
+ assert(type(s) == "string", "Can only TSerial.unpack strings.")
+ loadstring("TSerial.table="..s)()
+ local t = TSerial.table
+ TSerial.table = nil
+ return t
+end
View
33 chunk.lua
@@ -1,4 +1,3 @@
-
Chunk = {}
function Chunk:new()
@@ -7,6 +6,8 @@ function Chunk:new()
self.__index = self
o.generated = false
+ o.r = nil
+ o.c = nil
o.block = {}
o.perlin = {}
o.coalNoise = {}
@@ -15,14 +16,10 @@ function Chunk:new()
o.perlin[r] = {}
o.coalNoise[r] = {}
for c = 1, 32 do
- o.block[r][c] = 0
+ o.block[r][c] = 255
o.perlin[r][c] = 0
end
end
- o.framebuffer = love.graphics.newFramebuffer(512, 512)
- o.framebuffer:setFilter("linear", "nearest")
- o.framebufferPerlin = love.graphics.newFramebuffer(512, 512)
- o.framebufferPerlin:setFilter("linear", "nearest")
return o
end
@@ -45,17 +42,17 @@ function Chunk:generate(seed, chunkR, chunkC)
value = value - absR * 0.02
end
- if value > 0.5 then self.block[r][c] = air
- elseif value > 1.4 - dirtMargin or value < -0.6 then self.block[r][c] = dirt
- else self.block[r][c] = stone
+ if value > 0.5 then self.block[r][c] = AIR
+ elseif value > 1.4 - dirtMargin or value < -0.6 then self.block[r][c] = DIRT
+ else self.block[r][c] = STONE
end
- if self.block[r][c] == stone and self.coalNoise[r][c] > 0.08 then self.block[r][c] = coalOre end
+ if self.block[r][c] == STONE and self.coalNoise[r][c] > 0.08 then self.block[r][c] = COAL_ORE end
end
end
self.generated = true
- self:render()
- self:renderPerlin()
+ self.r = chunkR
+ self.c = chunkC
end
function Chunk:generatePerlin(seed, chunkR, chunkC)
@@ -193,11 +190,15 @@ function Chunk:isGenerated()
end
function Chunk:render()
+ if self.framebuffer == nil then
+ self.framebuffer = love.graphics.newFramebuffer(512, 512)
+ self.framebuffer:setFilter("linear", "nearest")
+ end
love.graphics.setRenderTarget(self.framebuffer)
love.graphics.setColor(255, 255, 255, 255)
for r = 1, 32 do
for c = 1, 32 do
- if self.block[r][c] ~= air then
+ if self.block[r][c] ~= AIR and self.block[r][c] ~= UNGENERATED then
love.graphics.draw(images[self.block[r][c]], (c-1)*16, (r-1)*16)
end
end
@@ -206,11 +207,15 @@ function Chunk:render()
end
function Chunk:renderPerlin()
+ if self.framebufferPerlin == nil then
+ self.framebufferPerlin = love.graphics.newFramebuffer(512, 512)
+ self.framebufferPerlin:setFilter("linear", "nearest")
+ end
love.graphics.setRenderTarget(self.framebufferPerlin)
love.graphics.setColor(255, 255, 255, 255)
for r = 1, 32 do
for c = 1, 32 do
- if self.block[r][c] ~= air then
+ if self.block[r][c] ~= AIR then
love.graphics.setColor(128 + 80 * self.perlin[r][c], 128 + 80 * self.perlin[r][c], 128 + 80 * self.perlin[r][c], 255)
love.graphics.rectangle("fill", (c-1)*16, (r-1)*16, 16, 16)
end
View
16 collision.lua
@@ -12,9 +12,9 @@ function checkCollisions(terrain, player)
-- Check right hit
for y = 0, player.height, player.height/2 do
- if player.againstRightWall and terrain:getBlock(math.ceil(player.y - y), math.ceil(player.x + player.width/2 + 0.01)) ~= air then
+ if player.againstRightWall and terrain:getBlock(math.ceil(player.y - y), math.ceil(player.x + player.width/2 + 0.01)) ~= AIR then
againstRightWall = true
- elseif not player.againstRightWall and player.x > player.oldX and terrain:getBlock(math.ceil(player.y - y), math.ceil(player.x + player.width/2)) ~= air then
+ elseif not player.againstRightWall and player.x > player.oldX and terrain:getBlock(math.ceil(player.y - y), math.ceil(player.x + player.width/2)) ~= AIR then
frac = (math.ceil(player.oldX + player.width/2) - (player.oldX + player.width/2)) / (player.x - player.oldX)
mid = player.oldY - y + frac * (player.y - player.oldY)
if mid >= math.ceil(player.y - y) - 1 and mid <= math.ceil(player.y - y) and frac < moveFrac then
@@ -25,9 +25,9 @@ function checkCollisions(terrain, player)
end--]]
-- Check left hit
for y = 0, player.height, player.height/2 do
- if player.againstLeftWall and terrain:getBlock(math.ceil(player.y - y), math.ceil(player.x - player.width/2 - 0.01)) ~= air then
+ if player.againstLeftWall and terrain:getBlock(math.ceil(player.y - y), math.ceil(player.x - player.width/2 - 0.01)) ~= AIR then
againstLeftWall = true
- elseif not player.againstLeftWall and player.x < player.oldX and terrain:getBlock(math.ceil(player.y - y), math.ceil(player.x - player.width/2)) ~= air then
+ elseif not player.againstLeftWall and player.x < player.oldX and terrain:getBlock(math.ceil(player.y - y), math.ceil(player.x - player.width/2)) ~= AIR then
frac = (math.ceil(player.oldX - player.width/2) - 1 - (player.oldX - player.width/2)) / (player.x - player.oldX)
mid = player.oldY - y + frac * (player.y - player.oldY)
if mid >= math.ceil(player.y - y) - 1 and mid <= math.ceil(player.y - y) and frac < moveFrac then
@@ -39,7 +39,7 @@ function checkCollisions(terrain, player)
if player.falling then
-- Check ceiling hit
for x = -player.width/2, player.width, player.width do
- if player.y < player.oldY and terrain:getBlock(math.ceil(player.y - player.height), math.ceil(player.x + x)) ~= air then
+ if player.y < player.oldY and terrain:getBlock(math.ceil(player.y - player.height), math.ceil(player.x + x)) ~= AIR then
frac = (math.ceil(player.oldY - player.height) - 1 - (player.oldY - player.height)) / (player.y - player.oldY)
mid = player.oldX + x + frac * (player.x - player.oldX)
if mid >= math.ceil(player.x + x) - 1 and mid <= math.ceil(player.x + x) and frac < moveFrac then
@@ -50,7 +50,7 @@ function checkCollisions(terrain, player)
end--]]
-- Check ground hit
for x = -player.width/2, player.width, player.width do
- if player.y > player.oldY and terrain:getBlock(math.ceil(player.y), math.ceil(player.x + x)) ~= air then
+ if player.y > player.oldY and terrain:getBlock(math.ceil(player.y), math.ceil(player.x + x)) ~= AIR then
frac = (math.ceil(player.oldY) - (player.oldY)) / (player.y - player.oldY)
mid = player.oldX + x + frac * (player.x - player.oldX)
if mid >= math.ceil(player.x + x) - 1 and mid <= math.ceil(player.x + x) and frac < moveFrac then
@@ -61,8 +61,8 @@ function checkCollisions(terrain, player)
end--]]
else
-- Check player has ground to stand on
- if terrain:getBlock(math.floor(player.y) + 1, math.floor(player.x - player.width / 2) + 1) == air
- and terrain:getBlock(math.floor(player.y) + 1, math.floor(player.x + player.width / 2) + 1) == air then
+ if terrain:getBlock(math.floor(player.y) + 1, math.floor(player.x - player.width / 2) + 1) == AIR
+ and terrain:getBlock(math.floor(player.y) + 1, math.floor(player.x + player.width / 2) + 1) == AIR then
player.falling = true
end
end
View
45 generator.lua
@@ -0,0 +1,45 @@
+this_thread = love.thread.getThread()
+require("love.timer")
+require("love.filesystem")
+love.filesystem.load("TSerial.lua")()
+love.filesystem.load("chunk.lua")()
+
+-- Block codes
+AIR = 0
+STONE = 1
+DIRT = 3
+COAL_ORE = 16
+UNGENERATED = 255
+
+rand = {mySeed = 1, lastN = -1}
+function rand:get(seed, n)
+ if n <= 0 then n = -2 * n
+ else n = 2 * n - 1
+ end
+
+ if seed ~= self.mySeed or self.lastN < 0 or n <= self.lastN then
+ self.mySeed = seed
+ math.randomseed(seed)
+ self.lastN = -1
+ end
+ while self.lastN < n do
+ num = math.random()
+ self.lastN = self.lastN + 1
+ end
+ return num - 0.5
+end
+
+run = true
+
+while run do
+ this_thread:send("ready", true)
+ commandMsg = this_thread:demand("command")
+ if commandMsg == "quit" then run = false
+ else
+ this_thread:send("ready", false)
+ local command = TSerial.unpack(commandMsg)
+ chunk = Chunk:new()
+ chunk:generate(command.seed, command.r, command.c)
+ this_thread:send("chunk", TSerial.pack(chunk))
+ end
+end
View
104 main.lua
@@ -5,27 +5,35 @@ love.filesystem.load("player.lua")()
love.filesystem.load("collision.lua")()
love.filesystem.setIdentity("lovecraft")
-air = 0
-stone = 1
-dirt = 3
-coalOre = 16
-rand = {mySeed = 1, lastN = -1}
-view = {zoom = 32, x = 0, y = 0}
+-- Modes
+MENU = 1
+GENERATING = 2
+PLAY = 3
+
+-- Block codes
+AIR = 0
+STONE = 1
+DIRT = 3
+COAL_ORE = 16
+UNGENERATED = 255
+
images = {}
-images[stone] = love.graphics.newImage("gfx/stone.png")
-images[stone]:setFilter("linear", "nearest")
-images[dirt] = love.graphics.newImage("gfx/dirt.png")
-images[dirt]:setFilter("linear", "nearest")
-images[coalOre] = love.graphics.newImage("gfx/coalOre.png")
-images[coalOre]:setFilter("linear", "nearest")
+images[STONE] = love.graphics.newImage("gfx/stone.png")
+images[STONE]:setFilter("linear", "nearest")
+images[DIRT] = love.graphics.newImage("gfx/dirt.png")
+images[DIRT]:setFilter("linear", "nearest")
+images[COAL_ORE] = love.graphics.newImage("gfx/coalOre.png")
+images[COAL_ORE]:setFilter("linear", "nearest")
breakImage = {}
for i = 1, 8 do
breakImage[i] = love.graphics.newImage("gfx/break" .. i .. ".png")
breakImage[i]:setFilter("linear", "nearest")
end
-
genChunk = love.graphics.newImage("gfx/genChunk.png")
genChunk:setFilter("linear", "nearest")
+
+rand = {mySeed = 1, lastN = -1}
+view = {zoom = 32, x = 0, y = 0}
oldMouse = {x = 0, y = 0}
cursor = {x = 0, y = 0}
cursorFade = false
@@ -35,32 +43,28 @@ selected = 1
mineBlock = {r = nil, c = nil}
mineProgress = 0
durability = {}
-durability[dirt] = 1
-durability[stone] = 2
-durability[coalOre] = 3
+durability[DIRT] = 0
+durability[STONE] = 0
+durability[COAL_ORE] = 0
placeTime = 0
function love.load()
+ generator = love.thread.newThread("generator", "generator.lua")
+ generator:start()
showPerlin = false
player = Player:new()
terrain = Terrain:new()
- for r = -2, 1 do
- for c = -2, 1 do
- chunk = Chunk:new()
- chunk:generate(terrain:getSeed(), r, c)
- terrain:addChunk(chunk, r, c)
- end
- end
+ terrain:generateInitial()
player.x = 0.5
player.y = -60
- while terrain:getBlock(math.floor(player.y), math.floor(player.x) + 1) == air do
+ while terrain:getBlock(math.floor(player.y), math.floor(player.x) + 1) == AIR do
player.y = player.y + 1
end
- while terrain:getBlock(math.floor(player.y), math.floor(player.x) + 1) ~= air do
+ while terrain:getBlock(math.floor(player.y), math.floor(player.x) + 1) ~= AIR do
player.y = player.y - 1
end
@@ -125,12 +129,12 @@ function love.update(dt)
inreach = (pythag(cursor.x, cursor.y, player.x, player.y - player.height/2) < 5)
if inreach then
local block = terrain:getBlock(math.ceil(cursor.y), math.ceil(cursor.x))
- if love.mouse.isDown("l") and block ~= air then
+ if love.mouse.isDown("l") and block ~= AIR and block ~= UNGENERATED then
if math.ceil(cursor.x) == mineBlock.c and math.ceil(cursor.y) == mineBlock.r then
mineProgress = mineProgress + dt / durability[block]
if mineProgress >= 1 then
player:give(block)
- terrain:setBlock(math.ceil(cursor.y), math.ceil(cursor.x), air)
+ terrain:setBlock(math.ceil(cursor.y), math.ceil(cursor.x), AIR)
mineProgress = 0
mineBlock.r = nil
mineBlock.c = nil
@@ -140,12 +144,12 @@ function love.update(dt)
mineBlock.c = math.ceil(cursor.x)
mineProgress = dt / durability[block]
end
- elseif love.mouse.isDown("r") and block == air and placeTime > 0.2 then
+ elseif love.mouse.isDown("r") and block == AIR and placeTime > 0.2 then
-- Temporary hack, change later
- if selected == 1 then block = dirt
- elseif selected == 2 then block = stone
- elseif selected == 3 then block = coalOre
+ if selected == 1 then block = DIRT
+ elseif selected == 2 then block = STONE
+ elseif selected == 3 then block = COAL_ORE
end
-- end hack
@@ -165,6 +169,14 @@ function love.update(dt)
end
placeTime = placeTime + dt
+
+ -- Generate new chunks
+ for r = math.floor((player.y - 48) / 32), math.floor((player.y + 48) / 32) do
+ for c = math.floor((player.x - 48) / 32), math.floor((player.x + 48) / 32) do
+ terrain:generate(r, c)
+ end
+ end
+ terrain:checkGenerator()
end
@@ -194,13 +206,13 @@ function love.draw()
love.graphics.setColor(0, 0, 0, 127)
if selected == 1 then love.graphics.setColor(0, 0, 0, 255) end
- love.graphics.print("Dirt: " .. player:checkInventory(dirt), 50, 50)
+ love.graphics.print("Dirt: " .. player:checkInventory(DIRT), 50, 50)
love.graphics.setColor(0, 0, 0, 127)
if selected == 2 then love.graphics.setColor(0, 0, 0, 255) end
- love.graphics.print("Stone: " .. player:checkInventory(stone), 50, 80)
+ love.graphics.print("Stone: " .. player:checkInventory(STONE), 50, 80)
love.graphics.setColor(0, 0, 0, 127)
if selected == 3 then love.graphics.setColor(0, 0, 0, 255) end
- love.graphics.print("Coal ore: " .. player:checkInventory(coalOre), 50, 110)
+ love.graphics.print("Coal ore: " .. player:checkInventory(COAL_ORE), 50, 110)
end
@@ -209,6 +221,8 @@ function love.keypressed(k, u)
if k == "p" then
showPerlin = not showPerlin
elseif k == "escape" then
+ generator:send("command", "quit")
+ generator:wait()
love.event.push("q")
elseif k == "[" then
if view.zoom > 1 then view.zoom = view.zoom / 2 end
@@ -257,17 +271,16 @@ end
function drawTerrain(terrain, zoom, x, y)
- local minR = math.max(-2, math.floor((y - zoom * (love.graphics.getHeight() / 2)) / 32))
- local maxR = math.min(1, math.floor((y + zoom * (love.graphics.getHeight() / 2)) / 32))
- local minC = math.max(-2, math.floor((x - zoom * (love.graphics.getWidth() / 2)) / 32))
- local maxC = math.min(1, math.floor((x + zoom * (love.graphics.getWidth() / 2)) / 32))
+ local minR = math.max(terrain.rMin, math.floor((y - zoom * (love.graphics.getHeight() / 2)) / 32))
+ local maxR = math.min(terrain.rMax, math.floor((y + zoom * (love.graphics.getHeight() / 2)) / 32))
+ local minC = math.max(terrain.cMin, math.floor((x - zoom * (love.graphics.getWidth() / 2)) / 32))
+ local maxC = math.min(terrain.cMax, math.floor((x + zoom * (love.graphics.getWidth() / 2)) / 32))
love.graphics.setColor(255, 255, 255, 255)
for r = minR, maxR do
for c = minC, maxC do
if terrain:hasChunk(r, c) then
+ if terrain:getChunk(r, c).framebuffer == nil then terrain:getChunk(r, c):render() end
love.graphics.draw(terrain:getChunk(r, c).framebuffer, (32*c-x)*zoom + love.graphics.getWidth()/2, (32*r-y)*zoom+love.graphics.getHeight()/2, 0, zoom/16, zoom/16)
- else
- love.graphics.draw(genChunk, (x-32*c)*zoom + love.graphics.getWidth()/2, (y-32*r)*zoom+love.graphics.getHeight()/2, 0, zoom/8, zoom/8)
end
end
end
@@ -276,17 +289,16 @@ end
function drawTerrainPerlin(terrain, zoom, x, y)
- local minR = math.max(-2, math.floor((y - zoom * (love.graphics.getHeight() / 2)) / 32))
- local maxR = math.min(1, math.floor((y + zoom * (love.graphics.getHeight() / 2)) / 32))
- local minC = math.max(-2, math.floor((x - zoom * (love.graphics.getWidth() / 2)) / 32))
- local maxC = math.min(1, math.floor((x + zoom * (love.graphics.getWidth() / 2)) / 32))
+ local minR = math.max(terrain.rMin, math.floor((y - zoom * (love.graphics.getHeight() / 2)) / 32))
+ local maxR = math.min(terrain.rMax, math.floor((y + zoom * (love.graphics.getHeight() / 2)) / 32))
+ local minC = math.max(terrain.cMin, math.floor((x - zoom * (love.graphics.getWidth() / 2)) / 32))
+ local maxC = math.min(terrain.cMax, math.floor((x + zoom * (love.graphics.getWidth() / 2)) / 32))
love.graphics.setColor(255, 255, 255, 255)
for r = minR, maxR do
for c = minC, maxC do
if terrain:hasChunk(r, c) then
+ if terrain:getChunk(r, c).framebufferPerlin == nil then terrain:getChunk(r, c):renderPerlin() end
love.graphics.draw(terrain:getChunk(r, c).framebufferPerlin, (32*c-x)*zoom + love.graphics.getWidth()/2, (32*r-y)*zoom+love.graphics.getHeight()/2, 0, zoom/16, zoom/16)
- else
- love.graphics.draw(genChunk, (x-32*c)*zoom + love.graphics.getWidth()/2, (y-32*r)*zoom+love.graphics.getHeight()/2, 0, zoom/8, zoom/8)
end
end
end
View
70 terrain.lua
@@ -1,3 +1,5 @@
+love.filesystem.load("TSerial.lua")()
+
Terrain = {}
function Terrain:new(seed)
@@ -7,11 +9,20 @@ function Terrain:new(seed)
o.seed = seed or os.time()
o.chunk = {}
+ o.generationQueue = {}
+ o.rMin = -2
+ o.rMax = 1
+ o.cMin = -2
+ o.cMax = 1
return o
end
function Terrain:addChunk(chunk, r, c)
+ if r < self.rMin then self.rMin = r end
+ if r > self.rMax then self.rMax = r end
+ if c < self.cMin then self.cMin = c end
+ if c > self.cMax then self.cMax = c end
if self.chunk[r] == nil then self.chunk[r] = {} end
self.chunk[r][c] = chunk
end
@@ -27,27 +38,68 @@ function Terrain:hasChunk(r, c)
end
function Terrain:setBlock(r, c, block)
- relR = (r - 1) % 32 + 1
- relC = (c - 1) % 32 + 1
- chunkR = (r - relR) / 32
- chunkC = (c - relC) / 32
+ local relR = (r - 1) % 32 + 1
+ local relC = (c - 1) % 32 + 1
+ local chunkR = (r - relR) / 32
+ local chunkC = (c - relC) / 32
if self:hasChunk(chunkR, chunkC) then
self:getChunk(chunkR, chunkC):setBlock(relR, relC, block)
end
end
function Terrain:getBlock(r, c)
- relR = (r - 1) % 32 + 1
- relC = (c - 1) % 32 + 1
- chunkR = (r - relR) / 32
- chunkC = (c - relC) / 32
+ local relR = (r - 1) % 32 + 1
+ local relC = (c - 1) % 32 + 1
+ local chunkR = (r - relR) / 32
+ local chunkC = (c - relC) / 32
if self:hasChunk(chunkR, chunkC) then
return self:getChunk(chunkR, chunkC):getBlock(relR, relC)
else
- return 0
+ return 255
end
end
function Terrain:getSeed()
return self.seed
end
+
+function Terrain:generateInitial()
+ for r = self.rMin, self.rMax do
+ for c = self.cMin, self.cMax do
+ chunk = Chunk:new()
+ chunk:generate(self:getSeed(), r, c)
+ terrain:addChunk(chunk, r, c)
+ end
+ end
+end
+
+function Terrain:generate(r, c)
+ if self:hasChunk(r, c) then return
+ else
+ table.insert(self.generationQueue, {r = r, c = c})
+ terrain:addChunk(Chunk:new(), r, c)
+ end
+end
+
+function Terrain:checkGenerator()
+ local chunk = generator:receive("chunk")
+ if chunk ~= nil then
+ chunkNew = TSerial.unpack(chunk)
+ chunk = self:getChunk(chunkNew.r, chunkNew.c)
+ for r = 1, 32 do
+ for c = 1, 32 do
+ chunk.block[r][c] = chunkNew.block[r][c]
+ chunk.perlin[r][c] = chunkNew.perlin[r][c]
+ chunk.coalNoise[r][c] = chunkNew.coalNoise[r][c]
+ end
+ end
+ chunk:render()
+ end
+ if generator:peek("ready") then
+ local chunkRC = table.remove(self.generationQueue, 1)
+ if chunkRC ~= nil then
+ local command = {seed = self:getSeed(), r = chunkRC.r, c = chunkRC.c}
+ generator:send("command", TSerial.pack(command))
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.