11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

子供たちにRubyを楽しく学んで貰いたく頑張った話

Last updated at Posted at 2025-11-30

はじめに

当記事は、別段先進的な内容もなく、とある英会話スクールで子どもたちにプログラミングを教えた経験から、得た事をメモしています。

というのも、最近スクラッチとかマイクラ系のプログラミングスクールのコンテンツを食い尽くした子ども達が、新天地を求めてご相談いただく事が増えてきた為です。

普段から開発に携わっている方々にとって特に新しい内容はありませんので、「ふーん」程度で読んでいただけると幸いです。

環境と使用Gem

  • Ubuntu(WSL) or Mac で動作確認済
  • ruby (3.4.3)
  • ruby2d (0.12.1)

プログラミング必須化が叫ばれてから随分立ちました

ひょんな頼まれ事からはじまり、キッズプログラミングというカテゴリーに身を置いて随分経過していますが。
何年も経っての感想ですが、子供のプログラミング教育というのは、つくづくカオスでボランティアだなぁと感じています。
しかし、何年も同じ子に関わっていると、その成長が見られるのはとても楽しみでもあります。

ちょこちょこ大人のメンターなどもやっていますが、やはり大人と違ったアプローチが必要で、子供を退屈させないノウハウの確保に随分苦労しました。

結局得た結論としては、「大人と同じ事を楽しい題材でやる」です。
会話が通じる相手ではないので、徹底して体験させる方向ですが、一番の大敵は「面白くない事」です。

子供はweb系全然面白くないんだってさ

最初全くツボがわからなかったのですが、HTML/CSS のような結果が即座に分かるようなものですら全然集中力が持たず、RailsでScaffoldを使ってお手軽に動的なモノを作れたとしても、それを操作する事は全くもって響かないようです。
webアプリは高校生くらいからで全然良いと思いました。

逆に不思議なのは、一見退屈そうな「Windowsから拡張機能使わずにWSLを立ち上げてVSCodeでコーディングする」的な流れは面白いらしく・・・いやほんま謎です。

子供は説明聞きたくないんだってさ

そもそも、子供(1年生〜4年生あたり)には説明的なパートが全く駄目で、5分と持ちません。3分でも駄目です。
基本手を動かしつつ、いつか覚えてくれたらいいなー的に用語を呟く程度でとどめておく事が完成形となりました。
もう清々しく説明を放棄しています。

伸びる子と伸びない子の謎

これも最大級の謎ですが、教室で暴れまくったり、トイレに籠城していたりするような子供が、数年後一気にレベルが上がったり、真面目な子がいつまでも条件式で悩んでいたりと、子供の伸びるタイミングが全く未知数なのです。
下手したら、2年くらい授業の殆どを遊んでいる子達もいるわけです。
でも、だいたい手のかかった記憶がある子の方が最終的に書けたり、プログラミングを楽しめるようになっています。
暴れる事と何か関係があるのか謎は深まるばかりです。

そして行き着く先は

よし、話が退屈ならゲーム作って遊ばせよう!
となるわけですが、教室のPCスペックで何かしようとすると、せいぜい2D系のクラシカルなゲームだろうという事になります。
Scratchはタイピング力向上の面で候補から外してますし、python + turtle 辺りが妥当かとも思いましたが、小学生だと 割とインデントで引っかかる子もいるので、「You!いっそRubyを使っちゃいなよ!」という選択肢になります。

子供にプログラミングを教えるために ruby2d を選定した理由ですが

  • ruby2dはWSL上で動かせて音も出る
  • ruby2dはOS問わず動くらしい(Winは未検証)
  • ruby2dはクラスの概念を教えやすい
  • ruby2dは広い範囲のRubyバージョンで問題なく動く

そして一番重要なのが、

Rubyが子供でも情報を調べやすい言語

という事です。

ruby2dのインストール

ここからはハンズオンパートです。

Get strartedのページの通り、

$ gem install ruby2d

でインストール後、

main.rb
require 'ruby2d'
show

を用意し、実行

$ ruby main.rb
end

すると、ウインドウが立ち上がれば成功です。

スクリーンショット 2025-11-29 21.58.10.png

Macだと基本Rubyさえ使えれば問題なく動きます。

WSLでもWin11であれば別段手こずることもないですが、

