
「Scratchは作れた。次はコードでゲームを作ってみたい」
そんな小学校3年生以上の子どもたちと保護者の方へ。この記事では、PICO-8(ピコエイト)でFlappy Bird風ゲームを作る方法を、完成まで順番に解説します。
「コードってむずかしそう」と感じるかもしれません。大丈夫です。この記事は、ただ手順を並べるだけではなく、このコードが何をしているのかと、うまく動かないときにどこを見るかをセットで説明します。
ゴールは2つです。
- 1本のゲームを最後まで完成させる
- コードで動かす楽しさを体験する
はじめに:PICO-8とLuaを3分で理解
PICO-8には、通常版と教育向けに使いやすい版があります。最初の一歩として、公式情報はここを見ておくと安心です。
- PICO-8(通常版):PICO-8公式ページ
- PICO-8教育版(ブラウザで体験しやすい):PICO-8 Education Edition
PICO-8は「コード」「スプライト(絵)」「効果音」「BGM」を1つの画面で作れる小さなゲーム開発環境です。子ども向けでも中身は本格的で、ゲーム開発の流れを最短で体験できます。
そして、PICO-8で使う言語がLua(ルア)です。Luaは文法が比較的シンプルで、初学者でも読みやすい言語です。ゲーム業界でもよく使われ、ロブロックス(Roblox)開発でもLuaが使われています。言語の公式情報はThe Programming Language Luaで確認できます。
関連記事として、Luaの学習イメージを深めたい方はRoblox Studioで学ぶLuaプログラミング入門を、学習の全体像を先に見たい方はプログラミングスクール詳細やScratch(スクラッチ)プログラミング教室もあわせて読むと理解が早くなります。

PICO-8ってどんなツール?
PICO-8は、レトロな見た目の小さなゲーム開発環境です。Lua(ルア)という言語を使って、キャラクターの動き、当たり判定、スコア表示まで全部コードで作れます。
Scratchが「ブロックで考える」学習なら、PICO-8は「同じ考えを文字で書く」学習です。ここを越えると、ほかの言語にも進みやすくなります。
なぜ最初にFlappy Birdを作るの?
Flappy Bird風ゲームには、ゲーム開発の基本がまとまって入っています。
- 重力(何もしないと落ちる)
- 入力(押したら跳ぶ)
- 障害物生成(定期的に出す)
- 衝突判定(当たったら終わり)
- スコア管理(通過したら加点)
この5つは、横スクロール、アクション、シューティングでもそのまま使います。エンジニア歴30年の現場感覚でも、最初の一本に最適です。
先に準備するもの
- PICO-8を起動
- 新規カートリッジを作成(
new) - まずは見た目より動作優先で進める
ここでのコツは「一気に書かない」ことです。 ステップ1を書いたら実行、ステップ2を書いたら実行、というふうに、少しずつ進めるとミスを早く見つけられます。
先に設計図を持とう
今回のコードは4つの部品でできています。
- 鳥の物理(落下+ジャンプ)
- 土管の生成と移動
- 当たり判定(ゲームオーバー)
- スコア表示
迷ったら「いまどの部品を作っているか」に戻るのがコツです。
コード手順(少しずつ積み上げる)
ここからは、一気に完成コードを書きません。 **1ステップごとに「入力 → 実行 → 確認」**で進みます。
ステップ0:先にスプライトを作る(絵を準備)
コードを書く前に、鳥の見た目を先に作っておくと作業が分かりやすくなります。

ESCでエディター一覧に切り替えて、SPRITEタブを開く- 左上の最初のマス(スプライト0番)に、8×8の鳥ドットを描く
- まずは単色でもOK。目とくちばしだけでも十分
ESCでコード画面に戻る
最初は丸で表示しても問題ありませんが、見た目があると子どもは一気に楽しくなります。 このあと「丸表示(circfill)」と「スプライト表示(spr)」のどちらでも進められます。
もし迷ったら(スプライト作成)
ESCを押しても切り替わらない → PICO-8のウィンドウがアクティブか確認して、もう一度ESCを押す- 色が塗れない → 画面下のパレットで色を選んでから描く
- 絵が小さすぎて難しい → まずは「四角 + 目」だけでOK。あとで直せます
ステップ1:まずはステージ(背景)を表示する

