WIP: gamecore-0.6.0 test

This commit is contained in:
Kazhnuz 2020-06-04 19:34:32 +02:00
parent 1768ccd30a
commit 3ae4916a3a
45 changed files with 2806 additions and 1028 deletions

View File

@ -36,4 +36,26 @@ function DebugSystem:update(dt)
lovebird.update(dt)
end
-- PRINT FUNCTIONS
-- Print and log debug string
function DebugSystem:print(context, string)
if (self.active) then
print("[DEBUG] ".. context .. ": " .. string)
end
end
function DebugSystem:warning(context, string)
if (self.active) then
print("[WARNING] " .. context .. ": " .. string)
end
end
function DebugSystem:error(context, string)
if (self.active) then
error("[ERROR] " .. context .. ": " .. string)
end
end
return DebugSystem

View File

@ -47,8 +47,8 @@ require(cwd .. "callbacks")
-- INIT FUNCTIONS
-- Initialize and configure the core object
function CoreSystem:new()
self.debug = DebugSystem(self)
function CoreSystem:new(DEBUGMODE)
self.debug = DebugSystem(self, DEBUGMODE)
self.options = Options(self)
self.input = Input(self)
self.screen = Screen(self)
@ -56,6 +56,10 @@ function CoreSystem:new()
self.lang = Lang(self)
end
function CoreSystem:registerGameSystem(gamesystem)
self.game = gamesystem
end
-- MOUSE FUNCTIONS
-- get directly the mouse when needed
@ -88,6 +92,10 @@ function CoreSystem:update(dt)
self.debug:update(dt)
self.input:update(dt)
if (self.game ~= nil) then
self.game:update(dt)
end
self.scenemanager:update(dt)
end

View File

@ -22,7 +22,8 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local InputManager = Object:extend()
local InputManager = Object:extend()
local VirtualPad = Object:extend()
-- INIT FUNCTIONS
-- Initialize and configure the controller system
@ -31,40 +32,23 @@ function InputManager:new(controller)
self.controller = controller
self.data = self.controller.options:getInputData()
self:initKeys()
self:initSources()
end
function InputManager:initKeys()
self.fakekeys = self:getKeyList(1)
self.sources = self:getSources()
self.fakesources = self:getSources()
function InputManager:initSources()
self.sources = {}
for sourceid, data in ipairs(self.data) do
local source = VirtualPad(self, sourceid, data)
table.insert(self.sources, source)
end
end
-- INFO FUNCTIONS
-- Get functions from the controller object
function InputManager:isDown(sourceid, padkey)
local isdown = false
if self.data[sourceid].type == "keyboard" then
local key = self.data[sourceid].keys[padkey]
isdown = love.keyboard.isDown(key)
else
print("Warning: unsupported input device")
end
return isdown
end
function InputManager:getSources()
local sources = {}
for i,v in ipairs(self.data) do
sources[i] = {}
sources[i].keys = self:getKeyList(i)
end
return sources
function InputManager:isDown(sourceid, key)
self.controller.debug:warning("core/input", "core.input:isDown is deprecated since 0.7.0 and will be removed in 0.8.0")
return self.sources[sourceid]:isDown(key)
end
function InputManager:getKeyList(sourceid)
@ -75,7 +59,6 @@ function InputManager:getKeyList(sourceid)
keys[k].isDown = false
keys[k].isPressed = false
keys[k].isReleased = false
keys[k].test = "ok"
end
end
@ -90,60 +73,132 @@ function InputManager:getKey(sourceid, padkey)
return key
end
function InputManager:getSources()
return self.sources
end
-- KEY MANAGEMENT FUNCTIONS
-- Manage pressed keys
function InputManager:flushKeys()
for i,v in ipairs(self.sources) do
self:flushSourceKeys(i)
source:flushKeys()
end
end
function InputManager:flushSourceKeys(sourceid)
self.keys = {}
for k,v in pairs(self.sources[sourceid].keys) do
v = {}
v.isDown = false
v.isPressed = false
v.isReleased = false
end
end
function InputManager:checkKeys(sourceid)
for k,v in pairs(self.sources[sourceid].keys) do
local isDown = self:isDown(sourceid, k)
if (isDown) then
if not (self.sources[sourceid].keys[k].isDown) then
self.sources[sourceid].keys[k].isDown = true
self.sources[sourceid].keys[k].isPressed = true
self.sources[sourceid].keys[k].isReleased = false
else
if (self.sources[sourceid].keys[k].isPressed) then
self.sources[sourceid].keys[k].isPressed = false
end
end
else
if (self.sources[sourceid].keys[k].isDown) then
self.sources[sourceid].keys[k].isDown = false
self.sources[sourceid].keys[k].isPressed = false
self.sources[sourceid].keys[k].isReleased = true
else
if (self.sources[sourceid].keys[k].isReleased) then
self.sources[sourceid].keys[k].isReleased = false
end
end
end
end
self.sources[sourceid]:flushKeys()
end
-- UPDATE FUNCTIONS
-- Check every step pressed keys
function InputManager:update(dt)
for i,v in ipairs(self.sources) do
self:checkKeys(i)
for i, source in ipairs(self.sources) do
source:checkKeys()
end
end
------------------------------------ VIRTUALPADS -------------------------------
-- Virtual representation of a pad
-- The role of the virtualpad is to return all the data a controller at any time
-- They can be flushed and deactivated for a while when needed
-- INIT FUNCTIONS
-- Initialize and configure the controller system
function VirtualPad:new(controller, id, data)
self.controller = controller
self.id = id
self.data = data
self.type = self.data.type or "nil"
self:initKeys()
end
function VirtualPad:initKeys()
local keys = {}
if (self.data ~= nil) then
for k,v in pairs(self.data.keys) do
keys[k] = {}
keys[k].isDown = false
keys[k].isPressed = false
keys[k].isReleased = false
end
end
self.keys = keys
self.fakekeys = keys
end
function VirtualPad:isDown(key)
local isdown = false
if self.type == "keyboard" then
isdown = love.keyboard.isDown(self.data.keys[key])
else
local warnstring = "unsupported input device " .. self.type .. " for source " .. self.id
core.debug:warning("core/input", warnstring)
end
return isdown
end
function VirtualPad:checkKeys()
for key, keydata in pairs(self.keys) do
self:checkKey(key)
end
end
function VirtualPad:checkKey(key)
local isDown = self:isDown(key)
if (isDown) then
if not (self.keys[key].isDown) then
core.debug:print("virtualpad", "key " .. key .. " is Pressed")
self.keys[key].isDown = true
self.keys[key].isPressed = true
self.keys[key].isReleased = false
else
if (self.keys[key].isPressed) then
core.debug:print("virtualpad", "key " .. key .. " is Down")
self.keys[key].isPressed = false
end
end
else
if (self.keys[key].isDown) then
self.keys[key].isDown = false
self.keys[key].isPressed = false
self.keys[key].isReleased = true
else
if (self.keys[key].isReleased) then
core.debug:print("virtualpad", "key " .. key .. " is Released")
self.keys[key].isReleased = false
end
end
end
end
function VirtualPad:getKeys()
return self.keys
end
function VirtualPad:getKey(key)
return self.keys[key]
end
function VirtualPad:flushKeys()
for key, _ in pairs(self.keys) do
self:flushKey(key)
end
end
function VirtualPad:flushKey(key)
self.keys[key].isDown = false
self.keys[key].isPressed = false
self.keys[key].isReleased = false
end
return InputManager

View File

@ -95,7 +95,7 @@ function LanguageManager:getTranslationStringList(lang, library)
if fileinfo ~= nil then
list = require(_path)
else
print("WARNING: file " .. _path .. " do not exists")
core.debug:warning("core/lang","file " .. _path .. " do not exists")
end
return list
@ -120,7 +120,7 @@ function LanguageManager:translate(library, string)
if (translation == nil) then
translation = string
print("WARNING: no translation path found for " .. string .. " in " .. library)
core.debug:warning("core/lang", "no translation path found for " .. string .. " in " .. library)
end
return translation

View File

@ -87,6 +87,14 @@ function CScreen.project(x, y)
return math.floor((x - tx) / fsv), math.floor((y - ty) / fsv)
end
function CScreen.getScale()
return fsv
end
function CScreen.getScreenCoordinate(x, y)
return math.floor((x + tx) * fsv), math.floor((y + ty) * fsv)
end
-- Change letterbox color
function CScreen.setColor(r, g, b, a)
cr = r

View File

@ -48,7 +48,7 @@ end
function Animator:update(dt)
if (self.currentAnimation == "") then
print("warning: no current animation data")
core.debug:warning("animator", "no current animation data")
return 0
end
@ -94,6 +94,10 @@ end
-- INFO FUNCTIONS
-- get information with these functions
function Animator:getCurrentAnimation()
return self.currentAnimation
end
function Animator:getAnimationDuration(animation)
return (self.animationData.endAt - self.animationData.startAt) / self.animationData.speed
end
@ -102,6 +106,10 @@ function Animator:getFrame()
return self.frame
end
function Animator:getRelativeFrame()
return self.frame - (self.animationData.startAt) + 1
end
function Animator:animationExist(name)
return (self.sprite.data.animations[self.currentAnimation] ~= nil)
end

View File

@ -90,7 +90,7 @@ function Assets:batchImport(datafile)
elseif (asset_type == "sfx") then
self:importSFX(assets)
else
print("Unkown asset type : " .. asset_type)
core.debug:warning("assets/importer", "unkown asset type " .. asset_type)
end
end
end

View File

