Last active
June 2, 2020 21:23
-
-
Save tnlogy/eb3ef6131c778cde6a7c415025730996 to your computer and use it in GitHub Desktop.
Tilemap editor for Codea
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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