https://www.ruby2d.com/learn/linux/ で、各カーネルで必要なパッケージが記載されていますので、入れ忘れに注意です。

ruby2dでブロック崩しを作りながらクラスの概念を教えてみる

では、実際にruby2dを動かしてみます。

ruby2dは、シンプルなライブラリで、 あまり特殊な機能も実装されておらず、キャラクターに関しては大体以下の3つを覚えておくとどうにかなります。

Circle クラスは、中心を座標とする丸い図形

Rectangle クラスは、左上を基準とする豆腐(正方形はSquareがある)

Sprite クラスは画像を読み込んで左上を基準とするスプライト

です。

その中で今回は CiecleRectangle のみを使います。

他クラス等、詳しく知りたい方は、shapes のページを見てください。

で、これらには移動速度や特殊なフラグなどのアクセサは想定されていないので、一つずつ変数を作るよりも、継承させたクラスを作ると捗る訳ですが、まずはベタに書いてみます。

自機(bar)を作る

ブロック崩しだと、まずは自機として、横長の棒が必要です。
main.rb を作り、

main.rb
require 'ruby2d'

bar = Rectangle.new(width: 100, height: 15)
bar.x = (Window.width - bar.width) / 2
bar.y = Window.height - bar.height * 3

show

こんな感じで書いて実行

$ ruby main.rb

すると、棒が現れます。

スクリーンショット 2025-11-24 12.10.12.png

移動は on :key で連続的に監視できますので、下記のコードを追加します。

main.rb


# 追記
bar_speed = 3

on :key do |event|
  key = event.key
  bar.x -= bar_speed if key == 'left'
  bar.x += bar_speed if key == 'right' 
end
# ここまで

show

次にbarがウインドからはみ出さないように制限を加えます。

main.rb


on :key do |event|
  key = event.key
  bar.x -= bar_speed if key == 'left'
  bar.x += bar_speed if key == 'right'
  # 追記
  # 左上が図形の起点なので、左側のリミットは 0 だが、右側のリミットは、画面幅から自身の幅を引いた座標の位置となる。
  bar.x = 0 if bar.x < 0
  bar.x = Window.width - bar.width if bar.x > Window.width - bar.width
  # ここまで
end

show

全体では、

main.rb
require 'ruby2d'

bar = Rectangle.new(width: 100, height: 15)
bar.x = (Window.width - bar.width) / 2
bar.y = Window.height - bar.height * 3

bar_speed = 3

on :key do |event|
  key = event.key
  bar.x -= bar_speed if key == 'left'
  bar.x += bar_speed if key == 'right'
  bar.x = 0 if bar.x < 0
  bar.x = Window.width - bar.width if bar.x > Window.width - bar.width
end

show

こんな感じです。

この時点で、全ての実装が終わる頃にはコード数がえげつない事になると想像できますので、まずはファイル自体を分けていきます。

新規に bar.rb というファイルを作成します。

そして、Ractangleを継承するBarクラスを作ります。

クラスとインスタンスの説明は有名な「たいやき」の話とかありますが、子供には通じません。彼らは説明など聞かないのです。

別のファイルに書き出す事だけを説明しておきます。
あとはひたすら真似をしてもらいましょう。

bar.rb
class Bar < Rectangle

end

この中に、 main.rb

bar = Rectangle.new(width: 100, height: 15)
bar.x = (Window.width - bar.width) / 2
bar.y = Window.height - bar.height * 3

bar_speed = 3

この部分を移植すると、

bar.rb
class Bar < Rectangle
  attr_accessor :speed

  def initialize
    super(width: 100, height: 15)
    self.x = (Window.width - self.width) / 2
    self.y = Window.height - self.height * 3
    self.speed = 3
  end
end

このようになりますので、できたBarクラスを main.rb から呼び出して動作するか試します。

attr_accessor の説明も、変数の代わりという程度だけ話しておきます。

当然 main.rbの方も変更が必要になり、変更点は以下となります。

  1. requirebar.rbを呼ぶ必要がある
  2. barRectangleクラスから Barクラスに置き換える
  3. bar_speed の変数はなくなっているので、 bar.speed に書き換える

具体的には、下記のようになります。

main.rb
require 'ruby2d'
require './bar' # bar.rbの読み込み

bar = Bar.new # RectangleクラスからBarクラスに

