diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84d802a..5d370f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **camera:** Add two new camera types: "middle" and "zoom".
+- **world:** Add a fake 3D world, à la Zelda or BeatThemAll, complete with shadow support
+
### Changed
- **world2D:** Use a list for bodies (hitboxes, etc) and one other for actors
diff --git a/examples/gameplay/moveplayer3D/actors/init.lua b/examples/gameplay/moveplayer3D/actors/init.lua
new file mode 100644
index 0000000..9d67a55
--- /dev/null
+++ b/examples/gameplay/moveplayer3D/actors/init.lua
@@ -0,0 +1,13 @@
+local Obj = {}
+
+-- On charge toutes les différentes types d'acteurs
+local cwd = (...):gsub('%.init$', '') .. "."
+Obj.Player = require(cwd .. "player")
+
+Obj.index = {}
+Obj.index["player"] = Obj.Player
+
+Obj.collisions = {}
+Obj.collisions["wall"] = require(cwd .. "wall")
+
+return Obj
diff --git a/examples/gameplay/moveplayer3D/actors/parent.lua b/examples/gameplay/moveplayer3D/actors/parent.lua
new file mode 100644
index 0000000..b71c7e0
--- /dev/null
+++ b/examples/gameplay/moveplayer3D/actors/parent.lua
@@ -0,0 +1,14 @@
+local Base = require "gamecore.modules.world.actors.actor3D"
+local Parent = Base:extend()
+
+function Parent:new(world, type, x, y, z, w, h, d, isSolid)
+ self.scene = world.scene
+ Parent.super.new(self, world, type, x, y, z, w, h, d, isSolid)
+end
+
+function Parent:draw()
+ Parent.super.draw(self)
+ self:drawMainHitbox()
+end
+
+return Parent
diff --git a/examples/gameplay/moveplayer3D/actors/player.lua b/examples/gameplay/moveplayer3D/actors/player.lua
new file mode 100644
index 0000000..85327b8
--- /dev/null
+++ b/examples/gameplay/moveplayer3D/actors/player.lua
@@ -0,0 +1,74 @@
+local cwd = (...):gsub('%.player$', '') .. "."
+local Parent = require(cwd .. "parent")
+local Player = Parent:extend()
+
+function Player:new(world, x, y, z, id)
+ Player.super.new(self, world, "player", x, y, 0, 16, 16, 24, true)
+ self:setGravity(480)
+
+ self:setSprite("player", 8, 12)
+ self:cloneSprite()
+end
+
+function Player:updateStart(dt)
+ self.xfrc, self.yfrc = 480*3, 480*3
+
+ if self.keys["up"].isDown then
+ self.ysp = -120
+ end
+ if self.keys["down"].isDown then
+ self.ysp = 120
+ end
+ if self.keys["left"].isDown then
+ self.xsp = -120
+ end
+ if self.keys["right"].isDown then
+ self.xsp = 120
+ end
+
+ if self.keys["A"].isDown and (self.onGround) then
+ self.zsp = 280
+ end
+end
+
+function Player:updateEnd(dt)
+ self:setAnimation()
+end
+
+function Player:setAnimation()
+ local gsp = utils.math.pointDistance(0, 0, self.xsp, self.ysp)
+ self:setCustomSpeed(math.abs(gsp) / 12)
+ self:setDirection(self.xsp)
+ if (self.isPunching) then
+ self:changeAnimation("punch", false)
+ else
+ if (self.onGround) then
+ if (math.abs(self.xsp) > 0) or (math.abs(self.ysp) > 0) then
+ self:changeAnimation("walk", false)
+ else
+ self:changeAnimation("idle", true)
+ end
+ else
+ self:changeAnimation("jump", true)
+ end
+ end
+end
+
+function Player:setDirection(direction)
+ direction = direction or 0
+ if direction ~= 0 then
+ direction = utils.math.sign(direction)
+ self.direction = direction
+ self:setSpriteScallingX(direction)
+ end
+end
+
+function Player:draw()
+ Player.super.draw(self)
+end
+
+function Player:drawHUD(id)
+ love.graphics.print(id .. " test", 4, 4)
+end
+
+return Player
diff --git a/examples/gameplay/moveplayer3D/actors/wall.lua b/examples/gameplay/moveplayer3D/actors/wall.lua
new file mode 100644
index 0000000..6de23b0
--- /dev/null
+++ b/examples/gameplay/moveplayer3D/actors/wall.lua
@@ -0,0 +1,10 @@
+local Base = require "gamecore.modules.world.actors.actor3D"
+local Wall = Base:extend()
+
+function Wall:new(world, x, y, z, w, h, d)
+ Wall.super.new(self, world, "wall", x, y, z, w, h, d, true)
+ self:setDebugColor(0,0,0)
+ self.boxes.Base(self, w, h, d)
+end
+
+return Wall
diff --git a/examples/gameplay/moveplayer3D/assets/arena.lua b/examples/gameplay/moveplayer3D/assets/arena.lua
new file mode 100644
index 0000000..f7ac116
--- /dev/null
+++ b/examples/gameplay/moveplayer3D/assets/arena.lua
@@ -0,0 +1,380 @@
+return {
+ version = "1.2",
+ luaversion = "5.1",
+ tiledversion = "1.2.2",
+ orientation = "orthogonal",
+ renderorder = "right-down",
+ width = 30,
+ height = 30,
+ tilewidth = 16,
+ tileheight = 16,
+ nextlayerid = 5,
+ nextobjectid = 18,
+ properties = {},
+ tilesets = {
+ {
+ name = "overworld",
+ firstgid = 1,
+ filename = "overworld.tsx",
+ tilewidth = 16,
+ tileheight = 16,
+ spacing = 0,
+ margin = 0,
+ columns = 32,
+ image = "overworld.png",
+ imagewidth = 512,
+ imageheight = 240,
+ tileoffset = {
+ x = 0,
+ y = 0
+ },
+ grid = {
+ orientation = "orthogonal",
+ width = 16,
+ height = 16
+ },
+ properties = {},
+ terrains = {},
+ tilecount = 480,
+ tiles = {}
+ }
+ },
+ layers = {
+ {
+ type = "tilelayer",
+ id = 1,
+ name = "Calque de Tile 1",
+ x = 0,
+ y = 0,
+ width = 30,
+ height = 30,
+ visible = true,
+ opacity = 1,
+ offsetx = 0,
+ offsety = 0,
+ properties = {},
+ encoding = "lua",
+ data = {
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 399, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 400, 3,
+ 3, 339, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 402, 337, 3,
+ 3, 339, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 434, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 339, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 337, 3,
+ 3, 431, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 432, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
+ }
+ },
+ {
+ type = "tilelayer",
+ id = 2,
+ name = "Rochers",
+ x = 0,
+ y = 0,
+ width = 30,
+ height = 30,
+ visible = true,
+ opacity = 1,
+ offsetx = 0,
+ offsety = 0,
+ properties = {},
+ encoding = "lua",
+ data = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 85, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 84, 85, 0, 0, 0, 0, 0, 0, 0, 0, 84, 85, 0, 0, 0, 0, 116, 117, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 116, 117, 0, 0, 0, 0, 0, 0, 0, 0, 116, 117, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 117, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 85, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 84, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 117, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 116, 117, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 84, 85, 0, 0, 0, 0, 0, 0, 116, 117, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 116, 117, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ }
+ },
+ {
+ type = "objectgroup",
+ id = 3,
+ name = "player",
+ visible = true,
+ opacity = 1,
+ offsetx = 0,
+ offsety = 0,
+ draworder = "topdown",
+ properties = {},
+ objects = {
+ {
+ id = 1,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 48,
+ y = 80,
+ width = 16,
+ height = 16,
+ rotation = 0,
+ visible = true,
+ properties = {
+ ["id"] = 1
+ }
+ },
+ {
+ id = 2,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 416,
+ y = 80,
+ width = 16,
+ height = 16,
+ rotation = 0,
+ visible = true,
+ properties = {
+ ["id"] = 1
+ }
+ },
+ {
+ id = 3,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 48,
+ y = 416,
+ width = 16,
+ height = 16,
+ rotation = 0,
+ visible = true,
+ properties = {
+ ["id"] = 3
+ }
+ },
+ {
+ id = 4,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 416,
+ y = 416,
+ width = 16,
+ height = 16,
+ rotation = 0,
+ visible = true,
+ properties = {
+ ["id"] = 4
+ }
+ }
+ }
+ },
+ {
+ type = "objectgroup",
+ id = 4,
+ name = "wall",
+ visible = true,
+ opacity = 1,
+ offsetx = 0,
+ offsety = 0,
+ draworder = "topdown",
+ properties = {},
+ objects = {
+ {
+ id = 5,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 0,
+ y = 0,
+ width = 480,
+ height = 64,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 6,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 448,
+ y = 64,
+ width = 32,
+ height = 416,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 7,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 32,
+ y = 448,
+ width = 416,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 8,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 0,
+ y = 64,
+ width = 32,
+ height = 416,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 9,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 112,
+ y = 128,
+ width = 32,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 10,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 272,
+ y = 128,
+ width = 32,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 12,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 368,
+ y = 112,
+ width = 32,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 13,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 192,
+ y = 224,
+ width = 32,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 14,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 352,
+ y = 272,
+ width = 32,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 15,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 256,
+ y = 368,
+ width = 32,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 16,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 128,
+ y = 384,
+ width = 32,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ },
+ {
+ id = 17,
+ name = "",
+ type = "",
+ shape = "rectangle",
+ x = 80,
+ y = 288,
+ width = 32,
+ height = 32,
+ rotation = 0,
+ visible = true,
+ properties = {}
+ }
+ }
+ }
+ }
+}
diff --git a/examples/gameplay/moveplayer3D/assets/arena.tmx b/examples/gameplay/moveplayer3D/assets/arena.tmx
new file mode 100644
index 0000000..29dee38
--- /dev/null
+++ b/examples/gameplay/moveplayer3D/assets/arena.tmx
@@ -0,0 +1,108 @@
+
+
diff --git a/examples/gameplay/moveplayer3D/assets/overworld.png b/examples/gameplay/moveplayer3D/assets/overworld.png
new file mode 100644
index 0000000..7823efb
Binary files /dev/null and b/examples/gameplay/moveplayer3D/assets/overworld.png differ
diff --git a/examples/gameplay/moveplayer3D/assets/overworld.tsx b/examples/gameplay/moveplayer3D/assets/overworld.tsx
new file mode 100644
index 0000000..9b3f454
--- /dev/null
+++ b/examples/gameplay/moveplayer3D/assets/overworld.tsx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/examples/gameplay/moveplayer3D/init.lua b/examples/gameplay/moveplayer3D/init.lua
new file mode 100644
index 0000000..55f9789
--- /dev/null
+++ b/examples/gameplay/moveplayer3D/init.lua
@@ -0,0 +1,40 @@
+-- scenes/moveplayer3D :: a basic player movement example in fake3D
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+local Scene = require "gamecore.modules.scenes"
+local MovePlayer = Scene:extend()
+
+local World = require "gamecore.modules.world.world3D"
+
+function MovePlayer:new()
+ MovePlayer.super.new(self)
+ self.assets:batchImport("examples.gameplay.plateform.assets")
+
+ World(self, "examples.gameplay.moveplayer3D.actors", "examples/gameplay/moveplayer3D/assets/arena.lua")
+ self.world:setPlayerNumber(1)
+
+ self.world:loadMap()
+ self.world.obj.collisions["wall"](self.world, 0,0,-16,1000, 1000, 16)
+end
+
+return MovePlayer
diff --git a/examples/init.lua b/examples/init.lua
index cde2dfe..df649cb 100644
--- a/examples/init.lua
+++ b/examples/init.lua
@@ -6,5 +6,6 @@ return {
Inventory = require "examples.menus.inventory",
Options = require "examples.menus.options",
MovePlayer = require "examples.gameplay.moveplayer",
+ MovePlayer3D = require "examples.gameplay.moveplayer3D",
Plateformer = require "examples.gameplay.plateform"
}
diff --git a/examples/mainmenu/init.lua b/examples/mainmenu/init.lua
index aefc793..7811b23 100644
--- a/examples/mainmenu/init.lua
+++ b/examples/mainmenu/init.lua
@@ -46,7 +46,7 @@ function MainMenu:new()
self:addSubMenu("gameplay", "gameplay")
self:addScene("gameplay", examples.MovePlayer, "movable")
self:addScene("gameplay", examples.Plateformer, "plateform")
-
+ self:addScene("gameplay", examples.MovePlayer3D, "movable3D")
self.menusystem:setSoundFromSceneAssets("navigate")
diff --git a/gamecore/modules/world/actors/actor2D.lua b/gamecore/modules/world/actors/actor2D.lua
index 292c06d..04aa065 100644
--- a/gamecore/modules/world/actors/actor2D.lua
+++ b/gamecore/modules/world/actors/actor2D.lua
@@ -193,6 +193,10 @@ end
-- DRAW FUNCTIONS
-- Draw the actors.
+function Actor3D:getShape()
+ return (self.x), (self.y), self.w, (self.h)
+end
+
function Actor2D:draw()
self:drawStart()
local x, y = math.floor(self.x), math.floor(self.y)
diff --git a/gamecore/modules/world/actors/actor3D.lua b/gamecore/modules/world/actors/actor3D.lua
new file mode 100644
index 0000000..a87142c
--- /dev/null
+++ b/gamecore/modules/world/actors/actor3D.lua
@@ -0,0 +1,297 @@
+-- actor3D.lua :: the implementation of a 2D actor. It contain every element
+-- needed to create your own 2D actors.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+local cwd = (...):gsub('%.actor3D$', '') .. "."
+local BaseActor = require(cwd .. "baseactor")
+local Actor3D = BaseActor:extend()
+
+local Hitbox = require(cwd .. "utils.hitbox3D")
+local Boxes = require(cwd .. "utils.boxes")
+
+-- INIT FUNCTIONS
+-- Initialise the actor and its base functions
+
+function Actor3D:new(world, type, x, y, z, w, h, d, isSolid)
+ Actor3D.super.new(self, world, type, x, y, z, w, h, d, isSolid)
+ self:initHitboxes()
+ self.world:registerShape(self)
+ self.boxes = Boxes
+ self.doCastShadows = true
+end
+
+function Actor3D:destroy()
+ self:removeOldShadowTargets()
+ if self.box ~= nil then
+ self.world:removeTerrain(self)
+ end
+ self.world:removeActor(self)
+ self.mainHitbox:destroy()
+ self.world:removeShape(self)
+ self.isDestroyed = true
+end
+
+-- PHYSICS FUNCTIONS
+-- Handle movement and collisions.
+
+function Actor3D:autoMove(dt)
+ self:updateHitboxes()
+ self.onGround = false
+ self:applyGravity(dt)
+
+ local dx, dy, dz = self:getFuturePosition(dt)
+ local newx, newy, newz, cols, colNumber = self:move(dx, dy, dz)
+
+ -- apply after the movement the friction, until the player stop
+ -- note: the friction is applied according to the delta time,
+ -- thus the friction should be how much speed is substracted in 1 second
+
+ self:solveAllCollisions(cols)
+
+ self:applyFriction(dt)
+end
+
+function Actor3D:changeSpeedToCollisionNormal(normal)
+ local xsp, ysp, zsp = self.xsp, self.ysp, self.zsp
+ local nx, ny, nz = normal.x, normal.y, normal.z
+
+ if (nx < 0 and xsp > 0) or (nx > 0 and xsp < 0) then
+ xsp = -xsp * self.bounceFactor
+ end
+
+ if (ny < 0 and ysp > 0) or (ny > 0 and ysp < 0) then
+ ysp = -ysp * self.bounceFactor
+ end
+
+ if (nz < 0 and zsp > 0) or (nz > 0 and zsp < 0) then
+ zsp = -zsp * self.bounceFactor
+ end
+
+ self.xsp, self.ysp, self.zsp = xsp, ysp, zsp
+end
+
+function Actor3D:move(dx, dy, dz)
+ local cols, colNumber = {}, 0
+ local oldx, oldy, oldz = self.x, self.y, self.z
+ if (self.isDestroyed == false) then
+ self.x, self.y, self.z, cols, colNumber = self.mainHitbox:checkCollision(dx, dy, dz, self.filter)
+ self.mainHitbox:updatePosition()
+ self.world:updateShape(self)
+ 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
+end
+
+function Actor3D:checkCollision(dx, dy, dz)
+ local x, y, z, cols, colNumber = dx, dy, dz, {}, 0
+ if (self.isDestroyed == false) then
+ x, y, z, cols, colNumber = self.mainHitbox:checkCollision(dx, dy, dz, self.filter)
+ end
+ return self.x, self.y, self.z, cols, colNumber
+end
+
+-- GRAVITY SYSTEM FUNCTIONS
+-- All functions related to gravity
+
+function Actor3D:applyGravity(dt)
+ local grav = self.grav * -1
+ self.zsp = self.zsp + (grav * dt)
+
+ if utils.math.sign(self.zsp) == utils.math.sign(grav) then
+ self:checkGround( )
+ end
+end
+
+function Actor3D:checkGround()
+ local dx, dy, dz = self.x, self.y, self.z - utils.math.sign(self.grav)
+ local newx, newy, newz, cols, colNumber = self:checkCollision(dx, dy, dz)
+ for i, col in ipairs(cols) do
+ if (col.type == "touch") or (col.type == "bounce") or (col.type == "slide") then
+ if not (self.grav == 0) then
+ if col.normal.z == utils.math.sign(self.grav) then self.onGround = true end
+ end
+ end
+ end
+end
+
+-- COORDINATE/MOVEMENT FUNCTIONS
+-- Handle coordinate
+
+function Actor3D:getViewCenter()
+ local x, y, z = self:getCenter()
+ return x, y - (self.d/2)
+end
+
+-- HITBOXES FUNCTIONS
+-- Functions related to actor hitboxes
+
+function Actor3D:addHitboxFromFrameData(framedata, animationID, frameID, hitboxID)
+ local sx, sy = self:getSpriteScalling()
+ local type = framedata[1]
+ local ox = framedata[2]
+ local oy = framedata[3]
+ local oz = framedata[4]
+ local w = framedata[5]
+ local h = framedata[6]
+ local d = framedata[7]
+ local isSolid = framedata[8] or false
+ local anim = animationID or "null"
+ local frame = frameID or 0
+ local id = hitboxID or 0
+ if (sx < 0) then
+ ox = self.w - ox - w
+ end
+ if (sy < 0) then
+ oz = self.d - oz - d
+ end
+
+ if (type == "main") then
+ self.mainHitbox:modify(ox, oy, oz, w, h, d)
+ else
+ local hitboxName = anim .. frame .. type .. id
+ self:addHitbox(hitboxName, type, ox, oy, oz, w, h, d, isSolid)
+ return hitboxName
+ end
+end
+
+function Actor3D:initMainHitbox()
+ self.mainHitbox = Hitbox(self, self.type, 0, 0, 0, self.w, self.h, self.d, self.isSolid)
+ self.mainHitbox:advertiseAsMainHitbox()
+end
+
+function Actor3D:addHitbox(name, type, ox, oy, oz, w, h, d, isSolid)
+ if (self.hitboxes[name] ~= nil) then
+ print("ERROR:", "The hitbox " .. name .. " already exists")
+ else
+ local hitbox = Hitbox(self, type, ox, oy, oz, w, h, d, isSolid)
+ self.hitboxes[name] = hitbox
+ return hitbox
+ end
+end
+
+function Actor3D:checkHitboxCollisions(name, filter)
+ self:checkHitboxCollisionsAtPoint(name, self.x, self.y, self.z, filter)
+end
+
+function Actor3D:checkHitboxCollisionsAtPoint(name, dx, dy, dz, filter)
+ local x, y, z, cols, colNumber = dx, dy, dz, {}, 0
+ local filter = filter or self.filter
+ if (self.isDestroyed == false) and (self.hitboxes[name] ~= nil) then
+ x, y, z, cols, colNumber = self.hitboxes[name]:checkCollision(dx, dy, dz, filter)
+ local type = self.hitboxes[name].type
+
+ for i, col in ipairs(cols) do
+ self:hitboxResponse(name, type, col)
+ end
+ end
+
+ return x, y, z, cols, colNumber
+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 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()
+ return (self.x), (self.y - self.z - self.d), self.w, (self.h + self.d)
+end
+
+function Actor3D:draw()
+ self:drawStart()
+ if (self.box ~= nil) then
+ self.box:draw(self.x, self.y, self.z)
+ else
+ local x, y = math.floor(self.x), math.floor(self.y - self.z - self.d + (self.h/2))
+ self:drawSprite(x, y)
+ end
+ self:drawEnd()
+end
+
+return Actor3D
diff --git a/gamecore/modules/world/actors/utils/boxes/init.lua b/gamecore/modules/world/actors/utils/boxes/init.lua
new file mode 100644
index 0000000..624e60f
--- /dev/null
+++ b/gamecore/modules/world/actors/utils/boxes/init.lua
@@ -0,0 +1,30 @@
+-- box3D :: drawable box with shadow handling for fake3D actors
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+local cwd = (...):gsub('%.init$', '') .. "."
+
+local Boxes = {}
+
+Boxes.Base = require(cwd .. "parent")
+
+return Boxes
diff --git a/gamecore/modules/world/actors/utils/boxes/parent.lua b/gamecore/modules/world/actors/utils/boxes/parent.lua
new file mode 100644
index 0000000..d8538c6
--- /dev/null
+++ b/gamecore/modules/world/actors/utils/boxes/parent.lua
@@ -0,0 +1,156 @@
+-- box3D :: drawable box with shadow handling for fake3D actors
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+local Box3D = Object:extend()
+
+function Box3D:new(owner, w, h, d)
+ self.owner = owner
+ self.world = owner.world
+ self.cameras = self.world.cameras
+
+ self.w = w
+ self.h = h
+ self.d = d
+
+ self.haveLine = true
+
+ self.shadowSources = {}
+ self.needRedraw = false
+
+ self.texture = {}
+ self:setTopTexture()
+ self:setBottomTexture()
+ self.texture.shadows = nil
+
+ self:register()
+end
+
+function Box3D:register()
+ self.owner.box = self
+ self.world:registerTerrain(self.owner)
+end
+
+function Box3D:setSizeFromOwner()
+ self:setSize(self.owner.w, self.owner.h, self.owner.d)
+end
+
+function Box3D:setSize()
+ self.w = w
+ self.h = h
+ self.d = d
+
+ self:regenerate()
+end
+
+function Box3D:setTopTexture()
+ local canvas = love.graphics.newCanvas(self.w, self.h)
+ love.graphics.setCanvas( canvas )
+ utils.graphics.resetColor()
+ love.graphics.rectangle("fill", 0, 0, self.w, self.h)
+ love.graphics.setCanvas( )
+ local imagedata = canvas:newImageData()
+ self.texture.top = love.graphics.newImage( imagedata )
+ imagedata:release()
+ canvas:release()
+end
+
+function Box3D:setBottomTexture()
+ local canvas = love.graphics.newCanvas(self.w, self.d)
+ love.graphics.setCanvas( canvas )
+ love.graphics.setColor(0.9, 0.9, 0.9, 1)
+ love.graphics.rectangle("fill", 0, 0, self.w, self.d)
+ utils.graphics.resetColor()
+ love.graphics.setCanvas( )
+ local imagedata = canvas:newImageData()
+ self.texture.bottom = love.graphics.newImage( imagedata )
+ imagedata:release()
+ canvas:release()
+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
+ 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)
+ love.graphics.setColor(0, 0, 0, 1)
+ if (self.haveLine) then
+ love.graphics.rectangle("line", x, (y-z) - (self.d), self.w, self.d + self.h)
+ end
+ utils.graphics.resetColor()
+ love.graphics.draw(self.texture.top, x, (y-z) - (self.d))
+ love.graphics.draw(self.texture.bottom, x, (y-z) - (self.d) + (self.h))
+ if (self.texture.shadows ~= nil) and (#self.shadowSources > 0) then
+ love.graphics.draw(self.texture.shadows, x, (y-z) - (self.d))
+ end
+end
+
+return Box3D
diff --git a/gamecore/modules/world/actors/utils/hitbox3D.lua b/gamecore/modules/world/actors/utils/hitbox3D.lua
new file mode 100644
index 0000000..61d444c
--- /dev/null
+++ b/gamecore/modules/world/actors/utils/hitbox3D.lua
@@ -0,0 +1,129 @@
+-- hitbox3D.lua :: a basic 3D hitbox object. It's used by the actors to check
+-- collisions and to handle different type of responses.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+local Hitbox3D = Object:extend()
+
+-- INIT FUNCTIONS
+-- Initialise the actor and its base functions
+
+function Hitbox3D:new(owner, type, ox, oy, oz, w, h, d, isSolid)
+ self.owner = owner
+ self.world = owner.world
+
+ self.type = type
+ self.ox = ox
+ self.oy = oy
+ self.oz = oz
+ self.x, self.y, self.z = self:getPosition()
+ self.w = w
+ self.h = h
+ self.d = d
+ self.isSolid = isSolid
+
+ self.isMainHitBox = false
+
+ self:setDebugColor(0,0,0)
+ self:register()
+end
+
+function Hitbox3D:advertiseAsMainHitbox()
+ self.isMainHitBox = true
+end
+
+function Hitbox3D:modify(ox, oy, oz, w, h, d)
+ self.ox = ox
+ self.oy = oy
+ self.oz = oz
+ self.x, self.y, self.z = self:getPosition()
+ self.w = w
+ self.h = h
+ self.d = d
+end
+
+function Hitbox3D:setDebugColor(r,g,b)
+ self.debug = {}
+ self.debug.r = r
+ self.debug.g = g
+ self.debug.b = b
+end
+
+function Hitbox3D:register()
+ self.world:registerBody(self)
+end
+
+function Hitbox3D:destroy()
+ self.world:removeBody(self)
+end
+
+-- COORDINATE FUNCTIONS
+-- Handle Hitbox position
+
+function Hitbox3D:updatePosition()
+ self.x, self.y, self.z = self:getPosition()
+ self.world:updateBody(self)
+ return self.x, self.y, self.z
+end
+
+function Hitbox3D:getPosition()
+ return self.ox + self.owner.x, self.oy + self.owner.y, self.oz + self.owner.z
+end
+
+function Hitbox3D:getOwnerPosition()
+ return self.x - self.ox, self.y - self.oy, self.z - self.oz
+end
+
+function Hitbox3D:getNewOwnerPosition(x, y, z)
+ return x - self.ox, y - self.oy, z - self.oz
+end
+
+function Hitbox3D:getCenter()
+ return self.x + (self.w/2), self.y + (self.h/2), self.z + (self.d/2)
+end
+
+-- COLLISION FUNCTIONS
+-- Handle Hitbox position
+
+function Hitbox3D:checkCollision(dx, dy, dz, filter)
+ self:updatePosition()
+
+ local dx, dy = self.ox + dx, self.oy + dy, self.oz + dz
+ local x, y, z, cols, colNumber = self.world:checkCollision(self, dx, dy, dz, filter)
+ local newx, newy, newz = self:getNewOwnerPosition(x, y, z)
+
+ return newx, newy, newz, cols, colNumber
+end
+
+-- DRAW FUNCTIONS
+-- Just some debug function to draw hitbox
+
+function Hitbox3D:draw()
+ local x, y, z = self:getPosition()
+ love.graphics.setColor(self.debug.r, self.debug.g, self.debug.b, 1)
+ utils.graphics.box(x, (y-z) - (self.d), self.w, self.h)
+ love.graphics.setColor(self.debug.r/2, self.debug.g/2, self.debug.b/2, 1)
+ utils.graphics.box(x, (y-z) - (self.d) + (self.h), self.w, self.d)
+ utils.graphics.resetColor()
+end
+
+return Hitbox3D
diff --git a/gamecore/modules/world/baseworld.lua b/gamecore/modules/world/baseworld.lua
index 858cbf1..52a5487 100644
--- a/gamecore/modules/world/baseworld.lua
+++ b/gamecore/modules/world/baseworld.lua
@@ -144,15 +144,30 @@ end
function BaseWorld:getVisibleActors(id)
- local camx, camy, camw, camh = self.cameras:getViewCoordinate(id)
- local paddingw = camw * PADDING_VALUE
- local paddingh = camh * PADDING_VALUE
- local x = camx - paddingw
- local y = camy - paddingh
- local w = camw + paddingw * 2
- local h = camh + paddingh * 2
+ local actors = {}
+ if (id ~= nil) then
+ local camx, camy, camw, camh = self.cameras:getViewCoordinate(id)
+ local paddingw = camw * PADDING_VALUE
+ local paddingh = camh * PADDING_VALUE
+ local x = camx - paddingw
+ local y = camy - paddingh
+ local w = camw + paddingw * 2
+ local h = camh + paddingh * 2
- return self:getActorsInRect(x, y, w, h)
+ actors = self:getActorsInRect(x, y, w, h)
+ else
+ actors = self:getActors()
+ end
+
+ table.sort(actors, function(a,b)
+ if (a.depth == b.depth) then
+ return a.creationID < b.creationID
+ else
+ return a.depth > b.depth
+ end
+ end)
+
+ return actors
end
-- BODIES MANAGEMENT FUNCTIONS
@@ -426,20 +441,7 @@ function BaseWorld:draw(dt)
end
function BaseWorld:drawActors(id)
- local actors
- if (id == nil) then
- actors = self:getActors()
- else
- actors = self:getVisibleActors(id)
- end
-
- table.sort(actors, function(a,b)
- if (a.depth == b.depth) then
- return a.creationID < b.creationID
- else
- return a.depth > b.depth
- end
- end)
+ local actors = self:getVisibleActors(id)
for i,v in ipairs(actors) do
v:draw()
diff --git a/gamecore/modules/world/camera.lua b/gamecore/modules/world/camera.lua
index 33fe4aa..8284b44 100644
--- a/gamecore/modules/world/camera.lua
+++ b/gamecore/modules/world/camera.lua
@@ -357,18 +357,19 @@ function CameraSystem:followAllActors(id)
if (#self.targets > 0) then
local minX, minY, maxX, maxY
for i, target in ipairs(self.targets) do
- local xx, yy = target:getViewCenter()
+ local x, y, w, h = target:getShape()
+ local x2, y2 = x + w, y + h
-- If start by initializing the value at the first found value
- if (minX == nil) then minX = xx - (target.w/2) end
- if (maxX == nil) then maxX = xx + (target.w/2) end
- if (minY == nil) then minY = yy - (target.h/2) end
- if (maxY == nil) then maxY = yy + (target.h/2) end
+ if (minX == nil) then minX = x end
+ if (maxX == nil) then maxX = x2 end
+ if (minY == nil) then minY = y end
+ if (maxY == nil) then maxY = y2 end
- minX = math.min(minX, xx - (target.w/2))
- maxX = math.max(maxX, xx + (target.w/2))
- minY = math.min(minY, yy - (target.h/2))
- maxY = math.max(maxY, yy + (target.h/2))
+ minX = math.min(minX, x)
+ maxX = math.max(maxX, x2)
+ minY = math.min(minY, y)
+ maxY = math.max(maxY, y2)
end
-- Add padding
diff --git a/gamecore/modules/world/world3D.lua b/gamecore/modules/world/world3D.lua
new file mode 100644
index 0000000..0d5ef76
--- /dev/null
+++ b/gamecore/modules/world/world3D.lua
@@ -0,0 +1,314 @@
+-- world3D.lua :: a basic fake3D world based on bump-2dpd.
+
+--[[
+ Copyright © 2019 Kazhnuz
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+local cwd = (...):gsub('%.world3D$', '') .. "."
+
+local BaseWorld = require(cwd .. "baseworld")
+local World3D = BaseWorld:extend()
+
+local Sti = require(cwd .. "libs.sti")
+local Bump = require(cwd .. "libs.bump")
+local Bump3D = require(cwd .. "libs.bump-3dpd")
+local Tsort = require(cwd .. "libs.tsort")
+local CameraSystem = require(cwd .. "camera")
+
+local PADDING_VALUE = 10/100
+
+function World3D:new(scene, actorlist, mapfile)
+ World3D.super.new(self, scene, actorlist, mapfile)
+end
+
+-- ACTORS FUNCTIONS
+-- Add support for bodies in Actor functions
+
+function World3D:initActors()
+ self.currentCreationID = 0
+ self.actors = {}
+ self.bodies = Bump3D.newWorld(50)
+ self:initShapes()
+ self:initTerrain()
+end
+
+function World3D:newActor(name, x, y, z)
+ self.obj.index[name](self, x, y, z)
+end
+
+function World3D:newCollision(name, x, y, z, w, h, d)
+ self.obj.collisions[name](self, x, y, z, w, h, d)
+end
+
+function World3D:moveActor(actor, x, y, z, filter)
+ return self.bodies:move(actor.mainHitbox, x, y, z, filter)
+end
+
+function World3D:getActorsInRect(x, y, w, h)
+ return self:getShapeInRect(x, y, w, h)
+end
+
+function BaseWorld:getVisibleActors(id)
+ local actors = {}
+ if (id ~= nil) then
+ local camx, camy, camw, camh = self.cameras:getViewCoordinate(id)
+ local paddingw = camw * PADDING_VALUE
+ local paddingh = camh * PADDING_VALUE
+ local x = camx - paddingw
+ local y = camy - paddingh
+ local w = camw + paddingw * 2
+ local h = camh + paddingh * 2
+
+ actors = self:getActorsInRect(x, y, w, h)
+ else
+ actors = self:getActors()
+ end
+
+ actors = self:zSortItems(actors)
+
+ return actors
+end
+
+
+-- PLAYER FUNCTIONS
+-- Load player stuff
+
+function World3D:addPlayer(actor, sourceid, haveCam)
+ local player = {}
+ player.actor = actor
+ player.sourceid = sourceid or 1
+
+ table.insert(self.players, player)
+
+ self.cameras:addTarget(player.actor)
+end
+
+-- MAP LOADING FUNCTIONS
+-- Handle loading of actors from map
+
+function World3D:batchActor(objectlayer, object)
+ local name = objectlayer.name
+ local gwidth = object.properties.gwidth or self.map.tilewidth
+ local gheight = object.properties.gheight or self.map.tileheight
+ local x = object.x
+ local y = object.y
+ local z = object.properties.z or 0
+ local w = object.width
+ local h = object.height
+
+ local cellHor = math.ceil(w / gwidth)
+ local cellVert = math.ceil(h / gheight)
+
+ for i=1, cellHor do
+ for j=1, cellVert do
+ self:newActor(name, x + (i-1)*gwidth, y + (j-1)*gheight, z)
+ end
+ end
+end
+
+function World3D:newActorFromMap(objectlayer, object)
+ local z = object.properties.z or 0
+ self:newActor(objectlayer.name, object.x, object.y, z)
+end
+
+function World3D:newCollisionFromMap(objectlayer, object)
+ local z = object.properties.z or 0
+ local d = object.properties.d or 16
+ self:newCollision(objectlayer.name, object.x, object.y, z, object.width, object.height, d)
+end
+
+function World3D:addPlayerFromMap(object, i)
+ local z = object.properties.z or 0
+ self:addPlayer(self.obj.Player(self, object.x, object.y, z), i)
+end
+
+-- BODIES MANAGEMENT FUNCTIONS
+-- Basic function to handle bodies. Wrappers around Bump2D functions
+
+function World3D:registerBody(body)
+ return self.bodies:add(body, body.x, body.y, body.z, body.w, body.h, body.d)
+end
+
+function World3D:updateBody(body)
+ return self.bodies:update(body, body.x, body.y, body.z, body.w, body.h, body.d)
+end
+
+function World3D:removeBody(body)
+ return self.bodies:remove(body)
+end
+
+function World3D:checkCollision(body, x, y, z, filter)
+ return self.bodies:check(body, x, y, z, filter)
+end
+
+function World3D:getBodiesInRect(x, y, w, h)
+ return {} --self.bodies:queryRect(x, y, w, h)
+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
+-- Handle onscreen shapes
+
+function World3D:initShapes()
+ self.shapes = Bump.newWorld(50)
+end
+
+function World3D:registerShape(actor)
+ local x, y, w, h = actor:getShape()
+ return self.shapes:add(actor, x, y, w, h)
+end
+
+function World3D:updateShape(actor)
+ local x, y, w, h = actor:getShape()
+ return self.shapes:update(actor, x, y, w, h)
+end
+
+function World3D:removeShape(actor)
+ return self.shapes:remove(actor)
+end
+
+function World3D:checkShapeIntersection(actor, x, y)
+ return self.shapes:check(actor, x, y)
+end
+
+function World3D:getShapeInRect(x, y, w, h)
+ return self.shapes:queryRect(x, y, w, h)
+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
+-- Functions to draw the world
+
+function World3D:zSortItems(items)
+ -- zSorting algorithm taken from bump3D example, adapted to gamecore.
+ local graph = Tsort.new()
+ local noOverlap = {}
+
+ -- Iterate through all visible items, and calculate ordering of all pairs
+ -- of overlapping items.
+ -- TODO: Each pair is calculated twice currently. Maybe this is slow?
+ for _, itemA in ipairs(items) do repeat
+ local x, y, w, h = self.shapes:getRect(itemA)
+ local otherItemsFilter = function(other) return other ~= itemA end
+ local overlapping, len = self.shapes:queryRect(x, y, w, h, otherItemsFilter)
+
+ if len == 0 then
+ table.insert(noOverlap, itemA)
+
+ break
+ end
+
+ local _, aY, aZ, _, aH, aD = self.bodies:getCube(itemA.mainHitbox)
+ aDepth = itemA.depth
+ aID = itemA.id
+ aType = itemA.type
+ aZ = math.ceil(aZ)
+ aY = math.ceil(aY)
+
+ for _, itemB in ipairs(overlapping) do
+ local _, bY, bZ, _, bH, bD = self.bodies:getCube(itemB.mainHitbox)
+ bDepth = itemB.depth
+ bID = itemB.id
+ bType = itemB.type
+ bZ = math.ceil(bZ)
+ bY = math.ceil(bY)
+
+ if aZ >= bZ + bD then
+ -- item A is completely above item B
+ graph:add(itemB, itemA)
+ elseif bZ >= aZ + aD then
+ -- item B is completely above item A
+ graph:add(itemA, itemB)
+ elseif aY + aH <= bY then
+ -- item A is completely behind item B
+ graph:add(itemA, itemB)
+ elseif bY + bH <= aY then
+ -- item B is completely behind item A
+ graph:add(itemB, itemA)
+ elseif (aY - aZ) + aH > (bY - bZ) + bH then
+ -- item A's forward-most point is in front of item B's forward-most point
+ graph:add(itemB, itemA)
+ elseif (aY - aZ) + aH < (bY - bZ) + bH then
+ -- item B's forward-most point is in front of item A's forward-most point
+ graph:add(itemA, itemB)
+ else
+ -- item A's forward-most point is the same than item B's forward-most point
+ if aDepth > bDepth then
+ graph:add(itemB, itemA)
+ elseif aDepth < bDepth then
+ graph:add(itemA, itemB)
+ else
+ if aID > bID then
+ graph:add(itemA, itemB)
+ elseif aID < bID then
+ graph:add(itemB, itemA)
+ else
+ err("two object can't have the same ID")
+ end
+ end
+ end
+ end
+ until true end
+
+ local sorted, err = graph:sort()
+ if err then
+ error(err)
+ end
+ for _, item in ipairs(noOverlap) do
+ table.insert(sorted, item)
+ end
+
+ return sorted
+
+end
+
+return World3D