improvement: rework titlescreen

This commit is contained in:
Kazhnuz 2019-12-29 10:58:58 +01:00
parent 43eade2369
commit c3a406ad2c
4 changed files with 627 additions and 7 deletions

View file

@ -0,0 +1,88 @@
local TweenManager = Object:extend()
local tween = require "game.modules.tweenmanager.libs.tween"
local Timer = require "core.modules.world.actors.utils.timer"
function TweenManager:new(subject)
self.subject = subject
self.time = 0
self.tweens = {}
self.switches = {}
self.timers = {}
end
function TweenManager:newTween(start, duration, target, easing)
local newTween = {}
-- we add the data into a tween wrapper
newTween.tween = tween.new(duration, self.subject, target, easing)
newTween.start = self.time + start -- /!\ START IS RELATIVE TO CURRENT TIME
newTween.clear = newTween.start + duration
table.insert(self.tweens, newTween)
end
function TweenManager:newTimer(start, name)
self.timers[name] = Timer(self, name, start)
end
function TweenManager:newSwitch(start, bools)
local newSwitch = {}
-- we add the data into a tween wrapper
newSwitch.bools = bools
newSwitch.start = self.time + start -- /!\ START IS RELATIVE TO CURRENT TIME
newSwitch.clear = newSwitch.start + 1
table.insert(self.switches, newSwitch)
end
function TweenManager:update(dt)
self.time = self.time + dt
for i, tweenWrapper in ipairs(self.tweens) do
if (self.time > tweenWrapper.start) then
tweenWrapper.tween:update(dt)
end
end
for i, switch in ipairs(self.switches) do
if (self.time > switch.start) then
-- We test each boolean in the switch
for i, bool in ipairs(switch.bools) do
-- if it's nil, we set it to true
if self.subject[bool] == nil then
self.subject[bool] = true
else
-- if it's not nil, we reverse the boolean
self.subject[bool] = (self.subject[bool] == false)
end
end
table.remove(self.switches, i)
end
end
for k, timer in pairs(self.timers) do
timer:update(dt)
end
self:clearEndedTweens()
end
function TweenManager:timerResponse(timername)
if self.subject.timerResponse == nil then
core.debug:warning("tweenmanager", "the subject have no timerResponse function")
return 0
end
self.subject:timerResponse(timername)
end
function TweenManager:clearEndedTweens(dt)
for i, tweenWrapper in ipairs(self.tweens) do
if (self.time > tweenWrapper.clear) then
table.remove(self.tweens, i)
end
end
end
return TweenManager

View file

