Implement multi-out fielding.
Extract announcer.lua Fielders now only dance at the end of the half-inning. Ball is now drawn over everything but the scoreboard and announcer.
This commit is contained in:
parent
5d01769eb1
commit
6dd8469409
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
||||||
SOURCE_FILES := src/utils.lua src/graphics.lua src/scoreboard.lua src/main.lua
|
SOURCE_FILES := src/utils.lua src/announcer.lua src/graphics.lua src/scoreboard.lua src/main.lua
|
||||||
|
|
||||||
all:
|
all:
|
||||||
pdc src BatterUp.pdx
|
pdc src BatterUp.pdx
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
local AnnouncementFont <const> = playdate.graphics.font.new("fonts/Roobert-20-Medium.pft")
|
||||||
|
local AnnouncementTransitionMs <const> = 300
|
||||||
|
local AnnouncerMarginX <const> = 26
|
||||||
|
|
||||||
|
local AnnouncerAnimatorInY <const> =
|
||||||
|
playdate.graphics.animator.new(AnnouncementTransitionMs, -70, 0, playdate.easingFunctions.outBounce)
|
||||||
|
local AnnouncerAnimatorOutY <const> =
|
||||||
|
playdate.graphics.animator.new(AnnouncementTransitionMs, 0, -70, playdate.easingFunctions.outQuint)
|
||||||
|
|
||||||
|
-- selene: allow(unscoped_variables)
|
||||||
|
announcer = {
|
||||||
|
textQueue = {},
|
||||||
|
animatorY = AnnouncerAnimatorInY,
|
||||||
|
}
|
||||||
|
|
||||||
|
local DurationMs <const> = 3000
|
||||||
|
|
||||||
|
function announcer.popIn(self)
|
||||||
|
self.animatorY = AnnouncerAnimatorInY
|
||||||
|
self.animatorY:reset()
|
||||||
|
|
||||||
|
playdate.timer.new(DurationMs, function()
|
||||||
|
self.animatorY = AnnouncerAnimatorOutY
|
||||||
|
self.animatorY:reset()
|
||||||
|
-- If this popIn() call was inside a timer, successive messages would be
|
||||||
|
-- allowed to transition out. However, the Out animation, shortly followed by
|
||||||
|
-- a new message popping in, is actually *more* jarring than the interrupt.
|
||||||
|
if #self.textQueue ~= 1 then
|
||||||
|
self:popIn()
|
||||||
|
table.remove(self.textQueue, 1)
|
||||||
|
else
|
||||||
|
playdate.timer.new(AnnouncementTransitionMs, function()
|
||||||
|
table.remove(self.textQueue, 1)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function announcer.say(self, text)
|
||||||
|
self.textQueue[#self.textQueue + 1] = text
|
||||||
|
if #self.textQueue == 1 then
|
||||||
|
self:popIn()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function announcer.draw(self, x, y)
|
||||||
|
if #self.textQueue == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
x = x - 5 -- Infield center is slightly offset from screen center
|
||||||
|
|
||||||
|
local gfx = playdate.graphics
|
||||||
|
local originalDrawMode = gfx.getImageDrawMode()
|
||||||
|
local width = math.max(150, (AnnouncerMarginX * 2) + AnnouncementFont:getTextWidth(self.textQueue[1]))
|
||||||
|
local animY = self.animatorY:currentValue()
|
||||||
|
|
||||||
|
gfx.setColor(gfx.kColorBlack)
|
||||||
|
gfx.fillRect(x - (width / 2), y + animY, width, 50)
|
||||||
|
gfx.setImageDrawMode(gfx.kDrawModeInverted)
|
||||||
|
AnnouncementFont:drawTextAligned(self.textQueue[1], x, y + 10 + animY, kTextAlignment.center)
|
||||||
|
gfx.setImageDrawMode(originalDrawMode)
|
||||||
|
end
|
61
src/main.lua
61
src/main.lua
|
@ -33,6 +33,7 @@ import 'CoreLibs/ui.lua'
|
||||||
--- speed: number,
|
--- speed: number,
|
||||||
--- }
|
--- }
|
||||||
|
|
||||||
|
import 'announcer.lua'
|
||||||
import 'graphics.lua'
|
import 'graphics.lua'
|
||||||
import 'scoreboard.lua'
|
import 'scoreboard.lua'
|
||||||
import 'utils.lua'
|
import 'utils.lua'
|
||||||
|
@ -58,15 +59,16 @@ local DanceBounceCount <const> = 4
|
||||||
local FielderDanceAnimator <const> = gfx.animator.new(1, 10, 0, easingHill)
|
local FielderDanceAnimator <const> = gfx.animator.new(1, 10, 0, easingHill)
|
||||||
FielderDanceAnimator.repeatCount = DanceBounceCount - 1
|
FielderDanceAnimator.repeatCount = DanceBounceCount - 1
|
||||||
|
|
||||||
|
-- selene: allow(unused_variable)
|
||||||
function fieldersDance()
|
function fieldersDance()
|
||||||
FielderDanceAnimator:reset(DanceBounceMs)
|
FielderDanceAnimator:reset(DanceBounceMs)
|
||||||
end
|
end
|
||||||
|
|
||||||
local BallOffscreen <const> = 999
|
local BallOffscreen <const> = 999
|
||||||
|
|
||||||
local PitchFlyMs <const> = 1000
|
local PitchFlyMs <const> = 1050
|
||||||
local PitchStartX <const> = 200
|
local PitchStartX <const> = 195
|
||||||
local PitchStartY <const>, PitchEndY <const> = 90, 240
|
local PitchStartY <const>, PitchEndY <const> = 105, 240
|
||||||
|
|
||||||
local PitchesX <const> = {
|
local PitchesX <const> = {
|
||||||
-- Fastball
|
-- Fastball
|
||||||
|
@ -207,12 +209,17 @@ end
|
||||||
local batter = newRunner()
|
local batter = newRunner()
|
||||||
|
|
||||||
--- "Throws" the ball from its current position to the given destination.
|
--- "Throws" the ball from its current position to the given destination.
|
||||||
function throwBall(destX, destY, easingFunc, flyTimeMs, floaty)
|
function throwBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||||
if not flyTimeMs then
|
if not flyTimeMs then
|
||||||
flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5
|
flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5
|
||||||
end
|
end
|
||||||
ball.heldBy = nil
|
ball.heldBy = nil
|
||||||
ballSizeAnimator:reset(flyTimeMs)
|
if customBallScaler then
|
||||||
|
ballSizeAnimator = customBallScaler
|
||||||
|
else
|
||||||
|
-- TODO? Scale based on distance?
|
||||||
|
ballSizeAnimator = gfx.animator.new(flyTimeMs, 9, 6, easingHill)
|
||||||
|
end
|
||||||
ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc)
|
ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc)
|
||||||
ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc)
|
ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc)
|
||||||
if floaty then
|
if floaty then
|
||||||
|
@ -305,12 +312,13 @@ function outRunner(runnerIndex)
|
||||||
outs = outs + 1
|
outs = outs + 1
|
||||||
outRunners[#outRunners + 1] = runners[runnerIndex]
|
outRunners[#outRunners + 1] = runners[runnerIndex]
|
||||||
table.remove(runners, runnerIndex)
|
table.remove(runners, runnerIndex)
|
||||||
fieldersDance()
|
|
||||||
updateForcedRunners()
|
updateForcedRunners()
|
||||||
|
|
||||||
announcer:say("YOU'RE OUT!")
|
announcer:say("YOU'RE OUT!")
|
||||||
if outs == 3 then
|
if outs == 3 then
|
||||||
local gameOver = inning == 9 and teams.away.score ~= teams.home.score
|
local gameOver = inning == 9 and teams.away.score ~= teams.home.score
|
||||||
if not gameOver then
|
if not gameOver then
|
||||||
|
fieldersDance()
|
||||||
announcer:say("SWITCHING SIDES...")
|
announcer:say("SWITCHING SIDES...")
|
||||||
end
|
end
|
||||||
while #runners > 0 do
|
while #runners > 0 do
|
||||||
|
@ -373,9 +381,15 @@ function updateFielders()
|
||||||
and touchedBase ~= touchingBaseCache.get(runner)
|
and touchedBase ~= touchingBaseCache.get(runner)
|
||||||
then
|
then
|
||||||
outRunner(i)
|
outRunner(i)
|
||||||
|
playdate.timer.new(750, function()
|
||||||
|
tryToThrowOut(fielder)
|
||||||
|
end)
|
||||||
elseif not touchingBaseCache.get(runner) then
|
elseif not touchingBaseCache.get(runner) then
|
||||||
if distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance then
|
if distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance then
|
||||||
outRunner(i)
|
outRunner(i)
|
||||||
|
playdate.timer.new(750, function()
|
||||||
|
tryToThrowOut(fielder)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -546,7 +560,14 @@ function updateBatting()
|
||||||
local ballDestX = ball.x + (ballVelX * HitMult)
|
local ballDestX = ball.x + (ballVelX * HitMult)
|
||||||
local ballDestY = ball.y + (ballVelY * HitMult)
|
local ballDestY = ball.y + (ballVelY * HitMult)
|
||||||
-- Hit!
|
-- Hit!
|
||||||
throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000)
|
throwBall(
|
||||||
|
ballDestX,
|
||||||
|
ballDestY,
|
||||||
|
playdate.easingFunctions.outQuint,
|
||||||
|
2000,
|
||||||
|
nil,
|
||||||
|
gfx.animator.new(2000, 9 + (mult * mult * 0.5), 6, easingHill)
|
||||||
|
)
|
||||||
|
|
||||||
fielders.first.target = Bases[First]
|
fielders.first.target = Bases[First]
|
||||||
batter.nextBase = Bases[First]
|
batter.nextBase = Bases[First]
|
||||||
|
@ -623,6 +644,9 @@ function updateGameState()
|
||||||
updateOutRunners()
|
updateOutRunners()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- TODO
|
||||||
|
function drawMinimap() end
|
||||||
|
|
||||||
function playdate.update()
|
function playdate.update()
|
||||||
playdate.timer.updateTimers()
|
playdate.timer.updateTimers()
|
||||||
|
|
||||||
|
@ -630,20 +654,16 @@ function playdate.update()
|
||||||
gfx.animation.blinker.updateAll()
|
gfx.animation.blinker.updateAll()
|
||||||
|
|
||||||
gfx.clear()
|
gfx.clear()
|
||||||
|
gfx.setColor(gfx.kColorBlack)
|
||||||
|
|
||||||
|
local offsetX, offsetY = 0, 0
|
||||||
if ball.x < BallOffscreen then
|
if ball.x < BallOffscreen then
|
||||||
-- TODO: Show baserunning minimap when panning?
|
offsetX, offsetY = getDrawOffset(Screen.W, Screen.H, ball.x, ball.y)
|
||||||
local offsetX, offsetY = getDrawOffset(Screen.W, Screen.H, ball.x, ball.y)
|
|
||||||
gfx.setDrawOffset(offsetX, offsetY)
|
gfx.setDrawOffset(offsetX, offsetY)
|
||||||
end
|
end
|
||||||
|
|
||||||
GrassBackground:draw(-400, -240)
|
GrassBackground:draw(-400, -240)
|
||||||
|
|
||||||
gfx.setColor(gfx.kColorBlack)
|
|
||||||
gfx.setLineWidth(2)
|
|
||||||
|
|
||||||
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
|
|
||||||
|
|
||||||
local fielderDanceHeight = FielderDanceAnimator:currentValue()
|
local fielderDanceHeight = FielderDanceAnimator:currentValue()
|
||||||
for _, fielder in pairs(fielders) do
|
for _, fielder in pairs(fielders) do
|
||||||
gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25)
|
gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25)
|
||||||
|
@ -654,7 +674,7 @@ function playdate.update()
|
||||||
gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
|
gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
if playdate.isCrankDocked() then -- or (crankChange < 2 and currentMode == Modes.running) then
|
if playdate.isCrankDocked() then
|
||||||
playdate.ui.crankIndicator:draw()
|
playdate.ui.crankIndicator:draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -667,7 +687,18 @@ function playdate.update()
|
||||||
PlayerFrown:draw(runner.x, runner.y)
|
PlayerFrown:draw(runner.x, runner.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
gfx.setLineWidth(2)
|
||||||
|
|
||||||
|
gfx.setColor(gfx.kColorWhite)
|
||||||
|
gfx.fillCircleAtPoint(ball.x, ball.y, ball.size)
|
||||||
|
|
||||||
|
gfx.setColor(gfx.kColorBlack)
|
||||||
|
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
|
||||||
|
|
||||||
gfx.setDrawOffset(0, 0)
|
gfx.setDrawOffset(0, 0)
|
||||||
|
if offsetX > 0 or offsetY > 0 then
|
||||||
|
drawMinimap()
|
||||||
|
end
|
||||||
drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning)
|
drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning)
|
||||||
announcer:draw(Center.x, 10)
|
announcer:draw(Center.x, 10)
|
||||||
end
|
end
|
||||||
|
|
|
@ -137,28 +137,3 @@ function buildCache(fetcher)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local AnnouncementFont <const> = playdate.graphics.font.new("fonts/Roobert-20-Medium.pft")
|
|
||||||
|
|
||||||
-- selene: allow(unscoped_variables)
|
|
||||||
announcer = {
|
|
||||||
textQueue = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
function announcer.say(self, text, durationMs)
|
|
||||||
self.textQueue[#self.textQueue + 1] = text
|
|
||||||
-- Could cause some timing funk if messages are queued up asyncronously.
|
|
||||||
-- I.e. `:say("hello")` <1.5 seconds pass> `:say("hello")` would result
|
|
||||||
-- in the second message being displayed for 4.5 seconds instead of just 3.
|
|
||||||
durationMs = durationMs and durationMs or (3000 * #self.textQueue)
|
|
||||||
playdate.timer.new(durationMs, function()
|
|
||||||
table.remove(self.textQueue, 1)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function announcer.draw(self, x, y)
|
|
||||||
if #self.textQueue == 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
AnnouncementFont:drawTextAligned(self.textQueue[1], x, y, kTextAlignment.center)
|
|
||||||
end
|
|
||||||
|
|
Loading…
Reference in New Issue