mirror of
https://github.com/not-kennethreitz/super-sphere2.git
synced 2026-06-05 15:10:18 +00:00
477 lines
10 KiB
Lua
477 lines
10 KiB
Lua
Timer = require('vendor/hump.timer')
|
|
Signal = require('vendor/hump.signal')
|
|
Class = require('vendor/middleclass')
|
|
lue = require('vendor/lue')
|
|
|
|
BreathTimer = Timer.new()
|
|
GameTimer = Timer.new()
|
|
|
|
local SCORE_MULTIPLIER = 54
|
|
local POISON_RATE = 80
|
|
|
|
|
|
-- Helper methods.
|
|
function comma_value(amount)
|
|
local formatted = amount
|
|
while true do
|
|
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
|
|
if (k==0) then
|
|
break
|
|
end
|
|
end
|
|
return formatted
|
|
end
|
|
|
|
local Breath = Class('Breath')
|
|
|
|
Breath.static.standard_interval = 0.8
|
|
Breath.static.fast_interval = 0.1
|
|
Breath.static.in_out_ratio = 0.8
|
|
Breath.static.recovery_time = 1
|
|
|
|
function Breath:initialize()
|
|
self.rate = 0.8
|
|
self.status = 0
|
|
self._inhale = true
|
|
self.timer = nil
|
|
end
|
|
|
|
function Breath:update(dt)
|
|
self:cycle(dt)
|
|
end
|
|
|
|
function Breath:cycle(dt)
|
|
if self.status < 0 then
|
|
self._inhale = true
|
|
end
|
|
|
|
if self._inhale then
|
|
self.status = self.status + ((dt/1) * (1 / self.rate))
|
|
else
|
|
self.status = self.status - ((dt/1) * (1 / self.rate))
|
|
end
|
|
|
|
if self.status > 1 then
|
|
self._inhale = false
|
|
end
|
|
end
|
|
|
|
|
|
-- Obstacles.
|
|
local Obstacle = Class('Obstacle')
|
|
Obstacle.static.start_fall_speed = 3.5
|
|
Obstacle.static.end_fall_speed = 0.7
|
|
Obstacle.static.end_speed_time = 60
|
|
Obstacle.static.fall_speed = Obstacle.static.start_fall_speed
|
|
|
|
function Obstacle:initialize(pos, fs, p)
|
|
self.breath = Breath()
|
|
if p == nil then
|
|
self.poison = math.random(1, 100) > POISON_RATE
|
|
else
|
|
self.poison = p
|
|
end
|
|
|
|
self.position = pos or math.random(1, 3)
|
|
if self.poison then
|
|
self.breath.rate = 0.07
|
|
self.approach = -0.05
|
|
self.approach = 0
|
|
self._fall_speed = math.random(1, 5)
|
|
else
|
|
self.approach = 0
|
|
end
|
|
|
|
-- self.approach = 0
|
|
self.color = Obstacle:random_color()
|
|
self.destroyed = false
|
|
self._fall_speed = fs
|
|
self.game = nil
|
|
|
|
-- Make poison obstacles render offscreen.
|
|
if self.poison then
|
|
target_approach = 1.1
|
|
else
|
|
target_approach = 1
|
|
end
|
|
|
|
-- Make the balls fall.
|
|
GameTimer:tween(self:fall_speed(), self, {approach = target_approach}, 'in-cubic')
|
|
end
|
|
|
|
function Obstacle:fall_speed()
|
|
return self._fall_speed or Obstacle.fall_speed
|
|
end
|
|
|
|
function Obstacle:draw()
|
|
x_locations = {(X/6)*1, (X/6)*3, (X/6)*5}
|
|
|
|
love.graphics.setColor(self.color)
|
|
love.graphics.circle('fill', x_locations[self.position], Y*self.approach, self:current_size())
|
|
end
|
|
|
|
function Obstacle:current_size()
|
|
if self.poison then
|
|
size = (X/80)
|
|
else
|
|
size = (X/40)
|
|
end
|
|
|
|
return size + (X/300)*self.breath.status
|
|
end
|
|
|
|
function Obstacle:random_color()
|
|
|
|
local c = lue:getHueColor(128, 200)
|
|
lue:setHueSpeed(255)
|
|
return c
|
|
end
|
|
|
|
function Obstacle:destroy()
|
|
self.destroyed = true
|
|
end
|
|
|
|
function Obstacle:update(dt)
|
|
-- Gets called every frame.
|
|
self.breath:update(dt)
|
|
|
|
-- Destroy fallen obstacles.
|
|
if self.approach > 1 then
|
|
self:destroy()
|
|
end
|
|
|
|
if self.poison then
|
|
-- Change the color of poision obstacles.
|
|
self.color = {255, 0, 0}
|
|
else
|
|
-- Random colors for normal ones.
|
|
self.color = Obstacle:random_color()
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
local Jump = Class('Jump')
|
|
Jump.static.duration = 0.08
|
|
|
|
function Jump:initialize()
|
|
self.current = 0
|
|
self.direction = 0
|
|
end
|
|
|
|
function Jump:now(direction)
|
|
-- Only jump if not currently in the air.
|
|
if not self:in_air() then
|
|
-- Jump = 1 at max.
|
|
self.current = 1
|
|
|
|
-- Which direction the jump is in.
|
|
self.direction = direction
|
|
|
|
-- Make the jump happen.
|
|
GameTimer:tween(Jump.duration, self, {current = 0})
|
|
-- Enssure that 0 is set aferwards.
|
|
GameTimer:after(Jump.duration + 0.05, function() self.current = 0 end)
|
|
-- Emit 'landed' signal.
|
|
GameTimer:after(Jump.duration, function() Signal.emit('landed') end)
|
|
-- Reset direction to 0.
|
|
GameTimer:after(Jump.duration + 0.05, function() self.direction = 0 end)
|
|
|
|
end
|
|
end
|
|
|
|
function Jump:in_air()
|
|
return self.current ~= 0
|
|
end
|
|
|
|
|
|
|
|
local Guy = Class('Guy')
|
|
|
|
function Guy:initialize()
|
|
self.jump = Jump()
|
|
self.breath = Breath()
|
|
self.position = 2
|
|
self.eaten = 0
|
|
|
|
self:script()
|
|
end
|
|
|
|
function Guy:script()
|
|
Signal.emit('normal-breathing')
|
|
end
|
|
|
|
function Guy:apparent_position()
|
|
-- goes from 1 to 0
|
|
|
|
if self.jump:in_air() then
|
|
return 0
|
|
-- return self.position - self.jump.direction
|
|
else
|
|
return self.position
|
|
end
|
|
|
|
|
|
end
|
|
|
|
function Guy:draw()
|
|
local x_breath_offset = (self.breath.status * (X / 500))
|
|
|
|
if self.jump:in_air() then
|
|
love.graphics.setColor(lue:getHueColor(128, 200))
|
|
else
|
|
love.graphics.setColor({255, 255, 255})
|
|
end
|
|
love.graphics.circle('fill', (self:x_center()), (self:y_center() - x_breath_offset), game.guy:current_size())
|
|
end
|
|
|
|
function Guy:x_center()
|
|
|
|
x_locations = {(X/6)*1, (X/6)*3, (X/6)*5}
|
|
track_distance = x_locations[2] - x_locations[1]
|
|
|
|
jump_offset = (-1 * self.jump.direction) * ((self.jump.current) * track_distance)
|
|
|
|
return x_locations[self.position] + jump_offset
|
|
-- else
|
|
-- return (X / 8) * 5 - ((X / 8) * 2) * self.current_jump
|
|
-- end
|
|
end
|
|
|
|
function Guy:y_center()
|
|
return Y
|
|
end
|
|
|
|
|
|
function Guy:current_size()
|
|
return (Y/10) + (self.eaten * 4) + (self.breath.status * (X/500))
|
|
end
|
|
|
|
function Guy:update(dt)
|
|
self.breath:update(dt)
|
|
BreathTimer:update(dt)
|
|
end
|
|
|
|
Signal.register('obstacle-caught', function(obstacle)
|
|
game.guy.eaten = game.guy.eaten + 1
|
|
end)
|
|
|
|
|
|
Signal.register('heavy-breathing', function()
|
|
|
|
-- Abandon previous tweens.
|
|
BreathTimer:clear()
|
|
|
|
BreathTimer:tween(0.1, game.guy.breath, {rate = Breath.fast_interval}, 'out-expo')
|
|
BreathTimer:after(0.15, function()
|
|
BreathTimer:tween(Breath.recovery_time, game.guy.breath, {rate = Breath.standard_interval}, 'in-quint')
|
|
end)
|
|
|
|
BreathTimer:after(Breath.recovery_time, function()
|
|
Signal.emit('normal-breathing')
|
|
end)
|
|
end)
|
|
|
|
|
|
|
|
local Game = Class('Game')
|
|
|
|
function Game:initialize(do_demo)
|
|
self.elapsed = 0
|
|
self.started = false
|
|
self.demo = do_demo
|
|
self.guy = Guy()
|
|
self.obstacles = {}
|
|
self.obstacle_interval = 2
|
|
self.obstacle_min_interval = Jump.duration * 7
|
|
|
|
GameTimer:clear()
|
|
|
|
GameTimer:script(function(wait)
|
|
|
|
if self.demo then
|
|
wait(0.01)
|
|
print('Demo starting...')
|
|
|
|
|
|
demo_obstacle = Obstacle(1, 1, false)
|
|
table.insert(self.obstacles, demo_obstacle)
|
|
|
|
wait(0.8)
|
|
|
|
self:left()
|
|
|
|
wait(0.2)
|
|
demo_obstacle = Obstacle(2, 1, true)
|
|
table.insert(self.obstacles, demo_obstacle)
|
|
|
|
wait(0.3)
|
|
self:right()
|
|
|
|
wait(0.5)
|
|
self:right()
|
|
wait(0.3)
|
|
self:left()
|
|
|
|
wait(0.3)
|
|
end
|
|
|
|
GameTimer:tween(60, self, {obstacle_interval = self.obstacle_min_interval}, 'out-quad')
|
|
|
|
Signal.emit('started')
|
|
print('Game starting...')
|
|
self.started = true
|
|
self:obsticate()
|
|
end)
|
|
|
|
-- Restore Poision rate.
|
|
POISON_RATE = 80
|
|
|
|
-- Restore fall speed.
|
|
Obstacle.static.fall_speed = Obstacle.static.start_fall_speed
|
|
|
|
-- Make obstacles fall faster and faster.
|
|
GameTimer:tween(Obstacle.static.end_speed_time, Obstacle.static, {fall_speed = Obstacle.static.end_fall_speed})
|
|
|
|
end
|
|
|
|
function Game:obsticate()
|
|
GameTimer:after(math.random(1, 3)/10, function()
|
|
|
|
-- if self:score() > 1 then
|
|
-- num_balls = math.random(1, 2)
|
|
-- else
|
|
-- num_balls = 1
|
|
-- end
|
|
|
|
num_balls = 1
|
|
|
|
self:new_obstacles(num_balls)
|
|
end)
|
|
|
|
GameTimer:after(self.obstacle_interval, function()
|
|
self:obsticate()
|
|
end)
|
|
end
|
|
|
|
function Game:update(dt)
|
|
lue:update(dt)
|
|
GameTimer:update(dt)
|
|
|
|
if self.started then
|
|
self.elapsed = self.elapsed + dt
|
|
end
|
|
|
|
self.guy:update(dt)
|
|
self:catch_obstacles()
|
|
|
|
for i, obstacle in ipairs(self.obstacles) do
|
|
|
|
obstacle:update(dt)
|
|
|
|
if obstacle.destroyed then
|
|
table.remove(self.obstacles, i)
|
|
end
|
|
end
|
|
|
|
for i, obstacle in ipairs(self.obstacles) do
|
|
|
|
if obstacle.approach == 1 and not obstacle.poison then
|
|
Signal.emit('game-over')
|
|
end
|
|
end
|
|
|
|
POISON_RATE = POISON_RATE - dt
|
|
if POISON_RATE < 30 then
|
|
POISON_RATE = 30
|
|
end
|
|
end
|
|
|
|
function Game:new_obstacles(n)
|
|
for i=1,n do
|
|
-- Timer.after(math.random(7, 10), function()
|
|
table.insert(self.obstacles, Obstacle())
|
|
-- end)
|
|
end
|
|
|
|
end
|
|
|
|
function Game:draw_obstacles()
|
|
for i, obstacle in ipairs(self.obstacles) do
|
|
obstacle:draw()
|
|
end
|
|
end
|
|
|
|
function Game:catch_obstacles()
|
|
for i, obstacle in ipairs(game.obstacles) do
|
|
if obstacle.position == game.guy:apparent_position() and obstacle.approach > 0.82 then
|
|
if not (obstacle.poision and obstacle.approach > 0.9) then
|
|
Signal.emit('obstacle-caught', obstacle)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Game:score()
|
|
return self.elapsed * SCORE_MULTIPLIER
|
|
end
|
|
|
|
function Game:visual_score()
|
|
return comma_value(math.floor(self:score()))
|
|
end
|
|
|
|
function Game:left()
|
|
if self.guy.position ~= 1 then
|
|
if not game.guy.jump:in_air() then
|
|
Signal.emit('jump', -1)
|
|
end
|
|
else
|
|
Signal.emit('mis-jump')
|
|
end
|
|
end
|
|
|
|
function Game:right()
|
|
if self.guy.position ~= 3 then
|
|
if not game.guy.jump:in_air() then
|
|
Signal.emit('jump', 1)
|
|
end
|
|
else
|
|
Signal.emit('mis-jump')
|
|
end
|
|
end
|
|
|
|
|
|
|
|
game = Game(true)
|
|
|
|
Signal.register('jump', function(direction)
|
|
game.guy.jump:now(direction)
|
|
game.guy.position = game.guy.position + direction
|
|
end)
|
|
|
|
Signal.register('obstacle-caught', function(obstacle)
|
|
|
|
if obstacle.poison then
|
|
Signal.emit('game-over')
|
|
else
|
|
obstacle:destroy()
|
|
end
|
|
|
|
Signal.emit('heavy-breathing')
|
|
end)
|
|
|
|
|
|
|
|
Signal.register('left-event', function()
|
|
game:left()
|
|
end)
|
|
|
|
Signal.register('right-event', function()
|
|
game:right()
|
|
end)
|
|
|
|
return game |