Build Flappy Bird with TCJSGame โ Complete Tutorial with Pause Button & Sound
๐ฎ Live Demo: https://tcjsgame.vercel.app/sample/inx.html
The Complete Game Code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, inital-scale=1.0" />
<title>Flappy bird</title>
<style type="text/css">
canvas{
background-color: grey;
width: 100%;
height:100vh;
}
body{
margin:0;
}
button{
position: fixed;
top:0;
right:0;
text-align: center;
width: 1cm;
height: 2cm;
font-size: 30px;
}
</style>
<script src="https://tcjsgame.vercel.app/mat/tcjsgame-v3.js"></script>
</head>
<body>
<script>
if(!localStorage.hs){
localStorage.hs = 0
}
console.log(localStorage.hs)
let display = new Display()
display.start(700, 300)
let hh = new Component(700,300,"hh.png",0,0,"image")
display.add(hh)
display.add(hh, 1)
display.add(hh, 2)
let bird = new Component(30,30,"player.png",10,10,"image")
bird.physics = true
bird.gravity = 0.03
display.add(bird)
bird.bounce = 0
let rotate = 0
let bgMusic = new Sound("/Vicke Blanka - Black Rover (Audio Video (TVใขใใกใใใฉใใฏใฏใญใผใใผใ็ฌฌ3ใฏใผใซ.mp3")
addEventListener("keypress", ()=>{
// bgMusic.play()
})
addEventListener("mousedown",(e)=>{
if (display.scene == 0) {
bird.gravity = -0.1
rotate = -3.14 * 5 / 1800
setTimeout(() => {
bird.gravity = 0.03
rotate = 3.14 * 3 / 360
}, 250)
} else if (display.scene == 1) {
display.scene = 0
bird.y = 0
tt.x = 700
tt2.x = 700 + 233.333333333
tt3.x = 700 + 466.666666667
bb.x = 700
bb2.x = 700 + 233.333333333
bb3.x = 700 + 466.666666667
tt.speedX = -1
tt2.speedX = -1
tt3.speedX = -1
bb.speedX = -1
bb2.speedX = -1
bb3.speedX = -1
num = 0
gj = setInterval(() => {
num++
nhs = Number(localStorage.hs)
if (nhs <= num) {
localStorage.hs = num
}
}, 1000)
}
})
addEventListener("keypress", (e)=>{
if(e.key == " "){
if(display.scene == 0){
bird.gravity = -0.1
rotate = -3.14*5/1800
setTimeout(()=>{
bird.gravity = 0.03
rotate = 3.14*3/360
},250)
}else if(display.scene == 1){
display.scene = 0
bird.y = 0
tt.x = 700
tt2.x = 700+233.333333333
tt3.x = 700+466.666666667
bb.x = 700
bb2.x = 700+233.333333333
bb3.x = 700+466.666666667
tt.speedX = -1
tt2.speedX = -1
tt3.speedX = -1
bb.speedX = -1
bb2.speedX = -1
bb3.speedX = -1
num = 0
gj = setInterval(() => {
num++
nhs = Number(localStorage.hs)
if (nhs <= num) {
localStorage.hs = num
}
}, 1000)
}
}
})
let height = Math.random()*200
let otherHeight = display.canvas.height - height - 75
let tt = new Component(30, height, "pole2.png", 700, 0,"image")
let tt2 = new Component(30, height, "pole2.png", 700+233.333333333, 0,"image")
let tt3 = new Component(30, height, "pole2.png", 700+466.666666667, 0,"image")
let bb = new Component(30, otherHeight, "pole2.png", 700, 300-otherHeight,"image")
let bb2 = new Component(30, otherHeight, "pole2.png", 700+233.333333333, 300-otherHeight,"image")
let bb3 = new Component(30, otherHeight, "pole2.png", 700+466.666666667, 300-otherHeight,"image")
console.log(bb)
tt.speedX = -1
tt2.speedX = -1
tt3.speedX = -1
bb.speedX = -1
bb2.speedX = -1
bb3.speedX = -1
display.add(bb)
display.add(bb2)
display.add(bb3)
display.add(tt)
display.add(tt2)
display.add(tt3)
let gameOver = new Component("25px","impact","red",90,90,"text")
let score = new Component("25px","impact","black",10,30,"text")
let iii;
let pause = new Component("24px","impact","red", 675, 25,"text")
pause.text = "||"
display.add(gameOver, 1)
let cc = [tt,tt2,tt3,bb,bb2,bb3]
setInterval(()=>{
tt.speedX-=0.1
tt2.speedX-=0.1
tt3.speedX-=0.1
bb.speedX-=0.1
bb2.speedX-=0.1
bb3.speedX-=0.1
}, 3000)
let nhs;
let gj;
gj = setInterval(()=>{num++
nhs = Number(localStorage.hs)
if(nhs<=num){
localStorage.hs = num
}
}, 1000)
display.add(score)
display.add(score, 1)
let num = 0
let rr
display.add(pause)
function update(){
score.text = num
gameOver.text=("Game Over! Press space to try again. HighScore :"+localStorage.hs)
bird.angle += rotate
if(bird.angle >= 0){
bird.angle = 0
rotate = 0
} else{
}
if(bird.y <= 0){
bird.y = 0
}
iii=0
cc.forEach((e)=>{
iii+=1
if(bird.crashWith(e)){
display.scene = 1
clearInterval(gj)
}
if(e.x < -30){
e.x = 700- num
if(e.y == 0){
e.height = Math.random()*200
}
if(iii == 4){
e.height = 300-tt.height-75
e.y = 300 - e.height
}
if(iii == 5){
e.height = 300-tt2.height-75
e.y = 300 - e.height
}
if(iii == 6){
e.height = 300-tt3.height-75
e.y = 300 - e.height
}
}
})
bird.hitBottom()
}
let pp = false
function paus(){
console.log("Entered")
if(pp == false){
console.log("Working")
document.getElementById("ppp").innerHTML = "<|"
display.scene = 2
pp=true
clearInterval(gj)
}else{
pp=false
console.log("Perfect")
document.getElementById("ppp").innerHTML = "||"
display.scene = 0
gj = setInterval(() => {
num++
nhs = Number(localStorage.hs)
if (nhs <= num) {
localStorage.hs = num
}
}, 1000)
}
}
</script>
<button id="ppp" onclick="paus()">||</button>
</body>
</html>
Game Assets You Need
| Image File | Description | Preview |
|---|---|---|
hh.png |
Sky/Background (700x300px) | ๐ค๏ธ |
player.png |
Bird character (30x30px) | ๐ฆ |
pole2.png |
Pipe obstacle (30x200+px) | ๐ข |
Step 1: HTML Structure with Pause Button
The HTML now includes a pause button in the top-right corner. The button has position: fixed so it stays visible even when scrolling. The CSS defines width: 1cm, height: 2cm, and font-size: 30px for easy tapping on mobile devices. The button has id="ppp" and calls paus() when clicked. The canvas fills the entire screen with width: 100% and height: 100vh, while the button floats above the game.
button{
position: fixed; /* Stays in place when scrolling */
top: 0; /* Align to top edge */
right: 0; /* Align to right edge */
width: 1cm; /* Square button width */
height: 2cm; /* Tall button height */
font-size: 30px; /* Large pause symbol */
}
Step 2: Three-Scene System with Pause Mode
The game now uses three scenes instead of two. Scene 0 is active gameplay, scene 1 is game over, and scene 2 is pause mode. The background is added to all three scenes with display.add(hh, 1) and display.add(hh, 2). When paused, the game stops updating and scoring pauses. The pause button text changes from "||" (play symbol) to "<|" (pause symbol) when clicked. This creates a professional game feel.
// Background appears in all scenes
display.add(hh) // Scene 0 (gameplay)
display.add(hh, 1) // Scene 1 (game over)
display.add(hh, 2) // Scene 2 (pause menu)
// Pause text component
let pause = new Component("24px","impact","red", 675, 25,"text")
pause.text = "||" // Pause symbol
display.add(pause) // Always visible
Step 3: Sound System Integration
The game now includes audio with let bgMusic = new Sound("/path/to/music.mp3"). The Sound class is built into TCJSGame and creates an HTML5 audio element. The music file path points to "Vicke Blanka - Black Rover" anime opening theme. A keypress event listener is set up but commented out with // bgMusic.play() so you can enable it when ready. Sound files must be in the correct format (MP3 works across all modern browsers). You can also add jump sound effects by uncommenting and modifying the code.
// Create sound object
let bgMusic = new Sound("music.mp3")
// Play when game starts (uncomment to enable)
addEventListener("keypress", ()=>{
bgMusic.play() // Plays background music
})
// Add jump sound effect
let jumpSound = new Sound("jump.wav")
// Call jumpSound.play() in jump code
Step 4: Dual Input Methods (Mouse + Keyboard)
Your game now supports both mouse clicks and keyboard spacebar. The mousedown event triggers jumps and restarts, making the game playable on mobile devices where keyboards aren't available. Two separate event listeners handle input: one for mouse/touch and one for keyboard. Both check display.scene to determine if the game is playing (scene 0) or game over (scene 1). The jump physics apply negative gravity -0.1 and rotation calculations using 3.14 * 5 / 1800 (approximately 0.0087 radians).
// Mouse/touch input (mobile friendly)
addEventListener("mousedown", (e)=>{
if (display.scene == 0) {
bird.gravity = -0.1 // Jump up
rotate = -3.14 * 5 / 1800 // Rotate upward
}
})
// Keyboard input (desktop)
addEventListener("keypress", (e)=>{
if(e.key == " " && display.scene == 0){
bird.gravity = -0.1 // Same jump mechanics
}
})
Step 5: Pause Function Logic
The paus() function toggles between paused and playing states. A boolean variable pp tracks the pause state (false = playing, true = paused). When paused (pp == false), the button text changes to "<|", display.scene = 2 switches to pause scene, and clearInterval(gj) stops the scoring timer. When unpaused, the button changes back to "||", display.scene = 0 resumes gameplay, and a new interval restarts scoring. This creates a complete pause system.
let pp = false // Global pause state
function paus(){
if(pp == false){
// Pause the game
document.getElementById("ppp").innerHTML = "<|"
display.scene = 2 // Enter pause scene
pp = true
clearInterval(gj) // Stop scoring
} else {
// Resume the game
document.getElementById("ppp").innerHTML = "||"
display.scene = 0 // Return to gameplay
pp = false
// Restart scoring interval
gj = setInterval(() => {
num++
if(num > localStorage.hs){
localStorage.hs = num
}
}, 1000)
}
}
Step 6: Restart Logic in Both Input Methods
Both mouse and keyboard listeners include restart logic when display.scene == 1 (game over). The restart sequence: sets display.scene = 0, resets bird.y = 0, and repositions all six pipes to their starting X positions (700, 933, 1166 pixels). Pipe speeds reset to -1, score num = 0, and a new scoring interval starts. The clearInterval(gj) in the collision detection stops the old timer before restart creates a new one, preventing multiple intervals running simultaneously.
// Restart code (same in both input handlers)
else if(display.scene == 1){
display.scene = 0
bird.y = 0
tt.x = 700 // Reset first pipe
tt2.x = 700 + 233 // Reset second pipe
tt3.x = 700 + 466 // Reset third pipe
bb.x = 700 // Reset bottom pipes
bb2.x = 700 + 233
bb3.x = 700 + 466
tt.speedX = -1 // Reset speed
num = 0 // Reset score
}
Step 7: Pause Text Component
A text component named pause displays the pause symbol "||" at position (675, 25) - top right corner near the button. It uses 24px impact font in red color. Unlike game objects, this component is always visible because it's never removed from the display. The actual button is an HTML <button> element that overlays the canvas, while the text component is drawn by the TCJSGame engine. This dual approach ensures the pause indicator is visible even if the button has styling issues.
let pause = new Component("24px","impact","red", 675, 25,"text")
pause.text = "||" // Pause symbol
display.add(pause) // Add to default scene
// In paus() function, button text changes
document.getElementById("ppp").innerHTML = "<|" // Play symbol when paused
Step 8: Background in All Three Scenes
The background component hh is added to scenes 0, 1, and 2 using display.add(hh), display.add(hh, 1), and display.add(hh, 2). This ensures that regardless of the game state (playing, game over, or paused), players always see the same background image. Without this, switching to pause scene (2) would show a blank canvas. This creates visual consistency and professional polish. The background is 700x300 pixels matching the canvas size.
// Background appears in ALL scenes
display.add(hh) // Scene 0 - Playing
display.add(hh, 1) // Scene 1 - Game Over
display.add(hh, 2) // Scene 2 - Paused
// Without scene 2 background, pause screen would be empty!
Step 9: Collision Detection During Gameplay
The update() function runs 50 times per second. It checks collisions between the bird and all six pipes using bird.crashWith(e). When a collision occurs, display.scene = 1 switches to game over, and clearInterval(gj) stops the scoring timer to prevent score increases after death. The ground collision uses bird.hitBottom() which stops the bird at the canvas bottom. The ceiling boundary if(bird.y <= 0){ bird.y = 0 } prevents flying above the top edge. This comprehensive collision system makes the game fair and responsive.
function update(){
iii=0
cc.forEach((e)=>{
iii+=1
// Collision detection
if(bird.crashWith(e)){
display.scene = 1 // Game over
clearInterval(gj) // Stop scoring
}
// Pipe repositioning (off-screen)
if(e.x < -30){
e.x = 700 - num
// Random height for top pipes
if(e.y == 0){
e.height = Math.random() * 200
}
}
})
bird.hitBottom() // Check ground collision
}
Step 10: Score Interval Management
The scoring system uses setInterval() that increments num every 1000 milliseconds (1 second). When the game pauses, clearInterval(gj) stops this timer. When unpausing, a new interval is created. This prevents score accumulation while paused. The high score check if(nhs <= num) updates localStorage only when the current score exceeds the saved high score. The game over text dynamically shows the high score using localStorage.hs. Two interval variables exist: gj for scoring and another for pipe speed increases every 3 seconds.
// Scoring interval
gj = setInterval(() => {
num++ // Increase score
nhs = Number(localStorage.hs) // Get high score
if (nhs <= num) { // Check if beat record
localStorage.hs = num // Save new record
}
}, 1000)
// Difficulty interval (speeds up pipes)
setInterval(()=>{
tt.speedX -= 0.1 // Pipes scroll faster
tt2.speedX -= 0.1
tt3.speedX -= 0.1
bb.speedX -= 0.1
bb2.speedX -= 0.1
bb3.speedX -= 0.1
}, 3000)
New Features Summary
| Feature | How It Works |
|---|---|
| Pause Button | Fixed position button toggles scene 2 |
| Three Scenes | 0=Game, 1=Game Over, 2=Paused |
| Sound Support | Built-in Sound class for MP3 files |
| Mouse Input | Click/tap works for mobile devices |
| Dual Controls | Both mouse and keyboard supported |
Customization Examples
// Change button position
button{
position: fixed;
bottom: 20px; /* Move to bottom */
left: 20px; /* Move to left */
}
// Add sound effects to jump
if(display.scene == 0){
let jumpSound = new Sound("jump.wav")
jumpSound.play()
bird.gravity = -0.1
}
// Add resume text in pause scene
let resumeText = new Component("20px","Arial","white",300,150,"text")
resumeText.text = "PAUSED - Click || to resume"
display.add(resumeText, 2) // Only in pause scene
Troubleshooting
| Issue | Solution |
|---|---|
| Sound won't play | Browser may block autoplay - add user interaction first |
| Pause button not working | Check button ID matches getElementById("ppp")
|
| Scene 2 shows nothing | Add background components to scene 2 |
| Score increments while paused | Ensure clearInterval(gj) is called in pause function |
Conclusion
You've built an enhanced Flappy Bird game with pause functionality, mouse/touch support, and sound integration! The three-scene system (0=gameplay, 1=game over, 2=paused) creates a professional game feel. The pause button toggles between states and pauses scoring. Dual input methods (mouse + keyboard) make the game playable on both desktop and mobile devices. Use the sound system to add background music and sound effects. Experiment with different button positions, add more scenes for menus, or create custom pause graphics. The complete code runs in any modern browser with full mobile support!
Built with TCJSGame โ Simple JavaScript Game Development
Top comments (0)