Skip to content

Instantly share code, notes, and snippets.

@tnlogy
Last active June 2, 2020 21:23
Show Gist options
  • Save tnlogy/eb3ef6131c778cde6a7c415025730996 to your computer and use it in GitHub Desktop.
Save tnlogy/eb3ef6131c778cde6a7c415025730996 to your computer and use it in GitHub Desktop.
Tilemap editor for Codea
--# Main
function setup()
displayMode(FULLSCREEN)
s = Scene()
e = Editor(64)
s:add(Background())
s:add(e)
s:add(DPad(e))
s:add(SelectTile(e))
end
function draw()
background(40, 40, 50)
s:draw()
end
function touched(touch)
s:touched(touch)
end
--# Scene
Scene = class()
function Scene:init()
self.layers = {}
end
function Scene:add(l)
table.insert(self.layers, l)
end
function Scene:draw()
for i,l in ipairs(self.layers) do
l:draw()
end
end
function Scene:touched(touch)
-- call touch function from top layer and down until one returns true
for i = #self.layers, 1,-1 do
local l = self.layers[i]
if l.touched ~= nil then
if l:touched(touch) == true then
return
end
end
end
end
--# Editor
Editor = class()
function Editor:init(tilesize)
self.w = tilesize
self.tile = 1 -- tile to draw
-- camera position
self.c = vec2(0,0)
self.grid = Grid(self.w)
self.level = Level("level",self.w)
end
function Editor:draw()
pushMatrix()
translate(self.c.x, self.c.y)
self.grid:draw(self.c)
self.level:draw()
popMatrix()
end
function Editor:move(delta)
self.c = self.c - delta
end
function Editor:selectTile(i)
self.tile = i
end
function Editor:touched(touch)
if touch.state == ENDED then
local t = vec2(touch.x, touch.y) - self.c
local tx,ty = math.floor(t.x/self.w), math.floor(t.y/self.w)
self.level:toggle(tx,ty,self.tile)
end
end
--# Grid
-- Draw a dashed grid that follows the camera
Grid = class()
function Grid:init(tilesize)
local w = tilesize
local mx = math.ceil(math.max(WIDTH, HEIGHT)/w) * w
-- create dashed grid image
self.dash = image(w,w)
setContext(self.dash)
stroke(0, 0, 0, 255)
strokeWidth(3)
for d = 10,w,10 do
line(d-5,0,d,0)
line(0,d-5,0,d)
end
setContext()
self.m = mesh()
self.m.texture = self.dash
for y = -mx,mx,w do
for x = -mx,mx,w do
self.m:addRect(x-w/2,y-w/2,w,w)
end
end
self.mx = mx
end
function Grid:draw(camera)
local dx = math.floor(camera.x /self.mx)
local dy = math.floor(camera.y /self.mx)
pushMatrix()
-- move dashed grid so its always in view
translate(-dx*self.mx, -dy*self.mx)
self.m:draw()
popMatrix()
end
--# Background
-- animated background
Background = class()
function Background:init(x)
local m = mesh()
m.shader = makePattern()
m.texture = readImage(asset.builtin.Blocks.Lava)
m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
m:setRectTex(1,0,0,5,5)
self.m = m
end
function Background:draw()
self.m.shader.time = ElapsedTime;
self.m:draw()
end
function makePattern()
pattern = shader()
pattern.vertexProgram = [[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying highp vec2 vTexCoord;
void main()
{
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]]
pattern.fragmentProgram = [[
uniform lowp sampler2D texture;
uniform highp float time;
varying highp vec2 vTexCoord;
void main()
{
lowp vec4 col = texture2D(texture,
mod(vTexCoord+vec2(time,0), vec2(1.,1.)));
gl_FragColor = col;
}
]]
return pattern
end
--# DPad
-- joystick to control the camera
DPad = class()
function DPad:init(listener)
local w, hw = 128, 64
self.pad = image(w,w)
setContext(self.pad)
fill(219, 183, 183, 185)
stroke(0, 0, 0, 194)
strokeWidth(3)
ellipse(w/2,w/2,w)
setContext()
self.m = mesh()
self.m.texture = self.pad
self.m:addRect(hw,hw,w,w)
self.mc = mesh()
self.mc.texture = self.pad
self.mc:addRect(0,0,hw,hw)
self.w, self.hw = w, hw
self.c = vec2(hw,hw)
self.pressed = false
self.cb = listener
end
function DPad:draw()
self.m:draw()
pushMatrix()
translate(self.c.x, self.c.y)
self.mc:draw()
popMatrix()
if self.pressed and self.cb then
self.cb:move(self.dv*DeltaTime*15)
end
end
function DPad:touched(touch)
local hw = self.hw
if touch.state == ENDED and self.pressed then
self.pressed = false
tween(.1,self.c,{x=hw,y=hw})
return true
end
local t = vec2(touch.x, touch.y)
local c = vec2(hw,hw)
local dist = t:dist(c)
if (dist < hw and touch.state == BEGAN) or
self.pressed then
local v = (t - c):normalize() * math.min(hw/2, dist)
self.c = c + v
self.dv = v
self.pressed = true
return true
end
end
--# Level
-- Model for a tilemap level
-- the level is stored in a Table
-- where the coordinate x,y is
-- encoded as x + y*mapsize and
-- x,y=0,0 is in the center of the map
-- The level is drawn with one mesh for every available tile, could be replaced with a sprite atlas
Level = class()
function Level:init(name,tilesize)
self.name=name
self.size=1024
self.w=tilesize
self.data = {}
self:load(name)
self:update()
end
function Level:toggle(x,y,tile)
local key=self:encode(x,y)
local d = self.data
if d[key] ~= tile then
d[key]=tile
else
d[key]=nil
end
self:update()
self:save()
end
function Level:encode(x,y)
-- x,y as a key in the array
local s=self.size
return (x+s/2) + (y+s/2)*s
end
function Level:decode(index)
local s=self.size
local x = index%s
local y = (index - x)/s
return x-s/2,y-s/2
end
function Level:draw()
for i,v in ipairs(self.ts) do
v:draw()
end
end
-- recreate the meshes on update
function Level:update()
local w = self.w
local hw = w/2
self.ts = {}
for i,v in ipairs(atlas) do
local m = mesh()
m.texture = v
table.insert(self.ts, m)
end
for index,v in pairs(self.data) do
local x,y=self:decode(index)
self.ts[v]:addRect(x*w+hw, y*w+hw,w,w)
end
end
function Level:save()
local js = json.encode(self.data)
saveProjectData(self.name,js)
end
function Level:load(name)
local js=readProjectData(name)
if js ~= nil then
self.data ={}
-- need to transform keys to int
for i,v in pairs(json.decode(js)) do
self.data[tonumber(i)]=v
end
end
end
-- The available tiles
atlas = {
readImage(asset.builtin.Blocks.Redstone),
readImage(asset.builtin.Blocks.Dirt_Grass),
readImage(asset.builtin.Blocks.Wood),
readImage(asset.builtin.Blocks.Greystone_Sand),
readImage(asset.builtin.Blocks.Leaves)
}
--# SelectTile
-- ui to select tile to draw with
SelectTile = class()
function SelectTile:init(cb)
self.curr = 1
self.cb = cb
end
function SelectTile:draw()
local w=64*#atlas
rectMode(CORNER)
pushMatrix()
translate(WIDTH/2-w/2,HEIGHT-64)
fill(61, 124)
rect(0,0,w,64)
fill(255, 150)
rect(self.curr*64-64,0,64,64)
for i,v in ipairs(atlas) do
sprite(v,-32+i*64,32,50,50)
end
popMatrix()
end
function SelectTile:touched(touch)
local w = 64*#atlas
local t = vec2(touch.x,touch.y)
if t.y > HEIGHT- 64 then
for i,v in ipairs(atlas) do
local x,y = WIDTH/2-w/2,HEIGHT-32
x=x-32+i*64
if vec2(x,y):dist(t)<32 then
self.curr = i
self.cb:selectTile(i)
return true
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment