diff --git a/CHANGELOG.md b/CHANGELOG.md index 2119b49..751c4ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **actor:** Rename all function related to YGravity to just \*Gravity +- **world:** Separate "queryRect()" into two functions + +- **world:** Make object creation more customizable by worlds ### Fixed diff --git a/gamecore/modules/world/actors/actor2D.lua b/gamecore/modules/world/actors/actor2D.lua index a201ea4..292c06d 100644 --- a/gamecore/modules/world/actors/actor2D.lua +++ b/gamecore/modules/world/actors/actor2D.lua @@ -32,9 +32,8 @@ local Hitbox = require(cwd .. "utils.hitbox2D") -- Initialise the actor and its base functions function Actor2D:new(world, type, x, y, w, h, isSolid) - self:setCoordinate(x, y) - Actor2D.super.new(self, world, type, isSolid) - self:initHitboxes(w, h) + Actor2D.super.new(self, world, type, x, y, 0, w, h, 0, isSolid) + self:initHitboxes() end function Actor2D:destroy() @@ -43,23 +42,16 @@ function Actor2D:destroy() self.isDestroyed = true end --- MOVEMENT FUNCTIONS --- Basic functions from the movement. - -function Actor2D:initMovement() - self.xsp = 0 - self.ysp = 0 - - self.xfrc = 0 - self.yfrc = 0 -end +-- PHYSICS FUNCTIONS +-- Handle movement and collisions. function Actor2D:autoMove(dt) self:updateHitboxes() self.onGround = false self:applyGravity(dt) - local newx, newy, cols, colNumber = self:move(self.x + self.xsp * dt, self.y + self.ysp * dt) + local dx, dy = self:getFuturePosition(dt) + local newx, newy, cols, colNumber = self:move(dx, dy) -- apply after the movement the friction, until the player stop -- note: the friction is applied according to the delta time, @@ -67,25 +59,12 @@ function Actor2D:autoMove(dt) self:solveAllCollisions(cols) - self.xsp = utils.math.toZero(self.xsp, self.xfrc * dt) - self.ysp = utils.math.toZero(self.ysp, self.yfrc * dt) + self:applyFriction(dt) end -function Actor2D:solveAllCollisions(cols) - for i, col in ipairs(cols) do - self:collisionResponse(col) - if (col.type == "touch") or (col.type == "bounce") or (col.type == "slide") then - self:changeSpeedToCollisionNormal(col.normal.x, col.normal.y) - end - end -end - -function Actor2D:collisionResponse(collision) - -- here come the response to the collision -end - -function Actor2D:changeSpeedToCollisionNormal(nx, ny) +function Actor2D:changeSpeedToCollisionNormal(normal) local xsp, ysp = self.xsp, self.ysp + local nx, ny = normal.x, normal.y if (nx < 0 and xsp > 0) or (nx > 0 and xsp < 0) then xsp = -xsp * self.bounceFactor @@ -115,27 +94,9 @@ function Actor2D:checkCollision(dx, dy) return self.x, self.y, cols, colNumber end -function Actor2D:initGravity() - if (self.world.gravity.isDefault) then - self.grav = self.world.gravity.grav - else - self.grav = 0 - end - - self.onGround = false -end - -- GRAVITY SYSTEM FUNCTIONS -- All functions related to gravity -function Actor2D:setGravity(grav) - -- It's basically now a function with two roles at once : - -- - activate the gravity - -- - use the gravity value the dev want - - self.grav = grav or self.world.gravity.grav -end - function Actor2D:applyGravity(dt) self.ysp = self.ysp + self.grav * dt @@ -157,63 +118,17 @@ function Actor2D:checkGround() end end +-- COORDINATE/MOVEMENT FUNCTIONS +-- Handle coordinate + +function Actor2D:getViewCenter() + local x, y = self:getCenter() + return x, y +end + -- HITBOXES FUNCTIONS -- Functions related to actor hitboxes -function Actor2D:initHitboxes(w, h) - self.w = w or 0 - self.h = h or 0 - - self:initMainHitbox() - - self.hitboxes = {} - self.hitboxListFile = "" - self.hitboxList = nil -end - -function Actor2D:setHitboxFile(file) - self.hitboxList = require(file) - self.hitboxListFile = file -end - -function Actor2D:getAutomaticHitboxLoading() - return (self.hitboxList ~= nil) -end - -function Actor2D:getHitboxFile() - return self.hitboxListFile -end - -function Actor2D:getHitboxList(animation, frame) - if (animation == nil) or (self.hitboxList == nil) then - return self.hitboxList - else - local list = self.hitboxList[animation] - - if (frame == nil) or (list == nil) then - return list - else - return list[frame] - end - end -end - -function Actor2D:updateHitboxes() - if (self.hitboxList ~= nil) then - self:purgeHitbox() - local animation, frame - animation = self:getCurrentAnimation() - frame = self:getRelativeFrame() - local hitboxList = self:getHitboxList(animation, frame) - - if (hitboxList ~= nil) then - for i,v in ipairs(hitboxList) do - self:addHitboxFromFrameData(v, animation, frame, i) - end - end - end -end - function Actor2D:addHitboxFromFrameData(framedata, animationID, frameID, hitboxID) local sx, sy = self:getSpriteScalling() local type = framedata[1] @@ -243,6 +158,7 @@ end function Actor2D:initMainHitbox() self.mainHitbox = Hitbox(self, self.type, 0, 0, self.w, self.h, self.isSolid) + self.mainHitbox:advertiseAsMainHitbox() end function Actor2D:addHitbox(name, type, ox, oy, w, h, isSolid) @@ -255,12 +171,6 @@ function Actor2D:addHitbox(name, type, ox, oy, w, h, isSolid) end end -function Actor2D:checkHitboxesCollisions(filter) - for k,v in pairs(self.hitboxes) do - self:checkHitboxCollisionsAtPoint(k, self.x, self.y, filter) - end -end - function Actor2D:checkHitboxCollisions(name, filter) self:checkHitboxCollisionsAtPoint(name, self.x, self.y, filter) end @@ -280,52 +190,6 @@ function Actor2D:checkHitboxCollisionsAtPoint(name, dx, dy, filter) return x, y, cols, colNumber end -function Actor2D:hitboxResponse(name, type, collision) - -- just a blank placeholder function -end - -function Actor2D:removeHitbox(name) - if (self.hitboxes[name] ~= nil) then - self.hitboxes[name]:destroy() - self.hitboxes[name] = nil - end -end - -function Actor2D:purgeHitbox() - for k,v in pairs(self.hitboxes) do - v:destroy() - end - self.hitboxes = {} -end - -function Actor2D:drawHitboxes() - for k,v in pairs(self.hitboxes) do - v:draw() - end - self.mainHitbox:draw() -end - - -function Actor2D:drawMainHitbox() - self.mainHitbox:draw() -end - --- COORDINATE FUNCTION --- Handle the coordinate system - -function Actor2D:setCoordinate(x, y) - self.x = x or self.x - self.y = y or self.y -end - -function Actor2D:getCenter() - return (self.x + (self.w / 2)), (self.y + (self.h / 2)) -end - -function Actor2D:getViewCenter() - return self:getCenter() -end - -- DRAW FUNCTIONS -- Draw the actors. diff --git a/gamecore/modules/world/actors/baseactor.lua b/gamecore/modules/world/actors/baseactor.lua index 987fbea..190747a 100644 --- a/gamecore/modules/world/actors/baseactor.lua +++ b/gamecore/modules/world/actors/baseactor.lua @@ -1,5 +1,5 @@ --- actor2D.lua :: the global implementation of an actor. Basically, it abstract --- everything that isn't 2D or 3D related to the actor system. +-- BaseActor.lua :: the global implementation of an actor. Basically, it abstract +-- everything that isn't only 2D or 3D related to the actor system. --[[ Copyright © 2019 Kazhnuz @@ -30,7 +30,7 @@ local Timer = require(cwd .. "utils.timer") -- INIT FUNCTIONS -- Initialise the actor and its base functions -function BaseActor:new(world, type, isSolid) +function BaseActor:new(world, type, x, y, z, w, h, d, isSolid) self.type = type or "" self.isSolid = isSolid or false self.depth = 0 @@ -39,7 +39,7 @@ function BaseActor:new(world, type, isSolid) self:initKeys() self:initTimers() self:setSprite() - self:initPhysics() + self:initPhysics(x, y, z, w, h, d) self:setDebugColor(1, 1, 1) self:register() @@ -69,17 +69,36 @@ function BaseActor:destroy() self.isDestroyed = true end --- PHYSICS INITIALISATION --- Basic initialization of the physic systems +-- PHYSICS FUNCTIONS +-- Raw implementation of everything common in physics + +function BaseActor:initPhysics(x, y, z, w, h, d) + self:setCoordinate(x, y, z) + + self.w = w or 0 + self.h = h or 0 + self.d = d or 0 + + self.xsp = 0 + self.ysp = 0 + self.zsp = 0 + + self.xfrc = 0 + self.yfrc = 0 + self.zfrc = 0 -function BaseActor:initPhysics() - self:initMovement() self:initGravity() self:setBounceFactor() self:setFilter() end +function BaseActor:setCoordinate(x, y, z, w, h, d) + self.x = x or self.x + self.y = y or self.y + self.z = z or self.z +end + function BaseActor:setBounceFactor(newBounceFactor) self.bounceFactor = newBounceFactor or 0 end @@ -98,14 +117,77 @@ function BaseActor:setFilter() end end -function BaseActor:initMovement( ) - -- Empty placeholder function +function BaseActor:getFuturePosition(dt) + local dx, dy + dx = self.x + self.xsp * dt + dy = self.y + self.ysp * dt + dz = self.z + self.zsp * dt + + return dx, dy, dz end -function BaseActor:initGravity( ) - -- Empty placeholder function +function BaseActor:applyFriction(dt) + self.xsp = utils.math.toZero(self.xsp, self.xfrc * dt) + self.ysp = utils.math.toZero(self.ysp, self.yfrc * dt) + self.zsp = utils.math.toZero(self.zsp, self.zfrc * dt) end +function BaseActor:solveAllCollisions(cols) + for i, col in ipairs(cols) do + self:collisionResponse(col) + if (col.type == "touch") or (col.type == "bounce") or (col.type == "slide") then + self:changeSpeedToCollisionNormal(col.normal) + end + end +end + +function BaseActor:collisionResponse(collision) + -- here come the response to the collision +end + +function BaseActor:changeSpeedToCollisionNormal(normal) + -- Empty function in baseactor +end + +-- COORDINATE/MOVEMENT FUNCTIONS +-- Handle coordinate + +function BaseActor:getCenter() + return (self.x + (self.w / 2)), (self.y + (self.h / 2)), (self.z + (self.d / 2)) +end + +function BaseActor:getViewCenter() + return self:getCenter() +end + +-- GRAVITY SYSTEM FUNCTIONS +-- All functions related to gravity + +function BaseActor:initGravity() + if (self.world.gravity.isDefault) then + self.grav = self.world.gravity.grav + else + self.grav = 0 + end + + self.onGround = false +end + +function BaseActor:setGravity(grav) + -- It's basically now a function with two roles at once : + -- - activate the gravity + -- - use the gravity value the dev want + + self.grav = grav or self.world.gravity.grav +end + +function BaseActor:applyGravity(dt) + -- Empty function in baseactor +end + +function BaseActor:checkGround() + -- Empty function in baseactor +end -- UPDATE FUNCTIONS -- Theses functions are activated every steps @@ -164,6 +246,101 @@ function BaseActor:timerResponse(name) -- here come the timer responses end +-- HITBOX FUNCTIONS +-- All functions to handle hitboxes + +function BaseActor:initHitboxes() + self:initMainHitbox() + + self.hitboxes = {} + self.hitboxListFile = "" + self.hitboxList = nil +end + +function BaseActor:initMainHitbox() + -- Empty function : don't load ANY real hitbox function into baseactor +end + +function BaseActor:setHitboxFile(file) + self.hitboxList = require(file) + self.hitboxListFile = file +end + +function BaseActor:getAutomaticHitboxLoading() + return (self.hitboxList ~= nil) +end + +function BaseActor:getHitboxFile() + return self.hitboxListFile +end + +function BaseActor:getHitboxList(animation, frame) + if (animation == nil) or (self.hitboxList == nil) then + return self.hitboxList + else + local list = self.hitboxList[animation] + + if (frame == nil) or (list == nil) then + return list + else + return list[frame] + end + end +end + +function BaseActor:updateHitboxes() + if (self.hitboxList ~= nil) then + self:purgeHitbox() + local animation, frame + animation = self:getCurrentAnimation() + frame = self:getRelativeFrame() + local hitboxList = self:getHitboxList(animation, frame) + + if (hitboxList ~= nil) then + for i,v in ipairs(hitboxList) do + self:addHitboxFromFrameData(v, animation, frame, i) + end + end + end +end + +function BaseActor:checkHitboxesCollisions(filter) + for k,v in pairs(self.hitboxes) do + self:checkHitboxCollisions(k, filter) + end +end + +function BaseActor:hitboxResponse(name, type, collision) + -- just a blank placeholder function +end + +function BaseActor:removeHitbox(name) + if (self.hitboxes[name] ~= nil) then + self.hitboxes[name]:destroy() + self.hitboxes[name] = nil + end +end + +function BaseActor:purgeHitbox() + for k,v in pairs(self.hitboxes) do + v:destroy() + end + self.hitboxes = {} +end + +function BaseActor:drawHitboxes() + for k,v in pairs(self.hitboxes) do + v:draw() + end + self:drawMainHitbox() +end + +function BaseActor:drawMainHitbox() + if (self.mainHitbox ~= nil) then + self.mainHitbox:draw() + end +end + -- DRAW FUNCTIONS -- Draw the actors. @@ -184,7 +361,6 @@ function BaseActor:drawHUD(id, height, width) end - -- SPRITES FUNCTIONS -- Handle the sprite of the actor diff --git a/gamecore/modules/world/actors/utils/hitbox2D.lua b/gamecore/modules/world/actors/utils/hitbox2D.lua index a29a7a5..6728908 100644 --- a/gamecore/modules/world/actors/utils/hitbox2D.lua +++ b/gamecore/modules/world/actors/utils/hitbox2D.lua @@ -39,10 +39,16 @@ function Hitbox2D:new(owner, type, ox, oy, w, h, isSolid) self.h = h self.isSolid = isSolid + self.isMainHitBox = false + self:setDebugColor(0,0,0) self:register() end +function Hitbox2D:advertiseAsMainHitbox() + self.isMainHitBox = true +end + function Hitbox2D:modify(ox, oy, w, h) self.ox = ox self.oy = oy diff --git a/gamecore/modules/world/baseworld.lua b/gamecore/modules/world/baseworld.lua index 045ba19..a1f707a 100644 --- a/gamecore/modules/world/baseworld.lua +++ b/gamecore/modules/world/baseworld.lua @@ -128,6 +128,21 @@ function BaseWorld:getActors() return self.actors end +function BaseWorld:getActorsInRect(x, y, w, h) + local query = {} + local x2, y2 = x + w, y + h + for i,v in ipairs(self.actors) do + if (v.x >= x) and (v.x + v.w >= x1) and + (v.y >= y) and (v.y + v.h >= y1) then + + table.insert(query, v) + end + end + + return v +end + + function BaseWorld:getVisibleActors(id) local camx, camy, camw, camh = self.cameras:getViewCoordinate(id) local paddingw = camw * PADDING_VALUE @@ -137,14 +152,7 @@ function BaseWorld:getVisibleActors(id) local w = camw + paddingw * 2 local h = camh + paddingh * 2 - local query = self:queryRect(x, y, w, h) - local returnquery = {} - - for i,v in ipairs(query) do - table.insert(returnquery, v.owner) - end - - return returnquery + return self:getActorsInRect(x, y, w, h) end -- BODIES MANAGEMENT FUNCTIONS @@ -175,18 +183,8 @@ function BaseWorld:checkCollision(actor, x, y, filter) return x, y, {}, 0 end -function BaseWorld:queryRect(x, y, w, h) - local query = {} - local x2, y2 = x + w, y + h - for i,v in ipairs(self.actors) do - if (v.x >= x) and (v.x + v.w >= x1) and - (v.y >= y) and (v.y + v.h >= y1) then - - table.insert(query, v) - end - end - - return v +function BaseWorld:getBodiesInRect(x, y, w, h) + return {} end -- INFO FUNCTIONS @@ -270,7 +268,7 @@ function BaseWorld:loadMapCollisions() if self:isCollisionIndexed(objectlayer.name) then print("DEBUG: loading actors in " .. objectlayer.name .. " collision layer") for k, object in pairs(objectlayer.objects) do - self:newCollision(objectlayer.name, object.x, object.y, object.width, object.height) + self:newCollisionFromMap(objectlayer, object) end self.map:removeLayer(objectlayer.name) end @@ -283,9 +281,9 @@ function BaseWorld:loadMapActors() print("DEBUG: loading actors in " .. objectlayer.name .. " actor layer") for k, object in pairs(objectlayer.objects) do if (object.properties.batchActor) then - self:batchActor(objectlayer.name, object) + self:batchActor(objectlayer, object) else - self:newActor(objectlayer.name, object.x, object.y) + self:newActorFromMap(objectlayer, object) end end self.map:removeLayer(objectlayer.name) @@ -293,7 +291,8 @@ function BaseWorld:loadMapActors() end end -function BaseWorld:batchActor(name, object) +function BaseWorld: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 @@ -311,6 +310,18 @@ function BaseWorld:batchActor(name, object) end end +function BaseWorld:newActorFromMap(objectlayer, object) + self:newActor(objectlayer.name, object.x, object.y) +end + +function BaseWorld:newCollisionFromMap(objectlayer, object) + self:newCollision(objectlayer.name, object.x, object.y, object.width, object.height) +end + +function BaseWorld:addPlayerFromMap(object, i) + self:addPlayer(self.obj.Player(self, object.x, object.y), i, true) +end + function BaseWorld:loadMapPlayers() for k, objectlayer in pairs(self.map.layers) do if (objectlayer.name == "player") then @@ -319,7 +330,7 @@ function BaseWorld:loadMapPlayers() for k, object in pairs(objectlayer.objects) do if (i <= self.playerNumber) then -- TODO: don't hardcode camera handling - self:addPlayer(self.obj.Player(self, object.x, object.y), i, true) + self:addPlayerFromMap(object, i) end i = i + 1 end diff --git a/gamecore/modules/world/world2D.lua b/gamecore/modules/world/world2D.lua index 98ce64e..6200c93 100644 --- a/gamecore/modules/world/world2D.lua +++ b/gamecore/modules/world/world2D.lua @@ -43,17 +43,92 @@ function World2D:initActors() self.bodies = Bump.newWorld(50) end +function World2D:newActor(name, x, y) + self.obj.index[name](self, x, y) +end + +function World2D:newCollision(name, x, y, w, h) + self.obj.collisions[name](self, x, y, w, h) +end + function World2D:registerActor(actor) World2D.super.registerActor(self, actor) end +function World2D:moveActor(actor, x, y, filter) + return self.bodies:move(actor.mainHitbox, x, y, filter) +end + +function World2D:getActorsInRect(x, y, w, h) + local bodies = self.bodies:queryRect(x, y, w, h) + local returnquery = {} + + for i,body in ipairs(bodies) do + if (body.isMainHitBox) then + table.insert(returnquery, body.owner) + end + end + + return returnquery +end + +-- PLAYER FUNCTIONS +-- Load player stuff + +function World2D:addPlayer(actor, sourceid, haveCam) + local player = {} + player.actor = actor + player.sourceid = sourceid or 1 + + table.insert(self.players, player) + + if (haveCam) then + local xx, yy = player.actor:getViewCenter() + self.cameras:addView(xx, yy, player.actor) + end +end + +-- MAP LOADING FUNCTIONS +-- Handle loading of actors from map + +function World2D: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 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) + end + end +end + +function World2D:newActorFromMap(objectlayer, object) + self:newActor(objectlayer.name, object.x, object.y) +end + +function World2D:newCollisionFromMap(objectlayer, object) + self:newCollision(objectlayer.name, object.x, object.y, object.width, object.height) +end + +function World2D:addPlayerFromMap(object, i) + self:addPlayer(self.obj.Player(self, object.x, object.y), i, true) +end + +-- BODIES MANAGEMENT FUNCTIONS +-- Basic function to handle bodies. Wrappers around Bump2D functions + function World2D:registerBody(body) return self.bodies:add(body, body.x, body.y, body.w, body.h) end --- ACTORS FUNCTIONS --- Wrappers around Bump2D functions - function World2D:updateBody(body) return self.bodies:update(body, body.x, body.y, body.w, body.h) end @@ -62,15 +137,11 @@ function World2D:removeBody(body) return self.bodies:remove(body) end -function World2D:moveActor(actor, x, y, filter) - return self.bodies:move(actor, x, y, filter) +function World2D:checkCollision(body, x, y, filter) + return self.bodies:check(body, x, y, filter) end -function World2D:checkCollision(actor, x, y, filter) - return self.bodies:check(actor, x, y, filter) -end - -function World2D:queryRect(x, y, w, h) +function World2D:getBodiesInRect(x, y, w, h) return self.bodies:queryRect(x, y, w, h) end