(2018.8.26. 公開) (2018.8.26. 対象をバージョン 11 系列に変更)
プログラムの作成例として、「さめがめ」(まきがめ)を作ってみます。同じコマの並びを見つけて消していくゲームです。昔からパソコンで遊んでいる人は知っているでしょう。(原作者さんたちのウェブサイトがあります:オリジナル作者のページ、Mac 版「まきがめ」のページ。)
具体的に「見える」ところから進めていくと、作りやすいし、その先のイメージもわきやすい。だから、まずは初期盤面、「5種類のコマを格子状にランダムに並べて表示する」ところから始めましょう。
まず、コマの画像を用意しないといけない。32x32のドット絵が5つあればいいので、適当なものを調達する。今回は、無料素材倶楽部さんの「果物(フルーツ)イラストアイコン集」を使わせていただきました。5種類好きなのを選んで、32x32に縮小して、5つ並べる。この 160x32 の画像 (fruit32.png
) を、テクスチャアトラスとして使います。
画面デザインを楽にするため、盤面の大きさは 640x480 で固定にする。入門編の「背景をスクロールする」で作成した set_screen_size()
関数を使う。
コマの大きさは 32x32 なので、画面いっぱいに並べると縦 15 個、横 20 個になる。このうち、縦を1個減らして、一番上の 32 ドット分をスコアや残りコマ数を表示するスペースにする。
横・縦に並べる数をそれぞれ xx
, yy
で表す。この場合は xx = 20
, yy = 14
となるけど、set_screen_size()
の中で画面の横・縦サイズを決めているので、 xx
, yy
はそこから計算した方がよい。これは、love.load()
の中で処理する。
-- === 画面サイズ設定 ===
set_screen_size() -- 画面の大きさを設定
xx = math.floor(width / 32) -- 盤面の幅
yy = math.floor(height / 32) - 1 -- 盤面の高さ
math.floor()
は、数値の小数点以下を切り捨てて整数にする関数。「画面サイズの設定」でも使った。
盤面のコマを表すには、数値をキーとするテーブル(配列)を使えばよい。テーブルの名前を board
として、board[1]
から board[xx * yy]
にコマの種類を入れる。コマの種類は、0
ならば空白、1..5
ならば1番目〜5番目のコマを表すものとする。
LuaJIT でテーブルを作るには、board = {}
と書く。
初期盤面を作るには、board[1]
から board[xx * yy]
まで、1つずつ 1..5
の数値をランダムに入れてやればよい。ランダムな数を発生させるには、love.math.random(n)
を使う。この関数は、引数 n
を与えると、1
から n
までの整数をランダムに1つ返してくれる。
LuaJIT の組み込み関数に math.random(n)
というのがあるが、乱数の品質が OS に依存する。love.math.random(n)
だと、どの機種で動かしても同じ品質を保証できる。
コマは5種類と決まっているが、あとで変更する可能性も考えて、ntiles
という変数に入れることにする。ntiles
は英語の "number of tiles"(コマの数)を省略したもの。変数名は何でもよいのだが、「何かの数」を表す変数には、このように "n" + 名前 + "s" という形の名前をつける人が多い。もちろん、ローマ字で koma_no_kazu
とか komakazu
などとしても全く問題ない。
-- === 初期盤面を作る ===
ntiles = 5 -- コマの種類
board = {} -- 盤面
for i = 1, xx * yy do -- xx*yy 回繰り返し
board[i] = love.math.random(ntiles) -- ランダムにコマを配置
end
表示には「背景をスクロールする」で紹介したスプライトバッチを使いたい。love.load()
の中で、コマの画像が入っているテクスチャアトラスを読み込み、これを画像として使うスプライトバッチを作成する。
5種類のコマを表示するための Quad も作成しておく。
-- === 画像関連初期化 ===
tiles = love.graphics.newImage("fruits32.png") -- コマ画像を読み込む
wid0, high0 = tiles:getDimensions() -- コマ画像のサイズ
batch = love.graphics.newSpriteBatch(tiles) -- スプライトバッチ
quads = {} -- Quad を保持しておくテーブル
for i = 1, ntiles do -- Quad をコマの種類分だけ作る
quads[i] = love.graphics.newQuad((i - 1) * 32, 0, 32, 32, wid0, high0)
end
そして、スプライトバッチに Quad を加えていく。この時、ループの回し方に工夫の余地がある。盤面を作成した時は、単純に「xx * yy
回」繰り返せばよかった。しかし、今回は、それぞれのコマを表示する画面座標が必要である。テーブルのキーと座標は、下の図のような関係になっている。
先ほどと同じように単純な一重(いちじゅう)ループで回すのであれば、座標はループ変数から計算する必要がある。(a % b
は「a
を b
で割った余り」を表す。)
一方、まず x 方向についてループし、それをさらに y 方向についてループする、二重(にじゅう)ループも可能である。この場合は、座標はループ変数から直接得られるが、テーブルのキーを計算する必要がある。
どちらのやり方でも構わないが、今回は二重ループを使ってみる。画面の最上部に 32 ドット分の空きをつくるため、コマの y 座標は (y + 1) * 32
で表されることに注意。
-- === スプライトバッチの初期化 ===
for y = 0, yy - 1 do
for x = 0, xx - 1 do
local i = y * xx + x + 1
batch:add(quads[board[i]], x * 32, (y + 1) * 32)
end
end
なお、この二重ループは、y が外で x が内になっている。これを逆にすると、スプライトの順序がテーブルのキーとずれてしまうため、話が面倒になる。
盤面の表示は love.draw()
中に書く。まず、set_screen_size()
関数に対応して、表示範囲を限定する love.graphics.setScissor()
を使う必要がある(参考:「背景をスクロール表示する」)。
-- === 描画範囲を設定 ===
love.graphics.setScissor(transx, transy, width * scale, height * scale)
love.graphics.translate(transx, transy) -- 原点移動
love.graphics.scale(scale, scale) -- 拡大率を設定
... -- 描画処理
-- === 描画範囲をリセット ===
love.graphics.setScissor()
次に、盤面を適当な色で塗りつぶす。ここでは、盤面を少し明るい青色、一番上のスコアなどを表示する部分を濃いめの青色にした。
-- === 盤面を塗りつぶす ===
love.graphics.setColor(0.39, 0.42, 0.92)
love.graphics.rectangle("fill", 0, 0, width, 32) -- 点数、残りコマ数など
love.graphics.setColor(0.47, 0.50, 1.00)
love.graphics.rectangle("fill", 0, 32, width, height - 32) -- 盤面
スコアの行は、こんな風に表示したい。左から順に、現在のスコア・残りコマ数・各コマの残り数・R(再開)、N(別の盤面)のキー操作説明。下に示した数字は、x 座標です。文字は 16 ポイントで書くつもりで、全角文字が 約 16 ドット、半角文字が約 8 ドットの幅と見積もっている。
-- === スコア・残りコマ数など表示 ===
love.graphics.setColor(1, 1, 1)
love.graphics.print(string.format("スコア %-d", point), 4, 8)
love.graphics.print(string.format("残り %-d", num), 120, 8)
for i = 1, ntiles do
love.graphics.draw(tiles, quads[i], 140 + i * 60, 0)
love.graphics.print(string.format("%-d", rest[i]), 140 + 36 + i * 60, 8)
end
love.graphics.print("[R]etry [N]ew", 220 + ntiles * 60, 8)
string.format("文字列", 数値)
は、LuaJIT の組み込み関数で、指定したフォーマットで数値を文字列に埋め込む。"%-d"
というところがフォーマット指定で、マイナス記号は「左詰めにする」、d は「整数を文字列に変換する」という意味。今回は特に凝ったフォーマットは使っていないが、本来は「数字の右端を合わせて表示する」とか、「小数点以下2ケタ分だけ表示する」というような用途に使う。
フォント指定を忘れてはいけない。これは love.load()
の最初の方に書いておく。
-- === フォント指定 (IPAゴシック 16ポイント) ===
font = love.graphics.newFont("ipag.ttf", 16)
love.graphics.setFont(font)
スコア、残り数などの変数も初期化しておく必要がある。これは、盤面を初期化するところに追加しておこう。
-- === 初期盤面を作る ===
ntiles = 5 -- コマの種類
board = {} -- 盤面
point = 0 -- スコア
num = xx * yy -- 残りコマ数
rest = {} -- 種類ごとの残りコマ数
for i = 1, ntiles do -- 残りコマ数をゼロクリア
rest[i] = 0
end
for i = 1, xx * yy do -- xx*yy 回繰り返し
board[i] = love.math.random(ntiles) -- ランダムにコマを配置
rest[board[i]] = rest[board[i]] + 1 -- 残りコマ数を1増やす
end
そして、肝心のスプライトバッチの表示。これは1行で済む。
-- === コマの表示 ===
love.graphics.draw(batch, 0, 0)
以上を合わせたのが次のプログラム。set_screen_size()
の中で、love.window.setTitle()
を使ってウィンドウタイトルを設定しています。"LÖVE" がタイプできない場合は、"Love2D" でもいいでしょう。
-- サンプルプログラム 101-01 main.lua
-- fruits32.png, ipag.ttf が必要
function set_screen_size()
width = 640 -- ゲーム画面の横幅
height = 480 -- ゲーム画面の高さ
love.window.setMode(width, height) -- これが効くなら問題なし
love.window.setTitle("さめがめ on LÖVE")
swidth = love.graphics.getWidth() -- 実画面の横幅
sheight = love.graphics.getHeight() -- 実画面の高さ
transx, transy = 0, 0
scale = 1
if swidth ~= width or sheight ~= height then
-- setMode が効いてない場合
if swidth / sheight > width / height then -- 実画面の方が横長
scale = sheight / height -- 縦方向で倍率を決める
transx = math.floor((swidth - width * scale) / 2) -- 空白の幅
transy = 0
else
scale = swidth / width -- 横方向で倍率を決める
transx = 0
transy = math.floor((sheight - height * scale) / 2) -- 空白の高さ
end
end
love.graphics.setBackgroundColor(0.82, 0.82, 0.82) -- ゲーム画面の外は灰色の枠
end
function love.load()
-- === 画面サイズ設定 ===
set_screen_size() -- 画面の大きさを設定
xx = math.floor(width / 32) -- 盤面の幅
yy = math.floor(height / 32) - 1 -- 盤面の高さ
-- === フォント指定 (IPAゴシック 16ポイント) ===
font = love.graphics.newFont("ipag.ttf", 16)
love.graphics.setFont(font)
-- === 初期盤面を作る ===
ntiles = 5 -- コマの種類
board = {} -- 盤面
point = 0 -- スコア
num = xx * yy -- 残りコマ数
rest = {} -- 種類ごとの残りコマ数
for i = 1, ntiles do -- 残りコマ数をゼロクリア
rest[i] = 0
end
for i = 1, xx * yy do -- xx*yy 回繰り返し
board[i] = love.math.random(ntiles) -- ランダムにコマを配置
rest[board[i]] = rest[board[i]] + 1 -- 残りコマ数を1増やす
end
-- === 画像関連初期化 ===
tiles = love.graphics.newImage("fruits32.png") -- コマ画像を読み込む
wid0, high0 = tiles:getDimensions() -- コマ画像のサイズ
batch = love.graphics.newSpriteBatch(tiles) -- スプライトバッチ
quads = {} -- Quad を保持しておくテーブル
for i = 1, ntiles do -- Quad をコマの種類分だけ作る
quads[i] = love.graphics.newQuad((i - 1) * 32, 0, 32, 32, wid0, high0)
end
-- === スプライトバッチの初期化 ===
for y = 0, yy - 1 do
for x = 0, xx - 1 do
local i = y * xx + x + 1
batch:add(quads[board[i]], x * 32, (y + 1) * 32)
end
end
end
function love.draw()
-- === 描画範囲を設定 ===
love.graphics.setScissor(transx, transy, width * scale, height * scale)
love.graphics.translate(transx, transy) -- 原点移動
love.graphics.scale(scale, scale) -- 拡大率を設定
-- === 盤面を塗りつぶす ===
love.graphics.setColor(0.39, 0.42, 0.92)
love.graphics.rectangle("fill", 0, 0, width, 32) -- 点数、残りコマ数など
love.graphics.setColor(0.47, 0.50, 1.00)
love.graphics.rectangle("fill", 0, 32, width, height - 32) -- 盤面
-- === スコア・残りコマ数など表示 ===
love.graphics.setColor(1, 1, 1)
love.graphics.print(string.format("スコア %-d", point), 4, 8)
love.graphics.print(string.format("残り %-d", num), 120, 8)
for i = 1, ntiles do
love.graphics.draw(tiles, quads[i], 140 + i * 60, 0)
love.graphics.print(string.format("%-d", rest[i]), 140 + 36 + i * 60, 8)
end
love.graphics.print("[R]etry [N]ew", 220 + ntiles * 60, 8)
-- === コマの表示 ===
love.graphics.draw(batch, 0, 0)
-- === 描画範囲をリセット ===
love.graphics.setScissor()
end
目次