@ -66,6 +66,10 @@ end
-- INFO FUNCTIONS
-- get information with these functions
function Sprite:getCurrentAnimation()
return self.animator:getCurrentAnimation()
end
function Sprite:animationExist(name)
return self.animator:animationExist(name)
end
@ -74,6 +78,18 @@ function Sprite:getDimensions()
return self.tileset:getDimensions()
end
function Sprite:getFrame()
return self.animator:getFrame()
end
function Sprite:getAnimationDuration(animation)
return self.animator:getAnimationDuration(animation)
end
function Sprite:getRelativeFrame()
return self.animator:getRelativeFrame()
end
-- DRAW FUNCTIONS
-- Draw sprites using these functions

View File

@ -0,0 +1,145 @@
-- game :: The main game subsystem. Basically a big object that handle all the
-- game-related data like characters, monsters, etc. While the core aim to be
-- reusable at will, the game is specifically made for the current game.
-- It's also what handle the savedata for games
--[[
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 cwd2 = (...):gsub('%.gamesystem.init$', '') .. "."
local GameSystem = Object:extend()
local binser = require(cwd2 .. "libs.binser")
local DEFAULT_SAVENUMBER = 3
function GameSystem:new()
self.currentSlot = -1
self.submodules = {}
self.playtime = 0
self:register()
end
function GameSystem:register()
core:registerGameSystem(self)
end
function GameSystem:registerSubmodules(submodule)
local name = submodule.name
self.submodules[name] = submodule
end
-- UPDATE FUNCTIONS
-- Update every submodules
function GameSystem:update(dt)
self.playtime = self.playtime + dt
for k, submodule in pairs(self.submodules) do
submodule:update(dt)
end
end
-- DATA MANAGEMENT FUNCTIONS
-- Get and set data in the gamesystem object
function GameSystem:setData(data)
local data = data
self.playtime = data.playtime
for k, submodule in pairs(self.submodules) do
submodule:setData(data[k])
end
end
function GameSystem:getData()
local data = {}
data.playtime = self.playtime
for k, submodule in pairs(self.submodules) do
data[k] = submodule:getData()
end
return data
end
-- SAVE MANAGEMENT FUNCTIONS
-- Get and set data in the gamesystem object
function GameSystem:getSaveNumber()
return DEFAULT_SAVENUMBER
end
function GameSystem:resetSave(saveid)
if utils.filesystem.exists("save" .. saveid .. ".save") then
love.filesystem.remove( "save" .. saveid .. ".save" )
end
end
function GameSystem:resetAllSaves()
for i=1, self:getSaveNumber() do
self:resetSave(i)
end
end
function GameSystem:getSavePath(saveid, absolute)
local saveDir = ""
if (absolute) then
saveDir = love.filesystem.getSaveDirectory() .. "/"
if not utils.filesystem.exists(saveDir) then
love.filesystem.createDirectory( "" )
end
end
local filepath = saveDir .. self:getSaveName(saveid)
return filepath
end
function GameSystem:getSaveName(saveid)
return "save" .. saveid .. ".save"
end
function GameSystem:saveFileExist(saveid)
return utils.filesystem.exists(self:getSaveName(saveid))
end
function GameSystem:read(saveid)
self.currentSlot = saveid or self.currentSlot
if (self.currentSlot > 0) then
local savepath = self:getSavePath(self.currentSlot, true)
if self:saveFileExist(self.currentSlot) then
local datas = binser.readFile(savepath)
self:setData(datas[1])
end
end
end
function GameSystem:write()
if (self.currentSlot > 0) then
local data = self:getData()
savepath = self:getSavePath(self.currentSlot, true)
binser.writeFile(savepath, data)
end
end
return GameSystem

View File

@ -0,0 +1,27 @@
local SubModule = Object:extend()
function SubModule:new(game, name)
self.name = name or error("SUBMODULE must have a name")
self.game = game
self.data = {}
self:register()
end
function SubModule:update(dt)
-- nothing by default
end
function SubModule:register()
self.game:registerSubmodules(self)
end
function SubModule:getData()
return self.data
end
function SubModule:setData(data)
self.data = data
end
return SubModule

View File

@ -150,7 +150,7 @@ end
function Scene:getKeys(sourceid)
if sourceid == nil then
print("WARNING", "no sourceid detected, will default to 1")
core.debug:warning("scene", "no sourceid detected, will default to 1")
end
local sourceid = sourceid or 1

View File

@ -26,30 +26,32 @@ local cwd = (...):gsub('%.actor2D$', '') .. "."
local BaseActor = require(cwd .. "baseactor")
local Actor2D = BaseActor:extend()
local Hitbox = require(cwd .. "utils.hitbox2D")
-- INIT FUNCTIONS
-- Initialise the actor and its base functions
function Actor2D:new(world, type, x, y, w, h, isSolid)
self:initHitbox(x, y, w, h)
Actor2D.super.new(self, world, type, isSolid)
Actor2D.super.new(self, world, type, x, y, 0, w, h, 0, isSolid)
self:initHitboxes()
end
-- MOVEMENT FUNCTIONS
-- Basic functions from the movement.
function Actor2D:initMovement()
self.xsp = 0
self.ysp = 0
self.xfrc = 0
self.yfrc = 0
function Actor2D:destroy()
self.world:removeActor(self)
self.mainHitbox:destroy()
self.isDestroyed = true
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,
@ -57,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
@ -88,36 +77,11 @@ function Actor2D:changeSpeedToCollisionNormal(nx, ny)
self.xsp, self.ysp = xsp, ysp
end
function Actor2D:checkGroundX()
local dx, dy = self.x + utils.math.sign(self.xgrav), self.y
local newx, newy, cols, colNumber = self:checkCollision(dx, dy)
for i, col in ipairs(cols) do
if (col.type == "touch") or (col.type == "bounce") or (col.type == "slide") then
if not (self.ygrav == 0) then
if col.normal.x ~= utils.math.sign(self.xgrav) then self.onGround = true end
end
end
end
end
function Actor2D:checkGroundY()
local dx, dy = self.x, self.y + utils.math.sign(self.ygrav)
local newx, newy, cols, colNumber = self:checkCollision(dx, dy)
for i, col in ipairs(cols) do
if (col.type == "touch") or (col.type == "bounce") or (col.type == "slide") then
if not (self.ygrav == 0) then
if col.normal.y ~= utils.math.sign(self.ygrav) then self.onGround = true end
end
end
end
end
function Actor2D:move(dx, dy)
local cols, colNumber = {}, 0
if (self.isDestroyed == false) then
self.x, self.y, cols, colNumber = self.world:moveActor(self, dx, dy, self.filter)
self.x, self.y, cols, colNumber = self.mainHitbox:checkCollision(dx, dy, self.filter)
self.mainHitbox:updatePosition()
end
return self.x, self.y, cols, colNumber
end
@ -125,73 +89,114 @@ end
function Actor2D:checkCollision(dx, dy)
local x, y, cols, colNumber = dx, dy, {}, 0
if (self.isDestroyed == false) then
x, y, cols, colNumber = self.world:moveActor(self, dx, dy, self.filter)
x, y, cols, colNumber = self.mainHitbox:checkCollision(dx, dy, self.filter)
end
return self.x, self.y, cols, colNumber
end
function Actor2D:initGravity()
local xgrav, ygrav
if (self.world.gravity.isDefault) then
self.xgrav = self.world.gravity.xgrav
self.ygrav = self.world.gravity.ygrav
else
self.xgrav = 0
self.ygrav = 0
end
self.onGround = false
end
function Actor2D:setXGravity(grav)
self.xgrav = grav
end
function Actor2D:setYGravity(grav)
self.ygrav = grav
end
-- GRAVITY SYSTEM FUNCTIONS
-- All functions related to gravity
function Actor2D:applyGravity(dt)
self.xsp = self.xsp + self.xgrav * dt
self.ysp = self.ysp + self.ygrav * dt
self.ysp = self.ysp + self.grav * dt
if utils.math.sign(self.ysp) == utils.math.sign(self.ygrav) then
self:checkGroundY( )
end
if utils.math.sign(self.xsp) == utils.math.sign(self.xgrav) then
self:checkGroundX( )
if utils.math.sign(self.ysp) == utils.math.sign(self.grav) then
self:checkGround( )
end
end
-- COORDINATE FUNCTIONS
-- Functions related to coordinate and hitbox
function Actor2D:checkGround()
local dx, dy = self.x, self.y + utils.math.sign(self.grav)
local newx, newy, cols, colNumber = self:checkCollision(dx, dy)
function Actor2D:initHitbox(x, y, w, h)
self.x = x or 0
self.y = y or 0
self.w = w or 0
self.h = h or 0
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.y ~= utils.math.sign(self.grav) then self.onGround = true end
end
end
end
end
function Actor2D:getCenter()
return (self.x + (self.w / 2)), (self.y + (self.h / 2))
end
-- COORDINATE/MOVEMENT FUNCTIONS
-- Handle coordinate
function Actor2D:getViewCenter()
return self:getCenter()
local x, y = self:getCenter()
return x, y
end
function Actor2D:drawHitbox()
local x, y = math.floor(self.x), math.floor(self.y)
love.graphics.setColor(self.debug.r, self.debug.g, self.debug.b, 1)
utils.graphics.box(x, y, self.w, self.h)
-- HITBOXES FUNCTIONS
-- Functions related to actor hitboxes
function Actor2D:addHitboxFromFrameData(framedata, animationID, frameID, hitboxID)
local sx, sy = self:getSpriteScalling()
local type = framedata[1]
local ox = framedata[2]
local oy = framedata[3]
local w = framedata[4]
local h = framedata[5]
local isSolid = framedata[6] 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
oy = self.h - oy - h
end
if (type == "main") then
self.mainHitbox:modify(ox, oy, w, h)
else
local hitboxName = anim .. frame .. type .. id
self:addHitbox(hitboxName, type, ox, oy, w, h, isSolid)
return hitboxName
end
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)
if (self.hitboxes[name] ~= nil) then
core.debug:warning("actor2D", "the hitbox " .. name .. " already exists")
else
local hitbox = Hitbox(self, type, ox, oy, w, h, isSolid)
self.hitboxes[name] = hitbox
return hitbox
end
end
function Actor2D:checkHitboxCollisions(name, filter)
self:checkHitboxCollisionsAtPoint(name, self.x, self.y, filter)
end
function Actor2D:checkHitboxCollisionsAtPoint(name, dx, dy, filter)
local x, y, cols, colNumber = dx, dy, {}, 0
local filter = filter or self.filter
if (self.isDestroyed == false) and (self.hitboxes[name] ~= nil) then
x, y, cols, colNumber = self.hitboxes[name]:checkCollision(dx, dy, filter)
local type = self.hitboxes[name].type
for i, col in ipairs(cols) do
self:hitboxResponse(name, type, col)
end
end
return x, y, cols, colNumber
end
-- DRAW FUNCTIONS
-- Draw the actors.
function Actor2D: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)