@ -0,0 +1,367 @@
local tween = {
_VERSION = 'tween 2.1.1',
_DESCRIPTION = 'tweening for lua',
_URL = 'https://github.com/kikito/tween.lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
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.
]]
}
-- easing
-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.
-- For all easing functions:
-- t = time == how much time has to pass for the tweening to complete
-- b = begin == starting property value
-- c = change == ending - beginning
-- d = duration == running time. How much time has passed *right now*
local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
-- linear
local function linear(t, b, c, d) return c * t / d + b end
-- quad
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
local function outQuad(t, b, c, d)
t = t / d
return -c * t * (t - 2) + b
end
local function inOutQuad(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
local function outInQuad(t, b, c, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end
-- cubic
local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end
local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
local function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
local function outInCubic(t, b, c, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end
-- quart
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end
local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
local function inOutQuart(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b
end
local function outInQuart(t, b, c, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end
-- quint
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end
local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
local function inOutQuint(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b
end
local function outInQuint(t, b, c, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end
-- sine
local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end
local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end
local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
local function outInSine(t, b, c, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
return inSine((t * 2) -d, b + c / 2, c / 2, d)
end
-- expo
local function inExpo(t, b, c, d)
if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end
local function outExpo(t, b, c, d)
if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end
local function inOutExpo(t, b, c, d)
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
end
local function outInExpo(t, b, c, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end
-- circ
local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end
local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end
local function inOutCirc(t, b, c, d)
t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
end
local function outInCirc(t, b, c, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end
-- elastic
local function calculatePAS(p,a,c,d)
p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s
return p, a, p / (2 * pi) * asin(c/a) -- p,a,s
end
local function inElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end
local function outElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end
local function inOutElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
end
local function outInElastic(t, b, c, d, a, p)
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end
-- back
local function inBack(t, b, c, d, s)
s = s or 1.70158
t = t / d
return c * t * t * ((s + 1) * t - s) + b
end
local function outBack(t, b, c, d, s)
s = s or 1.70158
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
end
local function inOutBack(t, b, c, d, s)
s = (s or 1.70158) * 1.525
t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end
local function outInBack(t, b, c, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end
-- bounce
local function outBounce(t, b, c, d)
t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
end
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
end
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end
local function inOutBounce(t, b, c, d)
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end
local function outInBounce(t, b, c, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end
tween.easing = {
linear = linear,
inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
}
-- private stuff
local function copyTables(destination, keysTable, valuesTable)
valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then
setmetatable(destination, mt)
end
for k,v in pairs(keysTable) do
if type(v) == 'table' then
destination[k] = copyTables({}, v, valuesTable[k])
else
destination[k] = valuesTable[k]
end
end
return destination
end
local function checkSubjectAndTargetRecursively(subject, target, path)
path = path or {}
local targetType, newPath
for k,targetValue in pairs(target) do
targetType, newPath = type(targetValue), copyTables({}, path)
table.insert(newPath, tostring(k))
if targetType == 'number' then
assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
elseif targetType == 'table' then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else
assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
end
end
end
local function checkNewParams(duration, subject, target, easing)
assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
local tsubject = type(subject)
assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject))
assert(type(target)== 'table', "target must be a table. Was " .. tostring(target))
assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target)
end
local function getEasingFunction(easing)
easing = easing or "linear"
if type(easing) == 'string' then
local name = easing
easing = tween.easing[name]
if type(easing) ~= 'function' then
error("The easing function name '" .. name .. "' is invalid")
end
end
return easing
end
local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
local t,b,c,d
for k,v in pairs(target) do
if type(v) == 'table' then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else
t,b,c,d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t,b,c,d)
end
end
end
-- Tween methods
local Tween = {}
local Tween_mt = {__index = Tween}
function Tween:set(clock)
assert(type(clock) == 'number', "clock must be a positive number or 0")
self.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock
if self.clock <= 0 then
self.clock = 0
copyTables(self.subject, self.initial)
elseif self.clock >= self.duration then -- the tween has expired
self.clock = self.duration
copyTables(self.subject, self.target)
else
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
end
return self.clock >= self.duration
end
function Tween:reset()
return self:set(0)
end
function Tween:update(dt)
assert(type(dt) == 'number', "dt must be a number")
return self:set(self.clock + dt)
end
-- Public interface
function tween.new(duration, subject, target, easing)
easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing)
return setmetatable({
duration = duration,
subject = subject,
target = target,
easing = easing,
clock = 0
}, Tween_mt)
end
return tween

View file

@ -0,0 +1,122 @@
local Background = Object:extend()
local zoneList = require("datas.gamedata.maps.shoot.zones")
function Background:new()
self.x1 = 0
self.x2 = 0
self.x3 = 0
self.y = 15
local randomZoneList = {}
for k,v in pairs(zoneList) do
table.insert(randomZoneList, k)
end
local id = math.ceil(love.math.random() * #randomZoneList)
self.zone = randomZoneList[id]
print(self.zone)
self:initRessources()
end
function Background:initRessources()
self.textureId = zoneList[self.zone].tiles
self.background = zoneList[self.zone].background
self.texture = {}
self.texture.floor = self:generateFloor(self.textureId)
self.texture.border = love.graphics.newImage("assets/backgrounds/borders.png")
self.quads = {}
local w, h = self.texture.border:getDimensions()
self.quads.borders = love.graphics.newQuad(0, self.textureId*10, 80, 10, w, h)
self:addParallax(self.background)
end
function Background:generateFloor(tile)
local canvas = love.graphics.newCanvas(31*16, 100)
local tile = tile or 1
local tileTexture = love.graphics.newImage("assets/backgrounds/normaltile.png")
local tileQuad = {}
local w, h = tileTexture:getDimensions()
tileQuad[1] = love.graphics.newQuad( 0, tile*24, 40, 24, w, h)
tileQuad[2] = love.graphics.newQuad(40, tile*24, 40, 24, w, h)
love.graphics.setCanvas( canvas )
for i=1, 5 do
for j=0, 18 do
local tiley = (i-1)*20 - 4
local tilex = (j-2)*31 + (i-1)*10
local variant = 1 + ((i + j) % 2)
love.graphics.draw(tileTexture, tileQuad[variant], tilex, tiley)
end
end
love.graphics.setCanvas( )
local imagedata = canvas:newImageData()
local texture = love.graphics.newImage( imagedata )
imagedata:release()
canvas:release()
return texture
end
function Background:addParallax(filename)
local filename = filename or "forest"
local backfolder = "assets/backgrounds/parallax/"
filename = backfolder .. filename
self.texture.back1 = love.graphics.newImage(filename .. "-back.png")
self.texture.back2 = love.graphics.newImage(filename .. "-fore.png")
end
function Background:update(dt)
local w, h = core.screen:getDimensions()
self.x1 = (self.x1 + 10*dt)
self.x2 = (self.x2 + 20*dt)
self.x3 = (self.x3 + 30*dt)
end
function Background:draw()
self:drawTiled(self.texture.back1, self.x1, 0, false)
self:drawTiled(self.texture.back2, self.x2, 95, true)
self:drawQuadTiled(self.texture.border, self.quads.borders, self.x3, 95, false)
self:drawTiled(self.texture.floor, self.x3, 105, false)
self:drawQuadTiled(self.texture.border, self.quads.borders, self.x3, 205, false)
end
function Background:drawTiled(drawable, x, y, fromBottom)
local w, h = drawable:getDimensions()
local screewn, _ = core.screen:getDimensions()
local x = math.floor(x % w)
local fromBottom = fromBottom or false
local oy = 0
if (fromBottom) then
oy = h
end
local imax = math.ceil(screewn / w) + 1
for i=0, imax do
love.graphics.draw(drawable, (i*w) - x, self.y + y, 0, 1, 1, 0, oy)
end
end
function Background:drawQuadTiled(drawable, quad, x, y, fromBottom)
local w, h = drawable:getDimensions()
local screewn, _ = core.screen:getDimensions()
local x = math.floor(x % w)
local fromBottom = fromBottom or false
local oy = 0
if (fromBottom) then
local oy = h
end
local imax = math.ceil(screewn / w) + 1
for i=0, imax do
love.graphics.draw(drawable, quad, (i*w) - x, self.y + y, 0, 1, 1, 0, oy)
end
end
return Background

View file

@ -25,31 +25,74 @@ local Scene = require "core.modules.scenes"
local TitleScreen = Scene:extend()
local gui = require "game.modules.gui"
local TweenManager = require "game.modules.tweenmanager"
local Background = require "scenes.titlescreen.background"
function TitleScreen:new()
TitleScreen.super.new(self)
self.borders = gui.newBorder(424, 30, 8)
self.assets:addImage("background", "assets/backgrounds/titlescreen.png")
self.assets:addImage("sonic", "assets/artworks/titlescreen_sonic.png")
self.assets:addImage("logo", "assets/artworks/logo.png")
self.assets:addImageFont("menu", "assets/gui/fonts/SA2font")
self.tweens = TweenManager(self)
self.background = Background(self)
self.borderY = 0
self.logoX = 270
self.sonicX = -180
self.darkenOpacity = 1
self.flashOpacity = 0
self.canShowPressStart = false
self.showPressStart = true
self.showPressStartTimer = 0.5
self.tweens:newTween(0.2, 0.5, {borderY = 30}, "inOutQuart")
self.tweens:newTween(0.5, 0.4, {darkenOpacity = 0}, "outExpo")
self.tweens:newTween(0.7, 0.6, {logoX = 0}, "inOutQuart")
self.tweens:newTween(0.7, 0.6, {sonicX = 0}, "inOutQuart")
self.tweens:newTween(1.3, 0.03, {flashOpacity = 1}, "inQuart")
self.tweens:newTween(1.45, 0.2, {flashOpacity = 0}, "outExpo")
self.tweens:newSwitch(1.4, { "canShowPressStart" })
self:register()
end
function TitleScreen:update(dt)
self.tweens:update(dt)
self.background:update(dt)
if (self.canShowPressStart) then
self.showPressStartTimer = self.showPressStartTimer - dt
if self.showPressStartTimer < 0 then
self.showPressStart = (self.showPressStart == false)
self.showPressStartTimer = 0.5
end
end
end
function TitleScreen:draw()
utils.graphics.resetColor()
self.assets:drawImage("background", 0, 0)
self.background:draw()
love.graphics.draw(self.borders, 0, 25, 0, 1, -1)
love.graphics.draw(self.borders, 424, 215, 0, -1, 1)
love.graphics.setColor(0, 0, 0, self.darkenOpacity)
love.graphics.rectangle("fill", 0, 0, 424, 240)
utils.graphics.resetColor( )
self.assets:drawImage("sonic", 90, 128, 0, 1, 1, 92, 106)
self.assets:drawImage("logo", 290, 60, 0, 1, 1, 150, 71)
love.graphics.draw(self.borders, 0, self.borderY, 0, 1, -1)
love.graphics.draw(self.borders, 424, 240 - self.borderY, 0, -1, 1)
self.assets:drawImage("sonic", 90 + self.sonicX, 128, 0, 1, 1, 92, 106)
self.assets:drawImage("logo", 290 + self.logoX, 60, 0, 1, 1, 150, 71)
if (self.canShowPressStart) and (self.showPressStart) then
local w = self.assets.fonts["menu"]:getWidth("PRESS START")
self.assets.fonts["menu"]:print("PRESS START", 424/1.5, 240/1.5, "center", 0, 1, 1)
end
love.graphics.setColor(1, 1, 1, self.flashOpacity)
love.graphics.rectangle("fill", 0, 0, 424, 240)
utils.graphics.resetColor( )
end
return TitleScreen