feat(actor3D): cast shadow below the actor.
This commit is contained in:
parent
c043bb8ecf
commit
a3b0f47127
4 changed files with 177 additions and 2 deletions
|
@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
- **camera:** Add two new camera types: "middle" and "zoom".
|
- **camera:** Add two new camera types: "middle" and "zoom".
|
||||||
|
|
||||||
- **world:** Add a fake 3D world, à la Zelda or BeatThemAll
|
- **world:** Add a fake 3D world, à la Zelda or BeatThemAll, complete with shadow support
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,14 @@ function Actor3D:new(world, type, x, y, z, w, h, d, isSolid)
|
||||||
self:initHitboxes()
|
self:initHitboxes()
|
||||||
self.world:registerShape(self)
|
self.world:registerShape(self)
|
||||||
self.boxes = Boxes
|
self.boxes = Boxes
|
||||||
|
self.doCastShadows = true
|
||||||
end
|
end
|
||||||
|
|
||||||
function Actor3D:destroy()
|
function Actor3D:destroy()
|
||||||
|
self:removeOldShadowTargets()
|
||||||
|
if self.box ~= nil then
|
||||||
|
self.world:removeTerrain(self)
|
||||||
|
end
|
||||||
self.world:removeActor(self)
|
self.world:removeActor(self)
|
||||||
self.mainHitbox:destroy()
|
self.mainHitbox:destroy()
|
||||||
self.world:removeShape(self)
|
self.world:removeShape(self)
|
||||||
|
@ -87,11 +92,19 @@ end
|
||||||
|
|
||||||
function Actor3D:move(dx, dy, dz)
|
function Actor3D:move(dx, dy, dz)
|
||||||
local cols, colNumber = {}, 0
|
local cols, colNumber = {}, 0
|
||||||
|
local oldx, oldy, oldz = self.x, self.y, self.z
|
||||||
if (self.isDestroyed == false) then
|
if (self.isDestroyed == false) then
|
||||||
self.x, self.y, self.z, cols, colNumber = self.mainHitbox:checkCollision(dx, dy, dz, self.filter)
|
self.x, self.y, self.z, cols, colNumber = self.mainHitbox:checkCollision(dx, dy, dz, self.filter)
|
||||||
self.mainHitbox:updatePosition()
|
self.mainHitbox:updatePosition()
|
||||||
self.world:updateShape(self)
|
self.world:updateShape(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if (oldx ~= self.x) or (oldy ~= self.y) or (oldz ~= self.z) or (self.shadowTargetsPrevious == nil) then
|
||||||
|
if (self.doCastShadows) then
|
||||||
|
self:castShadow()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return self.x, self.y, self.z, cols, colNumber
|
return self.x, self.y, self.z, cols, colNumber
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -201,9 +214,71 @@ function Actor3D:checkHitboxCollisionsAtPoint(name, dx, dy, dz, filter)
|
||||||
return x, y, z, cols, colNumber
|
return x, y, z, cols, colNumber
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- SHADOW FUNCTIONS
|
||||||
|
-- Handle everything related to shadow
|
||||||
|
|
||||||
|
function Actor3D:castShadow()
|
||||||
|
local shadowTargets = self.world:getTerrainInRect(self.x, self.y, self.w, self.d)
|
||||||
|
-- initialize the shadowTargetsPrevious variable if it doesn't exist
|
||||||
|
if (self.shadowTargetsPrevious == nil) then
|
||||||
|
self.shadowTargetsPrevious = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, target in ipairs(shadowTargets) do
|
||||||
|
-- We test if the actor is below the current actor
|
||||||
|
if (target ~= self) and (target.box ~= nil) then
|
||||||
|
|
||||||
|
if (target.z + target.d <= self.z + self.d) then
|
||||||
|
-- Remove the target of the list of item targeted last update,
|
||||||
|
-- in order to only have object no longer shadowed after the
|
||||||
|
-- end of the loop
|
||||||
|
for j, oldtarget in ipairs(self.shadowTargetsPrevious) do
|
||||||
|
if (target == oldtarget) then
|
||||||
|
table.remove(self.shadowTargetsPrevious, j)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We update the shadow source
|
||||||
|
local x, y = math.floor(self.x - target.x), math.floor(self.y - target.y)
|
||||||
|
target.box:setShadowSource(self, x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- At this point, if a target is still in the shadowTargetsPrevious list,
|
||||||
|
-- it mean that it's not shadowed. So we can simply remove the shadow.
|
||||||
|
self:removeOldShadowTargets()
|
||||||
|
|
||||||
|
self.shadowTargetsPrevious = shadowTargets
|
||||||
|
end
|
||||||
|
|
||||||
|
function Actor3D:removeOldShadowTargets()
|
||||||
|
if (self.shadowTargetsPrevious ~= nil) then
|
||||||
|
for i, target in ipairs(self.shadowTargetsPrevious) do
|
||||||
|
if (target.box ~= nil) then
|
||||||
|
target.box:removeShadowSource(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Actor3D:redrawShadowCanvas()
|
||||||
|
if (self.box ~= nil) then
|
||||||
|
self.box:redrawShadowCanvas()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- DRAW FUNCTIONS
|
-- DRAW FUNCTIONS
|
||||||
-- Draw the actors.
|
-- Draw the actors.
|
||||||
|
|
||||||
|
function Actor3D:drawShadow(x, y)
|
||||||
|
love.graphics.setColor(0, 0, 0, 1)
|
||||||
|
love.graphics.rectangle("fill", x, y, self.w, self.h)
|
||||||
|
utils.graphics.resetColor()
|
||||||
|
end
|
||||||
|
|
||||||
function Actor3D:getShape()
|
function Actor3D:getShape()
|
||||||
return (self.x), (self.y - self.z - self.d), self.w, (self.h + self.d)
|
return (self.x), (self.y - self.z - self.d), self.w, (self.h + self.d)
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,7 +24,9 @@
|
||||||
local Box3D = Object:extend()
|
local Box3D = Object:extend()
|
||||||
|
|
||||||
function Box3D:new(owner, w, h, d)
|
function Box3D:new(owner, w, h, d)
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
|
self.world = owner.world
|
||||||
|
self.cameras = self.world.cameras
|
||||||
|
|
||||||
self.w = w
|
self.w = w
|
||||||
self.h = h
|
self.h = h
|
||||||
|
@ -32,15 +34,20 @@ function Box3D:new(owner, w, h, d)
|
||||||
|
|
||||||
self.haveLine = true
|
self.haveLine = true
|
||||||
|
|
||||||
|
self.shadowSources = {}
|
||||||
|
self.needRedraw = false
|
||||||
|
|
||||||
self.texture = {}
|
self.texture = {}
|
||||||
self:setTopTexture()
|
self:setTopTexture()
|
||||||
self:setBottomTexture()
|
self:setBottomTexture()
|
||||||
|
self.texture.shadows = nil
|
||||||
|
|
||||||
self:register()
|
self:register()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Box3D:register()
|
function Box3D:register()
|
||||||
self.owner.box = self
|
self.owner.box = self
|
||||||
|
self.world:registerTerrain(self.owner)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Box3D:setSizeFromOwner()
|
function Box3D:setSizeFromOwner()
|
||||||
|
@ -80,6 +87,60 @@ function Box3D:setBottomTexture()
|
||||||
canvas:release()
|
canvas:release()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Box3D:setShadowSource(actor, x, y)
|
||||||
|
local foundShadow = false
|
||||||
|
|
||||||
|
for i,v in ipairs(self.shadowSources) do
|
||||||
|
if (v.actor == actor) then
|
||||||
|
if (v.x ~= x) or (v.y ~= y) then
|
||||||
|
v.x = x
|
||||||
|
v.y = y
|
||||||
|
self.needRedraw = true
|
||||||
|
end
|
||||||
|
foundShadow = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (foundShadow == false) then
|
||||||
|
local shadow = {}
|
||||||
|
shadow.actor = actor
|
||||||
|
shadow.x = x
|
||||||
|
shadow.y = y
|
||||||
|
self.needRedraw = true
|
||||||
|
|
||||||
|
table.insert(self.shadowSources, shadow)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Box3D:removeShadowSource(actor)
|
||||||
|
for i,v in ipairs(self.shadowSources) do
|
||||||
|
if (v.actor == actor) then
|
||||||
|
table.remove(self.shadowSources, i)
|
||||||
|
self.needRedraw = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Box3D:redrawShadowCanvas()
|
||||||
|
if (self.needRedraw) then
|
||||||
|
print("DEBUG: Generating " .. #self.shadowSources .. " shadows")
|
||||||
|
local canvas = love.graphics.newCanvas(self.w, self.h)
|
||||||
|
love.graphics.setCanvas( canvas )
|
||||||
|
for i,v in ipairs(self.shadowSources) do
|
||||||
|
v.actor:drawShadow(v.x, v.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setCanvas( )
|
||||||
|
|
||||||
|
local imagedata = canvas:newImageData()
|
||||||
|
self.texture.shadows = love.graphics.newImage( imagedata )
|
||||||
|
imagedata:release()
|
||||||
|
canvas:release()
|
||||||
|
|
||||||
|
self.needRedraw = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function Box3D:draw(x, y, z)
|
function Box3D:draw(x, y, z)
|
||||||
love.graphics.setColor(0, 0, 0, 1)
|
love.graphics.setColor(0, 0, 0, 1)
|
||||||
if (self.haveLine) then
|
if (self.haveLine) then
|
||||||
|
@ -88,6 +149,10 @@ function Box3D:draw(x, y, z)
|
||||||
utils.graphics.resetColor()
|
utils.graphics.resetColor()
|
||||||
love.graphics.draw(self.texture.top, x, (y-z) - (self.d))
|
love.graphics.draw(self.texture.top, x, (y-z) - (self.d))
|
||||||
love.graphics.draw(self.texture.bottom, x, (y-z) - (self.d) + (self.h))
|
love.graphics.draw(self.texture.bottom, x, (y-z) - (self.d) + (self.h))
|
||||||
|
if (self.texture.shadows ~= nil) and (#self.shadowSources > 0) then
|
||||||
|
print("DEBUG: Drawing " .. #self.shadowSources .. " shadow textures")
|
||||||
|
love.graphics.draw(self.texture.shadows, x, (y-z) - (self.d))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Box3D
|
return Box3D
|
||||||
|
|
|
@ -46,6 +46,7 @@ function World3D:initActors()
|
||||||
self.actors = {}
|
self.actors = {}
|
||||||
self.bodies = Bump3D.newWorld(50)
|
self.bodies = Bump3D.newWorld(50)
|
||||||
self:initShapes()
|
self:initShapes()
|
||||||
|
self:initTerrain()
|
||||||
end
|
end
|
||||||
|
|
||||||
function World3D:newActor(name, x, y, z)
|
function World3D:newActor(name, x, y, z)
|
||||||
|
@ -161,6 +162,17 @@ function World3D:getBodiesInRect(x, y, w, h)
|
||||||
return {} --self.bodies:queryRect(x, y, w, h)
|
return {} --self.bodies:queryRect(x, y, w, h)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
-- Update everything in the world
|
||||||
|
|
||||||
|
function World3D:updateActors(dt)
|
||||||
|
World3D.super.updateActors(self, dt)
|
||||||
|
local actors = self:getActors()
|
||||||
|
for i,v in ipairs(actors) do
|
||||||
|
v:redrawShadowCanvas()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- SHAPE SYSTEM
|
-- SHAPE SYSTEM
|
||||||
-- Handle onscreen shapes
|
-- Handle onscreen shapes
|
||||||
|
|
||||||
|
@ -190,6 +202,29 @@ function World3D:getShapeInRect(x, y, w, h)
|
||||||
return self.shapes:queryRect(x, y, w, h)
|
return self.shapes:queryRect(x, y, w, h)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- TERRAIN SYSTEM
|
||||||
|
-- Handle onscreen shapes
|
||||||
|
|
||||||
|
function World3D:initTerrain()
|
||||||
|
self.terrains = Bump.newWorld(50)
|
||||||
|
end
|
||||||
|
|
||||||
|
function World3D:registerTerrain(actor)
|
||||||
|
return self.terrains:add(actor, actor.x, actor.y, actor.w, actor.h)
|
||||||
|
end
|
||||||
|
|
||||||
|
function World3D:updateTerrain(actor)
|
||||||
|
return self.terrains:update(actor, actor.x, actor.y, actor.w, actor.h)
|
||||||
|
end
|
||||||
|
|
||||||
|
function World3D:removeTerrain(actor)
|
||||||
|
return self.terrains:remove(actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
function World3D:getTerrainInRect(x, y, w, h)
|
||||||
|
return self.terrains:queryRect(x, y, w, h)
|
||||||
|
end
|
||||||
|
|
||||||
-- DRAW FUNCTIONS
|
-- DRAW FUNCTIONS
|
||||||
-- Functions to draw the world
|
-- Functions to draw the world
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue