first commit
This commit is contained in:
commit
e5f9c2ea99
9 changed files with 259 additions and 0 deletions
30
bird.js
Normal file
30
bird.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
const birdElem = document.querySelector( "[data-bird]" )
|
||||
const BIRD_SPEED = 0.5
|
||||
const JUMP_DURATION = 150
|
||||
|
||||
let timeSinceLastJump = Number.POSITIVE_INFINITY
|
||||
|
||||
const setTop = ( top ) => birdElem.style.setProperty( "--bird-top", top )
|
||||
|
||||
const getTop = () => parseFloat( getComputedStyle( birdElem ).getPropertyValue( "--bird-top" ) )
|
||||
|
||||
const handleJump = ( event ) => {
|
||||
if ( event.code === "Space" ) timeSinceLastJump = 0
|
||||
}
|
||||
|
||||
export const setupBird = () => {
|
||||
setTop( window.innerHeight / 2 )
|
||||
document.removeEventListener( "keydown", handleJump )
|
||||
document.addEventListener( "keydown", handleJump )
|
||||
}
|
||||
|
||||
export const updateBird = ( delta ) => {
|
||||
if ( timeSinceLastJump < JUMP_DURATION )
|
||||
setTop( getTop() - BIRD_SPEED * delta )
|
||||
else
|
||||
setTop( getTop() + BIRD_SPEED * delta )
|
||||
|
||||
timeSinceLastJump += delta
|
||||
}
|
||||
|
||||
export const getBirdRect = () => birdElem.getBoundingClientRect()
|
BIN
images/background.png
Normal file
BIN
images/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
BIN
images/bird.png
Normal file
BIN
images/bird.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
BIN
images/bottomPipe.png
Normal file
BIN
images/bottomPipe.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7 KiB |
BIN
images/topPipe.png
Normal file
BIN
images/topPipe.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
21
index.html
Normal file
21
index.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="script.js" type="module"></script>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 class="title" data-title>
|
||||
Press Any Key To Start
|
||||
<small data-subtitle class="subtitle hide"></small>
|
||||
</h1>
|
||||
<img data-bird src="images/bird.png" class="bird"></img>
|
||||
</body>
|
||||
|
||||
</html>
|
81
pipe.js
Normal file
81
pipe.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
const HOLE_HEIGHT = 200
|
||||
const PIPE_WIDTH = 138
|
||||
const PIPE_INTERVAL = 1500
|
||||
const PIPE_SPEED = 0.5
|
||||
let pipes = []
|
||||
let timeSinceLastPipe
|
||||
let passedPipeCount
|
||||
|
||||
const createPipe = () => {
|
||||
const pipeElem = document.createElement( "div" )
|
||||
const topElem = createPipeSegment( "top" )
|
||||
const bottomElem = createPipeSegment( "bottom" )
|
||||
pipeElem.append( topElem )
|
||||
pipeElem.append( bottomElem )
|
||||
pipeElem.classList.add( "pipe" )
|
||||
pipeElem.style.setProperty(
|
||||
"--hole-top",
|
||||
randomNumberBetween(
|
||||
HOLE_HEIGHT * 1.5,
|
||||
window.innerHeight - HOLE_HEIGHT * 0.5
|
||||
)
|
||||
)
|
||||
const pipe = {
|
||||
get left () {
|
||||
return parseFloat(
|
||||
getComputedStyle( pipeElem ).getPropertyValue( "--pipe-left" )
|
||||
)
|
||||
},
|
||||
set left ( value ) {
|
||||
pipeElem.style.setProperty( "--pipe-left", value )
|
||||
},
|
||||
remove: () => {
|
||||
pipes = pipes.filter( p => p !== pipe )
|
||||
pipeElem.remove()
|
||||
},
|
||||
rects: () => [
|
||||
topElem.getBoundingClientRect(),
|
||||
bottomElem.getBoundingClientRect(),
|
||||
]
|
||||
}
|
||||
pipe.left = window.innerWidth
|
||||
document.body.append( pipeElem )
|
||||
pipes.push( pipe )
|
||||
}
|
||||
|
||||
const createPipeSegment = ( position ) => {
|
||||
const segment = document.createElement( "div" )
|
||||
segment.classList.add( "segment", position )
|
||||
return segment
|
||||
}
|
||||
|
||||
const randomNumberBetween = ( min, max ) => Math.floor( Math.random() * ( max - min + 1 ) + min )
|
||||
|
||||
export const setupPipes = () => {
|
||||
document.documentElement.style.setProperty( "--pipe-width", PIPE_WIDTH )
|
||||
document.documentElement.style.setProperty( "--hole-height", HOLE_HEIGHT )
|
||||
pipes.forEach( pipe => pipe.remove() )
|
||||
timeSinceLastPipe = PIPE_INTERVAL
|
||||
passedPipeCount = 0
|
||||
}
|
||||
|
||||
export const updatePipes = ( delta ) => {
|
||||
timeSinceLastPipe += delta
|
||||
|
||||
if ( timeSinceLastPipe > PIPE_INTERVAL ) {
|
||||
timeSinceLastPipe -= PIPE_INTERVAL
|
||||
createPipe()
|
||||
}
|
||||
|
||||
pipes.forEach( pipe => {
|
||||
if ( pipe.left + PIPE_WIDTH < 0 ) {
|
||||
passedPipeCount++
|
||||
return pipe.remove()
|
||||
}
|
||||
pipe.left = pipe.left - delta * PIPE_SPEED
|
||||
} )
|
||||
}
|
||||
|
||||
export const getPassedPipesCount = () => passedPipeCount
|
||||
|
||||
export const getPipeRects = () => pipes.flatMap( pipe => pipe.rects() )
|
60
script.js
Normal file
60
script.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { updateBird, setupBird, getBirdRect } from "./bird.js"
|
||||
import {
|
||||
updatePipes,
|
||||
setupPipes,
|
||||
getPassedPipesCount,
|
||||
getPipeRects,
|
||||
} from "./pipe.js"
|
||||
|
||||
const title = document.querySelector( "[data-title]" )
|
||||
const subtitle = document.querySelector( "[data-subtitle]" )
|
||||
|
||||
let lastTime
|
||||
const updateLoop = ( time ) => {
|
||||
if ( lastTime == null ) {
|
||||
lastTime = time
|
||||
window.requestAnimationFrame( updateLoop )
|
||||
return
|
||||
}
|
||||
const delta = time - lastTime
|
||||
updateBird( delta )
|
||||
updatePipes( delta )
|
||||
if ( checkLose() ) return handleLose()
|
||||
lastTime = time
|
||||
window.requestAnimationFrame( updateLoop )
|
||||
}
|
||||
|
||||
const checkLose = () => {
|
||||
const birdRect = getBirdRect()
|
||||
const insidePipe = getPipeRects().some( rect => isCollision( birdRect, rect ) )
|
||||
const outsideWorld = birdRect.top < 0 || birdRect.bottom > window.innerHeight
|
||||
return outsideWorld || insidePipe
|
||||
}
|
||||
|
||||
const isCollision = ( rect1, rect2 ) => {
|
||||
return (
|
||||
rect1.left < rect2.right &&
|
||||
rect1.top < rect2.bottom &&
|
||||
rect1.right > rect2.left &&
|
||||
rect1.bottom > rect2.top
|
||||
)
|
||||
}
|
||||
|
||||
const handleStart = () => {
|
||||
title.classList.add( "hide" )
|
||||
setupBird()
|
||||
setupPipes()
|
||||
lastTime = null
|
||||
window.requestAnimationFrame( updateLoop )
|
||||
}
|
||||
|
||||
const handleLose = () => setTimeout( () => {
|
||||
title.classList.remove( "hide" )
|
||||
subtitle.classList.remove( "hide" )
|
||||
console.log( !( !document.cookie ) )
|
||||
const highscore = 0
|
||||
subtitle.innerHTML = `Score: ${getPassedPipesCount()}<br />Highscore: ${highscore}`
|
||||
document.addEventListener( "keypress", handleStart, { once: true } )
|
||||
}, 100 )
|
||||
|
||||
document.addEventListener( "keypress", handleStart, { once: true } )
|
67
styles.css
Normal file
67
styles.css
Normal file
|
@ -0,0 +1,67 @@
|
|||
*, *::after, *::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background-image: url(images/background.png);
|
||||
background-size: cover;
|
||||
font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
inset: 0;
|
||||
margin: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bird {
|
||||
--bird-top: -1000;
|
||||
--bird-size: 60px;
|
||||
position: absolute;
|
||||
width: var(--bird-size);
|
||||
left: var(--bird-size);
|
||||
top: calc(var(--bird-top) * 1px);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.pipe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: calc(var(--pipe-width) * 1px);
|
||||
left: calc(var(--pipe-left) * 1px);
|
||||
}
|
||||
|
||||
.pipe > .segment {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
background-size: 100% 600px;
|
||||
}
|
||||
|
||||
.pipe > .top {
|
||||
top: 0;
|
||||
bottom: calc(var(--hole-top) * 1px);
|
||||
background-image: url(images/topPipe.png);
|
||||
background-position: bottom;
|
||||
}
|
||||
|
||||
.pipe > .bottom {
|
||||
bottom: 0;
|
||||
top: calc(100vh - (var(--hole-top) * 1px) + calc(var(--hole-height) * 1px));
|
||||
background-image: url(images/bottomPipe.png);
|
||||
}
|
Reference in a new issue