最初は鳥も土管も出さず、画面が描けるかだけ確認します(ESCで画面を切り替えてコマンドを入力できる画面に移動します。そしてRUNと打ち込み、ENTERを押してください)。

function _update()
end
function _draw()
cls(12)
print("flappy bird start",28,60,7)
end

確認ポイント:
- 画面が水色(
cls(12))になる - 文字が表示される
このコードは何をしている?
_update():今は空。毎フレームの計算場所_draw():毎フレーム画面を描く場所
ステップ2:鳥を表示する
次は、鳥のデータを作って画面に出します。
bird={x=32,y=64,r=3}
function _update()
end
function _draw()
cls(12)
circfill(bird.x,bird.y,bird.r,10)
end
確認ポイント:
- 画面左側に黄色い丸(鳥)が出る
このコードは何をしている?
bird.xとbird.y:鳥の場所bird.r:鳥の大きさcircfill(...):円を描く
ステップ3:鳥を落とす(重力)
今度は vy(たて方向の速さ)を追加します。
bird={x=32,y=64,vy=0,r=3}
function _update()
bird.vy+=0.25
bird.y+=bird.vy
end
function _draw()
cls(12)
circfill(bird.x,bird.y,bird.r,10)
end
確認ポイント:
- 鳥がだんだん速く下へ落ちる
このコードは何をしている?
bird.vy+=0.25:毎フレーム、下向きの速さを足す(重力)bird.y+=bird.vy:その速さで実際に動かす
ステップ4:鳥をジャンプさせる
ボタン(XかO)を押したら、上向きの速さに切り替えます。
function _update()
bird.vy+=0.25
if btnp(4) or btnp(5) then
bird.vy=-2.6
end
bird.y+=bird.vy
end
確認ポイント:
- 何もしないと落ちる
- X/Oを押すと上に跳ぶ
このコードは何をしている?
btnp()は「押した瞬間」だけ反応bird.vy=-2.6で上向き速度をセット
ステップ5:土管を1本だけ表示して動かす
まずはクローンなしで、土管1本を動かします。
pipe={x=128,w=8,gap_y=55,gap_h=24}
function _update()
bird.vy+=0.25
if btnp(4) or btnp(5) then bird.vy=-2.6 end
bird.y+=bird.vy
pipe.x-=1
if pipe.x<-10 then
pipe.x=128
end
end
function _draw()
cls(12)
circfill(bird.x,bird.y,bird.r,10)
rectfill(pipe.x,0,pipe.x+pipe.w,pipe.gap_y,3)
rectfill(pipe.x,pipe.gap_y+pipe.gap_h,pipe.x+pipe.w,127,3)
end
確認ポイント:
- 土管が右から左へ動く
- 画面外に出たら右から戻る
ステップ6:土管をクローン方式(複数)にする
ここで pipe 1本方式から、pipes={} の複数方式に切り替えます。 (この時点で pipe=... のコードは削除してください)
pipes={}
frame=0
function _update()
frame+=1
-- 鳥
bird.vy+=0.25
if btnp(4) or btnp(5) then bird.vy=-2.6 end
bird.y+=bird.vy
-- 生成
if frame%45==0 then
local gap_y=flr(rnd(50))+35
add(pipes,{x=128,gap_y=gap_y,gap_h=24,passed=false})
end
-- 移動と削除
for p in all(pipes) do
p.x-=1
if p.x<-10 then del(pipes,p) end
end
end
function _draw()
cls(12)
circfill(bird.x,bird.y,bird.r,10)
for p in all(pipes) do
rectfill(p.x,0,p.x+8,p.gap_y,3)
rectfill(p.x,p.gap_y+p.gap_h,p.x+8,127,3)
end
end
確認ポイント:
- 土管が時間差で増える
- 古い土管は左で消える
このコードは何をしている?
frame%45==0:45フレームごとに新しい土管を追加add():配列に追加all():配列の全要素を順番に処理del():画面外の土管を消す
ステップ7:当たり判定を入れる(ゲームオーバー)
gameover=false
function _update()
if gameover then
if btnp(4) or btnp(5) then
_init()
end
return
end
-- (ここに鳥と土管の更新処理)
-- 上下画面外
if bird.y<0 or bird.y>127 then
gameover=true
end
-- 土管当たり
for p in all(pipes) do
local hit_x = bird.x+bird.r>p.x and bird.x-bird.r<p.x+8
local hit_top = bird.y-bird.r<p.gap_y
local hit_bottom = bird.y+bird.r>p.gap_y+p.gap_h
if hit_x and (hit_top or hit_bottom) then
gameover=true
end
end
end
確認ポイント:
- 土管や上下端に当たると止まる
- X/Oでリスタートできる
ステップ8:スコアを入れる(完成)
score=0
function _update()
-- (既存処理)
for p in all(pipes) do
p.x-=1
if not p.passed and p.x+8<bird.x then
p.passed=true
score+=1
end
if p.x<-10 then del(pipes,p) end
end
end
function _draw()
-- (既存描画)
print("score:"..score,2,2,7)
if gameover then
rectfill(20,52,108,76,0)
print("game over",44,58,8)
print("press x/o",42,66,7)
end
end
確認ポイント:
- 土管を越えた瞬間だけスコア+1
- 同じ土管で2回以上加点されない
コピペで動く完成形コード(このまま全部貼り付け)
new で空のコードにしてから、下のコードをまるごと置き換えてください。
貼り付け後に Ctrl+R(または run)で、そのまま動きます。
※ SFX 0番やMUSIC 0番をまだ作っていなくても、ゲーム本体は動きます(音だけ鳴りません)。
bird={}
pipes={}
frame=0
score=0
gameover=false
bgm_stopped=false
function _init()
bird={x=32,y=64,vy=0,r=3}
pipes={}
frame=0
score=0
gameover=false
bgm_stopped=false
music(0)
end
function spawn_pipe()
local gap_y=flr(rnd(50))+35
add(pipes,{x=128,gap_y=gap_y,gap_h=24,passed=false})
end
function update_bird()
bird.vy+=0.25
if btnp(4) or btnp(5) then
bird.vy=-2.6
sfx(0)
end
bird.y+=bird.vy
end
function update_pipes()
if frame%45==0 then
spawn_pipe()
end
for p in all(pipes) do
p.x-=1
if not p.passed and p.x+8<bird.x then
p.passed=true
score+=1
end
if p.x<-10 then
del(pipes,p)
end
end
end
function check_collision()
if bird.y<0 or bird.y>127 then
gameover=true
end
for p in all(pipes) do
local hit_x=bird.x+bird.r>p.x and bird.x-bird.r<p.x+8
local hit_top=bird.y-bird.r<p.gap_y
local hit_bottom=bird.y+bird.r>p.gap_y+p.gap_h
if hit_x and (hit_top or hit_bottom) then
gameover=true
end
end
end
function _update()
if gameover then
if not bgm_stopped then
music(-1)
bgm_stopped=true
end
if btnp(4) or btnp(5) then
_init()
end
return
end
frame+=1
update_bird()
update_pipes()
check_collision()
end
function draw_pipes()
for p in all(pipes) do
rectfill(p.x,0,p.x+8,p.gap_y,3)
rectfill(p.x,p.gap_y+p.gap_h,p.x+8,127,3)
end
end
function _draw()
cls(12)
draw_pipes()
circfill(bird.x,bird.y,bird.r,10)
print("score:"..score,2,2,7)
if gameover then
rectfill(20,52,108,76,0)
print("game over",44,58,8)
print("press x/o",42,66,7)
end
end
※ 鳥をスプライトで表示したい場合は、_draw() の circfill(...) を spr(0,bird.x-4,bird.y-4) に置き換えるだけでOKです。
つまずきやすいポイントと対策
1. 鳥が跳ばない
if btnp(4) or btnp(5)の中にbird.vy=-2.6があるか確認btn()とbtnp()を混同していないか確認
2. 速すぎて無理ゲー
- 重力
0.25 -> 0.18 - ジャンプ
-2.6 -> -2.2 - 土管速度
p.x-=1はそのまま、まず鳥側を調整
3. 土管が出ない
frame%45==0が消えていないかadd(pipes,{...})の記述ミス(カンマ漏れ)を確認for p in all(pipes) doのpipesのつづりが合っているか確認(pipeだと別物になります)
4. スコアが勝手に増える
passedの更新順を確認if not p.passed and ...が入っているか確認
5. 当たっていないのにゲームオーバー
bird.rを3 -> 2で調整p.x+8の土管幅を実際の描画と合わせる
6. スタート直後にすぐ落ちて終わる
bird.y=64が_init()に入っているか確認- 重力
0.25が強すぎると感じたら0.20に下げる - ジャンプ
-2.6を-2.8にして、少し上がりやすくする
エラーが出たときの見方(ここを知っていると強い)
PICO-8は、コードに間違いがあると画面下にエラーメッセージを出します。 こわく見えますが、実は「どこを直せばいいか」を教えてくれるヒントです。
よくあるエラー1:) expected(カッコが足りない)
例:
add(pipes,{x=128,gap_y=gap_y,gap_h=24,passed=false}
この場合は、最後の ) が足りません。 対処は、行の最後まで見て、開いたカッコが全部閉じているかを確認することです。
よくあるエラー2:then expected(if文の then がない)
例:
if frame%45==0
正しくは:
if frame%45==0 then
対処は、ifを書いたら then、forを書いたら do、最後に end を1セットで確認することです。
よくあるエラー3:attempt to index(名前まちがい)
例:
for p in all(pipe) doと書いてしまう(正しくはpipes)
配列名や変数名のつづりが1文字違うだけでエラーになります。 対処は、定義した名前をコピペで使うことです。
よくあるエラー4:画面は出るのに動かない
これは文法エラーではなくロジックエラーのことが多いです。
_update()があるか_draw()があるかreturnが早すぎる場所にないか
この3つを順番に見ると、かなり解決できます。
困ったときの順番(デバッグの型)
- 最後に書いた3行だけ戻って見る
- エラー行の前後を声に出して読む
- カッコ・カンマ・then/do/endを数える
- つづり(bird, pipes, score)を一致させる
- 数値を安全側にして動作確認(重力を弱く、隙間を広く)
この順番で見れば、ほとんどのエラーは直せます。 プロの現場でも、やっていることは同じです。
改造アイデア(自分の作品にする)
- 土管間隔を
45 -> 35にして難易度アップ gap_hを24 -> 30にして初心者向けcircfillをspr()に変えてスプライト化sfx()でジャンプ音を追加- タイトル画面(state管理)を追加
効果音(SFX)とBGMの作り方・使い方
最後に、ゲームを一気に「作品」にする音の入れ方をまとめます。
1. 効果音(SFX)を作る
ESCでエディター一覧に切り替えて、SFXタブを開く- SFX番号
0を選ぶ(ジャンプ音用) - 波形を選ぶ(最初は四角波か三角波が作りやすい)
- 高めの音を短く2〜3音入れる
- 再生ボタンで確認する
ジャンプ音は「短く」「高め」にすると気持ちよく聞こえます。
2. 効果音をコードで鳴らす
ジャンプした瞬間に、SFX 0 を鳴らします。
if btnp(4) or btnp(5) then
bird.vy=-2.6
sfx(0)
end
このコードは何をしている?
sfx(0):SFXエディターの0番を鳴らす- ボタンを押した瞬間だけ音が鳴るので、連続再生でうるさくなりにくい
3. BGMを作る(MUSIC)
ESCでエディター一覧に切り替えて、MUSICタブを開く- 楽曲番号
0を選ぶ - 1行目に、使いたいSFX番号(例: 1,2,3,4)を並べる
- ループ再生したい場合はループ設定をONにする
- 再生ボタンで確認する
最初は「SFX1=ドラム」「SFX2=ベース」「SFX3=メロディ」くらいの3レーン構成で十分です。
4. BGMをコードで鳴らす
ゲーム開始時にBGMを流すには、_init() に追加します。
function _init()
-- 既存の初期化
bird={x=32,y=64,vy=0,r=3}
pipes={}
frame=0
score=0
gameover=false
music(0)
end
ゲームオーバーで音を止めたいときは、次を使います。
if gameover then
music(-1)
end
5. 音が鳴らないときのチェック
sfx(0)の番号と、作ったSFX番号が一致しているかmusic(0)の楽曲0番にSFXが並んでいるか- 音量が小さすぎないか(SFXエディターで確認)
- コード内で
music(-1)を早いタイミングで呼んでいないか
「絵は出るけど音が出ない」時は、番号ミスのことがいちばん多いです。 まずは sfx(0) を _draw ではなく入力イベント内で鳴らし、確実に反応するか試してください。
まとめ
PICO-8でFlappy Bird風ゲームを作ると、Scratchで身につけた考え方がコードとしてつながります。最初は長く見えるコードでも、部品ごとに読むと必ず理解できます。
まずは一本完成させましょう。完成した時点で、あなたはもうゲーム開発者です。
PICO-8の前に基礎を固めたい方は、Scratch(スクラッチ)プログラミング教室から始めるのがオススメです。コース全体はプログラミングスクール詳細にまとめています。

