diff options
| author | Elizabeth Hunt <me@liz.coffee> | 2026-04-25 20:42:30 -0700 |
|---|---|---|
| committer | Elizabeth Hunt <me@liz.coffee> | 2026-04-25 20:42:30 -0700 |
| commit | a81b080cc830d3073fda40ec777ab24f72cacfb9 (patch) | |
| tree | b92a2461993a2aedb3ea946ee10aa09ca7df3c5d | |
| parent | 24b0469237285a05d9ffc05d18e43cc5dcdef00f (diff) | |
| download | dyl8-a81b080cc830d3073fda40ec777ab24f72cacfb9.tar.gz dyl8-a81b080cc830d3073fda40ec777ab24f72cacfb9.zip | |
Getting a sword slashing animation working
| -rw-r--r-- | dyl.lua | 248 | ||||
| -rw-r--r-- | dyl.p8 | 26 | ||||
| -rw-r--r-- | entity.lua | 24 | ||||
| -rw-r--r-- | util.lua | 116 | ||||
| -rw-r--r-- | world.lua | 27 |
5 files changed, 297 insertions, 144 deletions
@@ -1,64 +1,139 @@ +_slashing_timer_sec = 0.12 +function spawn_slashing_particle(e) + slash_animation = entity() + _slash_animation_distance = 3 + function slash_animation:transition_state() + -- TODO: Find a better place to put this lol. This has nothing to do with state + -- transitions + slash_animation:equipped_from(e, _slash_animation_distance) + end + return slash_animation:b_type(Entities.Enemy) + :b_line_of_sight(e.line_of_sight) + :b_position(vec2(e.position)) + :b_sprite_position(vec2(position)) + :b_render_order(-1) + :b_live_for(_slashing_timer_sec) + :b_add_state( + States.Active, { + animation = { + pos_y = { sequence = { 115, 116, 117 }, dt = _slashing_timer_sec / 3, reflect = vec2(true, true) }, + neg_y = { sequence = { 115, 116, 117 }, dt = _slashing_timer_sec / 3 }, + pos_x = { sequence = { 112, 113, 114 }, dt = _slashing_timer_sec / 3 }, + neg_x = { sequence = { 112, 113, 114 }, dt = _slashing_timer_sec / 3, reflect = vec2(true, false) } + } + } + ) + :b_state(States.Active) + :build() +end + +enemy = entity() +function enemy:transition_state() +end +enemy:b_type(Entities.Enemy) + :b_position(vec2(30, 40)) + :b_sprite_position(vec2(30, 30)) + :b_line_of_sight(vec2(1, 0)) + :b_render_order(1) + :b_collidable() + :b_add_state( + States.Idle, { + animation = { + pos_y = { sequence = { 32, 33 }, dt = 1 }, + neg_y = { sequence = { 32, 33 }, dt = 1 }, + pos_x = { sequence = { 32, 33 }, dt = 1 }, + neg_x = { sequence = { 32, 33 }, dt = 1, reflect = vec2(true, false) } + } + } + ) + :b_add_state( + States.Walk, { + animation = { + pos_x = { sequence = { 32, 34 }, dt = 0.20 }, + neg_x = { sequence = { 32, 34 }, dt = 0.20, reflect = vec2(true, false) }, + pos_y = { sequence = { 32, 34 }, dt = 0.20 }, + neg_y = { sequence = { 32, 34 }, dt = 0.20 } + } + } + ) + :b_state(States.Walk) + :b_equipped({ sword }) + :build() + + bow = entity() function bow:transition_state() end -bow - :b_type(Entities.Bow) +bow:b_type(Entities.Bow) :b_position(vec2(0, 0)) :b_sprite_position(vec2(0, 0)) :b_line_of_sight(vec2(1, 0)) :b_render_order(1) :b_collidable() - :b_add_state(States.Equipped, { - animation={ - pos_y = { sequence = { 96 }, dt = 1, reflect = vec2(false, true) }, - neg_y = { sequence = { 96 }, dt = 1 }, - pos_x = { sequence = { 80 }, dt = 1 }, - neg_x = { sequence = { 80 }, dt = 1, reflect = vec2(true, false) } - } - }) - :b_add_state(States.Drawn, { - animation={ - pos_y = { sequence = { 81 }, dt = 1, reflect = vec2(false, true) }, - neg_y = { sequence = { 81 }, dt = 1 }, - pos_x = { sequence = { 81 }, dt = 1 }, - neg_x = { sequence = { 81 }, dt = 1, reflect = vec2(true, false) } - } - }) - :b_add_state(States.Drawing, { - animation = bow.states[States.Equipped] - }) + :b_add_state( + States.Equipped, { + animation = { + pos_y = { sequence = { 96 }, dt = 1, reflect = vec2(false, true) }, + neg_y = { sequence = { 96 }, dt = 1 }, + pos_x = { sequence = { 80 }, dt = 1 }, + neg_x = { sequence = { 80 }, dt = 1, reflect = vec2(true, false) } + } + } + ) + :b_add_state( + States.Drawn, { + animation = { + pos_y = { sequence = { 81 }, dt = 1, reflect = vec2(false, true) }, + neg_y = { sequence = { 81 }, dt = 1 }, + pos_x = { sequence = { 81 }, dt = 1 }, + neg_x = { sequence = { 81 }, dt = 1, reflect = vec2(true, false) } + } + } + ) + :b_add_state( + States.Drawing, { + animation = bow.states[States.Equipped] + } + ) :b_state(States.Equipped) :build() sword = entity() -_slashing_timer_ms = 500 function sword:transition_state() - if(btn(5)) then self:transition_to(States.Slashing) - elseif self.state == States.Slashing and self.state_stopwatch > _slashing_timer_ms then self:transition_to(States.Equipped) end + if button_just_pressed(5) and self.state == States.Equipped then + self:transition_to(States.Slashing) + spawn_slashing_particle(self) + elseif self.state == States.Slashing and self.state_stopwatch > _slashing_timer_sec then + self:transition_to(States.Equipped) + end end sword :b_type(Entities.Sword) :b_line_of_sight(vec2(1, 0)) :b_position(vec2(0, 0)) :b_sprite_position(vec2(0, 0)) - :b_render_order(1) + :b_render_order(0) :b_collidable() - :b_add_state(States.Equipped, { - animation={ - pos_y = { sequence = { 82 }, dt = 1, reflect = vec2(false, true) }, - neg_y = { sequence = { 82 }, dt = 1, reflect = vec2(true, false)}, - pos_x = { sequence = { 82 }, dt = 1 }, - neg_x = { sequence = { 82 }, dt = 1, reflect = vec2(true, false) } - } - }) - :b_add_state(States.Slashing, { - animation={ - pos_y = { sequence = { 82, 82+16 }, dt = 1, reflect = vec2(false, true) }, - neg_y = { sequence = { 82, 82+16 }, dt = 1, reflect = vec2(true, false)}, - pos_x = { sequence = { 82, 82+16 }, dt = 1 }, - neg_x = { sequence = { 82, 82+16 }, dt = 1, reflect = vec2(true, false) } - } - }) + :b_add_state( + States.Equipped, { + animation = { + pos_y = { sequence = { 99 }, dt = 1, reflect = vec2(false, true) }, + neg_y = { sequence = { 99 }, dt = 1, reflect = vec2(true, false) }, + pos_x = { sequence = { 98 }, dt = 1 }, + neg_x = { sequence = { 98 }, dt = 1, reflect = vec2(true, false) } + } + } + ) + :b_add_state( + States.Slashing, { + animation = { + pos_y = { sequence = { 82, 98 }, dt = _slashing_timer_sec / 2, reflect = vec2(false, true) }, + neg_y = { sequence = { 82, 98 }, dt = _slashing_timer_sec / 2, reflect = vec2(true, false) }, + pos_x = { sequence = { 82, 99 }, dt = _slashing_timer_sec / 2, reflect = vec2(false, false)}, + neg_x = { sequence = { 82, 99 }, dt = _slashing_timer_sec / 2, reflect = vec2(true, false) } + } + } + ) :b_state(States.Equipped) :build() @@ -70,61 +145,74 @@ function player:transition_state() self:transition_to(States.Walk) end end -player:b_type(Entities.Player) +player + :b_type(Entities.Player) :b_line_of_sight(vec2(1, 0)) - :b_render_order(0) + :b_render_order(2) :b_collidable() - :b_position(vec2(30,30)) - :b_sprite_position(vec2(30,30)) - :b_add_state(States.Idle, { - animation = { - pos_y = { sequence = { 3, 19 }, dt = 1 }, - neg_y = { sequence = { 6, 22 }, dt = 1 }, - pos_x = { sequence = { 0, 1 }, dt = 1 }, - neg_x = { sequence = { 0, 1 }, dt = 1, reflect = vec2(true, false) } - } - }) - :b_add_state(States.Walk, { - animation = { - pos_x = { sequence = { 2, 0 }, dt = 0.20 }, - neg_x = { sequence = { 2, 0 }, dt = 0.20, reflect = vec2(true, false) }, - pos_y = { sequence = { 4, 5 }, dt = 0.20 }, - neg_y = { sequence = { 7, 8 }, dt = 0.20 } - } - }) - :b_state("idle") + :b_position(vec2(30, 30)) + :b_sprite_position(vec2(30, 30)) + :b_add_state( + States.Idle, { + animation = { + pos_y = { sequence = { 3, 19 }, dt = 1 }, + neg_y = { sequence = { 6, 22 }, dt = 1 }, + pos_x = { sequence = { 0, 1 }, dt = 1 }, + neg_x = { sequence = { 0, 1 }, dt = 1, reflect = vec2(true, false) } + } + } + ) + :b_add_state( + States.Walk, { + animation = { + pos_x = { sequence = { 2, 0 }, dt = 0.20 }, + neg_x = { sequence = { 2, 0 }, dt = 0.20, reflect = vec2(true, false) }, + pos_y = { sequence = { 4, 5 }, dt = 0.20 }, + neg_y = { sequence = { 7, 8 }, dt = 0.20 } + } + } + ) + :b_state(States.Idle) :b_equipped({ sword }) :build() _walk_speed = 35 -- powerup increase? function handle_input() - dpos = vec2(0, 0) - if btn(0) then dpos.x -= 1 end - if btn(1) then dpos.x += 1 end - if btn(3) then dpos.y += 1 end - if btn(2) then dpos.y -= 1 end + dpos = vec2(0, 0) + if btn(0) then dpos.x -= 1 end + if btn(1) then dpos.x += 1 end + if btn(3) then dpos.y += 1 end + if btn(2) then dpos.y -= 1 end - player.velocity = dpos:normal() * _walk_speed + player.velocity = dpos:normal() * _walk_speed end function _init() end -step_t = time() -function _update60() - cls(0) -- clear here in case we want to print to screen outside of _draw +_step_t = time() +function _update() + -- clear here in case we want to print to screen outside of _draw + cls(0) + + old_step = _step_t + _step_t = time() + step_dt = _step_t - old_step - old_step = step_t - step_t = time() - step_dt = step_t - old_step + foreach(update_hooks, function(f) f(step_dt) end) - handle_input() - qsort(World, function(a, b) return a.equipped != nil end) - foreach(World, function (e) e:update(step_dt) end) + handle_input() + World.foreach(function(e) if (not e.equipped) then e:update(step_dt) end end) + World.foreach(function(e) if (e.equipped) then e:update(step_dt) end end) - run_collisions() + run_collisions() + + World.cull_the_dead() end function _draw() - foreach(qsort(World, function(a, b) return a.render_order < b.render_order end), function (e) e:render() end) -end + qsort(World, function(a, b) return b.render_order < a.render_order end) + World.foreach(function(e) + e:render() + end) +end
\ No newline at end of file @@ -61,21 +61,21 @@ __gfx__ 00504000005040000049000000490000006500000055500000500000005000000000000000000000000000000000000000000000000000000000000000000000 00040000000400000400000004000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000000000000000000004000000000000800000000000050000000600000000000000000000000000000000000000000000000000000000000000000000 -00000000000900000000000000490000000008e00006500000050000000500000000000000000000000000000000000000000000000000000000000000000000 -0049440000474400009000000097000000008e000055550000050000000500000000000000000000000000000000000000000000000000000000000000000000 -040000400407004004477770000570000058e0000055650000955000009550000000000000000000000000000000000000000000000000000000000000000000 -00555500005705000090000000005700006500000005050000055500000555000000000000000000000000000000000000000000000000000000000000000000 -00000000000550000000000000000570060000000000000000500000000500000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000070000000000000000000800000000000050000000600000000000000000000000000000000000000000000000000000000000000000000 +00000000000900000070000000000000000008e00006500000050000000500000000000000000000000000000000000000000000000000000000000000000000 +0049440000474400007000000000000000008e000055550000050000000500000000000000000000000000000000000000000000000000000000000000000000 +040000400407004009490000000900000058e0000055650000955000009550000000000000000000000000000000000000000000000000000000000000000000 +00555500005705000040000000447777006500000005050000055500000555000000000000000000000000000000000000000000000000000000000000000000 +00000000000550000000000000090000060000000000000000500000000500000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00056000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000005650600000000000000000000005006500000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000000560000000000000000005606056600650000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000000060000000000000000650005665000660000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000000650000005605000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000000000000056600000000000000006000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00056000000600000000000000000000006000000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000005650600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000560000000000005600000005665000000066000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000060000000000050000000066506060000056000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000650000005605000000000005000000000005600000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000056600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000600000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 @@ -2,11 +2,13 @@ Entities = { Player = 0, Sword = 1, Enemy = 2, - Bow = 3 + Bow = 3, + Particle = 4 } States = { Walk = "walk", Idle = "idle", + Active = "active", Equipped = "equipped", Slashing = "slashing", Drawing = "drawing", @@ -51,6 +53,10 @@ function Entity:b_collidable() self.collision = true return self end +function Entity:b_live_for(t) + self.life_time = t + return self +end function Entity:b_line_of_sight(vec) self.line_of_sight = vec2(vec) return self @@ -69,10 +75,13 @@ function Entity:transition_to(state_name) self.state = state_name self.state_stopwatch = 0 end - print(self.state) return self end +function Entity:kill() + self.life_time = -1 +end + function Entity:update(dt) self:integrate(dt) @@ -86,6 +95,10 @@ function Entity:update(dt) assert(self.transition_state) self:transition_state() + + if self.life_time ~= nil then + self.life_time -= dt + end end function Entity:update_line_of_sight() @@ -147,10 +160,11 @@ function Entity:integrate(dt) end _equipped_item_distance = 6 -function Entity:equipped_from(parent) +function Entity:equipped_from(parent, dist) + dist = dist or _equipped_item_distance self.line_of_sight = vec2(parent.line_of_sight) - offset = (parent.line_of_sight * _equipped_item_distance) + offset = (parent.line_of_sight * dist) self.position = parent.position + offset self.sprite_position = parent.sprite_position + offset @@ -203,5 +217,5 @@ function entity() collision = false }, Entity )) - return World[id] + return World.get(id) end @@ -1,49 +1,51 @@ +update_hooks = {} + -- https://pico-8.fandom.com/wiki/Qsort -function qsort(a,c,l,r) - c,l,r=c or function(a,b) return a<b end,l or 1,r or #a - if l<r then - if c(a[r],a[l]) then - a[l],a[r]=a[r],a[l] - end - local lp,k,rp,p,q=l+1,l+1,r-1,a[l],a[r] - while k<=rp do - local swaplp=c(a[k],p) - -- "if a or b then else" - -- saves a token versus - -- "if not (a or b) then" - if swaplp or c(a[k],q) then - else - while c(q,a[rp]) and k<rp do - rp-=1 - end - a[k],a[rp],swaplp=a[rp],a[k],c(a[rp],p) - rp-=1 - end - if swaplp then - a[k],a[lp]=a[lp],a[k] - lp+=1 - end - k+=1 - end - lp-=1 - rp+=1 - -- sometimes lp==rp, so - -- these two lines *must* - -- occur in sequence; - -- don't combine them to - -- save a token! - a[l],a[lp]=a[lp],a[l] - a[r],a[rp]=a[rp],a[r] - qsort(a,c,l,lp-1 ) - qsort(a,c, lp+1,rp-1 ) - qsort(a,c, rp+1,r) - end +function qsort(a, c, l, r) + c, l, r = c or function(a, b) return a < b end, l or 1, r or #a + if l < r then + if c(a[r], a[l]) then + a[l], a[r] = a[r], a[l] + end + local lp, k, rp, p, q = l + 1, l + 1, r - 1, a[l], a[r] + while k <= rp do + local swaplp = c(a[k], p) + -- "if a or b then else" + -- saves a token versus + -- "if not (a or b) then" + if swaplp or c(a[k], q) then + else + while c(q, a[rp]) and k < rp do + rp -= 1 + end + a[k], a[rp], swaplp = a[rp], a[k], c(a[rp], p) + rp -= 1 + end + if swaplp then + a[k], a[lp] = a[lp], a[k] + lp += 1 + end + k += 1 + end + lp -= 1 + rp += 1 + -- sometimes lp==rp, so + -- these two lines *must* + -- occur in sequence; + -- don't combine them to + -- save a token! + a[l], a[lp] = a[lp], a[l] + a[r], a[rp] = a[rp], a[r] + qsort(a, c, l, lp - 1) + qsort(a, c, lp + 1, rp - 1) + qsort(a, c, rp + 1, r) + end return a end function filter(a, pred) filtered = {} - for k,v in ipairs(a) do + for k, v in ipairs(a) do if pred(v) then filtered[k] = v end @@ -53,9 +55,33 @@ end function fmt(x) t = type(x) - if t == "number" then return "" .. x - elseif t == "string" then return x - elseif t == "boolean" and x then return "true" - elseif t == "boolean" and not x then return "false" end - return "NA" + if t == "number" then + return "" .. x + elseif t == "string" then + return x + elseif t == "boolean" and x then + return "true" + elseif t == "boolean" and not x then + return "false" + end + return "_unknown_" +end + +_button_states = {} +function _update_button_states(_dt) + for button = 0, 20 do + if btn(button) then + if _button_states[button] == "up" then + _button_states[button] = "just_pressed" + else + _button_states[button] = "held" + end + else + _button_states[button] = "up" + end + end end +update_hooks[1] = _update_button_states +function button_just_pressed(id) + return _button_states[id] == "just_pressed" +end
\ No newline at end of file @@ -1,5 +1,30 @@ +_World = {} World = {} + function World.add(entity) - World[entity.id] = entity + _World[entity.id] = entity return World +end + +function World.foreach(f) + for id, entity in pairs(_World) do + f(entity) + end +end + +function World.cull_the_dead() + to_reap = {} + for id, e in pairs(_World) do + if (e.life_time ~= nil and e.life_time <= 0) then + to_reap[id] = e + end + end + for id, e in pairs(to_reap) do + _World[id] = nil + end + return _World +end + +function World.get(id) + return _World[id] end
\ No newline at end of file |
