ホーム » 技術 » [love2d]ブロック崩しを作りながら色々な初期設定する

[love2d]ブロック崩しを作りながら色々な初期設定する


本文中にアフィリエイトリンクが含まれる場合があります。
表示が乱れた場合は再ロードをお試しください。

ゲーム開発を始めたばかりの方にとって「最初の画面設定」や「変数の使い方」は、いまいちイメージがつかみにくい部分かもしれません。
この記事では [Love2D] を使ってブロック崩しゲームを作りながら、画面サイズの指定 や 変数の宣言方法、そして 図形や文字の描画 といった基礎をひとつずつ確認していきます。

以下は前回の記事です。

基本的に自分の忘備録もかねています。プロではないので間違っていたらすみません。

作ったものは以下に公開しようかなと思っています。今回のコードもあります。

games/love2d/block at main · N-Samurai/games

ゲーム開発の画面の大きさの設定

まずゲームの画面の大きさなどを設定します。

以下の二つは内容というよりも、共通化したおまじない程度に考えてもらえばいいと思います。

大事なことは800:600でこのゲームを作っているということです。

conf.lua

以下のように設定してみました。

  • love.conf
    LÖVEが最初に読み込む設定用の特別な関数。引数 t に色々な設定項目が入っている。
  • t.window.title
    ウィンドウのタイトルバーに表示される文字列。
  • t.window.width / t.window.height
    ウィンドウの初期サイズ。
  • t.window.highdpi
    macOS の Retina ディスプレイや Windows の高DPI環境で高解像度描画をするかどうか。false にすると通常解像度で描画。
  • t.window.resizable
    ユーザーがマウスでウィンドウサイズを変更できるかどうか。
function love.conf(t)
  t.window.title = "Lurker test"
  t.window.width = 800
  t.window.height = 600
  t.window.highdpi = false
  t.window.resizable = true
end

main.lua

ゲームの基本的な解像度を800:600に指定して、ウインドウサイズを取得してスケーリングで表示が伸びないようにしました。

local gameWidth, gameHeight = 800, 600  -- 内部解像度
local scale, offsetX, offsetY = 1, 0, 0

function love.load()
    love.window.setMode(0, 0, { resizable = true }) -- フルスクリーン解像度取得可能
    updateScale()
end

function love.resize(w, h)
    updateScale()
end

function updateScale()
    local windowWidth, windowHeight = love.graphics.getDimensions()
    local scaleX = windowWidth / gameWidth
    local scaleY = windowHeight / gameHeight
    scale = math.min(scaleX, scaleY) -- 縦横比維持

    -- 中央寄せオフセット
    offsetX = (windowWidth - gameWidth * scale) / 2
    offsetY = (windowHeight - gameHeight * scale) / 2
end

function love.draw()
    love.graphics.push()
    love.graphics.translate(offsetX, offsetY)
    love.graphics.scale(scale)

    -- ここから下が内部解像度800×600での描画
    love.graphics.clear(0.2, 0.2, 0.25)
    love.graphics.setColor(1, 1, 1)
    love.graphics.print("内部解像度: 800x600", 20, 20)

    -- テスト用: 四角を描画
    love.graphics.rectangle("line", 0, 0, gameWidth, gameHeight)

    love.graphics.pop()
end

図形の描画

基本的な図形の描画を以下に示します。

以下のようにlove.draw()で囲んで描画します。

function love.draw()
    -- テスト用: 四角を描画
    love.graphics.rectangle("line", 0, 0, gameWidth, gameHeight)
end

1. 四角形を描く

-- 矩形を描く
love.graphics.rectangle(mode, x, y, width, height)

-- mode: "fill"(塗りつぶし)または "line"(枠線のみ)
love.graphics.setColor(1, 0, 0)  -- 赤 (R=1, G=0, B=0)
love.graphics.rectangle("fill", 100, 100, 200, 150)

2. 円・楕円を描く

-- 円を描く
love.graphics.circle(mode, x, y, radius)

-- 楕円を描く
love.graphics.ellipse(mode, x, y, radiusX, radiusY)

love.graphics.setColor(0, 1, 0)  -- 緑
love.graphics.circle("line", 400, 300, 80)
love.graphics.setColor(0, 0, 1)  -- 青
love.graphics.ellipse("fill", 400, 300, 100, 50)

3. 多角形を描く

-- 頂点座標を順番に指定
love.graphics.polygon(mode, x1, y1, x2, y2, x3, y3, ...)

love.graphics.setColor(1, 1, 0) -- 黄色
love.graphics.polygon("fill", 100, 100, 150, 50, 200, 100, 150, 150)

4. 線を引く

love.graphics.setColor(1, 1, 1) -- 白
love.graphics.setLineWidth(3)
love.graphics.line(50, 50, 200, 200)

5. 点を打つ

love.graphics.points(x, y, ...)
love.graphics.setColor(1, 0, 1) -- マゼンタ
love.graphics.points(300, 300, 320, 320, 340, 300)

これだけで、以下のように基本的なゲーム画面を作ることができます。

変数の宣言

変数の宣言は以下のように行います。

local lives = 3   -- local: このブロックの中だけ有効
speed = 200       -- global: どこからでも使える

ゲームの中だと、プレイヤーの移動であったり、ボールの挙動、ブロックの判定など色々使います。

以下のようにまとめて宣言もできます。

local player = { x=400, y=500, width=50, height=25, speed=1000 }
local block  = { width=50, height=50 }
local ball   = { x=425, y=490, size=10, vx=180, vy=-220, active=false }  --

キーの入力

キーの入力は以下のようにisDownで設定します。

function love.update(dt)
    -- 上下左右キーで移動
    if love.keyboard.isDown("up") then
        player.y = player.y - player.speed * dt
    end
    if love.keyboard.isDown("down") then
        player.y = player.y + player.speed * dt
    end
    if love.keyboard.isDown("left") then
        player.x = player.x - player.speed * dt
    end
    if love.keyboard.isDown("right") then
        player.x = player.x + player.speed * dt
    end
end

移動制限の追加

このままだとプレイヤーの挙動が画面外に行ってしまうため、移動制限を追加しました。

local function clamp(v, lo, hi)
  if v < lo then return lo elseif v > hi then return hi else return v end
end
function love.update(dt)
  lurker:update()  -- ★ これを毎フレーム最初に

  if love.keyboard.isDown("right") then player.x = player.x + player.speed*dt end
  if love.keyboard.isDown("left")  then player.x = player.x - player.speed*dt end

  player.x = clamp(player.x, 0, gameWidth  - player.size)

end

当たり判定の追加

壁とボール

四角の座標は描画されている者の左上から始まります。

丸の座標は中心から始まります。

壁とボールの当たり判定は、右側と左側と上側の3つ必要です。

一つ目の右側は、ボールのx座標とボールのsizeを足したものが800より大きいとき

二つ目の左側は、ボールのx座標からsizeを引いたものが0より小さくなったら

三つ目の左側は、ボールのy座標からsizeを引いたものが0より小さくなったら

速度ベクトルを反転させます。

プレイヤーとボール

今度はプレイヤーとボールです。

操作パドルの中で接触したら速度ベクトルを反転させます。

つまり、ボールのx座標とボールの半径を足したものよりプレイヤーのxが小さく、ボールのx座標とボールの半径を引いたものよりプレイヤーのx座標とパドルの大きさを足したものが大きければいいという範囲になります。

さらに、y座標も同様にやります。くどいので省略します。

ブロックのアルゴリズム

判定は同様の考え方です。ただたくさんあります。

今、ブロックは横に16個、縦に5個並ぶように設計しています。なので、配列で一行目二列目をrow=1,col=2という風に表していきます。

例えば、2行3列のブロックの当たり判定は、3×ブロックのx座標から、3×ブロックのx座標+ブロックの横幅です。

生存フラグ