教室の場所やアクセスは教室へのアクセスから確認できます。サイト全体の入口は沖縄のマインクラフト・プログラミング教室 クロスウェーブです。
この記事を書いた人の経歴や活動は、すずきたかまさ(鈴木孝昌)プロフィール ― 沖縄マイクラ部・クロスウェーブ代表にまとめています。
今すぐ、LINEから無料体験を予約できます。「見学だけでもいいですか?」という問い合わせも大歓迎です。まずは一度、教室の雰囲気を体験しに来てください。
沖縄マイクラ部プログラミングスクール「クロスウェーブ」 代表:鈴木 孝昌 (Google/Meta本社招待・政府PM・日本ソフトウェア大賞・マイクラカップTBS賞) 沖縄県宜野湾市伊佐2-20-15 伊佐ビル2F
マイクラ部への参加方法
マイクラ部への参加を希望される方はLINEアカウントへ登録を頂くか、メールにて「webcrafts098@gmail.com」までご連絡をお願いします。
沖縄マイクラ部プログラミングスクール「クロスウェーブ」では、マインクラフトカップへの参加を目指す仲間を募集しています。子供たちへのプログラミング教育として「Python」「Scratch」「MakeCode」「JavaScript」「Unity」「Godot」などを指導しています。動画編集講座(Premiere Pro・DaVinci Resolve・CapCut)やHTML/CSSでのWeb制作講座も開催中です。
マイクラカップ参加希望の方へ
マイクラカップへの参加を希望される方は、人数把握のため以下のフォームからも申請をお願いします。申込時はマインクラフト教育版のライセンス費用が発生いたします。
開催地域
沖縄県宜野湾市
沖縄マイクラ部プログラミングスクール「クロスウェーブ」にて開催しています。
沖縄県うるま市
FMうるまにて開催しています。
FMうるま
沖縄マイクラ部について
沖縄マイクラ部は教室ではなく、親子の勉強会というスタイルで運営しています。保護者の方も一緒に参加していただけますので、お気軽にご参加ください。
お問い合わせ
お問い合わせはLINEオフィシャルアカウント、またはメール(webcrafts098@gmail.com)からお気軽にどうぞ。イベント情報は「開催イベント一覧」からご確認ください。