View File

@ -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
core.debug:warning("actor3D", "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

View File

@ -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
@ -87,7 +106,10 @@ end
function BaseActor:setFilter()
-- Init the bump filter
self.filter = function(item, other)
if (other.isSolid) then
if (other.owner == self) then
-- ignore every collision with our own hitboxes
return nil
elseif (other.isSolid) then
return "slide"
else
return "cross"
@ -95,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
@ -161,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.
@ -181,7 +361,6 @@ function BaseActor:drawHUD(id, height, width)
end
-- SPRITES FUNCTIONS
-- Handle the sprite of the actor
@ -236,6 +415,49 @@ function BaseActor:setSpriteScallingY(sy)
self.sprite.sy = sy
end
function BaseActor:getCurrentAnimation()
if (self.sprite.clone == nil) then
return self.assets.sprites[self.sprite.name]:getCurrentAnimation()
else
return self.sprite.clone:getCurrentAnimation()
end
end
function BaseActor:getSpriteScalling()
return self.sprite.sx, self.sprite.sy
end
function BaseActor:getFrame()
if (self.sprite.name ~= nil) then
if (self.sprite.clone ~= nil) then
return self.sprite.clone:getFrame()
else
return self.assets.sprites[self.sprite.name]:getFrame()
end
end
end
function BaseActor:getRelativeFrame()
if (self.sprite.name ~= nil) then
if (self.sprite.clone ~= nil) then
return self.sprite.clone:getRelativeFrame()
else
return self.assets.sprites[self.sprite.name]:getRelativeFrame()
end
end
end
function BaseActor:getAnimationDuration()
if (self.sprite.name ~= nil) then
if (self.sprite.clone ~= nil) then
return self.sprite.clone:getAnimationDuration()
else
return self.assets.sprites[self.sprite.name]:getAnimationDuration()
end
end
end
function BaseActor:drawSprite(x, y, r, sx, sy, ox, oy, kx, ky)
if (self.sprite.name ~= nil) then
local x = x + self.sprite.ox

View File

@ -0,0 +1,46 @@
-- gfx.lua :: a basic 2D GFX.
--[[
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('%.gfx3D$', '') .. "."
local Actor3D = require(cwd .. "actor3D")
local GFX = Actor3D:extend()
function GFX:new(world, x, y, z, spritename)
local width, height = world.scene.assets.sprites[spritename]:getDimensions()
GFX.super.new(self, world, "gfx", x - (width/2), y - (width/2), z - (height/2), width, width, height)
self:setSprite(spritename)
self:cloneSprite()
local duration = self.sprite.clone:getAnimationDuration()
self:addTimer("destroy", duration)
self.depth = -100
end
function GFX:timerResponse(name)
if (name == "destroy") then
self:destroy()
end
end
return GFX

View File

@ -0,0 +1,32 @@
-- 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")
Boxes.Textured = require(cwd .. "textured")
Boxes.Mapped = require(cwd .. "mapped")
return Boxes

View File

@ -0,0 +1,51 @@
-- mapped.lua :: a sti-mapped box
--[[
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('%.mapped$', '') .. "."
local Box3D = require(cwd .. "parent")
local MappedBox = Box3D:extend()
function MappedBox:new(owner, x, y, z, w, h, d)
self.x = x
self.y = y
self.z = z
MappedBox.super.new(self, owner, w, h, d)
self.haveLine = false
end
function MappedBox:drawTextureContent()
local tx, ty = self.x, self.y - (self.z + self.d)
core.debug:print("mappedbox", "getting map layers at position " .. tx .. ";" .. ty)
love.graphics.push()
love.graphics.origin()
love.graphics.translate(math.floor(-tx), math.floor(-ty))
self.world:drawMap()
love.graphics.pop()
end
return MappedBox

View File

@ -0,0 +1,160 @@
-- 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, isVisible)
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.isVisible = isVisible or true
if (self.isVisible) then
self:setTexture()
end
self.shadows = love.graphics.newCanvas(self.w, self.h)
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:setTexture()
local canvas = love.graphics.newCanvas(self.w, self.h + self.d)
love.graphics.setCanvas( canvas )
utils.graphics.resetColor()
self:drawTextureContent()
love.graphics.setCanvas( )
local imagedata = canvas:newImageData()
self.texture = love.graphics.newImage( imagedata )
imagedata:release()
canvas:release()
end
function Box3D:drawTextureContent()
self:drawTopTexture()
self:drawBottomTexture()
end
function Box3D:drawTopTexture()
utils.graphics.resetColor()
love.graphics.rectangle("fill", 0, 0, self.w, self.h)
end
function Box3D:drawBottomTexture()
love.graphics.setColor(0.9, 0.9, 0.9, 1)
love.graphics.rectangle("fill", 0, self.h, self.w, self.d)
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
love.graphics.setCanvas( self.shadows )
love.graphics.clear()
for i,v in ipairs(self.shadowSources) do
v.actor:drawShadow(v.x, v.y)
end
love.graphics.setCanvas( )
self.needRedraw = false
end
end
function Box3D:draw(x, y, z)
if (self.isVisible) then
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, x, (y-z) - (self.d))
end
if (self.shadows ~= nil) and (#self.shadowSources > 0) then
love.graphics.draw(self.shadows, x, (y-z) - (self.d))
end
end
return Box3D

View File

@ -0,0 +1,53 @@
-- textured.lua :: a textured box
--[[
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('%.textured$', '') .. "."
local Box3D = require(cwd .. "parent")
local TexturedBox = Box3D:extend()
function TexturedBox:new(owner, w, h, d, topTexture, bottomTexture)
local bottomTexture = bottomTexture or topTexture
self.topTexture = owner.assets.images[topTexture]
self.bottomTexture = owner.assets.images[bottomTexture]
TexturedBox.super.new(self, owner, w, h, d)
self.haveLine = false
end
function TexturedBox:drawTopTexture()
local w, h = self.topTexture:getDimensions()
local sx = self.w / w
local sy = self.h / h
self.topTexture:draw(0, 0, 0, sx, sy)
end
function TexturedBox:drawBottomTexture()
local w, h = self.topTexture:getDimensions()
local sx = self.w / w
local sy = self.d / h
self.bottomTexture:draw(0, self.h, 0, sx, sy)
end
return TexturedBox

View File

@ -0,0 +1,123 @@
-- hitbox2D.lua :: a basic 2D 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 Hitbox2D = Object:extend()
-- INIT FUNCTIONS
-- Initialise the actor and its base functions
function Hitbox2D:new(owner, type, ox, oy, w, h, isSolid)
self.owner = owner
self.world = owner.world
self.type = type
self.ox = ox
self.oy = oy
self.x, self.y = self:getPosition()
self.w = w
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
self.x, self.y = self:getPosition()
self.w = w
self.h = h
end
function Hitbox2D:setDebugColor(r,g,b)
self.debug = {}
self.debug.r = r
self.debug.g = g
self.debug.b = b
end
function Hitbox2D:register()
self.world:registerBody(self)
end
function Hitbox2D:destroy()
self.world:removeBody(self)
end
-- COORDINATE FUNCTIONS
-- Handle Hitbox position
function Hitbox2D:updatePosition()
self.x, self.y = self:getPosition()
self.world:updateBody(self)
return self.x, self.y
end
function Hitbox2D:getPosition()
return self.ox + self.owner.x, self.oy + self.owner.y
end
function Hitbox2D:getOwnerPosition()
return self.x - self.ox, self.y - self.oy
end
function Hitbox2D:getNewOwnerPosition(x, y)
return x - self.ox, y - self.oy
end
function Hitbox2D:getCenter()
return self.x + (self.w/2), self.y + (self.h/2)
end
-- COLLISION FUNCTIONS
-- Handle Hitbox position
function Hitbox2D:checkCollision(dx, dy, filter)
self:updatePosition()
local dx, dy = self.ox + dx, self.oy + dy
local x, y, cols, colNumber = self.world:checkCollision(self, dx, dy, filter)
local newx, newy = self:getNewOwnerPosition(x, y)
return newx, newy, cols, colNumber
end
-- DRAW FUNCTIONS
-- Just some debug function to draw hitbox
function Hitbox2D:draw()
local x, y = self:getPosition()
love.graphics.setColor(self.debug.r, self.debug.g, self.debug.b, 1)
utils.graphics.box(x, y, self.w, self.h)
utils.graphics.resetColor()
end
return Hitbox2D

View File

@ -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

View File

@ -26,7 +26,7 @@ local cwd = (...):gsub('%.baseworld$', '') .. "."
local BaseWorld = Object:extend()
local Sti = require(cwd .. "libs.sti")
local mapObjects = require(cwd .. "maps")
local CameraSystem = require(cwd .. "camera")
local PADDING_VALUE = 10/100
@ -34,17 +34,18 @@ local PADDING_VALUE = 10/100
-- INIT FUNCTIONS
-- All functions to init the world and the map
function BaseWorld:new(scene, actorlist, mapfile)
function BaseWorld:new(scene, actorlist, mapfile, maptype)
self.scene = scene
self.actorlist = actorlist
self.mapfile = mapfile
self.mapObjects = mapObjects
self.cameras = CameraSystem(self)
self:initActors()
self:initPlayers()
self:setActorList(self.actorlist)
self:initMap(self.mapfile)
self:initMap(self.mapfile, maptype)
self:setGravity()
self:register()
@ -60,20 +61,22 @@ function BaseWorld:setActorList(actorlist)
end
end
function BaseWorld:initMap(mapfile)
self.haveMap = false
self.haveBackgroundColor = false
self.backcolor = {128, 128, 128}
function BaseWorld:initMap(mapfile, maptype)
if (mapfile ~= nil) then
self.maptype = maptype or "sti"
else
self.maptype = "empty"
end
self.mapfile = mapfile
end
function BaseWorld:setGravity(xgrav, ygrav, isDefault)
local xgrav = xgrav or 0
local ygrav = ygrav or 0
local isDefault = isDefault or 0
function BaseWorld:setGravity(grav, isDefault)
local grav = grav or 0
local isDefault = isDefault or false
self.gravity = {}
self.gravity.xgrav, self.gravity.ygrav = xgrav, ygrav
self.gravity.grav = grav
self.gravity.isDefault = isDefault
end
@ -84,7 +87,7 @@ end
function BaseWorld:reset()
self:initActors()
self:initPlayers()
self:initMap(self.mapfile)
self:initMap(self.mapfile, self.maptype)
self.cameras:initViews()
collectgarbage()
@ -99,11 +102,17 @@ function BaseWorld:initActors( )
self.currentCreationID = 0
end
function BaseWorld:newActor(name, x, y)
function BaseWorld:newActor(name, x, y, z)
local debugstring = " at (" .. x .. ";" .. y .. ")."
core.debug:print("world2D", "adding actor " .. name .. debugstring)
self.obj.index[name](self, x, y)
end
function BaseWorld:newCollision(name, x, y, w, h)
function BaseWorld:newCollision(name, x, y, z, w, h, d)
local debugstringpos = "at (" .. x .. ";" .. y .. ")"
local debugstringsize = "size is (" .. w .. ";" .. h .. ")"
local debugstring = " " .. debugstringpos .. ". " .. debugstringsize .. "."
core.debug:print("world2D", "creating collision " .. name .. debugstring)
self.obj.collisions[name](self, x, y, w, h)
end
@ -121,19 +130,15 @@ function BaseWorld:removeActor(actor)
end
end
function BaseWorld:moveActor(actor, x, y, filter)
-- as the baseworld have no collision function, we return empty collision
-- datas, but from the same type than bump2D will return
return x, y, {}, 0
function BaseWorld:countActors()
return #self.actors
end
function BaseWorld:checkCollision(actor, x, y, filter)
-- as the baseworld have no collision function, we return empty collision
-- datas, but from the same type than bump2D will return
return x, y, {}, 0
function BaseWorld:getActors()
return self.actors
end
function BaseWorld:queryRect(x, y, w, h)
function BaseWorld:getActorsInRect(x, y, w, h)
local query = {}
local x2, y2 = x + w, y + h
for i,v in ipairs(self.actors) do
@ -147,23 +152,64 @@ function BaseWorld:queryRect(x, y, w, h)
return v
end
function BaseWorld:countActors()
return #self.actors
end
function BaseWorld:getActors()
return self.actors
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
return self:queryRect(x, y, w, h)
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
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
-- Basic function to handle bodies. Empty function here as baseworld doesn't
-- handle bodies
function BaseWorld:registerBody(body)
return nil
end
function BaseWorld:updateBody(body)
return x, y, {}, 0
end
function BaseWorld:removeBody(body)
return nil
end
function BaseWorld:moveActor(actor, x, y, filter)
-- as the baseworld have no collision function, we return empty collision
-- datas, but from the same type than bump2D will return
return x, y, {}, 0
end
function BaseWorld:checkCollision(actor, x, y, filter)
-- as the baseworld have no collision function, we return empty collision
-- datas, but from the same type than bump2D will return
return x, y, {}, 0
end
function BaseWorld:getBodiesInRect(x, y, w, h)
return {}
end
-- INFO FUNCTIONS
@ -180,21 +226,6 @@ end
-- PLAYER MANAGEMENT FUNCTIONS
-- Basic function to handle player actors
function BaseWorld:loadMap()
local mapfile = self.mapfile
if mapfile == nil then
self.haveMap = false
self.haveBackgroundColor = false
self.backcolor = {128, 128, 128}
else
self.haveMap = true
self.map = Sti(mapfile)
self.haveBackgroundColor = true
self.backcolor = self.map.backgroundcolor or {128, 128, 128}
self:loadMapObjects()
end
end
function BaseWorld:initPlayers()
self.players = {}
self.playerNumber = 1
@ -204,19 +235,22 @@ function BaseWorld:setPlayerNumber(playerNumber)
self.playerNumber = playerNumber or 1
end
function BaseWorld:addPlayer(actor, sourceid, haveCam)
function BaseWorld:addPlayer(x, y, z, id)
local player = {}
player.actor = actor
player.sourceid = sourceid or 1
if id <= self.playerNumber then
player.actor = self:newPlayer(x, y, z)
player.sourceid = sourceid or 1
table.insert(self.players, player)
table.insert(self.players, player)
if (haveCam) then
local xx, yy = player.actor:getViewCenter()
self.cameras:addView(xx, yy, player.actor)
self.cameras:addTarget(player.actor)
end
end
function BaseWorld:newPlayer(x, y, z)
return self.obj.Player(self, x, y)
end
function BaseWorld:sendInputToPlayers(actor)
for i,v in ipairs(self.players) do
--TODO: make the player get from a selected source inputs
@ -236,95 +270,58 @@ end
-- MAP FUNCTIONS
-- All map wrappers
function BaseWorld:loadMap()
self:createMapController()
self:loadMapObjects()
end
function BaseWorld:createMapController()
if (self.maptype == "empty") then
self.mapObjects.Base(self)
elseif (self.maptype == "sti") then
self.mapObjects.Sti(self, self.mapfile)
end
end
function BaseWorld:loadMapObjects()
self:loadMapCollisions()
self:loadMapPlayers()
self:loadMapActors()
end
function BaseWorld:loadMapCollisions()
for k, objectlayer in pairs(self.map.layers) do
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)
end
self.map:removeLayer(objectlayer.name)
end
end
end
function BaseWorld:loadMapActors()
for k, objectlayer in pairs(self.map.layers) do
if self:isActorIndexed(objectlayer.name) then
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)
else
self:newActor(objectlayer.name, object.x, object.y)
end
end
self.map:removeLayer(objectlayer.name)
end
end
end
function BaseWorld:batchActor(name, object)
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 BaseWorld:loadMapPlayers()
for k, objectlayer in pairs(self.map.layers) do
if (objectlayer.name == "player") then
print("DEBUG: loading actors in player layer")
local i = 1
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)
end
i = i + 1
end
self.map:removeLayer(objectlayer.name)
end
if (self.map ~= nil) then
self.map:loadObjects()
end
end
function BaseWorld:getDimensions()
if self.haveMap then
return self.map.width * self.map.tilewidth,
self.map.height * self.map.tileheight
if (self.map ~= nil) then
return self.map:getDimensions()
else
return core.screen:getDimensions()
end
end
function BaseWorld:setBackgroundColor(r, g, b)
self.backcolor = {r, g, b}
self.haveBackgroundColor = true
if (self.map ~= nil) then
self.map:setBackgroundColor(r, g, b)
end
end
function BaseWorld:removeBackgroundColor()
self.haveBackgroundColor = false
if (self.map ~= nil) then
self.map:setBackgroundColor()
end
end
function BaseWorld:getBackgroundColor()
return self.backcolor[1]/256, self.backcolor[2]/256, self.backcolor[3]/256
if (self.map ~= nil) then
return self.map:getBackgroundColor()
else
return 0, 0, 0
end
end
function BaseWorld:resizeMap(w, h)
if (self.map ~= nil) then
local w, h = utils.math.floorCoord(w, h)
self.map:resize(w, h)
end
end
-- Lock MANAGEMENT FUNCTIONS
@ -360,12 +357,11 @@ function BaseWorld:updateActors(dt)
end
function BaseWorld:updateMap(dt)
if self.haveMap then
if (self.map ~= nil) then
self.map:update(dt)
end
end
-- DRAW FUNCTIONS
-- All function to draw the map, world and actors
@ -382,26 +378,12 @@ function BaseWorld:draw(dt)
self:drawMap(i)
self:drawActors(i)
self.cameras:detachView(i)
self.cameras:drawHUD(i)
end
end
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()
@ -409,28 +391,14 @@ function BaseWorld:drawActors(id)
end
function BaseWorld:drawMap(id)
local tx, ty, scale = 0, 0, 1
if id ~= nil then
-- Du à la manière dont fonctionne STI, on est obligé de récupérer les info
-- de position de camera pour afficher la carte par rapport à ces infos
tx, ty = self.cameras:getViewCoordinate(id)
scale = self.cameras:getViewScale(id) or 1
local vx, vy = self.cameras:getOnScreenViewRelativePosition(id)
tx = math.floor(tx - math.abs(vx))
ty = math.floor(ty - math.abs(vy))
end
if self.haveMap then
self.map:draw(-tx, -ty, scale, scale)
if (self.map ~= nil) then
self.map:draw()
end
end
function BaseWorld:drawBackgroundColor()
if self.haveBackgroundColor then
local r, g, b = self.backcolor[1], self.backcolor[2], self.backcolor[3]
love.graphics.setColor(r/256, g/256, b/256)
love.graphics.rectangle("fill", 0, 0, 480, 272)
utils.graphics.resetColor()
if (self.map ~= nil) then
self.map:drawBackgroundColor()
end
end

View File

@ -1,337 +0,0 @@
-- camera.lua :: a basic camera adapted to the asset/world system.
-- Use hump.camera as the view backend.
--[[
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('%.camera$', '') .. "."
local CameraSystem = Object:extend()
local View = require(cwd .. "libs.hump.camera")
local SPLITSCREEN_ISVERTICAL = false
local SCREEN_LIMIT = 4
-- INIT FUNCTIONS
-- Initialize the camera system
function CameraSystem:new(world)
self.scene = world.scene
self.world = world
self.verticalSplit = SPLITSCREEN_ISVERTICAL
self:initViews()
end
function CameraSystem:initViews()
self.views = {}
self.views.list = {}
self.views.basewidth, self.views.baseheight = core.screen:getDimensions()
self.views.width, self.views.height = self:getViewsDimensions()
self.views.posList = {}
self.views.posList.dual = {}
self.views.posList.multi = {}
if (self.verticalSplit) then
self.views.posList.dual[1] = {}
self.views.posList.dual[1].x = 0
self.views.posList.dual[1].y = (self.views.baseheight/4)
self.views.posList.dual[2] = {}
self.views.posList.dual[2].x = 0
self.views.posList.dual[2].y = -(self.views.baseheight/4)
else
self.views.posList.dual[1] = {}
self.views.posList.dual[1].x = -(self.views.basewidth/4)
self.views.posList.dual[1].y = 0
self.views.posList.dual[2] = {}
self.views.posList.dual[2].x = (self.views.basewidth/4)
self.views.posList.dual[2].y = 0
end
self.views.posList.multi[1] = {}
self.views.posList.multi[1].x = -(self.views.basewidth /4)
self.views.posList.multi[1].y = (self.views.baseheight/4)
self.views.posList.multi[2] = {}
self.views.posList.multi[2].x = (self.views.basewidth /4)
self.views.posList.multi[2].y = (self.views.baseheight/4)
self.views.posList.multi[3] = {}
self.views.posList.multi[3].x = -(self.views.basewidth /4)
self.views.posList.multi[3].y = -(self.views.baseheight/4)
self.views.posList.multi[4] = {}
self.views.posList.multi[4].x = (self.views.basewidth /4)
self.views.posList.multi[4].y = -(self.views.baseheight/4)
end
-- INFO FUNCTIONS
-- Get informations from the camera system
function CameraSystem:getViewNumber()
return #self.views.list
end
function CameraSystem:haveView()
return (self:getViewNumber() == 0)
end
function CameraSystem:getViewsDimensions(viewNumber)
local basewidth, baseheight = self.views.basewidth, self.views.baseheight
local viewnumber = viewNumber or self:getViewNumber()
if (viewnumber <= 1) then
return basewidth, baseheight
elseif (viewnumber == 2) then
if (self.verticalSplit) then
return (basewidth), (baseheight/2)
else
return (basewidth/2), (baseheight)
end
else
return (basewidth/2), (baseheight/2)
end
end
function CameraSystem:recalculateViewsPositions()
if #self.views.list == 1 then
self.views.list[1].pos.onScreen.x = 0
self.views.list[1].pos.onScreen.y = 0
else
for i,v in ipairs(self.views.list) do
local x, y = self:getViewPositions(i)
self.views.list[i].pos.onScreen.x = x
self.views.list[i].pos.onScreen.y = y
end
end
end
function CameraSystem:getViewPositions(id)
local viewNumber = #self.views.list
if (viewNumber == 2) and ((id == 1) or (id == 2)) then
return self.views.posList.dual[id].x, self.views.posList.dual[id].y
elseif (viewNumber > 2) then
return self.views.posList.multi[id].x, self.views.posList.multi[id].y
end
end
-- WRAPPER and UTILS
-- Access data from the views
function CameraSystem:addView(x, y, target)
if (#self.views.list < SCREEN_LIMIT) then
local id = #self.views.list + 1
local view = {}
view.pos = {}
view.pos.x = x or 0
view.pos.y = y or 0
view.pos.onScreen = {}
view.cam = View(view.pos.x, view.pos.y, 1, 0, true)
-- TODO: add a target system in order to make a camera able
-- to target a specific object
view.target = target
table.insert(self.views.list, view)
self.views.width, self.views.height = self:getViewsDimensions()
self:recalculateViewsPositions()
end
end
function CameraSystem:getView(id)
return self.views.list[id]
end
function CameraSystem:getViewCam(id)
local view = self:getView(id)
return view.cam
end
function CameraSystem:attachView(id)
if (id ~= nil) then
local cam = self:getViewCam(id)
local viewx, viewy, vieww, viewh = self:getOnScreenViewCoordinate(id)
cam:attach()
love.graphics.setScissor(viewx, viewy, vieww, viewh)
end
end
function CameraSystem:detachView(id)
if (id ~= nil) then
local cam = self:getViewCam(id)
love.graphics.setScissor( )
cam:detach()
end
end
function CameraSystem:getViewCoordinate(id)
local cam = self:getViewCam(id)
local camx, camy, camw, camh
camx = cam.x - (self.views.width/2)
camy = cam.y - (self.views.height/2)
camw = self.views.width
camh = self.views.height
return camx, camy, camw, camh
end
function CameraSystem:getOnScreenViewCoordinate(id)
local view = self:getView(id)
local viewx, viewy, vieww, viewh
local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
viewx = (basex) + view.pos.onScreen.x - (self.views.width / 2)
viewy = (basey) + view.pos.onScreen.y - (self.views.height / 2)
vieww = self.views.width
viewh = self.views.height
return viewx, viewy, vieww, viewh
end
function CameraSystem:getOnScreenViewRelativePosition(id)
local view = self:getView(id)
local viewx, viewy
local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
viewx = view.pos.onScreen.x
viewy = view.pos.onScreen.y
return viewx, viewy
end
function CameraSystem:getOnScreenViewCenter(id)
local view = self:getView(id)
local viewx, viewy
local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
viewx = (basex) + view.pos.onScreen.x
viewy = (basey) + view.pos.onScreen.y
return viewx, viewy
end
function CameraSystem:getViewScale(id)
local cam = self:getViewCam(id)
return cam.scale
end
function CameraSystem:limitView(id)
local viewx, viewy, vieww, viewh = self:getViewCoordinate(id)
local worldw, worldh = self.world:getDimensions()
local posx = self.views.list[id].pos.x
local posy = self.views.list[id].pos.y
local minx = self.views.width / 2
local miny = self.views.height / 2
local maxx = worldw - minx
local maxy = worldh - miny
self.views.list[id].pos.x = utils.math.between(posx, minx, maxx)
self.views.list[id].pos.y = utils.math.between(posy, miny, maxy)
self:computeCamPosition(id)
end
-- UPDATE and MOVE functions
-- Move and update the camera system
function CameraSystem:update(dt)
for i,v in ipairs(self.views.list) do
self:followActor(i)
end
end
function CameraSystem:moveView(id, x, y)
self.views.list[id].pos.x = x
self.views.list[id].pos.y = y
self:computeCamPosition(id)
self:limitView(id)
end
function CameraSystem:computeCamPosition(id)
local decalx = self.views.list[id].pos.onScreen.x
local decaly = self.views.list[id].pos.onScreen.y
local realx = self.views.list[id].pos.x
local realy = self.views.list[id].pos.y
self.views.list[id].cam.x = realx - decalx
self.views.list[id].cam.y = realy + decaly
-- FIXME: this workaround certainly will cause some problem but that's the only
-- solution we seem to have right now
-- We invert the y decalage for the camera in order to work around a problem
-- that invert the y between it and the clipping.
end
function CameraSystem:followActor(id)
local view = self:getView(id)
if view.target ~= nil then
local x, y = view.target:getViewCenter()
x = math.floor(x)
y = math.floor(y)
self:moveView(id, x, y)
end
end
-- DRAW FUNCTIONS
-- Basic callback to draw stuff
function CameraSystem:drawDebugViewBox(id)
local viewx, viewy, vieww, viewh = self:getOnScreenViewCoordinate(id)
utils.graphics.box(viewx, viewy, vieww, viewh)
local xx, yy = self:getOnScreenViewCenter(id)
love.graphics.line(xx-3, yy, xx+3, yy)
love.graphics.line(xx, yy-3, xx, yy+3)
local string = id .. " x:" .. viewx .. " y:" .. viewy
love.graphics.print(string, viewx + 4, viewy + 4)
print(viewy)
end
function CameraSystem:drawHUD(id)
local viewx, viewy, vieww, viewh = self:getOnScreenViewCoordinate(id)
local view = self:getView(id)
local string2 = id .. " (" .. viewx .. ":" .. (viewh-viewy) .. ") "
love.graphics.setScissor(viewx, viewy, vieww, viewh)
love.graphics.translate(viewx, viewy)
view.target:drawHUD(id, vieww, viewh)
love.graphics.translate(-viewx, -(viewh-viewy))
love.graphics.setScissor( )
end
return CameraSystem

View File

@ -0,0 +1,373 @@
-- camera.lua :: a basic camera adapted to the asset/world system.
--[[
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 CameraSystem = Object:extend()
--local View = require(cwd .. "libs.hump.camera")
local camutils = require(cwd .. "utils")
local SPLITSCREEN_ISVERTICAL = false
local SCREEN_LIMIT = 4
-- INIT FUNCTIONS
-- Initialize the camera system
function CameraSystem:new(world)
self.scene = world.scene
self.world = world
self.mode = "split"
self.verticalSplit = SPLITSCREEN_ISVERTICAL
self.targets = {}
self:initViews()
end
function CameraSystem:setMode(mode)
self.mode = mode
core.debug:print("camera", "mode is now set to " .. mode)
return mode
end
function CameraSystem:initViews()
self.views = {}
self.views.list = {}
self.views.basewidth, self.views.baseheight = core.screen:getDimensions()
self.views.width, self.views.height = self:getViewsDimensions()
self.views.posList = camutils.getViewsPositions(self.views.basewidth, self.views.baseheight, self.verticalSplit)
end
-- INFO FUNCTIONS
-- Get informations from the camera system
function CameraSystem:getViewNumber()
return #self.views.list
end
function CameraSystem:haveView()
return (self:getViewNumber() == 0)
end
function CameraSystem:getViewsDimensions()
local basewidth, baseheight = self.views.basewidth, self.views.baseheight
local viewnumber = self:getViewNumber()
return camutils.getViewsDimensions(viewnumber, basewidth, baseheight, self.verticalSplit)
end
function CameraSystem:recalculateViewsPositions()
if #self.views.list == 1 then
self.views.list[1].onScreen.x = 0
self.views.list[1].onScreen.y = 0
else
for i,v in ipairs(self.views.list) do
local x, y = self:getViewPositions(i)
self.views.list[i].onScreen.x = x
self.views.list[i].onScreen.y = y
end
end
end
function CameraSystem:getViewPositions(id)
local viewNumber = #self.views.list
if (viewNumber == 2) and ((id == 1) or (id == 2)) then
return self.views.posList.dual[id].x, self.views.posList.dual[id].y
elseif (viewNumber > 2) then
return self.views.posList.multi[id].x, self.views.posList.multi[id].y
end
end
-- WRAPPER and UTILS
-- Access data from the views
function CameraSystem:addTarget(target)
if (#self.views < SCREEN_LIMIT) then
if (#self.views.list == 0) or (self.mode == "split") then
local x, y = target:getViewCenter()
self:addView(x, y, target)
table.insert(self.targets, target)
elseif (self.mode == "middle") or (self.mode == "zoom") then
table.insert(self.targets, target)
end
end
end
function CameraSystem:addView(x, y, target)
if (#self.views.list < SCREEN_LIMIT) then
local id = #self.views.list + 1
local view = {}
view.pos = {}
view.x = x or 0
view.y = y or 0
view.scale = 1
view.onScreen = {}
-- TODO: add a target system in order to make a camera able
-- to target a specific object
view.target = target
table.insert(self.views.list, view)
self.views.width, self.views.height = self:getViewsDimensions()
self:regenerateCanvases()
self:recalculateViewsPositions()
end
end
function CameraSystem:regenerateCanvases()
for i, view in ipairs(self.views.list) do
view.canvas = love.graphics.newCanvas(self.views.width, self.views.height)
end
end
function CameraSystem:getView(id)
return self.views.list[id]
end
function CameraSystem:attachView(id)
if (id ~= nil) then
local view = self:getView(id)
self.current_canvas = love.graphics.getCanvas()
love.graphics.setCanvas(view.canvas)
love.graphics.clear()
if id ~= nil then
-- Du à la manière dont fonctionne STI, on est obligé de récupérer les info
-- de position de camera pour afficher la carte par rapport à ces infos
tx, ty = self:getViewCoordinate(id)
scale = self:getViewScale(id) or 1
tx = math.floor(tx) * -1
ty = math.floor(ty) * -1
local w, h = core.screen:getDimensions()
end
love.graphics.push()
love.graphics.origin()
love.graphics.translate(math.floor(tx), math.floor(ty))
end
end
function CameraSystem:detachView(id)
if (id ~= nil) then
local view = self:getView(id)
love.graphics.pop()
love.graphics.setCanvas(self.current_canvas)
local tx, ty = self:getOnScreenViewCoordinate(id)
local scale = core.screen:getScale() * view.scale
love.graphics.push()
love.graphics.origin()
love.graphics.translate(math.floor(tx), math.floor(ty))
love.graphics.scale(scale, scale)
love.graphics.draw(view.canvas)
local unscale = 1 / view.scale
love.graphics.scale(unscale, unscale)
self:drawHUD(id)
love.graphics.pop()
end
end
function CameraSystem:getViewCoordinate(id)
local view = self:getView(id)
local viewx, viewy, vieww, viewh
vieww = self.views.width / view.scale
viewh = self.views.height / view.scale
viewx = view.x - (vieww / 2)
viewy = view.y - (viewh / 2)
return viewx, viewy, vieww, viewh
end
function CameraSystem:getOnScreenViewCoordinate(id)
local view = self:getView(id)
local viewx, viewy, vieww, viewh
local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
viewx = (basex) + view.onScreen.x - (self.views.width / 2)
viewy = (basey) + view.onScreen.y - (self.views.height / 2)
viewx, viewy = core.screen:getScreenCoordinate(viewx, viewy)
vieww = self.views.width * core.screen:getScale()
viewh = self.views.height * core.screen:getScale()
return viewx, viewy, vieww, viewh
end
function CameraSystem:getOnScreenViewRelativePosition(id)
local view = self:getView(id)
local viewx, viewy
local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
viewx = view.onScreen.x
viewy = view.onScreen.y
return core.screen:getScreenCoordinate(viewx, viewy)
end
function CameraSystem:getOnScreenViewCenter(id)
local view = self:getView(id)
local viewx, viewy
local basex, basey = (self.views.basewidth / 2), (self.views.baseheight / 2)
viewx = (basex) + view.onScreen.x
viewy = (basey) + view.onScreen.y
return core.screen:getScreenCoordinate(viewx, viewy)
end
function CameraSystem:setViewScale(id, scale)
local view = self:getView(id)
view.scale = scale
local _, _, w, h = self:getViewCoordinate(id)
view.canvas = love.graphics.newCanvas(math.ceil(w), math.ceil(h))
end
function CameraSystem:getViewScale(id)
local view = self:getView(id)
return view.scale
end
function CameraSystem:limitView(id)
local viewx, viewy, vieww, viewh = self:getViewCoordinate(id)
local worldw, worldh = self.world:getDimensions()
local posx = self.views.list[id].x
local posy = self.views.list[id].y
local minx = self.views.width / 2
local miny = self.views.height / 2
local maxx = worldw - minx
local maxy = worldh - miny
self.views.list[id].x = utils.math.between(posx, minx, maxx)
self.views.list[id].y = utils.math.between(posy, miny, maxy)
end
-- UPDATE and MOVE functions
-- Move and update the camera system
function CameraSystem:update(dt)
for i,v in ipairs(self.views.list) do
if (self.mode == "middle") or (self.mode == "zoom") then
self:followAllActors(i)
else
self:followActor(i)
end
end
end
function CameraSystem:moveView(id, x, y)
self.views.list[id].x = x
self.views.list[id].y = y
self:limitView(id)
end
function CameraSystem:followActor(id)
local view = self:getView(id)
if view.target ~= nil then
local x, y = view.target:getViewCenter()
x = math.floor(x)
y = math.floor(y)
self:moveView(id, x, y)
end
end
function CameraSystem:followAllActors(id)
local view = self:getView(id)
local PADDING = 16
-- TODO: make all the padding and stuff part of object definition instead ?
-- It would especially allow better work in future fake3D worlds
if (#self.targets > 0) then
local minX, minY, maxX, maxY
for i, target in ipairs(self.targets) do
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 = 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, x)
maxX = math.max(maxX, x2)
minY = math.min(minY, y)
maxY = math.max(maxY, y2)
end
-- Add padding
minX = minX - PADDING
minY = minY - PADDING
maxX = maxX + PADDING
maxY = maxY + PADDING
local x, y
x = (minX + maxX) / 2
y = (minY + maxY) / 2
local scale = 1
if (self.mode == "zoom") then
local ww, hh = core.screen:getDimensions()
local scalex = (maxX-minX)/ww
local scaley = (maxY-minY)/hh
scale = math.max(scale, scalex, scaley)
self:setViewScale(id, 1/scale)
self.world:resizeMap(ww * 3, hh * 3)
end
self:moveView(id, x, y)
end
end
-- DRAW FUNCTIONS
-- Basic callback to draw stuff
function CameraSystem:drawHUD(id)
local view = self:getView(id)
local viewx, viewy, vieww, viewh = self:getOnScreenViewCoordinate(id)
view.target:drawHUD(id, vieww, viewh)
end
return CameraSystem

View File

@ -0,0 +1,83 @@
-- camutils.lua :: some camera utilities
--[[
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 camutils = {}
function camutils.getViewsPositions(basewidth, baseheight, verticalSplit)
local posList = {}
posList.dual = {}
posList.multi = {}
if (verticalSplit) then
posList.dual[1] = {}
posList.dual[1].x = 0
posList.dual[1].y = (baseheight/4)
posList.dual[2] = {}
posList.dual[2].x = 0
posList.dual[2].y = -(baseheight/4)
else
posList.dual[1] = {}
posList.dual[1].x = -(basewidth/4)
posList.dual[1].y = 0
posList.dual[2] = {}
posList.dual[2].x = (basewidth/4)
posList.dual[2].y = 0
end
posList.multi[1] = {}
posList.multi[1].x = -(basewidth /4)
posList.multi[1].y = -(baseheight/4)
posList.multi[2] = {}
posList.multi[2].x = (basewidth /4)
posList.multi[2].y = -(baseheight/4)
posList.multi[3] = {}
posList.multi[3].x = -(basewidth /4)
posList.multi[3].y = (baseheight/4)
posList.multi[4] = {}
posList.multi[4].x = (basewidth /4)
posList.multi[4].y = (baseheight/4)
return posList
end
function camutils.getViewsDimensions(viewnumber, basewidth, baseheight, verticalSplit)
if (viewnumber <= 1) then
return basewidth, baseheight
elseif (viewnumber == 2) then
if (verticalSplit) then
return (basewidth), (baseheight/2)
else
return (basewidth/2), (baseheight)
end
else
return (basewidth/2), (baseheight/2)
end
end
return camutils

View File

@ -1,216 +0,0 @@
--[[
Copyright (c) 2010-2015 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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 _PATH = (...):match('^(.*[%./])[^%.%/]+$') or ''
local cos, sin = math.cos, math.sin
local camera = {}
camera.__index = camera
-- Movement interpolators (for camera locking/windowing)
camera.smooth = {}
function camera.smooth.none()
return function(dx,dy) return dx,dy end
end
function camera.smooth.linear(speed)
assert(type(speed) == "number", "Invalid parameter: speed = "..tostring(speed))
return function(dx,dy, s)
-- normalize direction
local d = math.sqrt(dx*dx+dy*dy)
local dts = math.min((s or speed) * love.timer.getDelta(), d) -- prevent overshooting the goal
if d > 0 then
dx,dy = dx/d, dy/d
end
return dx*dts, dy*dts
end
end
function camera.smooth.damped(stiffness)
assert(type(stiffness) == "number", "Invalid parameter: stiffness = "..tostring(stiffness))
return function(dx,dy, s)
local dts = love.timer.getDelta() * (s or stiffness)
return dx*dts, dy*dts
end
end
local function new(x,y, zoom, rot, smoother)
x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2
zoom = zoom or 1
rot = rot or 0
smoother = smoother or camera.smooth.none() -- for locking, see below
return setmetatable({x = x, y = y, scale = zoom, rot = rot, smoother = smoother}, camera)
end
function camera:lookAt(x,y)
self.x, self.y = x, y
return self
end
function camera:move(dx,dy)
self.x, self.y = self.x + dx, self.y + dy
return self
end
function camera:position()
return self.x, self.y
end
function camera:rotate(phi)
self.rot = self.rot + phi
return self
end
function camera:rotateTo(phi)
self.rot = phi
return self
end
function camera:zoom(mul)
self.scale = self.scale * mul
return self
end
function camera:zoomTo(zoom)
self.scale = zoom
return self
end
function camera:attach(x,y,w,h, noclip)
x,y = x or 0, y or 0
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
self._sx,self._sy,self._sw,self._sh = love.graphics.getScissor()
if not noclip then
love.graphics.setScissor(x,y,w,h)
end
local cx,cy = x+w/2, y+h/2
love.graphics.push()
love.graphics.translate(cx, cy)
love.graphics.scale(self.scale)
love.graphics.rotate(self.rot)
love.graphics.translate(-self.x, -self.y)
end
function camera:detach()
love.graphics.pop()
love.graphics.setScissor(self._sx,self._sy,self._sw,self._sh)
end
function camera:draw(...)
local x,y,w,h,noclip,func
local nargs = select("#", ...)
if nargs == 1 then
func = ...
elseif nargs == 5 then
x,y,w,h,func = ...
elseif nargs == 6 then
x,y,w,h,noclip,func = ...
else
error("Invalid arguments to camera:draw()")
end
self:attach(x,y,w,h,noclip)
func()
self:detach()
end
-- world coordinates to camera coordinates
function camera:cameraCoords(x,y, ox,oy,w,h)
ox, oy = ox or 0, oy or 0
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
-- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center
local c,s = cos(self.rot), sin(self.rot)
x,y = x - self.x, y - self.y
x,y = c*x - s*y, s*x + c*y
return x*self.scale + w/2 + ox, y*self.scale + h/2 + oy
end
-- camera coordinates to world coordinates
function camera:worldCoords(x,y, ox,oy,w,h)
ox, oy = ox or 0, oy or 0
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
-- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y)
local c,s = cos(-self.rot), sin(-self.rot)
x,y = (x - w/2 - ox) / self.scale, (y - h/2 - oy) / self.scale
x,y = c*x - s*y, s*x + c*y
return x+self.x, y+self.y
end
function camera:mousePosition(ox,oy,w,h)
local mx,my = love.mouse.getPosition()
return self:worldCoords(mx,my, ox,oy,w,h)
end
-- camera scrolling utilities
function camera:lockX(x, smoother, ...)
local dx, dy = (smoother or self.smoother)(x - self.x, self.y, ...)
self.x = self.x + dx
return self
end
function camera:lockY(y, smoother, ...)
local dx, dy = (smoother or self.smoother)(self.x, y - self.y, ...)
self.y = self.y + dy
return self
end
function camera:lockPosition(x,y, smoother, ...)
return self:move((smoother or self.smoother)(x - self.x, y - self.y, ...))
end
function camera:lockWindow(x, y, x_min, x_max, y_min, y_max, smoother, ...)
-- figure out displacement in camera coordinates
x,y = self:cameraCoords(x,y)
local dx, dy = 0,0
if x < x_min then
dx = x - x_min
elseif x > x_max then
dx = x - x_max
end
if y < y_min then
dy = y - y_min
elseif y > y_max then
dy = y - y_max
end
-- transform displacement to movement in world coordinates
local c,s = cos(-self.rot), sin(-self.rot)
dx,dy = (c*dx - s*dy) / self.scale, (s*dx + c*dy) / self.scale
-- move
self:move((smoother or self.smoother)(dx,dy,...))
end
-- the module
return setmetatable({new = new, smooth = camera.smooth},
{__call = function(_, ...) return new(...) end})

View File

@ -0,0 +1,7 @@
local cwd = (...):gsub('%.init$', '') .. "."
local mapObjects = {}
mapObjects.Sti = require(cwd .. "sti")
mapObjects.Base = require(cwd .. "parent")
return mapObjects

View File

@ -0,0 +1,81 @@
local ParentMap = Object:extend()
-- INIT FUNCTION
-- Initialize the map
function ParentMap:new(world, r, g, b)
self.world = world
local r = r or 128
local g = g or 128
local b = b or 128
self.backgroundColor = {r, g, b}
self:register()
end
function ParentMap:register()
self.world.map = self
end
-- UPDATE FUNCTION
-- Update or modify the map
function ParentMap:resize(w, h)
-- Empty Placeholder function
end
function ParentMap:update(dt)
-- Empty Placeholder function
end
function ParentMap:loadObjects()
self:loadCollisions()
self:loadPlayers()
self:loadActors()
end
function ParentMap:loadCollisions()
-- Empty Placeholder function
end
function ParentMap:loadPlayers()
-- Empty Placeholder function
end
function ParentMap:loadActors()
-- Empty Placeholder function
end
function ParentMap:setBackgroundColor(r, g, b)
local r = r or 128
local g = g or 128
local b = b or 128
self.backgroundColor = {r, g, b}
end
function ParentMap:setBackgroundColorFromTable(backgroundColor)
self.backgroundColor = backgroundColor or {128, 128, 128}
end
function ParentMap:getBackgroundColor()
return self.backgroundColor[1]/256, self.backgroundColor[2]/256, self.backgroundColor[3]/256
end
function ParentMap:getDimensions()
return core.screen:getDimensions()
end
function ParentMap:getBox()
local w, h = self:getDimensions()
return 0, 0, w, h
end
function ParentMap:drawBackgroundColor()
local r, g, b = self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3]
love.graphics.setColor(r/256, g/256, b/256)
love.graphics.rectangle("fill", 0, 0, 480, 272)
utils.graphics.resetColor()
end
return ParentMap

View File

@ -0,0 +1,150 @@
local cwd = (...):gsub('%.sti$', '') .. "."
local Parent = require(cwd .. "parent")
local STI = require(cwd .. "libs.sti")
local StiMap = Parent:extend()
function StiMap:new(world, mapfile)
self.sti = STI(mapfile)
StiMap.super.new(self, world)
self:setBackgroundColorFromTable(self.sti.backgroundcolor)
end
function StiMap:getDimensions()
return self.sti.width * self.sti.tilewidth,
self.sti.height * self.sti.tileheight
end
-- UPDATE FUNCTION
-- Update or modify the map
function StiMap:resize(w, h)
self.sti:resize(w, h)
end
function StiMap:update(dt)
self.sti:update(dt)
end
-- LOADING FUNCTION
-- Load actors directly into the world
function StiMap:loadCollisions()
for k, objectlayer in pairs(self.sti.layers) do
if self.world:isCollisionIndexed(objectlayer.name) then
local debugstring = "loading " .. #objectlayer.objects .. " objects in " .. objectlayer.name .. " collision layer"
core.debug:print("map/sti", debugstring)
for k, object in pairs(objectlayer.objects) do
self:newCollision(objectlayer, object)
end
self.sti:removeLayer(objectlayer.name)
end
end
end
function StiMap:loadActors()
for k, objectlayer in pairs(self.sti.layers) do
if self.world:isActorIndexed(objectlayer.name) then
local debugstring = "loading " .. #objectlayer.objects .. " objects in " .. objectlayer.name .. " actor layer"
core.debug:print("map/sti", debugstring)
for k, object in pairs(objectlayer.objects) do
if (object.properties.batchActor) then
self:batchActor(objectlayer, object)
else
self:newActor(objectlayer, object)
end
end
self.sti:removeLayer(objectlayer.name)
end
end
end
function StiMap:loadPlayers()
for k, objectlayer in pairs(self.sti.layers) do
if (objectlayer.name == "player") then
local debugstring = "loading at most " .. #objectlayer.objects .. " actors in " .. objectlayer.name .. " actor layer"
core.debug:print("map/sti", debugstring)
local i = 1
for k, object in pairs(objectlayer.objects) do
self:newPlayer(object, i)
i = i + 1
end
self.sti:removeLayer(objectlayer.name)
end
end
end
function StiMap:batchActor(objectlayer, object)
local name = objectlayer.name
local gwidth = object.properties.gwidth or self.sti.tilewidth
local gheight = object.properties.gheight or self.sti.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.world:newActor(name, x + (i-1)*gwidth, y + (j-1)*gheight, z)
end
end
end
function StiMap:newActor(objectlayer, object)
local z = object.properties.z or 0
local adaptPosition = object.properties.adaptPosition or false
local y = object.y
if (adaptPosition) then
y = y + z
end
self.world:newActor(objectlayer.name, object.x, y, z)
end
function StiMap:newCollision(objectlayer, object)
local z = object.properties.z or 0
local d = object.properties.d or 16
local fromTop = object.properties.fromTop or false
local y = object.y
if (fromTop) then
local poschange = z .. ";" .. y .. " => " .. z-d .. ";" .. y+z
core.debug:print("map/sti", "create from top, set z and y: " .. poschange)
y = y + z
z = z - d
end
self.world:newCollision(objectlayer.name, object.x, y, z, object.width, object.height, d)
end
function StiMap:newPlayer(object, i)
local z = object.properties.z or 0
local adaptPosition = object.properties.adaptPosition or false
local y = object.y
if (adaptPosition) then
core.debug:print("map/sti", "adapting position, set y: " .. y .. " => ", y+z)
y = y + z
end
self.world:addPlayer(object.x, y, z, i)
end
-- DRAW FUNCTIONS
-- Draw the map
function StiMap:draw()
for _, layer in ipairs(self.sti.layers) do
if layer.visible and layer.opacity > 0 and (layer.type == "tilelayer") then
self.sti:drawLayer(layer)
end
end
end
return StiMap

View File

@ -26,7 +26,6 @@ local cwd = (...):gsub('%.world2D$', '') .. "."
local BaseWorld = require(cwd .. "baseworld")
local World2D = BaseWorld:extend()
local Sti = require(cwd .. "libs.sti")
local Bump = require(cwd .. "libs.bump")
local CameraSystem = require(cwd .. "camera")
@ -35,41 +34,56 @@ function World2D:new(scene, actorlist, mapfile)
end
-- ACTORS FUNCTIONS
-- Wrappers around Bump2D functions
-- Add support for bodies in Actor functions
function World2D:initActors()
self.currentCreationID = 0
self.actors = Bump.newWorld(50)
self.actors = {}
self.bodies = Bump.newWorld(50)
end
function World2D:registerActor(actor)
actor.creationID = self.currentCreationID
self.currentCreationID = self.currentCreationID + 1
return self.actors:add(actor, actor.x, actor.y, actor.w, actor.h)
end
function World2D:removeActor(actor)
return self.actors:remove(actor)
World2D.super.registerActor(self, actor)
end
function World2D:moveActor(actor, x, y, filter)
return self.actors:move(actor, x, y, filter)
return self.bodies:move(actor.mainHitbox, x, y, filter)
end
function World2D:checkCollision(actor, x, y, filter)
return self.actors:check(actor, x, y, filter)
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
function World2D:queryRect(x, y, w, h)
return self.actors:queryRect(x, y, w, h)
-- 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
function World2D:countActors()
return self.actors:countItems()
function World2D:updateBody(body)
return self.bodies:update(body, body.x, body.y, body.w, body.h)
end
function World2D:getActors()
return self.actors:getItems()
function World2D:removeBody(body)
return self.bodies:remove(body)
end
function World2D:checkCollision(body, x, y, filter)
return self.bodies:check(body, x, y, filter)
end
function World2D:getBodiesInRect(x, y, w, h)
return self.bodies:queryRect(x, y, w, h)
end
return World2D

View File

@ -0,0 +1,268 @@
-- 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 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 World3D: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:newPlayer(x, y, z)
return self.obj.Player(self, x, y, z)
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

View File

@ -25,7 +25,7 @@
local OptionsManager = Object:extend()
local cwd = (...):gsub('%.options$', '') .. "."
local binser = require(cwd .. "libs.binser")
local binser = require(cwd .. "modules.gamesystem.libs.binser")
local TRANSLATION_PATH = "datas/languages/"
@ -33,10 +33,10 @@ local TRANSLATION_PATH = "datas/languages/"
-- Initialize and configure the game options
function OptionsManager:new(controller)
self.controller = controller
-- We begin by creating an empty data table before reading the data.
self.data = {}
self:read()
self.controller = controller
end
function OptionsManager:reset()
@ -45,7 +45,7 @@ function OptionsManager:reset()
self.data.video.crtfilter = false
self.data.video.resolution = 1
self.data.video.border = true
self.data.video.vsync = false
self.data.video.vsync = true
self.data.video.fullscreen = false
-- We load the default files
@ -152,11 +152,11 @@ function OptionsManager:read()
filepath = self:getFile(true)
if utils.filesystem.exists("options.data") then
local loadedDatas = binser.readFile(filepath)
print("data file found, loading it")
self.controller.debug:print("core/options", "data file found, loading it")
self:setData(loadedDatas[1])
else
self:reset()
print("no data file found, reseting data")
self.controller.debug:print("core/options", "no data file found, reseting data")
end
end

View File

@ -66,6 +66,14 @@ function ScreenManager:getMousePosition()
return CScreen.project(love.mouse.getX(), love.mouse.getY())
end
function ScreenManager:getScale()
return CScreen.getScale()
end
function ScreenManager:getScreenCoordinate(x, y)
return CScreen.getScreenCoordinate(x, y)
end
-- INFO FUNCTIONS
-- Get screen informations

View File

@ -27,5 +27,6 @@ local cwd = (...):gsub('%.init$', '') .. "."
return {
math = require(cwd .. "math"),
graphics = require(cwd .. "graphics"),
filesystem = require(cwd .. "filesystem")
filesystem = require(cwd .. "filesystem"),
table = require(cwd .. "table")
}

View File

@ -0,0 +1,52 @@
-- loveutils.table : simple functions for table manipulation and computation.
-- TODO: could be a part of loveutils.math ?
--[[
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 Table = {}
function Table.reduce(list, fn)
local acc
for k, v in ipairs(list) do
if 1 == k then
acc = v
else
acc = fn(acc, v)
end
end
return acc
end
function Table.sum(table)
local sum = 0
for _, v in pairs(table) do
sum = sum + v
end
return sum
end
function Table.average(table)
return Table.sum(table) / #table
end
return Table

View File

@ -23,88 +23,25 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Game = Object:extend()
local GameSystem = require "core.modules.gamesystem"
local Game = GameSystem:extend()
local PigManager = require "game.pigmanager"
local Inventory = require "game.inventory"
local binser = require "core.libs.binser"
Game.gui = require "game.modules.gui"
function Game:new()
self.slot = -1
self.gametime = 0
Game.super.new(self)
self.inventory = Inventory(self)
self.pigmanager = PigManager(self)
end
function Game:setData(data)
local data = data
self.gametime = data.gametime
end
function Game:getData()
local data = {}
data.gametime = self.gametime
return data
end
function Game:read(save_id)
self.slot = save_id
if (self.slot > 0) then
filepath = self:getSaveFile(self.slot, true)
if love.filesystem.exists("save" .. self.slot .. ".save") then
local loadedDatas = binser.readFile(filepath)
self:setData(loadedDatas[1])
end
end
end
function Game:write(save_id)
if (self.slot > 0) then
local data = self:getData()
filepath = self:getSaveFile(self.slot, true)
binser.writeFile(filepath, data)
end
end
function Game:getSaveFile(saveslot, absolute)
local dir = ""
if absolute then
dir = love.filesystem.getSaveDirectory() .. "/"
if not love.filesystem.exists(dir) then
love.filesystem.createDirectory( "" )
end
end
local filepath = dir .. "save" .. saveslot .. ".save"
return filepath
end
function Game:resetSaves()
for i=1, 3 do
filepath = self:getSaveFile(i, true)
if love.filesystem.exists("save" .. i .. ".save") then
love.filesystem.remove( "save" .. i .. ".save" )
end
end
end
function Game:update(dt)
self.gametime = self.gametime + dt
end
function Game:getTime()
local time = self.gametime
local time = self.playtime
local hours, minute, seconds
seconds = math.floor(self.gametime)
seconds = math.floor(self.playtime)
minutes = math.floor(seconds / 60)
hours = math.floor(minutes / 60)
seconds = seconds % 60

View File

@ -1,7 +1,8 @@
local Inventory = Object:extend()
local Submodule = require "core.modules.gamesystem.submodule"
local Inventory = Submodule:extend()
function Inventory:new(controller)
self.controller = controller
Inventory.super.new(self, controller, "inventory")
self.data = {}
self.data.weapons = {}
@ -19,12 +20,4 @@ function Inventory:addItem(name)
self.data.items[name] = self.data.items[name] + 1
end
function Inventory:setData(data)
self.data = data
end
function Inventory:getData(data)
return self.data
end
return Inventory

View File

@ -1,7 +1,8 @@
local PigManager = Object:extend()
local Submodule = require "core.modules.gamesystem.submodule"
local PigManager = Submodule:extend()
function PigManager:new(controller)
self.controller = controller
PigManager.super.new(self, controller, "pigmanager")
self.data = {}
end
@ -39,12 +40,4 @@ function PigManager:getPig(pigID)
return pigData
end
function PigManager:getData()
return self.data
end
function PigManager:setData(data)
self.data = data
end
return PigManager

View File

@ -14,9 +14,9 @@ end
function Entity:setGravityFlag(flag)
if (flag) then
self:setYGravity(self.gacc)
self:setGravity(self.gacc)
else
self:setYGravity()
self:setGravity()
end
end
@ -50,11 +50,7 @@ function Entity:purge()
self:remove()
end
function Entity:checkGround(ny)
if not (self.grav == 0) then
if ny < 0 then self.onGround = true end
end
end
function Entity:getDirection()
if not (utils.math.sign(self.xsp) == 0) then