Add perfect-power indicator to throwMeter
Some other tightening-up in there. E.g. clears the lastReadThrow when on a new fielder. Add type annotations to assets files.
This commit is contained in:
parent
35c7754207
commit
56c0c27d75
|
@ -2,66 +2,109 @@
|
|||
-- Instead, edit the source file directly: assets.lua2p.
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
DarkPlayerBack = playdate.graphics.image.new("images/game/DarkPlayerBack.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
Glove = playdate.graphics.image.new("images/game/Glove.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
PerfectPowerFlickerRight = playdate.graphics.image.new("images/game/PerfectPowerFlickerRight.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
DarkSkinFan = playdate.graphics.image.new("images/game/DarkSkinFan.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
PlayerFrown = playdate.graphics.image.new("images/game/PlayerFrown.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
PerfectPowerFlickerLeft = playdate.graphics.image.new("images/game/PerfectPowerFlickerLeft.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
BigBat = playdate.graphics.image.new("images/game/BigBat.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
LightSkinFan = playdate.graphics.image.new("images/game/LightSkinFan.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
GloveHoldingBall = playdate.graphics.image.new("images/game/GloveHoldingBall.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
GameLogo = playdate.graphics.image.new("images/game/GameLogo.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
Hat = playdate.graphics.image.new("images/game/Hat.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
DarkPlayerBase = playdate.graphics.image.new("images/game/DarkPlayerBase.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
MenuImage = playdate.graphics.image.new("images/game/MenuImage.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
PlayerSmile = playdate.graphics.image.new("images/game/PlayerSmile.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
Minimap = playdate.graphics.image.new("images/game/Minimap.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
GrassBackground = playdate.graphics.image.new("images/game/GrassBackground.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
GrassBackgroundSmall = playdate.graphics.image.new("images/game/GrassBackgroundSmall.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
LightPlayerBase = playdate.graphics.image.new("images/game/LightPlayerBase.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
PerfectPowerBg = playdate.graphics.image.new("images/game/PerfectPowerBg.png")
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
LightPlayerBack = playdate.graphics.image.new("images/game/LightPlayerBack.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type pd_sampleplayer
|
||||
BatCrackReverb = playdate.sound.sampleplayer.new("sounds/BatCrackReverb.wav")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type pd_sampleplayer
|
||||
BootTune = playdate.sound.sampleplayer.new("music/BootTune.wav")
|
||||
-- luacheck: ignore
|
||||
---@type pd_sampleplayer
|
||||
BootTuneOrgany = playdate.sound.sampleplayer.new("music/BootTuneOrgany.wav")
|
||||
-- luacheck: ignore
|
||||
---@type pd_sampleplayer
|
||||
TinnyBackground = playdate.sound.sampleplayer.new("music/TinnyBackground.wav")
|
||||
-- luacheck: ignore
|
||||
---@type pd_sampleplayer
|
||||
MenuMusic = playdate.sound.sampleplayer.new("music/MenuMusic.wav")
|
||||
|
||||
Logos = {
|
||||
{ name = "Base", image = playdate.graphics.image.new("images/game/logos/Base.png") },
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
{ name = "Cats", image = playdate.graphics.image.new("images/game/logos/Cats.png") },
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
{ name = "Hearts", image = playdate.graphics.image.new("images/game/logos/Hearts.png") },
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
{ name = "Checkmarks", image = playdate.graphics.image.new("images/game/logos/Checkmarks.png") },
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
{ name = "Smiles", image = playdate.graphics.image.new("images/game/logos/Smiles.png") },
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
{ name = "FingerGuns", image = playdate.graphics.image.new("images/game/logos/FingerGuns.png") },
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
{ name = "Frown", image = playdate.graphics.image.new("images/game/logos/Frown.png") },
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
{ name = "Arrows", image = playdate.graphics.image.new("images/game/logos/Arrows.png") },
|
||||
-- luacheck: ignore
|
||||
---@type pd_image
|
||||
{ name = "Turds", image = playdate.graphics.image.new("images/game/logos/Turds.png") },
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
!(function dirLookup(dir, extension, newFunc, sep, handle)
|
||||
!(function dirLookup(dir, extension, newFunc, type, sep, handle)
|
||||
sep = sep or "\n"
|
||||
handle = handle ~= nil and handle or function(varName, value)
|
||||
return varName .. ' = ' .. value
|
||||
|
@ -13,6 +13,7 @@
|
|||
local varName = file:gsub(".*/(.*)." .. extension, "%1")
|
||||
file = file:gsub("src/", "")
|
||||
assetCode = assetCode .. '-- luacheck: ignore\n'
|
||||
assetCode = assetCode .. '---@type ' .. type ..'\n'
|
||||
assetCode = assetCode .. handle(varName, newFunc .. '("' .. file .. '")') .. sep
|
||||
end
|
||||
end
|
||||
|
@ -23,13 +24,13 @@ function generatedFileWarning()
|
|||
return "-- GENERATED FILE - DO NOT EDIT\n-- Instead, edit the source file directly: assets.lua2p."
|
||||
end)!!(generatedFileWarning())
|
||||
|
||||
!!(dirLookup('images/game', 'png', 'playdate.graphics.image.new'))
|
||||
!!(dirLookup('sounds', 'wav', 'playdate.sound.sampleplayer.new'))
|
||||
!!(dirLookup('music', 'wav', 'playdate.sound.sampleplayer.new'))
|
||||
!!(dirLookup('images/game', 'png', 'playdate.graphics.image.new', 'pd_image'))
|
||||
!!(dirLookup('sounds', 'wav', 'playdate.sound.sampleplayer.new', 'pd_sampleplayer'))
|
||||
!!(dirLookup('music', 'wav', 'playdate.sound.sampleplayer.new', 'pd_sampleplayer'))
|
||||
Logos = {
|
||||
{ name = "Base", image = playdate.graphics.image.new("images/game/logos/Base.png") },
|
||||
|
||||
!!(dirLookup('images/game/logos -not -name "Base.png"', 'png', 'playdate.graphics.image.new', ",\n", function(varName, value)
|
||||
!!(dirLookup('images/game/logos -not -name "Base.png"', 'png', 'playdate.graphics.image.new', 'pd_image', ",\n", function(varName, value)
|
||||
return '{ name = "' .. varName .. '", image = ' .. value .. ' }'
|
||||
end))
|
||||
}
|
||||
|
|
|
@ -65,7 +65,6 @@ end
|
|||
---@param floaty boolean | nil
|
||||
---@param customBallScaler pd_animator | nil
|
||||
function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||
throwMeter:reset()
|
||||
self.heldBy = nil
|
||||
|
||||
-- Prevent silly insta-catches
|
||||
|
|
|
@ -4,20 +4,33 @@ local gfx <const> = playdate.graphics
|
|||
local ThrowMeterHeight <const> = 50
|
||||
local ThrowMeterLingerSec <const> = 1.5
|
||||
|
||||
local flickerTimer = gfx.animation.blinker.new(50, 50, true)
|
||||
flickerTimer:start()
|
||||
|
||||
---@param x number
|
||||
---@param y number
|
||||
function throwMeter:draw(x, y)
|
||||
gfx.setLineWidth(1)
|
||||
gfx.drawRect(x, y, 14, ThrowMeterHeight)
|
||||
if self.lastReadThrow then
|
||||
-- TODO: If ratio is "perfect", show some additional effect
|
||||
-- TODO: If meter has moved to a new fielder, empty it.
|
||||
local ratio = (self.lastReadThrow - throwMeter.MinCharge) / (self.idealPower - throwMeter.MinCharge)
|
||||
local ratio = 1
|
||||
if not wasPerfect then
|
||||
ratio = (self.lastReadThrow - throwMeter.MinCharge) / (self.idealPower - throwMeter.MinCharge)
|
||||
end
|
||||
local height = ThrowMeterHeight * ratio
|
||||
gfx.fillRect(x + 2, y + ThrowMeterHeight - height, 10, height)
|
||||
-- TODO: Dither or bend if the user throws too hard
|
||||
-- Or maybe dither if it's too soft - bend if it's too hard
|
||||
if self.wasPerfect then
|
||||
PerfectPowerBg:draw(x - 11, y - 9)
|
||||
if flickerTimer.on then
|
||||
PerfectPowerFlickerLeft:draw(x - 11, y - 9)
|
||||
else
|
||||
PerfectPowerFlickerRight:draw(x - 11, y - 9)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- TODO: Dither or bend if the user throws too hard
|
||||
-- Or maybe dither if it's too soft - bend if it's too hard
|
||||
end
|
||||
|
||||
function throwMeter:drawNearFielder(fielder)
|
||||
|
@ -25,6 +38,9 @@ function throwMeter:drawNearFielder(fielder)
|
|||
return
|
||||
end
|
||||
if fielder then
|
||||
if fielder ~= self.lastThrower then
|
||||
self.lastReadThrow = nil
|
||||
end
|
||||
self.lastThrower = fielder
|
||||
actionQueue:upsert("throwMeterLinger", 200 + ThrowMeterLingerSec * 1000, function()
|
||||
local dt = 0
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 654 B |
Binary file not shown.
After Width: | Height: | Size: 611 B |
Binary file not shown.
After Width: | Height: | Size: 614 B |
|
@ -46,7 +46,8 @@ Pitches = {
|
|||
currentValue = function()
|
||||
return getPitchMissBy(accuracy) + C.PitchStart.x + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10))
|
||||
end,
|
||||
reset = function() end,
|
||||
reset = function()
|
||||
end,
|
||||
},
|
||||
y = gfx.animator.new(C.PitchFlyMs * 1.3, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
|
||||
}
|
||||
|
@ -116,54 +117,66 @@ end
|
|||
throwMeter = {
|
||||
MinCharge = 25,
|
||||
|
||||
value = 0,
|
||||
idealPower = 50,
|
||||
|
||||
--- Used at draw-time only.
|
||||
---@type number
|
||||
lastReadThrow = nil,
|
||||
|
||||
--- Used at draw-time only.
|
||||
---@type Fielder | nil
|
||||
lastThrower = nil,
|
||||
|
||||
--- Used at draw-time only.
|
||||
---@type boolean
|
||||
wasPerfect = false,
|
||||
}
|
||||
|
||||
function throwMeter:reset()
|
||||
self.value = 0
|
||||
end
|
||||
|
||||
local crankQueue = {}
|
||||
|
||||
local MaxPowerRatio <const> = 1.5
|
||||
|
||||
--- Returns nil when a throw is NOT requested.
|
||||
---@param chargeAmount number
|
||||
---@return number | nil powerRatio, number | nil accuracy, boolean isPerfect
|
||||
function throwMeter:readThrow(chargeAmount)
|
||||
local ret = self:applyCharge(chargeAmount)
|
||||
if ret then
|
||||
local ratio = ret / self.idealPower
|
||||
local accuracy
|
||||
if ratio >= 1 then
|
||||
accuracy = 1 / ratio / 2
|
||||
else
|
||||
accuracy = 1 -- Accuracy is perfect on slow throws
|
||||
end
|
||||
return ratio * 1.5, accuracy, math.abs(ratio - 1) < 0.05
|
||||
local power = self:readCharge(chargeAmount)
|
||||
if not power then
|
||||
return nil, nil, false
|
||||
end
|
||||
return nil, nil, false
|
||||
|
||||
local ratio = math.min(power / self.idealPower, MaxPowerRatio)
|
||||
self.wasPerfect = math.abs(ratio - 1) < 0.05
|
||||
|
||||
local accuracy = 1
|
||||
-- Only throw off accuracy on slow throws
|
||||
if ratio >= 1 and not self.wasPerfect then
|
||||
accuracy = 1 / ratio
|
||||
end
|
||||
|
||||
return ratio * 1.5, accuracy, self.wasPerfect
|
||||
end
|
||||
|
||||
local CrankRecordSec <const> = 0.33
|
||||
|
||||
--- If (within approx. a third of a second) the crank has moved more than 45 degrees, call that a throw.
|
||||
---@param chargeAmount number
|
||||
function throwMeter:applyCharge(chargeAmount)
|
||||
---@return number | nil
|
||||
function throwMeter:readCharge(chargeAmount)
|
||||
if chargeAmount == 0 then
|
||||
return
|
||||
return nil
|
||||
end
|
||||
|
||||
local currentTimeMs = playdate.getCurrentTimeMilliseconds()
|
||||
local removedOne = false
|
||||
while #crankQueue ~= 0 and (currentTimeMs - crankQueue[1].time) > 0.33 do
|
||||
local minTimeHasPassed = false
|
||||
while #crankQueue ~= 0 and (currentTimeMs - crankQueue[1].time) > CrankRecordSec do
|
||||
table.remove(crankQueue, 1)
|
||||
removedOne = true -- At least 1/3 second has passed
|
||||
minTimeHasPassed = true
|
||||
end
|
||||
|
||||
crankQueue[#crankQueue + 1] = { time = currentTimeMs, chargeAmount = math.abs(chargeAmount) }
|
||||
|
||||
if not removedOne then
|
||||
if not minTimeHasPassed then
|
||||
return nil
|
||||
end
|
||||
|
||||
|
@ -174,7 +187,6 @@ function throwMeter:applyCharge(chargeAmount)
|
|||
|
||||
if currentCharge > throwMeter.MinCharge then
|
||||
self.lastReadThrow = currentCharge
|
||||
print(tostring(currentCharge) .. " from " .. #crankQueue)
|
||||
crankQueue = {}
|
||||
return currentCharge
|
||||
else
|
||||
|
|
Loading…
Reference in New Issue