Hello.

Making a game

in 3 engines

There are many ways to get into programming.  Embrace the differences and teach those around you.

OinkPop in JavaScript

<!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()
}

Use requestAnimationFrame for animation things

 

Use setTimeout for time things

You don't need fancy libraries

(but they can help)

github.com/gamemakeracademy/

oinkpop-js

OinkPop in Scratch

Completely visual,

no syntax to learn

github.com/gamemakeracademy/

oinkpop-scratch

OinkPop in Godot

Scenes + Scripts

Mechanics are very similar to JS version

Abstractions for:

Particles

Collision detection

Animation

Cross-platform

Loads more...

YouTube teachers:

Heartbeast

GDQuest

Game Development Center

Game Endeavor

github.com/gamemakeracademy/

oinkpop-godot

Some Tools + Resources

Use Aseprite for pixel art

Use Pixaki for pixel art on mobile

MortMort on YouTube

Use Chiptone for sound effects

Making music feels harder for me than these two things

Free resources:

 

kenney.nl

onemansymphony.com

opengameart.org

unsplash.com

undraw.co

 

notion.so

excalidraw.com

Publish your games

Godot exports to iOS, Android, Windows, Linux, macOS, Web

Scratch and JS export to web

Use Capacitor to wrap web games for iOS, Android, Electron

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

Making A Game In 3 Engines (Feb 2021)

By Christopher Pitt

Making A Game In 3 Engines (Feb 2021)

  • 1,108