Penalty Kick: One Shot, Two Inputs, Five Rounds of Mounting Pressure
A minimal penalty shootout: angle and power feed a probability model, every miss scores for the keeper, and a five-round sprint decides the match.
분석
- Four-state machine: boot → menu → gameplay → result, with instant rematch and menu return
- Five penalty rounds per match, one kick each: a goal scores for the player, a save or miss scores for the AI
- After five rounds the scores are compared: player wins, AI wins, or draw
- Aiming is just two parameters — angle and power; defaults are angle 0, power 50
- Set angle/power → hit Shoot → the probability model resolves goal / save / miss on the spot
- Every miss hands the keeper a point, so the score seesaws and pressure rises with each round
- On the result screen, Play Again resets everything for a rematch; Main Menu exits to the menu
- penalty/physics-engine resolves shots analytically: 0.7 × (1 − |angle|/45 × 0.5) × power/100, clamped to 0.1–0.9
- The shot.take event drives simulateShot, which resolves, scores, and advances the round synchronously, emitting game.over after round 5
- Module split: common-ui for buttons, aim-controller for inputs, physics-engine for resolution, audio-manager as the audio hook
- Presentation left as placeholder: the 3D scene container and scoreboard are static in this build — the rule pipeline comes first
핵심 코드
데모
Hit Start Match, then click Shoot through five penalty rounds: each kick is resolved instantly by the angle/power probability model, every miss scores for the keeper, and the final score decides the match. One-click rematch included.
아티클
Penalty Kick: One Shot, Two Inputs, Five Rounds of Mounting Pressure
One-Sentence Essence
This penalty shootout — a rebuild of Penalty World Cup 2026 — is a minimal viable sample of the shoot-aim archetype: each round the player makes exactly one decision, setting the aim angle and shot power before hitting Shoot, and a probability model resolves the kick as a goal, a save, or a miss on the spot. Five rounds later, the score decides the match. All the tension of a real penalty is compressed into a short loop of one input, one resolution, and a score that seesaws with every kick.
Behavior Loop
- Kick-off: from the main menu, hit Start Match; the score resets to zero, the round counter to 1, and the penalty scene loads.
- Aim: set two parameters — angle (
aimAngle) and power (power); untouched, they default to angle 0 and power 50. - Shoot: the Shoot button fires
shot.take, and the physics module resolves the kick synchronously — wider angles and extreme power both lower the scoring probability. - Score: a goal adds to playerScore; a save or a miss adds to aiScore. Every miss hands the keeper a point, so the score seesaws.
- Full time: after five rounds
game.overfires, the result state compares both scores, and the match is ruled a player win, an AI win, or a draw. - Rematch: Play Again wipes the data and restarts instantly; Main Menu returns to the title.
You can play through this entire loop in the demo at the end of this article.
Gameplay Primitives
Aim(angle, power)– writes the aim parameters (aim.adjust → aim-controller)Shoot()– executes the kick (shot.take → physics-engine.simulateShot)ResolveShot(angle, power)– probabilistic three-way resolution: goal / save / missScoreGoal()/ScoreAgainst()– goal for the player, miss for the AIAdvanceRound()– round +1, triggering full time after round 5JudgeWinner()– compares the scores and writes gameWinner (player / ai / draw)ResetMatch()– rematch: round=1, both scores zeroed, gameWinner=none
Parameter Cheat Sheet
| Parameter | Description | Default | Unit | |-----------|-------------|---------|------| | totalRounds | Kicks per match | 5 | rounds | | aimAngle | Horizontal aim angle (after reset) | 0 | degrees | | power | Shot power (after reset) | 50 | 0–100 | | Speed mapping | 15 + power/100 × 20 | 15–35 | m/s | | Goal width | Regulation goal | 7.32 | m | | Goal height | Regulation goal | 2.44 | m | | Kick distance | Penalty spot to goal line | 11 | m | | Gravity | Ballistic drop | 9.8 | m/s² | | Angle penalty | Probability scaled down linearly by \|angle\|/45 | 45 | degrees (cap) | | Probability clamp | Min/max goal probability | 0.1–0.9 | – | | Power penalty | power<30 decays linearly; >90 multiplies by 0.85 | – | – |
Design Trade-offs
- Analytic resolution over frame-by-frame ballistics: the module actually ships a full stepwise simulation (gravity, ground bounce, post and keeper collision), but the released build resolves shots analytically — the outcome lands in a single frame, frame-rate independent, runnable on any device. The cost: no visible ball flight, which is pure presentation loss.
- Five one-sided rounds instead of ten alternating: the original design had both sides take five kicks in turn; the build folds the AI's turn into an adversarial score — your miss is the keeper's point. Match length halves, and the psychological weight of each kick grows.
- Punishing both ends of the probability curve: chasing the corner with angle or blasting at full power both get taxed by the curve, so the optimum sits at "medium-high power, small angle" — mirroring the real-world wisdom that placement beats force.
- Placeholder presentation, rules first: in this build the 3D scene container and scoreboard are still placeholders (static text, no data binding), and the audio module is a logging stub. The sample's goal is to prove the state machine and resolution pipeline end-to-end; 3D staging and sound are queued for the next iteration.
Implementation Notes
- State machine: four states — boot → menu → gameplay → result — with transitions on boot.complete / game.start / game.over / game.restart / menu.return, declared in the blueprint's stateMachine.
- Event flow:
aim.adjust→ aim-controller writes aimAngle/power; the Shoot button emitsshot.take→ physics-engine.simulateShot resolves, scores, and advances the round synchronously, emittinggame.overafter round 5 plus ashot.resultevent for extensions like audio to subscribe to. - Data model: eight keys carry the whole match — round / totalRounds / playerScore / aiScore / aimAngle / power / shotResult / gameWinner; the result state's onEnter compares the scores with condition nodes and writes gameWinner.
- Module split: common-ui handles button events, penalty/aim-controller owns input parameters, penalty/physics-engine owns resolution and the probability model, and penalty/audio-manager reserves the audio hooks.
- Runtime: v2-kernel drives the LogicGraph; the engine is declared as threejs (the skin layer renders through PIXI, with both runtime libraries loaded), and the whole package exports as a fully static, offline-playable demo.
The value of this build is not in its visuals but in how it compresses the entire aim → resolve → score → full-time rule pipeline into two parameters and five rounds: the rules stay small enough that the cost of every design trade-off remains visible.
함께 볼 만한 글
A timing lab: 20 beats, one shrinking ring, one judgment circle. Drag Perfect Window (40–150ms) and BPM (60–180) sliders to feel where timing feel comes from.
A 5-round penalty duel compressed into an angle-by-power table: three judgement bands — miss zone, risk zone, sweet spot — rule goal, save or miss.
120 seconds, 3 lives, 323 dots: eat and dodge through a block-city maze, where the 8-second power-pellet reversal is the only rhythm switch that matters.