on :key do |event|
  key = event.key
  bar.x -= bar.speed if key == 'left' # bar_speed書き換え
  bar.x += bar.speed if key == 'right' # bar_speed書き換え
  bar.x = 0 if bar.x < 0
  bar.x = Window.width - bar.width if bar.x > Window.width - bar.width
end

show

ここまでで、動作に変更がなければ成功です。

ついでに、もう一息いきましょう。

main.rbの on :key 部分のコードを

on :key do |event|
  key = event.key
  bar.move(key)
end

のようにしたいので、ruby:bar.rbmoveメソッドを作ります。

bar.rb
  def move(key)
    self.x -= self.speed if key == 'left'
    self.x += self.speed if key == 'right'
    self.x = 0 if bar.x < 0
    self.x = Window.width - self.width if bar.x > Window.width - self.width
  end

で、Window.width - self.width のように複数使われており、コードが伸びているような部分に対して、 private メソッドに切り出す話もしますが、privateメソッドの説明も簡単に「クラス内でしか使わないメソッド」というニュアンスのみ伝えています。細かい話はしません。

そして、bar.rb 全体を以下の状態とします。

bar.rb
class Bar < Rectangle
  attr_accessor :speed

  def initialize
    super(width: 100, height: 15)
    self.x = (Window.width - self.width) / 2
    self.y = Window.height - self.height * 3
    self.speed = 3
  end

  def move(key)
    self.x -= self.speed if key == 'left'
    self.x += self.speed if key == 'right'
    # 変更
    self.x = left_limit if self.x < left_limit
    self.x = right_limit if self.x > right_limit
    # ここまで変更
  end

  # 追記
  private

  def left_limit
    0
  end

  def right_limit
    Window.width - self.width
  end
  # ここまで追記
end

main.rbもすっきりさせます。

main.rb
require 'ruby2d'
require './bar'

bar = Bar.new

on :key do |event|
  key = event.key
  bar.move(key)
end

show

このような感じで、リファクタリングをしながらコードを書いてもらい話を進めます。
このパターンはあまり嫌がらないようです。リファクタリング後と動きが変わらないのに退屈していないのが謎です。

いきなり飛んで最終形態

進行の話を入れていくと結論までかなり遠いので、ここからは完成形のコードを貼ります。

ファイルは全部で4つ

  • main.rb
  • bar.rb
  • ball.rb
  • block.rb

となります。+Gemfile等置いても良いと思います。

main.rb
require 'ruby2d'
require './bar'
require './ball'
require './block'

bar = Bar.new
ball = Ball.new
blocks = Block.set

update do
  ball.move(bar)
  bar.refrect(ball)
  Block.refrect(ball, blocks)
end

on :key_down do |event|
  key = event.key
  ball.game_start(key)
end

on :key do |event|
  key = event.key
  bar.move(key)
end

show
bar.rb
class Bar < Rectangle
  attr_accessor :speed

  def initialize
    super(width: 100, height: 15)
    self.x = (Window.width - self.width) / 2
    self.y = Window.height - self.height * 3
    self.speed = 3
  end

  def move(key)
    self.x -= self.speed if key == 'left'
    self.x += self.speed if key == 'right'
    self.x = left_limit if self.x < left_limit
    self.x = right_limit if self.x > right_limit
  end

  def refrect(ball)
    x_range = ball.x >= self.x && ball.x <= self.x + self.width
    y_contain = ball.y + ball.radius >= self.y
    if x_range && y_contain
      ball.y_flug = false
    end
  end

  private

  def width_limit(ball)
    self.x
  end

  def left_limit
    0
  end

  def right_limit
    Window.width - self.width
  end
end
ball.rb
class Ball < Circle
  attr_accessor :speed, :status, :x_flug, :y_flug 

  def initialize
    super(radius: 10)
    self.speed = 3
    self.status = :follow
    self.x_flug = true
    self.y_flug = false
  end

  def move(bar)
    case status
    when :follow
      follow(bar)
    when :launch
      launch
    end
  end

  def game_start(key)
    if key == 'space' && self.status == :follow
      self.status = :launch
    end
  end

  private

  def follow(bar)
    self.x = bar.x + bar.width - self.radius
    self.y = bar.y - self.radius
  end

  def launch
    behavior
    wall_refrect
    bottom_fall
  end

  def behavior
    if self.x_flug
      self.x += self.speed
    else
      self.x -= self.speed
    end
    if self.y_flug
      self.y += self.speed 
    else
      self.y -= self.speed
    end
  end

  def wall_refrect
    top_refrect
    left_recrect
    right_refrect
  end

  def top_refrect
    if self.contains?(self.x, 0)
      self.y_flug = true
    end
  end

  def left_recrect
    if self.contains?(0, self.y)
      self.x_flug = true
    end
  end

  def right_refrect
    if self.contains?(Window.width, self.y)
      self.x_flug = false
    end 
  end

  def bottom_fall
    if self.y > Window.height
      self.status = :follow
      self.x_flug = true
      self.y_flug = false
    end
  end
