Created
January 10, 2020 16:26
-
-
Save a327ex/1b2ac2a19cac37617cd9f605a115aec0 to your computer and use it in GitHub Desktop.
Revisions
-
a327ex created this gist
Jan 10, 2020 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,828 @@ function init() white = {1, 1, 1, 1} black = {0, 0, 0, 1} red = {1.0, 0.1, 0.2, 1} doodle = g.newShader("doodle.frag") doodle:send("doodle_max_offset", {0.0020, 0.0020}) doodle:send("doodle_noise_scale", {6, 8}) doodle:send("doodle_frame_count", 8) doodle:send("doodle_frame_time", 0.2) combine = g.newShader("combine.frag") doodle_canvas = g.newCanvas(gw, gh) game_canvas = g.newCanvas(gw, gh) new_animation("hit1", 96, 47) new_animation("smoke1", 50, 50, {1}) player = Player(gw/2, gh - 6) projectiles = {} effects = {} enemies = {} ui = {} ammo_bars = {} hp_bars = {} timer:every(2, function() table.insert(enemies, Enemy1(rng:float(60, gw - 60), -60)) end) local h = (gh - 55) local bh = h/player.max_ammo for i = 1, player.max_ammo do table.insert(ammo_bars, UIBar(gw - 24, 50 + (i-1)*(bh), bh - 3, "ammo")) end player_ammo_ui = PlayerAmmoUI(gw - 24, 22) local hh = (gh/2)/player.max_hp for i = 1, player.max_hp do table.insert(hp_bars, UIBar(24, 55 + (i-1)*(hh), hh - 3, "hp")) end player_hp_ui = PlayerHPUI(24, 22) end function update(dt) doodle:send("time", time) player:update(dt) update_objects(projectiles, dt) update_objects(enemies, dt) update_objects(effects, dt) update_objects(ammo_bars, dt) update_objects(hp_bars, dt) player_ammo_ui:update(dt) player_hp_ui:update(dt) end function draw() g.setCanvas(game_canvas) g.clear() g.setColor(white) camera:attach() draw_objects(projectiles) draw_objects(enemies) draw_objects(effects) draw_objects(ammo_bars) draw_objects(hp_bars) player_ammo_ui:draw() player_hp_ui:draw() player:draw() camera:detach() g.setCanvas() g.setCanvas(doodle_canvas) g.clear() g.setColor(white) g.setShader(doodle) g.draw(game_canvas, 0, 0, 0, 1, 1) g.setShader() g.setCanvas() g.setColor(1, 1, 1, 1) g.setBlendMode("alpha", "premultiplied") g.draw(doodle_canvas, 0, 0, 0, sx, sy) g.setBlendMode("alpha") end Player = Class:extend() function Player:new(x, y) self.x, self.y = x, y self.rs = 16 self.sx, self.sy = 1, 1 self.e1x, self.e1y = -self.rs/2.5, -self.rs/2.5 self.e2x, self.e2y = self.rs/2.5, -self.rs/2.5 self.e1ox, self.e1oy = 0, 0 self.e2ox, self.e2oy = 0, 0 self.r = 0 self.scale_spring = Spring(1) timer:everyi(2, function() timer:tween(1, self, {sx = 1.025, sy = 1.025}, cubic_in, function() timer:tween(1, self, {sx = 1, sy = 1}, linear) end) end) self.rso = 0; timer:everyi({0.1, 0.2}, function() self.rso = rng:float(-1, 1) end) self.attack_spring = Spring(1) self.attack_timer = 0 self.attack_pulse_timer = 0 self.attack_cd = 0.06 self.shape = HC.circle(self.x, self.y, 16) self.shape.parent = self self.max_ammo = 35 self.ammo = self.max_ammo self.max_hp = 10 self.hp = self.max_hp self.reloading = false self.reload_timer = 0 self.reload_cd = 0.6 self.reload_spring = Spring(1) end function Player:update(dt) self.scale_spring:update(dt) self.attack_spring:update(dt) self.reload_spring:update(dt) self.s = self.scale_spring.x*self.attack_spring.x*self.reload_spring.x self.r = angle_to_mouse(self.x, self.y) if m.isDown(1) and not m.isDown(2) then self.attack_timer = self.attack_timer + dt if self.attack_timer > self.attack_cd and self.ammo > 0 then self.attack_timer = 0 local r = rng:float(-6, 6) local v = rng:float(600, 700) table.insert(projectiles, Projectile(self.x + self.s*1.5*self.rs*math.cos(self.r) + r*math.cos(self.r + math.pi/2), self.y + self.s*1.5*self.rs*math.sin(self.r) + r*math.sin(self.r + math.pi/2), v*math.cos(self.r), v*math.sin(self.r))) self.scale_spring:pull(0.1) for i = 1, 4 do table.insert(effects, EllipseParticle(self.x + self.s*1.5*self.rs*math.cos(self.r), self.y + self.s*1.5*self.rs*math.sin(self.r), self.r + rng:float(-math.pi/2, math.pi/2), rng:float(100, 400))) end table.insert(effects, ShootCircle(self.x + self.s*1.5*self.rs*math.cos(self.r), self.y + self.s*1.5*self.rs*math.sin(self.r))) table.insert(effects, ShootCapsule(self.x + self.s*1.5*self.rs*math.cos(self.r), self.y + self.s*1.5*self.rs*math.sin(self.r), self.r + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300))) player_ammo_ui:jiggle() ammo_bars[self.ammo]:spend() self.ammo = self.ammo - 1 end self.attack_pulse_timer = self.attack_pulse_timer + dt if self.attack_pulse_timer > 4*self.attack_cd then self.attack_pulse_timer = 0 self.attack_spring:pull(0.25) end end local r = math.deg(self.r) local mx = camera:get_mouse_position() local rd = 1 if mx > self.x then rd = -1 else rd = 1 end if mouse_pressed(2) then self.reloading = true self.reload_spring:pull(0.4) self.reload_text = ReloadText(self.x + rd*62, self.y - 14, self.reload_cd, rd*math.pi/32) table.insert(effects, self.reload_text) end if mouse_released(2) then self.reloading = false self.reload_spring:pull(0.2) if self.reload_text then self.reload_text:die() self.reload_text = nil end end if self.reloading then self.reload_timer = self.reload_timer + dt if self.reload_text then self.reload_text.t = self.reload_timer/self.reload_cd end player_ammo_ui.t = self.reload_timer/self.reload_cd if self.reload_timer > self.reload_cd then for i = 1, self.max_ammo do ammo_bars[i]:refresh() end player_ammo_ui:refresh() table.insert(effects, CircleEffect(self.x, self.y, self.s*self.rs)) self.reload_spring:pull(0.4) self.reload_timer = 0 self.ammo = self.max_ammo if self.reload_text then self.reload_text:die() self.reload_text = nil end end else self.reload_timer = 0 end self.shape:moveTo(self.x, self.y) end function Player:draw() push(self.x, self.y, 0, self.s*self.sx, self.s*self.sy) circle(self.x, self.y, self.rs + self.rso, 2) pop() circlef(self.x + self.s*1.5*self.rs*math.cos(self.r), self.y + self.s*1.5*self.rs*math.sin(self.r), 3) end function Player:hit(damage) self.hp = self.hp - 1 slow(0.5, 0.5) flash(2, black) end PlayerHPUI = Class:extend() function PlayerHPUI:new(x, y) self.timer = Timer() self.x, self.y = x, y self.sx, self.sy = 1.25, 1.25 self.scale_spring = Spring(1) end function PlayerHPUI:update(dt) self.timer:update(dt) self.scale_spring:update(dt) self.hp = player.hp self.max_hp = player.max_hp end function PlayerHPUI:draw() push(self.x, self.y, 0, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy) draw_text(self.hp, self.x, self.y, 0, 1, 1, font_medium) pop() end function PlayerHPUI:refresh() self.scale_spring:pull(0.5) end function PlayerHPUI:jiggle() self.scale_spring:pull(0.2) end PlayerAmmoUI = Class:extend() function PlayerAmmoUI:new(x, y) self.timer = Timer() self.x, self.y = x, y self.sx, self.sy = 1.25, 1.25 self.oy = 0 self.scale_spring = Spring(1) self.timer:tween(0.1, self, {sx = 1, sy = 1}, cubic_in, function() self.sx, self.sy = 1, 1 end) end function PlayerAmmoUI:update(dt) self.timer:update(dt) self.scale_spring:update(dt) self.ammo = player.ammo self.max_ammo = player.max_ammo end function PlayerAmmoUI:draw() if player.reloading then if player.reload_text then if not player.reload_text.visible then return end end end push(self.x, self.y + self.oy, 0, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy) draw_text(self.ammo, self.x, self.y + self.oy, 0, 1, 1, font_medium) pop() if player.reloading then local x = self.x local y1 for i = 1, #ammo_bars do if ammo_bars[i].spent then y1 = ammo_bars[i].y break end end local y2 = ammo_bars[#ammo_bars].y if y1 then g.setLineWidth(4) g.line(x, y1, x, y1 + (y2-y1)*self.t) g.setLineWidth(1) end end end function PlayerAmmoUI:refresh() self.t = 0 self.scale_spring:pull(0.5) end function PlayerAmmoUI:jiggle() self.scale_spring:pull(0.2) end UIBar = Class:extend() function UIBar:new(x, y, h, type) self.type = type self.timer = Timer() self.x, self.y = x, y self.sx, self.sy = 1.25, 1.25 self.w = 16 self.ow = 16 self.h = h self.spent = false self.scale_spring = Spring(1) self.timer:tween(0.1, self, {sx = 1, sy = 1}, cubic_in, function() self.sx, self.sy = 1, 1 end, "refreshs") end function UIBar:update(dt) self.timer:update(dt) self.scale_spring:update(dt) end function UIBar:draw() if self.type == "ammo" then if player.reloading then if player.reload_text then if not player.reload_text.visible then return end end end end push(self.x, self.y, 0, self.sx*self.scale_spring.x, self.sy*self.scale_spring.x) if self.fake_spent then rect(self.x, self.y, self.w, self.h, nil, nil, 2, white) else rectf(self.x, self.y, self.w, self.h, nil, nil, white) end pop() end function UIBar:refresh() self.spent = false self.fake_spent = false self.scale_spring:pull(0.4) self.timer:tween(0.05, self, {w = self.ow}, linear, function() self.w = self.ow end, "refresh") end function UIBar:spend() self.spent = true self.scale_spring:pull(0.4) self.timer:tween(0.05, self, {w = 0}, linear, function() self.w = 0 end, "spend") table.insert(effects, ShootCircle(self.x, self.y)) table.insert(effects, ShootCapsule(self.x, self.y, rng:float(-math.pi/4, 0), rng:float(50, 150))) end function UIBar:fake_spend() self.fake_spent = true self.scale_spring:pull(0.4) end function UIBar:spend2() self.spent = true self.scale_spring:pull(0.4) table.insert(effects, ShootCircle(self.x, self.y, 24)) self.timer:tween(0.1, self, {w = 0}, linear, function() self.w = 0 end, "spend") for i = 1, 8 do table.insert(effects, DeathParticle(self.x, self.y, rng:float(0, 2*math.pi), rng:float(100, 400))) end end CircleEffect = Class:extend() function CircleEffect:new(x, y, r, color) self.x, self.y = x, y self.r = r self.lw = 8 self.color = color or white timer:tween(0.15, self, {r = 4*self.r, lw = 1}, linear, function() self.dead = true end) end function CircleEffect:update(dt) end function CircleEffect:draw() circle(self.x, self.y, self.r, self.lw, self.color) end ReloadText = Class:extend() function ReloadText:new(x, y, duration, r) self.timer = Timer() self.x, self.y = x, y self.r = r self.sx, self.sy = 1, 1 self.characters = {} self.visuals = {1, 1, 1, 1, 1, 1} self.visible = true self.font = font_medium self.w, self.h = self.font:getWidth("RELOAD"), self.font:getHeight() self.scale_spring = Spring(1) self.scale_spring:pull(0.15) self.t = 0 local characters = {"R", "E", "L", "O", "A", "D"} for i = 1, 6 do self.timer:after((i-1)*(0.15/6), function() self.visuals[i] = 3 if self.visuals[i-1] then self.visuals[i-1] = 2 end if self.visuals[i-2] then self.visuals[i-2] = 1 end table.insert(self.characters, characters[i]) end) end self.timer:after(0.15, function() self.visuals = {1, 1, 1, 1, 1, 1} self.timer:every(0.05, function() self.visible = not self.visible end, math.floor((duration - 0.15)/0.05)) self.timer:after((duration - 0.15), function() self.visible = true end) self.timer:every(0.035, function() local random_characters = "0123456789abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWYXZ" for i, c in ipairs(self.characters) do if rng:bool(10) then local r = rng:int(1, #random_characters) self.characters[i] = random_characters:sub(r, r) end if rng:bool(10) then self.visuals[i] = rng:int(1, 4) end end end, math.floor((duration - 0.15)/0.035)) end) end function ReloadText:update(dt) self.timer:update(dt) self.scale_spring:update(dt) end function ReloadText:draw() if not self.visible then return end local w, h = 0, 0 local x, y = self.x - self.w/2, self.y - self.h/2 g.setFont(self.font) push(self.x, self.y, self.r, self.scale_spring.x*self.sx, self.scale_spring.x*self.sy) for i = 1, #self.characters do local cw, ch = self.font:getWidth(self.characters[i]), self.font:getHeight() if self.visuals[i] == 1 then g.setColor(white) g.print(self.characters[i], x + w, y + h) elseif self.visuals[i] == 2 then g.setColor(white) rectf(x + w + cw/2, y + h + ch/2, cw, ch) g.setColor(black) g.print(self.characters[i], x + w, y + h) elseif self.visuals[i] == 3 then g.setColor(white) rectf(x + w + cw/2, y + h + ch/2, cw, ch) elseif self.visuals[i] == 4 then g.setColor(black) rectf(x + w + cw/2, y + h + ch/2, cw, ch) end w = w + self.font:getWidth(self.characters[i]) end g.setColor(white) g.rectangle("fill", x, y - 5, self.w*self.t, 3) pop() end function ReloadText:die() self.dead = true end RefreshEffect = Class:extend() function RefreshEffect:new(x, y, w, h) self.x, self.y = x, y self.w, self.h = w, h self.oy = h/3 timer:tween(0.15, self, {h = 0}, linear, function() self.dead = true end) end function RefreshEffect:update(dt) end function RefreshEffect:draw() g.rectangle("fill", self.x - self.w/2, self.y - self.oy, self.w, self.h) end Projectile = Class:extend() function Projectile:new(x, y, vx, vy, opts) for k, v in pairs(opts or {}) do self[k] = v end self.x, self.y = x, y self.vx, self.vy = vx, vy self.sx, self.sy = 1, 1 self.w, self.h = 16, 4 self.shape = HC.rectangle(self.x - self.w/2, self.y - self.h/2, self.w, self.h) self.shape.parent = self self.damage = rng:int(40, 60) end function Projectile:update(dt) self.vy = self.vy + 300*dt self.x = self.x + self.vx*dt self.y = self.y + self.vy*dt self.r = math.atan2(self.vy, self.vx) if self.x < 0 then self:die(0) end if self.x > gw then self:die(math.pi) end if self.y < 0 then self:die(math.pi/2) end if self.y > gh then self:die(-math.pi/2) end self.shape:moveTo(self.x, self.y) self.shape:setRotation(self.r) end function Projectile:draw() push(self.x, self.y, self.r, self.sx, self.sy) rectf(self.x, self.y, self.w, self.h, nil, nil, white) pop() end function Projectile:die(r) self.dead = true for i = 1, 2 do table.insert(effects, EllipseParticle(self.x, self.y, (r or math.atan2(-self.vy, -self.vx)) + rng:float(-math.pi/4, math.pi/4), rng:float(100, 300))) end table.insert(effects, ShootCircle(self.x, self.y)) end Enemy1 = Class:extend() function Enemy1:new(x, y) self.timer = Timer() self.x, self.y = x, y self.w = 16 self.r = 0 self.sx, self.sy = 1, 1 self.d = 1 self.v = 100 self.vs = {} local r = 0 for i = 1, 8 do local w = rng:float(0.95, 1.05) self.vs[2*(i-1)+1] = self.w*w*math.cos(r) self.vs[2*i] = self.w*w*math.sin(r) r = r + math.pi/4 end self.vr = rng:float(-4*math.pi, 4*math.pi) self.shape = HC.polygon(unpack(to_polygon(self.x, self.y, self.vs))) self.shape.parent = self self.hp = 800 end function Enemy1:update(dt) self.timer:update(dt) self.y = self.y + self.d*self.v*dt self.r = self.r + self.vr*dt self.shape:moveTo(self.x, self.y) self.shape:setRotation(self.r) for other in pairs(HC.neighbors(self.shape)) do if other.parent:is(Projectile) then local collides = self.shape:collidesWith(other) if collides then self:hit(other.parent.damage, other.parent.r) other.parent:die() table.insert(effects, HitEffect(other.parent.x, other.parent.y)) end end end if self.y > gh then self.fast = true self.d = -1 self.v = 150 self.y = gh - 2 table.insert(effects, DeathCircle(self.x, self.y, 2*self.w, red)) table.insert(effects, CircleEffect(self.x, self.y, self.w, red)) for i = player.max_hp, 1, -1 do if not hp_bars[i].spent and not hp_bars[i].fake_spent then hp_bars[i]:fake_spend() break end end flash(1, black) end if self.y < 0 and self.fast then self.dead = true player:hit(1) table.insert(effects, DeathCircle(self.x, self.y, 2*self.w, red)) table.insert(effects, CircleEffect(self.x, self.y, self.w, red)) for i = player.max_hp, 1, -1 do if hp_bars[i].fake_spent and not hp_bars[i].spent then hp_bars[i]:spend2() break end end end end function Enemy1:draw() if self.y > gh + 64 then return end local color = red if self.hit_flash then color = white end push(self.x, self.y, self.r, self.sx, self.sy) polygon(to_polygon(self.x, self.y, self.vs), 2, color) pop() end function Enemy1:hit(damage, r) self.sx, self.sy = 1.35, 1.35 self.hit_flash = true self.timer:tween(0.1, self, {sx = 1, sy = 1}, linear, function() self.hit_flash = false end, "hit") self.hp = self.hp - damage if self.hp <= 0 then self.dead = true table.insert(effects, DeathCircle(self.x, self.y, 2*self.w)) for i = 1, 4 do table.insert(effects, DustParticle(self.x, self.y, white)) end if self.fast then for i = 1, player.max_hp do if hp_bars[i].fake_spent then hp_bars[i]:refresh() break end end end end end DamageNumber = Class:extend() function DamageNumber:new(x, y, vx, vy, t) self.x, self.y = x, y self.sx, self.sy = 1.00, 1.00 self.vx, self.vy = vx, vy self.t = t self.scale_spring = Spring(1) self.scale_spring:pull(0.25) self.r = 0 -- if vx > 0 then self.vr = rng:float(2*math.pi, 4*math.pi) else self.vr = rng:float(-4*math.pi, -2*math.pi) end timer:after(0.25, function() timer:tween(0.05, self, {sx = 0, sy = 0}, linear, function() self.dead = true end) end) end function DamageNumber:update(dt) self.scale_spring:update(dt) self.vy = self.vy + 400*dt self.x = self.x + self.vx*dt self.y = self.y + self.vy*dt -- self.r = self.r + self.vr*dt end function DamageNumber:draw() push(self.x, self.y, self.r, 1.25*self.scale_spring.x*self.sx, 1.25*self.scale_spring.x*self.sy) draw_text(self.t, self.x, self.y, 0, 1, 1, font_small) pop() end HitEffect = Class:extend() function HitEffect:new(x, y) self.x, self.y = x, y self.r = rng:float(0, 2*math.pi) self.animation = Animation(0.025, get_animation_frames("hit1"), "once", {[0] = function() self.dead = true end}) self.sx, self.sy = 1.2, 1.2 timer:tween(0.025*get_animation_frames("hit1"), self, {sx = 1, sy = 1}, linear) end function HitEffect:update(dt) self.animation:update(dt) end function HitEffect:draw() draw_animation("hit1", self.animation:get_current_frame(), self.x, self.y, self.r, 1.35*self.sx, 1.35*self.sy) end DustParticle = Class:extend() function DustParticle:new(x, y, color) self.x, self.y = x, y self.animation = Animation(1, get_animation_frames("smoke1"), "once") self.color = color or red self.sx, self.sy = 0, 0 self.v = rng:float(100, 220) self.r = rng:float(0, 2*math.pi) self.rs = 0 self.vr = rng:float(0, 2*math.pi) timer:after(0.1, function() self.color = color or white end) timer:tween(rng:float(0.04, 0.06), self, {sx = 0.7, sy = 0.7}, cubic_in_out, function() timer:tween(rng:float(0.3, 0.4), self, {sx = 0, sy = 0, v = 0}, linear, function() self.dead = true end) end) end function DustParticle:update(dt) self.animation:update(dt) self.rs = self.rs + self.vr*dt self.x, self.y = self.x + self.v*math.cos(self.r)*dt, self.y + self.v*math.sin(self.r)*dt end function DustParticle:draw() g.setShader(combine) g.setColor(self.color) draw_animation("smoke1", self.animation:get_current_frame(), self.x, self.y, self.rs, 3.5*self.sx, 3.5*self.sy) g.setShader() g.setColor(white) end AnimatedEffect = Class:extend() function AnimatedEffect:new(x, y, name, delay, loop_mode) self.x, self.y = x, y self.r, self.sx, self.sy = 0, 1, 1 self.name = name self.delay = delay self.loop_mode = loop_mode or "once" self.animation = Animation(delay, get_animation_frames(name), self.loop_mode, {[0] = function() self.dead = true end}) end function AnimatedEffect:update(dt) self.animation:update(dt) end function AnimatedEffect:draw() draw_animation(self.name, self.animation:get_current_frame(), self.x, self.y, self.r, self.sx, self.sy) end EllipseParticle = Class:extend() function EllipseParticle:new(x, y, r, v) self.x, self.y = x, y self.r = r self.v = v self.w, self.h = 9, 3 timer:tween({0.2, 0.5}, self, {v = 0}, linear, function() self.dead = true end) end function EllipseParticle:update(dt) self.x = self.x + self.v*math.cos(self.r)*dt self.y = self.y + (self.v*math.sin(self.r) + 100)*dt self.w = remap(self.v, 0, 400, 0, 9) self.h = remap(self.v, 0, 400, 0, 3) end function EllipseParticle:draw() push(self.x, self.y, math.atan2(self.v*math.sin(self.r) + 100, self.v*math.cos(self.r))) ellipsef(self.x, self.y, self.w, self.h) pop() end DeathCircle = Class:extend() function DeathCircle:new(x, y, r, color) self.x, self.y = x, y self.r = r self.color = color or white timer:tween(0.26, self, {r = 0}, cubic_in_out, function() self.dead = true end) end function DeathCircle:update(dt) end function DeathCircle:draw() circlef(self.x, self.y, self.r, self.color) end DeathParticle = Class:extend() function DeathParticle:new(x, y, r, v, color) self.x, self.y = x, y self.r = r self.v = v self.w, self.h = 14, 4.5 self.color = color or white timer:tween({0.2, 0.4}, self, {v = 0}, linear, function() self.dead = true end) end function DeathParticle:update(dt) self.x = self.x + self.v*math.cos(self.r)*dt self.y = self.y + (self.v*math.sin(self.r) + 0)*dt self.w = remap(self.v, 0, 400, 0, 14) self.h = remap(self.v, 0, 400, 0, 4.5) end function DeathParticle:draw() push(self.x, self.y, math.atan2(self.v*math.sin(self.r) + 0, self.v*math.cos(self.r))) rectf(self.x, self.y, self.w, self.h, nil, nil, self.color) pop() end ShootCircle = Class:extend() function ShootCircle:new(x, y, rs) self.x, self.y = x, y self.rs = rs or 12 timer:tween(0.1, self, {rs = 0}, linear, function() self.dead = true end) end function ShootCircle:update(dt) end function ShootCircle:draw() circlef(self.x, self.y, self.rs) end ShootCapsule = Class:extend() function ShootCapsule:new(x, y, r, v) self.x, self.y = x, y self.vx, self.vy = v*math.cos(r), v*math.sin(r) self.w, self.h = 6, 3 self.r = 0 self.vr = rng:float(-4*math.pi, 4*math.pi) end function ShootCapsule:update(dt) self.vy = self.vy + 600*dt self.x = self.x + self.vx*dt self.y = self.y + self.vy*dt self.r = self.r + self.vr*dt if self.y > gh or self.y < 0 or self.x > gw or self.x < 0 then self.dead = true table.insert(effects, ShootCircle(self.x, self.y)) end end function ShootCapsule:draw() push(self.x, self.y, self.r) rectf(self.x, self.y, self.w, self.h) pop() end