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:
|
||||
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,
|
||||
--- }
|
||||
|
||||
import 'announcer.lua'
|
||||
import 'graphics.lua'
|
||||
import 'scoreboard.lua'
|
||||
import 'utils.lua'
|
||||
|
@ -58,15 +59,16 @@ local DanceBounceCount <const> = 4
|
|||
local FielderDanceAnimator <const> = gfx.animator.new(1, 10, 0, easingHill)
|
||||
FielderDanceAnimator.repeatCount = DanceBounceCount - 1
|
||||
|
||||
-- selene: allow(unused_variable)
|
||||
function fieldersDance()
|
||||
FielderDanceAnimator:reset(DanceBounceMs)
|
||||
end
|
||||
|
||||
local BallOffscreen <const> = 999
|
||||
|
||||
local PitchFlyMs <const> = 1000
|
||||
local PitchStartX <const> = 200
|
||||
local PitchStartY <const>, PitchEndY <const> = 90, 240
|
||||
local PitchFlyMs <const> = 1050
|
||||
local PitchStartX <const> = 195
|
||||
local PitchStartY <const>, PitchEndY <const> = 105, 240
|
||||
|
||||
local PitchesX <const> = {
|
||||
-- Fastball
|
||||
|
@ -207,12 +209,17 @@ end
|
|||
local batter = newRunner()
|
||||
|
||||
--- "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
|
||||
flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5
|
||||
end
|
||||
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)
|
||||
ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc)
|
||||
if floaty then
|
||||
|
@ -305,12 +312,13 @@ function outRunner(runnerIndex)
|
|||
outs = outs + 1
|
||||
outRunners[#outRunners + 1] = runners[runnerIndex]
|
||||
table.remove(runners, runnerIndex)
|
||||
fieldersDance()
|
||||
updateForcedRunners()
|
||||
|
||||
announcer:say("YOU'RE OUT!")
|
||||
if outs == 3 then
|
||||
local gameOver = inning == 9 and teams.away.score ~= teams.home.score
|
||||
if not gameOver then
|
||||
fieldersDance()
|
||||
announcer:say("SWITCHING SIDES...")
|
||||
end
|
||||
while #runners > 0 do
|
||||
|
@ -373,9 +381,15 @@ function updateFielders()
|
|||
and touchedBase ~= touchingBaseCache.get(runner)
|
||||
then
|
||||
outRunner(i)
|
||||
playdate.timer.new(750, function()
|
||||
tryToThrowOut(fielder)
|
||||
end)
|
||||
elseif not touchingBaseCache.get(runner) then
|
||||
if distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance then
|
||||
outRunner(i)
|
||||
playdate.timer.new(750, function()
|
||||
tryToThrowOut(fielder)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -546,7 +560,14 @@ function updateBatting()
|
|||
local ballDestX = ball.x + (ballVelX * HitMult)
|
||||
local ballDestY = ball.y + (ballVelY * HitMult)
|
||||
-- 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]
|
||||
batter.nextBase = Bases[First]
|
||||
|
@ -623,6 +644,9 @@ function updateGameState()
|
|||
updateOutRunners()
|
||||
end
|
||||
|
||||
-- TODO
|
||||
function drawMinimap() end
|
||||
|
||||
function playdate.update()
|
||||
playdate.timer.updateTimers()
|
||||
|
||||
|
@ -630,20 +654,16 @@ function playdate.update()
|
|||
gfx.animation.blinker.updateAll()
|
||||
|
||||
gfx.clear()
|
||||
gfx.setColor(gfx.kColorBlack)
|
||||
|
||||
local offsetX, offsetY = 0, 0
|
||||
if ball.x < BallOffscreen then
|
||||
-- TODO: Show baserunning minimap when panning?
|
||||
local offsetX, offsetY = getDrawOffset(Screen.W, Screen.H, ball.x, ball.y)
|
||||
offsetX, offsetY = getDrawOffset(Screen.W, Screen.H, ball.x, ball.y)
|
||||
gfx.setDrawOffset(offsetX, offsetY)
|
||||
end
|
||||
|
||||
GrassBackground:draw(-400, -240)
|
||||
|
||||
gfx.setColor(gfx.kColorBlack)
|
||||
gfx.setLineWidth(2)
|
||||
|
||||
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
|
||||
|
||||
local fielderDanceHeight = FielderDanceAnimator:currentValue()
|
||||
for _, fielder in pairs(fielders) do
|
||||
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)
|
||||
end
|
||||
|
||||
if playdate.isCrankDocked() then -- or (crankChange < 2 and currentMode == Modes.running) then
|
||||
if playdate.isCrankDocked() then
|
||||
playdate.ui.crankIndicator:draw()
|
||||
end
|
||||
|
||||
|
@ -667,7 +687,18 @@ function playdate.update()
|
|||
PlayerFrown:draw(runner.x, runner.y)
|
||||
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)
|
||||
if offsetX > 0 or offsetY > 0 then
|
||||
drawMinimap()
|
||||
end
|
||||
drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning)
|
||||
announcer:draw(Center.x, 10)
|
||||
end
|
||||
|
|
|
@ -137,28 +137,3 @@ function buildCache(fetcher)
|
|||
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