end
block.rb
class Block < Rectangle
  def initialize
    super(width: 60, height: 20)
    self.x = 80
    self.y = 50
  end

  def self.set
    blocks = []
    row_colors = %w(red orange yellow green blue purple)
    cols = (0..7)
    row_colors.each_with_index do |color, row_index|
      cols.each_with_index do |col, col_index|
        block = self.new
        block.color = color
        block.x = block.x + block.width * col_index
        block.y = block.y + block.height * row_index
        block.width -= 1
        block.height -= 1
        blocks << block
      end
    end
    blocks
  end

  def self.refrect(ball, blocks)
    blocks.each do |block|
      block.refrect(ball, blocks)
    end
  end

  def refrect(ball, blocks)
    bottom_refrect(ball, blocks)
    top_refrect(ball, blocks)
    left_refrect(ball, blocks)
    right_refrect(ball, blocks)
  end

  def self.remove(blocks)
    blocks.each do |block|
      block.remove
    end
  end

  private

  def bottom_refrect(ball, blocks)
    if ball.contains?(ball.x, self.y + self.height) && width_include?(ball) 
      ball.y_flug = true
      self.remove
      blocks.delete(self)
      true
    end
  end

  def top_refrect(ball, blocks)
    if ball.contains?(ball.x, self.y) && width_include?(ball)
      ball.y_flug = false
      self.remove
      blocks.delete(self)
      true
    end
  end

  def left_refrect(ball, blocks)
    if ball.contains?(self.x, ball.y) && height_include?(ball)
      ball.x_flug = false
      self.remove
      blocks.delete(self)
      true
    end
  end

  def right_refrect(ball, blocks)
    if ball.contains?(self.x + self.width, ball.y) && height_include?(ball)
      ball.x_flug = true
      self.remove
      blocks.delete(self)
      true
    end
  end

  def width_include?(ball)
    self.x <= ball.x && self.x + self.width >= ball.x
  end

  def height_include?(ball)
    self.y <= ball.y && self.y + self.height >= ball.y
  end
end

動かすと、こんな感じです。

スクリーンショット 2025-11-29 23.15.06.png

これでも最低限遊ぶ事が出来る事と、書き進める上で

  • クラス
  • インスタンス
  • requireとパスの指定
  • インスタンスメソッド
  • クラスメソッド
  • メソッドの戻り値
  • privateメソッド

の説明を盛り込む事が出来ています。

実際には、このパターンの前に1ファイルで完結するゲームを1〜2種類経験して貰っていたり、+αの要素で、

  • ライフ機能
  • ゲームオーバー画面
  • コンティニュー機能
  • リスタート機能
  • スコア機能

だったりを足して作り込んでいきます。
後は適当に遊ばせていると、色々な数値を変更したり、スピードを上げたりといろいろ遊んでくれます。

すんごい速度にしたり、ブロックを死ぬほど増やしたり、
子供の想像力は素晴らしいと思います。

また、次のステップとして、スプライトを使ったシューティングゲームなども進めていますが、フォルダ構造をフレームワークっぽくしてモデルやモジュール、config系のファイルなども作ったり、rakeでコマンド起動化したり、少しずつRailsっぽい構造に寄せるよう仕向けています。
きっと、これが役に立つはずだと信じて・・・。

最後に

ruby2d自体は2年ほどメンテナンスが止まっています。

Platform supportの情報も当然古いので、現状書いたプログラムの吐き出し方法をどうするか?などの問題も抱えています。

現状はゲームを作る事がゴールなので、これでも良いのですが、そろそろ次なるネタを考えねばなりません。

ここまで御覧いただきありがとうございます。良いクリスマスを。

11
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?