summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bloodParticle.lua82
-rw-r--r--dustParticle.lua135
-rw-r--r--world.lua47
3 files changed, 263 insertions, 1 deletions
diff --git a/bloodParticle.lua b/bloodParticle.lua
new file mode 100644
index 0000000..12aa2af
--- /dev/null
+++ b/bloodParticle.lua
@@ -0,0 +1,82 @@
+local BloodParticle = {}
+BloodParticle.__index = BloodParticle
+
+local SHRINK_RATE = 6
+local GRAVITY = 300
+local PARTICLE_COUNT = 32
+local BURST_SPEED_MIN = 40
+local BURST_SPEED_MAX = 160
+
+local BLOOD_COLORS = {
+ {0.22, 0.38, 0.22, 1},
+ {0.18, 0.30, 0.18, 1},
+ {0.28, 0.44, 0.28, 1},
+ {0.14, 0.22, 0.14, 1},
+}
+
+local GLOW_COLOR = {0.05, 0.7, 0.05, 0.0} -- meh
+
+function BloodParticle.new(x, y, radius, color, vx, vy)
+ local self = setmetatable({}, BloodParticle)
+ self.x = x
+ self.y = y
+ self.radius = radius
+ self.initialRadius = radius
+ self.color = color
+ self.vx = vx or 0
+ self.vy = vy or 0
+ self.stuck = false
+ self.kill = false
+ return self
+end
+
+function BloodParticle:update(dt, isSolidAt)
+ if self.stuck or self.kill then return end
+
+ self.x = self.x + self.vx * dt
+ self.y = self.y + self.vy * dt
+ self.vy = self.vy + GRAVITY * dt
+
+ self.radius = self.radius - SHRINK_RATE * dt
+
+ local hitWall = isSolidAt and isSolidAt(self.x, self.y)
+ if hitWall or self.radius <= self.initialRadius * 0.5 then
+ self.stuck = true
+ self.radius = math.max(self.radius, self.initialRadius * 0.35)
+ self.vx, self.vy = 0, 0
+ return
+ end
+
+ if self.radius <= 0 then
+ self.kill = true
+ end
+end
+
+function BloodParticle:draw()
+ if self.kill then return end
+
+ love.graphics.setBlendMode("add")
+ love.graphics.setColor(GLOW_COLOR)
+ love.graphics.circle("fill", self.x, self.y, self.radius * 2.2)
+ love.graphics.setBlendMode("alpha")
+
+ love.graphics.setColor(self.color)
+ love.graphics.circle("fill", self.x, self.y, self.radius)
+ love.graphics.setColor(1, 1, 1, 1)
+end
+
+function BloodParticle.burst(x, y)
+ local particles = {}
+ for _ = 1, PARTICLE_COUNT do
+ local angle = math.random() * math.pi * 2
+ local speed = BURST_SPEED_MIN + math.random() * (BURST_SPEED_MAX - BURST_SPEED_MIN)
+ local vx = math.cos(angle) * speed
+ local vy = math.sin(angle) * speed - 40
+ local radius = 1.5 + math.random() * 8.0
+ local color = BLOOD_COLORS[math.random(#BLOOD_COLORS)]
+ table.insert(particles, BloodParticle.new(x, y, radius, color, vx, vy))
+ end
+ return particles
+end
+
+return BloodParticle
diff --git a/dustParticle.lua b/dustParticle.lua
new file mode 100644
index 0000000..8e7edd4
--- /dev/null
+++ b/dustParticle.lua
@@ -0,0 +1,135 @@
+local DustParticle = {}
+DustParticle.__index = DustParticle
+
+local MAX_DUST = 30
+local DRIFT_SPEED_MIN = 4
+local DRIFT_SPEED_MAX = 12
+local WOBBLE_AMP_MIN = 3
+local WOBBLE_AMP_MAX = 8
+local WOBBLE_FREQ_MIN = 0.8
+local WOBBLE_FREQ_MAX = 2.5
+local GLOW_FREQ_MIN = 1.5
+local GLOW_FREQ_MAX = 4.0
+local GLOW_RADIUS_MIN = 2
+local GLOW_RADIUS_MAX = 5
+local MARGIN = 16
+
+local DUST_COLORS = {
+ {1.0, 0.95, 0.75},
+ {0.85, 0.9, 1.0},
+ {1.0, 0.85, 0.6},
+ {0.75, 0.85, 1.0},
+}
+
+function DustParticle.new(x, y)
+ local self = setmetatable({}, DustParticle)
+ self.x = x
+ self.y = y
+
+ local angle = math.random() * math.pi * 2
+ local speed = DRIFT_SPEED_MIN + math.random() * (DRIFT_SPEED_MAX - DRIFT_SPEED_MIN)
+ self.dx = math.cos(angle) * speed
+ self.dy = math.sin(angle) * speed
+
+ self.perpX = -math.sin(angle)
+ self.perpY = math.cos(angle)
+
+ self.wobbleAmp = WOBBLE_AMP_MIN + math.random() * (WOBBLE_AMP_MAX - WOBBLE_AMP_MIN)
+ self.wobbleFreq = WOBBLE_FREQ_MIN + math.random() * (WOBBLE_FREQ_MAX - WOBBLE_FREQ_MIN)
+ self.wobblePhase = math.random() * math.pi * 2
+
+ self.glowFreq = GLOW_FREQ_MIN + math.random() * (GLOW_FREQ_MAX - GLOW_FREQ_MIN)
+ self.glowPhase = math.random() * math.pi * 2
+
+ self.baseAlpha = 0.15 + math.random() * 0.25
+ self.color = DUST_COLORS[math.random(#DUST_COLORS)]
+ self.time = 0
+ return self
+end
+
+function DustParticle:update(dt)
+ self.time = self.time + dt
+ local wobble = math.sin(self.time * self.wobbleFreq + self.wobblePhase) * self.wobbleAmp
+ self.x = self.x + (self.dx + self.perpX * wobble * 0.3) * dt
+ self.y = self.y + (self.dy + self.perpY * wobble * 0.3) * dt
+end
+
+function DustParticle:draw()
+ local glowT = math.sin(self.time * self.glowFreq + self.glowPhase)
+ local glowRadius = GLOW_RADIUS_MIN + (GLOW_RADIUS_MAX - GLOW_RADIUS_MIN) * (glowT * 0.5 + 0.5)
+ local alpha = self.baseAlpha * (0.6 + 0.4 * (glowT * 0.5 + 0.5))
+
+ love.graphics.setBlendMode("add")
+ love.graphics.setColor(self.color[1], self.color[2], self.color[3], alpha * 0.4)
+ love.graphics.circle("fill", self.x, self.y, glowRadius)
+ love.graphics.setColor(self.color[1], self.color[2], self.color[3], alpha)
+ love.graphics.circle("fill", self.x, self.y, 1)
+ love.graphics.setBlendMode("alpha")
+ love.graphics.setColor(1, 1, 1, 1)
+end
+
+function DustParticle:isOutOfBounds(viewX, viewY, viewW, viewH)
+ return self.x < viewX - MARGIN or self.x > viewX + viewW + MARGIN
+ or self.y < viewY - MARGIN or self.y > viewY + viewH + MARGIN
+end
+
+local DustSystem = {}
+DustSystem.__index = DustSystem
+
+function DustSystem.new()
+ local self = setmetatable({}, DustSystem)
+ self.particles = {}
+ self.spawned = false
+ return self
+end
+
+function DustSystem:update(dt, camX, camY, camW, camH)
+ if not self.spawned then
+ for _ = 1, MAX_DUST do
+ local x = camX + math.random() * camW
+ local y = camY + math.random() * camH
+ table.insert(self.particles, DustParticle.new(x, y))
+ end
+ self.spawned = true
+ end
+
+ for _, p in ipairs(self.particles) do
+ p:update(dt)
+ end
+
+ for i = #self.particles, 1, -1 do
+ local p = self.particles[i]
+ if p:isOutOfBounds(camX, camY, camW, camH) then
+ local edge = math.random(4)
+ local x, y
+ if edge == 1 then
+ x = camX - MARGIN * 0.5
+ y = camY + math.random() * camH
+ elseif edge == 2 then
+ x = camX + camW + MARGIN * 0.5
+ y = camY + math.random() * camH
+ elseif edge == 3 then
+ x = camX + math.random() * camW
+ y = camY - MARGIN * 0.5
+ else
+ x = camX + math.random() * camW
+ y = camY + camH + MARGIN * 0.5
+ end
+ self.particles[i] = DustParticle.new(x, y)
+ end
+ end
+
+ while #self.particles < MAX_DUST do
+ local x = camX + math.random() * camW
+ local y = camY + math.random() * camH
+ table.insert(self.particles, DustParticle.new(x, y))
+ end
+end
+
+function DustSystem:draw()
+ for _, p in ipairs(self.particles) do
+ p:draw()
+ end
+end
+
+return DustSystem
diff --git a/world.lua b/world.lua
index a4d7f12..0678804 100644
--- a/world.lua
+++ b/world.lua
@@ -4,6 +4,8 @@ local Player = require("player")
local Camera = require("camera")
local Textbox = require("textbox")
local Enemy = require("enemy")
+local BloodParticle = require("bloodParticle")
+local DustSystem = require("dustParticle")
local World = {}
World.__index = World
@@ -39,6 +41,9 @@ function World:new()
self.contactKind = {}
self.pendingPlayerDeath = nil
self.playerTextbox = Textbox:new()
+ self.bloodParticles = {}
+ self.bloodSplatters = {}
+ self.dustSystem = DustSystem.new()
return self
end
@@ -395,6 +400,21 @@ function World:isTileSolidAtPixel(x, y)
return gid and gid ~= 0
end
+function World:spawnBlood(x, y)
+ local newParticles = BloodParticle.burst(x, y)
+ for _, p in ipairs(newParticles) do
+ table.insert(self.bloodParticles, p)
+ end
+end
+
+function World:drawBloodSplatters()
+ for _, s in ipairs(self.bloodSplatters) do
+ love.graphics.setColor(s.color)
+ love.graphics.circle("fill", s.x, s.y, s.radius)
+ end
+ love.graphics.setColor(1, 1, 1, 1)
+end
+
local function drawTileLayer(layer, mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
if not layer or not layer.visible or not layer.data then return end
local w = layer.width or 0
@@ -485,6 +505,7 @@ function World:draw()
love.graphics.setCanvas(self.refractionCanvas)
love.graphics.clear(0, 0, 0, 1)
drawTileLayer(self.tilemap:getFargroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
+ self:drawBloodSplatters()
drawTileLayer(self.tilemap:getDecorationBackgroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
drawTileLayer(self.tilemap:getBackgroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
drawTileLayer(self.tilemap:getGroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
@@ -494,6 +515,9 @@ function World:draw()
for _, e in ipairs(self.entities) do
if e.draw then e:draw() else World.drawEntityDefault(self, e) end
end
+ for _, p in ipairs(self.bloodParticles) do
+ p:draw()
+ end
drawTileLayer(self.tilemap:getForegroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
drawTileLayer(self.tilemap:getDecorationForegroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
love.graphics.setCanvas(mainCanvas)
@@ -544,6 +568,7 @@ function World:draw()
drawTileLayer(self.tilemap:getDecorationBackgroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
drawTileLayer(self.tilemap:getGroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
drawTileLayer(self.tilemap:getFargroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
+ self:drawBloodSplatters()
drawTileLayer(self.tilemap:getDecorationBackgroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
love.graphics.setColor(0.25, 0.5, 0.9, 0.6)
@@ -569,13 +594,17 @@ function World:draw()
if not useRefraction then
for _, e in ipairs(self.entities) do
- print(e)
if e.draw then e:draw() else World.drawEntityDefault(self, e) end
end
+ for _, p in ipairs(self.bloodParticles) do
+ p:draw()
+ end
drawTileLayer(self.tilemap:getForegroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
drawTileLayer(self.tilemap:getDecorationForegroundLayer(), mapTileW, mapTileH, tileGidInfo, viewMinX, viewMinY, viewMaxX, viewMaxY)
end
+ self.dustSystem:draw()
+
if DEBUG then
World.drawPhysicsBodyOutlines(self.entities)
World.drawPhysicsBodyOutlines(self.groundEntities)
@@ -619,6 +648,9 @@ function World:update(dt)
self.physicsWorld:update(PHYSICS_DT)
if self.pendingPlayerDeath and self.player then
+ local px = self.player.x + self.player.width / 2
+ local py = self.player.y + self.player.height / 2
+ self:spawnBlood(px, py)
self.player:die(self.pendingPlayerDeath.nx, self.pendingPlayerDeath.ny)
self.pendingPlayerDeath = nil
end
@@ -659,6 +691,18 @@ function World:update(dt)
if spike.update then spike:update(dt) end
end
+ local solidCheck = function(x, y) return self:isTileSolidAtPixel(x, y) end
+ for i = #self.bloodParticles, 1, -1 do
+ local p = self.bloodParticles[i]
+ p:update(dt, solidCheck)
+ if p.stuck then
+ table.insert(self.bloodSplatters, { x = p.x, y = p.y, radius = p.radius, color = p.color })
+ table.remove(self.bloodParticles, i)
+ elseif p.kill then
+ table.remove(self.bloodParticles, i)
+ end
+ end
+
for i = #self.activeSplashes, 1, -1 do
self.activeSplashes[i].t = self.activeSplashes[i].t + dt * 2
if self.activeSplashes[i].t >= 1 then
@@ -674,6 +718,7 @@ function World:update(dt)
if self.camera then
self.camera:setTarget(self.player)
self.camera:update(dt)
+ self.dustSystem:update(dt, self.camera.x, self.camera.y, self.camera.width, self.camera.height)
end
if self.playerTextbox.active and self.player then