<!doctype html>
<html lang="en">
<head>
<title>OinkPop</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="intro">
<h1>tap to gobble</h1>
</div>
<div class="play">
<div class="score">
0 yums<br>0 seconds
</div>
<div class="controls">
<button class="up"></button>
<button class="right"></button>
<button class="down"></button>
<button class="left"></button>
</div>
<div class="oink"></div>
</div>
<div class="outro">
<h1>you gobbled 0 yums in 0 seconds</h1>
</div>
</body>
</html>
<style>
@font-face {
font-family: PixelRPG;
src: url('data:font/sfnt;base64,AAEAAAAOAIAAAwB...');
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
font-family: PixelRPG;
background-color: #9ae6b4;
cursor: default;
}
/*...*/
</style>
#!/bin/sh
if [ -z "$1" ]; then
echo "usage: data-url file" >&2
exit 1
fi
mimetype=$(file -bN --mime-type "$1")
content=$(base64 < "$1")
echo "data:$mimetype;base64,$content"
# -> saved in /usr/local/bin/data-uri
var music = "data:audio/mpeg;base64,SUQzBAAAAAAA..."
var biteSounds = [
"data:audio/mpeg;base64,SUQzBAAAAAAA...",
"data:audio/mpeg;base64,SUQzBAAAAAAA...",
"data:audio/mpeg;base64,SUQzBAAAAAAA...",
"data:audio/mpeg;base64,SUQzBAAAAAAA...",
]
var popSound = "data:audio/mpeg;base64,SUQzBAAAAAAAI..."
var arrowFrames = [
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
]
// ...
var shouldGoUp = false
var shouldGoRight = false
var shouldGoLeft = false
var shouldGoDown = false
var facingScale = 1
document.addEventListener("keydown", function(event) {
if (event.key === "ArrowUp") {
shouldGoUp = true
}
if (event.key === "ArrowRight") {
shouldGoRight = true
if (!isEating) {
facingScale = -1
}
}
if (event.key === "ArrowLeft") {
shouldGoLeft = true
if (!isEating) {
facingScale = 1
}
}
// ...
})
document.addEventListener("mousedown", function(event) {
if (event.target.matches(".controls .up")) {
shouldGoUp = true
}
if (event.target.matches(".controls .right")) {
shouldGoRight = true
if (!isEating) {
facingScale = -1
}
}
if (event.target.matches(".controls .down")) {
shouldGoDown = true
}
if (event.target.matches(".controls .left")) {
shouldGoLeft = true
if (!isEating) {
facingScale = 1
}
}
if (event.target.matches(".intro")) {
startPlaying()
}
})
var isPlaying = false
var intro = document.querySelector(".intro")
var play = document.querySelector(".play")
var startPlaying = function() {
if (isPlaying) {
return
}
isPlaying = true
intro.style.display = "none"
play.style.display = "flex"
reset()
animate()
playMusic()
setTimeout(spawnYum, yumBreather)
secondsTimer = setInterval(function() {
secondsPlayed += 1
}, 1000)
}
var yum = undefined
var yumTimeout = undefined
var yumExpiry = 5000
var yumBreather = 1500
var spawnYum = function() {
yum = document.createElement('div')
yum.className = 'yum'
yum.style.backgroundImage = `url('${yumFrame}')`
yum.style.top = `${Math.round(play.offsetHeight * Math.random())}px`
yum.style.left = `${Math.round(play.offsetWidth * Math.random())}px`
play.appendChild(yum)
yumTimeout = setTimeout(function() {
play.removeChild(yum)
yum = undefined
setTimeout(function() {
spawnYum()
}, yumBreather)
}, yumExpiry)
}
var animationFrame = undefined
var animate = function(timestamp) {
animateOinkRunning(timestamp)
animateOinkMoving(timestamp)
animateOinkWrapping(timestamp)
animateOinkFacing(timestamp)
animateControls(timestamp)
var shouldStopGame = animateCollisions(timestamp)
animateScore(timestamp)
if (!shouldStopGame) {
animationFrame = requestAnimationFrame(animate)
}
}
var previousTimestamp = undefined
var previousFrame = 0
var animateOinkRunning = function(timestamp) {
if (isEating) {
return
}
if (shouldGoUp || shouldGoRight || shouldGoDown || shouldGoLeft) {
if (previousTimestamp === undefined) {
previousTimestamp = timestamp
}
var elapsed = timestamp - previousTimestamp
if (elapsed >= 120) {
previousTimestamp = undefined
var nextFrame = previousFrame + 1
if (nextFrame > 2) {
nextFrame = 0
}
oink.style.backgroundImage = `url('${oinkFrames[nextFrame]}')`
previousFrame = nextFrame
}
} else {
previousFrame = 0
oink.style.backgroundImage = `url('${oinkFrames[0]}')`
}
}
var speed = 5
var oinkScale = 1
var oink = document.querySelector(".oink")
var animateOinkMoving = function(timestamp) {
if (isEating) {
return
}
if (shouldGoUp) {
oink.style.top = `${oink.offsetTop - speed}px`
}
if (shouldGoRight) {
oink.style.left = `${oink.offsetLeft + speed}px`
}
if (shouldGoDown) {
oink.style.top = `${oink.offsetTop + speed}px`
}
if (shouldGoLeft) {
oink.style.left = `${oink.offsetLeft - speed}px`
}
}
var animateOinkFacing = function(timestamp) {
oink.style.transform = `scaleY(${oinkScale}) scaleX(${facingScale * oinkScale})`;
}
var controls = document.querySelector(".controls")
var animateControls = function(timestamp) {
controls.style.backgroundImage = `url('${arrowFrames[0]}')`
if (shouldGoUp) {
controls.style.backgroundImage = `url('${arrowFrames[1]}')`
}
if (shouldGoRight) {
controls.style.backgroundImage = `url('${arrowFrames[2]}')`
}
if (shouldGoDown) {
controls.style.backgroundImage = `url('${arrowFrames[3]}')`
}
if (shouldGoLeft) {
controls.style.backgroundImage = `url('${arrowFrames[4]}')`
}
}
var yumsEaten = 0
var secondsPlayed = 0
var animateScore = function(timestamp) {
score1.innerHTML = `${yumsEaten} yums<br>${secondsPlayed} seconds`
score2.innerHTML = `you gobbled ${yumsEaten} yums in ${secondsPlayed} seconds`
}
var oinkWidth = oink.offsetWidth
var oinkHeight = oink.offsetHeight
var animateOinkWrapping = function(timestamp) {
if (oink.offsetTop < 0) {
oink.style.top = `${play.offsetHeight - oinkHeight}px`
}
if (oink.offsetLeft + oinkWidth > play.offsetWidth) {
oink.style.left = 0
}
if (oink.offsetTop + oinkHeight > play.offsetHeight) {
oink.style.top = 0
}
if (oink.offsetLeft < 0) {
oink.style.left = `${play.offsetWidth - oinkWidth}px`
}
}
var yumsEatenLimit = 10
var animateCollisions = function(timestamp) {
if (!yum) {
return
}
var oinkBox = oink.getBoundingClientRect()
var yumBox = yum.getBoundingClientRect()
if (
oinkBox.left < yumBox.right &&
oinkBox.right > yumBox.left &&
oinkBox.top < yumBox.bottom &&
oinkBox.bottom > yumBox.top
) {
// ...oink collides with yum! (next slide)
}
}
yumsEaten += 1
playBiteSound()
oinkScale *= 1.1
isEating = true
oink.style.backgroundImage = `url('${oinkFrames[3]}')`
if (yumsEaten === yumsEatenLimit) {
// ...stop the game! (next slide)
}
removeYum()
setTimeout(function() {
isEating = false
}, yumPause)
return false
cancelAnimationFrame(animationFrame)
removeYum(true)
setTimeout(function() {
oink.style.display = "none"
stopMusic()
playPopSound()
setTimeout(function() {
play.style.display = "none"
outro.style.display = "flex"
isPlaying = false
setTimeout(function() {
outro.style.display = "none"
intro.style.display = "flex"
}, 5000)
}, 2500)
}, 1500)
clearTimeout(secondsTimer)
return true
var musicPlayer = undefined
var biteSoundPlayer = undefined
var playMusic = function() {
if (!musicPlayer) {
musicPlayer = new Audio()
musicPlayer.addEventListener("ended", function() {
this.currentTime = 0
this.play()
})
}
musicPlayer.src = music
musicPlayer.play()
}
var stopMusic = function() {
musicPlayer.pause()
}
var playBiteSound = function() {
biteSounds = shuffle(biteSounds)
if (!biteSoundPlayer) {
biteSoundPlayer = new Audio()
}
biteSoundPlayer.src = biteSounds[0]
biteSoundPlayer.play()
}
Particles
Collision detection
Animation
Cross-platform
Loads more...
Heartbeast
GDQuest
Game Development Center
Game Endeavor
Use Pixaki for pixel art on mobile
kenney.nl
onemansymphony.com
opengameart.org
unsplash.com
undraw.co
notion.so
excalidraw.com
OinkPop (made in Godot)
published to iOS
OinkPop (made in Scratch)
published to iOS using Capacitor
sheeptester.github.io/htmlifier
Snake Catcher (made in Godot)
published to iOS and Android
Slither Deep (made in Godot)
published to iOS, Android, Windows, Linux, macOS
Sprout (making in Godot)
OinkPop (making in JS)
publishing to iOS using Capacitor