diff --git a/CHANGELOG.md b/CHANGELOG.md index 84d802a..181810d 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 + ### Changed - **world2D:** Use a list for bodies (hitboxes, etc) and one other for actors diff --git a/gamecore/modules/world/actors/actor3D.lua b/gamecore/modules/world/actors/actor3D.lua new file mode 100644 index 0000000..2ae451f --- /dev/null +++ b/gamecore/modules/world/actors/actor3D.lua @@ -0,0 +1,209 @@ +-- 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") + +-- 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() +end + +function Actor3D:destroy() + self.world:removeActor(self) + self.mainHitbox:destroy() + 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 + if (self.isDestroyed == false) then + self.x, self.y, self.z, cols, colNumber = self.mainHitbox:checkCollision(dx, dy, dz, self.filter) + self.mainHitbox:updatePosition() + 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) + self.zsp = self.zsp - (self.grav * dt) + + if utils.math.sign(self.zsp) == utils.math.sign(self.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 + +-- DRAW FUNCTIONS +-- Draw the actors. + +function Actor3D:draw() + self:drawStart() + local x, y = math.floor(self.x), math.floor(self.y - self.z) + self:drawSprite(x, y) + self:drawEnd() +end + +return Actor3D 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/world3D.lua b/gamecore/modules/world/world3D.lua new file mode 100644 index 0000000..4a68d9c --- /dev/null +++ b/gamecore/modules/world/world3D.lua @@ -0,0 +1,144 @@ +-- 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 CameraSystem = require(cwd .. "camera") + +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) +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) + -- Just a placeholder before adding a better algorythm + World3D.super.getActorsInRect(x, y, w, h) +end + +function World3D:getVisibleActors(id) + + return self.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 + +return World3D