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