Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Use unicode-monotype fonts, be nice
- local s = [[
- ░ ▒ ▓ █ ▄ ▌ ▐ ▀
- │ ─ ├ ┼ ┤ ┌ ┐ ┘ └ ┴ ┬
- ║ ═ ╠ ╬ ╣ ╔ ╗ ╝ ╚ ╩ ╦
- ╞ ╪ ╡ ╒ ╕ ╛ ╘ ╧ ╤
- ╟ ╫ ╢ ╓ ╖ ╜ ╙ ╨ ╥
- ]]
- --os.execute('chcp 65001')
- --[[
- Reference:
- Here - spritesheet by 2, 3 resolution (sx, sy).
- \sx 1 2 3
- s ╔═══════╦═══════╦═══════╗─
- y ║ │ │ ║my
- ║ ┌───┐ │ ┌───┐ │ ┌───┐ ║─
- ║ │ ▲ │ │ │ ▲ │ │ │ ▲ │ ║
- 1 ║ │◢█◣│ │ │◢▓◣│ │ │◢▒◣│ ║
- ║ │▀ ▀│ │ │▀v▀│ │ │▀V▀│ ║
- ║ └───┘ │ └───┘ │ └───┘ ║
- ║ │ │ ║
- ╠───────╬───────╬───────╣
- ║ │ │ ║
- ║ ┌───┐ │ ┌───┐ │ ┌───┐ ║
- ║ │ ▲ │ │ │ ▲ │ │ │ ▲ │ ║
- 2 ║ │◢▓◣│ │ │◢█◣│ │ │◢▒◣│ ║
- ║ │▀V▀│ │ │▀Y▀│ │ │▀W▀│ ║
- ║ └───┘ │ └───┘ │ └───┘ ║
- ║ │ │ ║
- ╚═══════╩═══════╩═══════╝
- │m│
- x
- w and h - width and height of one sprite.
- mx and my - margin by x, margin by y.
- The final dimensions are calculated automatically
- New grid for this example make as:
- grid = require'grid'
- ship_img = love.graphics.newImage('ship.png')
- ship_grid = grid(img, 3, 2, mx, my)
- where 3 and 2 - image-resolution, mx, my = margin by pixels
- TIP: you need to double-margin between sprites, it useful for
- Now, we want to add two animations of ship blast-off and ship flying, like this:
- 1. Launch
- ╔═══════╦═══════╦═══════╗
- ║ │ │ ║
- ║ ┌───┐ │ ┌───┐ │ ┌───┐ ║
- ║ │ ▲ │ │ │ ▲ │ │ │ ▲ │ ║
- ║ │◢█◣│ │ │◢█◣│ │ │◢█◣│ ║
- ║ │▀v▀│ │ │▀Y▀│ │ │▀W▀│ ║
- ║ └───┘ │ └───┘ │ └───┘ ║
- ║ │ │ ║
- ╚═══════╩═══════╩═══════╝
- 2. Flying
- ╔═══════╦═══════╦═══════╗
- ║ │ │ ║
- ║ ┌───┐ │ ┌───┐ │ ┌───┐ ║
- ║ │ ▲ │ │ │ ▲ │ │ │ ▲ │ ║
- ║ │◢▓◣│ │ │◢█◣│ │ │◢▒◣│ ║
- ║ │▀V▀│ │ │▀Y▀│ │ │▀W▀│ ║
- ║ └───┘ │ └───┘ │ └───┘ ║
- ║ │ │ ║
- ╚═══════╩═══════╩═══════╝
- Syntax: [grid]:newAnimation('name', {'rowA:col1-col2 delay'[, 'row:col1-col2 delay'...]})
- ship_grid:newAnimation('launch', {'1:1-3 0.5'}) -- high delay, 1.5s summary launching animation
- ship_grid:newAnimation('flying', {'2:1-3 0.3'}) -- high delay, ~1s summary lenth of animation
- Create ship, with sprite-instance, update and draw.
- function love.load()
- ship = {x = 10, y = 20, speed = 0, angle = 0}
- ship.sprite = [grid]:new(ship.x, ship.y, ship.angle)
- end
- function love.update(dt)
- if love.keyboard.isDown('left') then ship.angle = ship.angle - 0.1*dt end
- if love.keyboard.isDown('right') then ship.angle = ship.angle - 0.1*dt end
- if love.keyboard.isDown('up') then ship.speed = ship.speed + 5*dt end
- ship.speed = ship.speed * 0.9*dt -- slowdown
- ship.x, ship.y = ship.x + math.cos(ship.angle)*ship.speed, ship.y + math.sin(ship.angle)*ship.speed
- ship.sprite:draw(ship.x, ship.y, ship.angle)
- -- We can flip sprite:
- local x, y = love.mouse.getPosition()
- ship.sprite:flip(x > ship.x) -- true if mouse position > ship.position
- -- ship.sprite:flip(y > ship.y, 'y') -- flip by y
- end
- function love.draw()
- ship_grid:draw()
- end
- ]]
- local min, max, floor, ceil, sin, cos, abs = math.min, math.max, math.floor, math.ceil, math.sin, math.cos, math.abs
- local type, graphics = type, love.graphics
- local conf = {autoflush = true, checks = true}
- local ch = {n = 'number', s = 'string', t = 'table', f = 'function', c = 'cdata'}
- local errorstr = 'Grid: arg#%d error: "%s" expected, got "%s" '
- local function checker(...)
- local list, ch, a, b = {...}, ch
- for i = 1, math.floor(#list/2) do
- a, b = list[i*2-1], list[i*2]
- a = a and type(a) == ch[b:sub(1, 1)]
- or b:sub(-1) == '?' and type(a) == 'nil'
- or error(errorstr:format(i, ch[b:sub(1, 1)], type(a)), 3)
- end
- end
- local function err(v, lvl, ...)
- error(v:format(...), lvl)
- end
- local function mt__gc(t, mt) --collect garbage method trick
- local mt = mt or getmetatable(t)
- local prox = newproxy(true)
- getmetatable(prox).__gc = function() mt.__gc(t) end
- t[prox] = true
- return setmetatable(t, mt)
- end
- local function _gcd(x, y, min) -- greatest common divisor
- min = min or 0.00000001 -- max precision, cause 0.1 + 0.2 = 0.300000000004
- if x > y then x, y = y, x end
- while y >= min do
- x, y = y, x%y
- end
- return x
- end
- local function gcd(...)
- local t = {...}
- table.sort(t)
- local res = t[1]
- for i = 2, #t do
- res = _gcd(res, t[i])
- end
- return res
- end
- local function checkTableType(t, tp, n)
- for i, v in ipairs(t) do
- if type(v) ~= tp then err('Grid: arg#%s error: %s expected, got %s', 4, i, tp, type(v)) end
- end
- return t
- end
- local function tableid(t)
- return tonumber(tostring(t):match('.-(0x%x+).*')) -- get link of table for storage
- end
- local gridlist = {} -- storage for links to grids/animations and DirtyHack
- local entitie = {}
- entitie.__index = function(self, key)
- return entitie[key] or
- key == 'grid' and gridlist[self.gridid].grid or
- key == 'anim' and gridlist[self.gridid].anim[self.animid]
- end
- function entitie:__gc()
- print('free '..self.id)
- self.grid:freeSprite(self.id)
- end
- local ffi = require("ffi")
- ffi.cdef[[
- typedef struct {
- long double x, y, r, sx, sy, ox, oy, kx, ky;
- bool fx, fy, paused;
- char loop;
- float time, speed;
- int id, quad; // sprite id, quad number
- int gridid; // table id of grid
- int animid; // table id of grid-animaton
- } grid_entitie;
- ]]
- local g_entitie = ffi.metatype("grid_entitie", entitie) -- OOP
- setmetatable(entitie, {
- --x, y, r, sx, sy, ox, oy, kx, ky
- __call = function(self, grid, ...)
- local t = checkTableType({...}, 'number', 1)
- local o = g_entitie()
- o.x, o.y, o.r, o.sx, o.sy, o.ox, o.oy, o.kx, o.ky =
- t[1] or 0, t[2] or 0, t[3] or 0, t[4] or 1, t[5] or 1, t[6] or 0, t[7] or 0, t[8] or 0, t[9] or 0
- o.time, o.speed = 0, 1.0
- o.fx, o.fy = false, false
- o.loop, o.paused = false, false
- o.gridid, o.animid = tableid(grid), 0
- o.id, o.quad = 1, 1
- return o
- end
- })
- function entitie:getGrid()
- return gridlist[self.gridid].grid
- end
- local flushlist = {}
- function entitie:draw(...)
- local t = checkTableType({...}, 'number')
- self.x, self.y, self.r, self.sx, self.sy, self.ox, self.oy, self.kx, self.ky =
- t[1] or 0, t[2] or 0, t[3] or 0, t[4] or 1, t[5] or 1, t[6] or 0, t[7] or 0, t[8] or 0, t[9] or 0
- self:update()
- end
- function entitie:getAnim()
- return gridlist[self.gridid].anim[self.animid]
- end
- function entitie:_flush()
- flushlist[self] = true
- end
- function entitie:getDrawData()
- return self.fx and self.x + self.w*self.sx or self.x,
- self.fy and self.y + self.h*self.sy or self.y,
- self.r,
- self.fx and -self.sx or self.sx,
- self.fy and -self.sy or self.sy,
- self.ox, self.oy, self.kx, self.ky
- end
- function entitie:setPosition(x, y)
- self.x, self.y = x, y
- if conf.autoflush then self:_flush() end
- end
- function entitie:getPosition(x, y)
- return self.x, self.y
- end
- function entitie:setOffset(x, y)
- self.ox, self.oy = x or self.ox, y or self.oy
- if conf.autoflush then self:_flush() end
- end
- function entitie:setScale(x, y)
- self.sx, self.sy = x or self.sx, y or self.sy
- if conf.autoflush then self:_flush() end
- end
- function entitie:setAngle(v)
- self.r = v or self.r
- if conf.autoflush then self:_flush() end
- end
- function entitie:setSprite(x, y)
- local a = self.anim
- if self.animid > 0 and not y then
- self.time = a.sprite[ ceil( self.time / a.delay % a.count)]
- else
- self.quad = self.grid:checkSprite(x, y)
- end
- if conf.autoflush then self:_flush() end
- end
- function entitie:setPause(v)
- self.paused = not not v
- end
- local mode = {}
- function mode.loop(t, delay, count) -- repeat-loop
- return ceil( t / delay % count)
- end
- function mode.wave(t, delay, count) -- bounce-loop
- return ceil(abs(t/delay % (count*2)-count))
- end
- local function round(v) return floor(v) - v > .5 and ceil(v) or floor(v) end
- function mode.sin(t, delay, count) -- sinus
- return round((sin( (3.14 * t) / (delay * count) ) *.5 + .5)*(count))
- end
- function mode.stop(t, delay, count) -- single
- return min(ceil(t / delay), count)
- end
- -- aliaces
- mode[1] = mode.loop
- mode[2] = mode.wave
- mode[3] = mode.sin
- mode[4] = mode.stop
- setmetatable(mode, {__index = function() return mode.loop end})
- function entitie:update(dt)
- local grid = self.grid
- self.time = not self.paused and self.time + (dt or grid.dt)*self.speed or self.time
- grid.batch:setColor(graphics.getColor())
- if self.animid > 0 then
- local a = self.anim
- self.quad = a.sprite[mode[self.loop](self.time, a.delay, a.count)]
- or 1
- end
- grid:set(self.id, self.quad, self:getDrawData())
- end
- function entitie:setSpeed(v)
- self.speed = v or 1
- end
- function entitie:setAnimation(name, speed, mode)
- local anims = self.grid.anim
- self.time = 0
- self.animid = anims[name] and anims[name].id or 0
- self.speed = speed or 1
- self.loop = mode or 1
- self.quad = 1
- end
- function entitie:flip(v, ord)
- if ord == 'y' then
- self.fy = not not v
- else
- self.fx = not not v
- end
- end
- local grid = {} -- Spritegrid class
- setmetatable(grid, {
- __call = function(self, img, sx, sy, mx, my, count) -- sx & sy - sprite count in grid, mx, my - margin
- local o = {img = img}
- o.batch = graphics.newSpriteBatch(o.img, count or 10000)
- o.free = {} -- Free id for reusing
- o.anim = {} -- Animations list
- o.sx = sx or 1 -- Resolution by sprites, one by default (1:1 grid)
- o.sy = sy or 1
- o.dt = love.timer.getDelta()
- local imgx, imgy = o.img:getDimensions()
- local mx, my = mx or 0, my or 0 -- margin of every sprite
- o.sw, o.sh = floor(imgx / o.sx), floor(imgy / o.sy) -- One sprite dimensions
- o.quad = {} -- Quad list
- for j = 0, o.sy-1 do
- for i = 0, o.sx-1 do
- local x, y, w, h = i*o.sw + mx, j*o.sh + my, o.sw - mx*2, o.sh - my*2
- table.insert(o.quad, graphics.newQuad(x, y, w, h, imgx, imgy)) -- quad initialisation
- end
- end
- o.id = tableid(o)
- self.__index = self
- gridlist[o.id] = {grid = o, anim = setmetatable({}, {__mode = 'kv'})}
- return mt__gc(o, {__index = self, __gc = function(self) gridlist[self.id] = false; print('grid freed') end})
- end
- })
- local function checkSet(t, x, y) -- check the existence of sprites in set
- local count = x * y
- for i = 1, #t do
- t[i] = floor(t[i])
- if t[i] < 1 or t[i] > count then
- err('Grid:newAnimation error: %d sprite not found, grid dimensions: [%d, %d], min/max: 1, %d', 4, i, x, y, count)
- end
- end
- return t
- end
- local function linearize(t, y) -- transform [x, y] - spritegrid to absolute [x] position
- for i, v in ipairs(t) do
- print(v[1], v[2], v[1] + (v[2]-1) * y)
- end
- local buf = {}
- if type(t[1]) == 'table' then
- for i, v in ipairs(t) do
- table.insert(buf, v[1] + (v[2]-1) * y)
- end
- end
- return #buf > 0 and buf or t
- end
- function grid:newAnimation(name, t, loop)
- checker(name, 's', t, 't', loop, 'b?')
- local tn = tonumber
- local set = {} -- raw quad-set data
- local delays = {} -- table of all delays for gcd
- if type(t) ~= 'table' or #t == 0 then error('Grid: newAnimation: arg#2 table expected, got '..type(t)) end
- for i, v in ipairs(t) do -- parse strings
- if type(v) ~= 'string' then error('Grid: newAnimation: arg#2 table of strings like "2:1-6 0.2" expected, got "'..type(t[1])..'" arg on '..i..' position') end
- local pattern = '(%d+):(%d+)%-(%d+)%s(%d+%.?%d*)' -- "a:b-c d.?e?"
- local y, x1, x2, d = v:match(pattern) -- column-start-end-delay
- y, x1, x2, d = tn(y), tn(x1), tn(x2), tn(d)
- print(name, y, x1, x2, d)
- if not (y and x1 and x2 and d) then error('Grid: newAnimation: arg#2-'..i..' error: pattern like "row:column1-column2 delay" in digits expected, got: '..v) end
- table.insert(set, {y = y, x1 = x1, x2 = x2, d = d})
- table.insert(delays, d)
- end
- local del = #delays > 1 and gcd(unpack(delays)) or delays[1] -- gcd
- t = {}
- for _, v in ipairs(set) do
- for i = v.x1, v.x2, (v.x1 > v.x2 and -1 or 1) do -- forward and reverse order like 1-10 and 10-1
- for n = 1, v.d/del do
- table.insert(t, {i, v.y})
- end
- end
- end
- print('YS', self.sx)
- local o = {
- sprite = checkSet(linearize(t, self.sx), self.sx, self.sy),
- count = #t,
- delay = del,
- loop = loop or 'loop',
- name = name,
- }
- o.id = tableid(o)
- self.anim[name] = o
- gridlist[tableid(self)].anim[tableid(self.anim[name])] = self.anim[name]
- end
- function grid:getQuad(x, y)
- if conf.checks then checker(x, 'n', y, 'n?') end
- return y and self.quad[(y-1)*self.sy+x] --вычисление позиции квада в списке
- or self.quad[x]
- end
- function grid:checkSprite(x, y)
- if y then
- return self.quad[(y-1)*self.sy+x] and (y-1)*self.sy+x or error('Error')
- else
- return self.quad[x] and x or error('error2')
- end
- end
- function grid:addSprite(n, ...)
- if #self.free > 0 then
- local p = self.free[#self.free]; table.remove(self.free, #self.free)
- print('reuse sprite '..p)
- return p, self.batch:set(p, n and self.quad[n] or self.quad[1], ...)
- end
- return self.batch:add(n and self.quad[n] or self.quad[1], ...)
- end
- function grid:freeSprite(n)
- table.insert(self.free, n)
- self.batch:set(n, 0, 0, 0, 0, 0)
- end
- function grid:new(...)
- if conf.checks then checkTableType({...}, 'number') end
- local entitie = entitie(self, ...)
- entitie.gridid = self.id
- entitie.id = self:addSprite(1, ...)
- return entitie
- end
- function grid:clear(id, n, ...)
- self.batch:clear()
- collectgarbage()
- end
- function grid:set(id, n, ...)
- self.batch:set(id, self:getQuad(n), ...)
- end
- function grid:getSpriteDimensions()
- return self.sw, self.sh
- end
- function grid:getDimensions()
- return self.sx, self.sy
- end
- function grid:draw(...)
- if conf.checks then checkTableType({...}, 'number') end
- if conf.autoflush then
- for k, _ in pairs(flushlist) do
- k:update()
- end
- flushlist = {}
- end
- self.dt = love.timer.getDelta()
- collectgarbage()
- graphics.draw(self.batch, ...)
- end
- function grid:autoupdate(v)
- conf.autoflush = not not v
- end
- function grid:debug(v)
- conf.checks = not not v
- end
- local function unittest()
- img = love.graphics.newImage'Sphere_4.png'
- img:setFilter('nearest')
- gr = grid(img, 16)
- gr:newAnimation('move', {'1:1-16 0.05'})
- gr:newAnimation('remove', {'1:16-1 0.02'})
- sprites = {}
- local r = math.random
- for i = 1, 1000000 do
- local e = gr:new(r(100), r(100), r(314)/100, r(-10, 10)/10, r(-10, 10)/10, -22, -22)
- e:setAnimation(r() == 1 and 'move'or 'remove')
- table.insert(sprites, e)
- end
- end
- --unittest()
- return grid
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement