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 @@ + + + + + +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 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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