local cols, rows = 16, 5
local bricks = {}
for j=1, rows do
  bricks[j] = {}
  for i=1, cols do
    bricks[j][i] = true   -- true = 生存, false = 破壊済み
  end
end

当たり判定

当たり判定は先ほどのものを流用します。

for j=1, rows do
  for i=1, cols do
    if bricks[j][i] then
      local rx = (i-1)*block.width
      local ry = (j-1)*block.height
      local hit, dx, dy = circleRectHit(ball.x, ball.y, ball.size, rx, ry, block.width, block.height)
      if hit then
        bricks[j][i] = false          -- ★ 破壊(=もう描かない/判定しない)
        -- 反射方向を決める(どの面に近いかでXかYを反転)
        if math.abs(dx) > math.abs(dy) then
          ball.vx = -ball.vx
        else
          ball.vy = -ball.vy
        end
        goto afterBricks              -- 1フレームに1個だけ壊す
      end
    end
  end
end

描画

生存フラグが立っているものだけを描画するように変更します。

for j=1, rows do
  for i=1, cols do
    if bricks[j][i] then
      local rx = (i-1)*block.width
      local ry = (j-1)*block.height
      local hit, dx, dy = circleRectHit(ball.x, ball.y, ball.size, rx, ry, block.width, block.height)
      if hit then
        bricks[j][i] = false          -- ★ 破壊(=もう描かない/判定しない)
        -- 反射方向を決める(どの面に近いかでXかYを反転)
        if math.abs(dx) > math.abs(dy) then
          ball.vx = -ball.vx
        else
          ball.vy = -ball.vy
        end
        goto afterBricks              -- 1フレームに1個だけ壊す
      end
    end
  end
end

文字の描画

文字の描画です。

基本的にwidthで幅を決めて、色と描画する場所を指定します。

function love.draw()
    local text = "中央揃えテキスト"
    local width = 800 -- 文字を収める領域の幅
    love.graphics.setColor(1, 1, 0)
    love.graphics.printf(text, 0, 200, width, "center") -- 中央揃え
end

"left", "center", "right" で左揃えなどを指定可能です。

フォントサイズを変更するためには以下のように指定することも可能です。

function love.load()
    fontSmall = love.graphics.newFont(14)
    fontLarge = love.graphics.newFont(32)
end

function love.draw()
    love.graphics.setFont(fontSmall)
    love.graphics.print("小さい文字", 50, 50)

    love.graphics.setFont(fontLarge)
    love.graphics.setColor(1, 0, 0)
    love.graphics.print("大きい文字", 50, 100)
end

hitの関数のところでscoreを加算して得点を表示するようにしました。

love.graphics.setFont(fontLarge)
    local width = 80 -- 文字を収める領域の幅
    love.graphics.setColor(1, 1, 0)
    love.graphics.printf(score, 600, 550, width, "center") -- 中央揃え

ここまででメインの機能の解説は終わりました。

あとは、スタート時にボタンを発射するとか失敗時の挙動とかを追加して完成です。

まとめ

今回の記事では、Love2D を使ったブロック崩し作りの第一歩として、以下の要素を学びました。

  • main.lua 内で内部解像度(800×600)を維持しつつ、リサイズやスケーリングを扱う方法
  • love.graphics を用いた図形や文字の描画(四角形、円、多角形、テキストなど)
  • 変数の宣言と利用(プレイヤーやボールの位置・速度などを管理)
  • キーボード入力の処理でプレイヤーを操作する方法
  • 当たり判定とブロック管理により、ゲームらしい挙動を実現する手順

ここまでで、ブロック崩しの基本的な骨組みは完成しました。
このあと「ボールを発射するタイミング」や「ゲームオーバー処理」「スコア表示」などを追加すれば、さらに完成度が高まります。

Love2D はシンプルなコードでゲームが作れるので、今回のサンプルをベースにして 効果音の追加 や 画像の差し替えアイテム要素の導入 など、自分だけのオリジナル要素を組み込んでみてください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です