(2018.7.28. 公開) (2018.8.26. 対象をバージョン 11 系列に変更)
同じ内容のゲームでも、PC のキーボードを使う代わりにジョイスティックやゲームパッドでコントロールすると、途端にゲームらしくなる。LÖVE でももちろん、ジョイスティックなどをサポートする機能が用意されている。
世の中にはいろいろなジョイスティック・ゲームパッドが売られている。今回動作を確認したのは下のような、スーパーファミコン風のゲームパッドです(バッファロー製)。XY 方向の十字キーと、A, B, X, Y, L, R, Select, Start の8つのボタンがあります。
たいていのジョイスティックは USB で接続するようになっており、PC などに接続すると自動的に認識される。LÖVE プログラムからジョイスティックを認識するには、2つの方法がある。
love.joystick.getJoysticks()
-- 現在接続されているジョイスティックの一覧を得るlove.joystickadded(j)
-- ジョイスティックが接続された時に呼び出される
ここでは、2番目の方法を使って見よう。love.joystickadded(j)
はコールバック関数である。「コールバック関数」とは、ある条件の元で自動的に呼び出される関数のことだった。この場合は、「ジョイスティックが接続されたとき」に、この関数が呼び出される。引数の j
は、Joystick
タイプのオブジェクトである。
次のプログラムは、ジョイスティックが接続されると、そのジョイスティックの名前や仕様を表示する。
-- サンプルプログラム 10-01 main.lua
function love.load()
love.graphics.setBackgroundColor(1, 1, 1) -- 背景を白に
love.graphics.setColor(0, 0, 0) -- 文字色を黒に
love.graphics.setFont(love.graphics.newFont(24)) -- フォントサイズを24に
end
-- ジョイスティックが接続された時に呼ばれるコールバック関数
function love.joystickadded(j)
joystick = j -- 接続されたジョイスティックオブジェクトを joystick に記録
end
function love.draw()
if joystick and joystick:isConnected() then -- ジョイスティックが接続されているなら
local name = joystick:getName() -- 名前
local ac = joystick:getAxisCount() -- 軸の数
local bc = joystick:getButtonCount() -- ボタンの数
love.graphics.print("Name = " .. name, 10, 10)
love.graphics.print("Axis count = " .. ac, 10, 40)
love.graphics.print("Button count = " .. bc, 10, 70)
end
end
Joystick:getName()
は Joystick
タイプのオブジェクトが持つメソッドで、ジョイスティックの名前を文字列で返す。実際に使う時は、Joystick
のところを、オブジェクトに置き換えること。上のプログラムでは、joystick
という変数に「現在接続されているジョイスティックを表すオブジェクト」が入っているので、これを使って joystick:getName()
と書いている。メソッドなので、コロン :
を使うことに注意。
ジョイスティックの仕様は、「軸の数」と「ボタンの数」で表される。「軸の数」は、レバーを動かせる方向の数で、通常ゲームに使うジョイスティックでは2つ(上下方向と左右方向)である。ボタンの数や配置は製品によって違うので、自分が使う機器で実験する必要がある。
実験用のプログラムを下に示す。上記のプログラムに加えて、各軸の値と、どのボタンが押されているかを表示する。
-- サンプルプログラム 10-02 main.lua
function love.load()
love.graphics.setBackgroundColor(1, 1, 1) -- 背景を白に
love.graphics.setColor(0, 0, 0) -- 文字色を黒に
love.graphics.setFont(love.graphics.newFont(24)) -- フォントサイズを24に
end
-- ジョイスティックが接続された時に呼ばれるコールバック関数
function love.joystickadded(j)
joystick = j -- 接続されたジョイスティックオブジェクトを joystick に記録
end
function love.draw()
if joystick and joystick:isConnected() then -- ジョイスティックが接続されているなら
local name = joystick:getName() -- 名前
local ac = joystick:getAxisCount() -- 軸の数
local bc = joystick:getButtonCount() -- ボタンの数
love.graphics.print("Name = " .. name, 10, 10)
love.graphics.print("Axis count = " .. ac, 10, 40)
love.graphics.print("Button count = " .. bc, 10, 70)
s1 = "Axis"
for i = 1, joystick:getAxisCount() do
s1 = s1 .. string.format(" [%d]%-5d", i, joystick:getAxis(i))
end
love.graphics.print(s1, 10, 100)
s1 = "Button"
for i = 1, joystick:getButtonCount() do
s1 = s1 .. string.format(" [%d]", i)
if joystick:isDown(i) then
s1 = s1 .. "O"
else
s1 = s1 .. "-"
end
end
love.graphics.print(s1, 10, 130)
end
end
十字キーの下方向と、Aボタンを押すと、下のような表示になる。
上のプログラム例でも使用したが、レバー(ゲームパッド型の場合は十字キー)の向きを調べるには、Joystick:getAxis(n)
を使う。n
は軸の番号で、左右方向が 1, 上下方向が 2。これは今回使ったゲームパッドの場合だが、多くのジョイスティック/ゲームパッドで共通ではないかと思われる。
十字キーや、レバーでスイッチを押すタイプのジョイスティックでは、Joystick:getAxis(n)
で得られる値は +1(右/下)、0(押されていない)、-1(左/上)のいずれかになる。ポテンショメーターを内蔵するジョイスティックの場合は、倒す角度で値が変わるものもあるだろう。どのような値が得られるかは、実験してみる必要がある。
「キー入力」のところで紹介した四角を動かすサンプルプログラムを、ジョイスティックでも動かせるように改造してみた。
-- サンプルプログラム 10-03 main.lua
-- 最初に1回だけ呼び出されるコールバック関数
function love.load()
width = love.graphics.getWidth() -- 現在の画面の横幅
height = love.graphics.getHeight() -- 現在の画面の高さ
love.graphics.setBackgroundColor(0, 0.24, 0) -- 背景を濃い緑色に
rsize = width / 25 -- 四角形のサイズ
x = 0 -- x 座標を0にする
y = 0 -- y 座標を0にする
speed = (width - rsize) / 3 -- 移動速度:1秒間に横幅の1/3
end
-- ジョイスティックが接続された時に呼ばれるコールバック関数
function love.joystickadded(j)
joystick = j -- 接続されたジョイスティックオブジェクトを joystick に記録
end
-- 定期的に呼び出されるコールバック関数
function love.update(dt)
local dx, dy, xx, yy -- これらの変数はこの関数の中でしか使わない
dx = 0
dy = 0
if joystick and joystick:isConnected() then -- ジョイスティックが接続されているなら
local ax = joystick:getAxis(1) -- 左右方向
if ax > 0 then
dx = speed * dt
elseif ax < 0 then
dx = -speed * dt
end
ax = joystick:getAxis(2) -- 上下方向
if ax > 0 then
dy = speed * dt
elseif ax < 0 then
dy = -speed * dt
end
end
if love.keyboard.isDown("right") then -- 右矢印キーが押されている
dx = speed * dt
elseif love.keyboard.isDown("left") then -- 左矢印キーが押されている
dx = -speed * dt
elseif love.keyboard.isDown("up") then -- 上矢印キーが押されている
dy = -speed * dt
elseif love.keyboard.isDown("down") then -- 下矢印キーが押されている
dy = speed * dt
end
-- 新しい位置を計算する
xx = x + dx
yy = y + dy
-- 画面の中に収まっていれば x, y を新しい位置で置き換える
if xx >= 0 and xx < width - rsize then x = xx end
if yy >= 0 and yy < height - rsize then y = yy end
end
-- 画面を書き換えるコールバック関数
function love.draw()
love.graphics.setColor(1, 0.5, 0.5) -- 淡い赤色
love.graphics.rectangle("fill", x, y, rsize, rsize)
-- 塗りつぶした四角形を描く
end
ボタンの状態を調べるには、Joystick:isDown(n)
を使うことができる。この関数は、「n
番目のボタンが押された状態である」なら true
を返し、そうでなければ false
を返す。先ほどのサンプルプログラム 10-02 では、この関数を使って「今ボタンが押されているかどうか」を画面に表示した。
しかし、ボタンについては、注意すべき点がある。例えば、Aボタンが「弾を撃つ」機能を持っているとしよう。そして、love.update()
の中でボタンの状態を調べて処理したとする。このとき、love.update()
が例えば1秒間に60回呼び出されたとすると、ボタンを押している間じゅう1秒間に60回の連射をすることになる。高橋名人やウメハラさんもびっくりである。
これを避けるには、「直前に弾を撃った時刻」を覚えておいて、一定時間が経過するまでは次の弾を出さないようにすればよい。「現在の時刻」は love.timer.getTime()
で取得できるので、例えば次のようになる。0.125秒=1/8秒ごとにすれば、8連射となる。
function love.load()
...
lasttime = love.timer.getTime() -- 直前の時刻の初期値
end
function love.update(dt)
local t = love.timer.getTime() -- 現在の時刻
if t - lasttime >= 0.125 then -- 0.125秒以上経過したか?
-- 弾を発生させる処理
lasttime = t -- 直前に撃った時刻を更新
end
...
end
プログラム例を示す。A ボタン、またはキーボードのスペースキーが押されていれば弾を発射するようにした。
-- サンプルプログラム 10-04 main.lua
-- 最初に1回だけ呼び出されるコールバック関数
function love.load()
width = love.graphics.getWidth() -- 現在の画面の横幅
height = love.graphics.getHeight() -- 現在の画面の高さ
love.graphics.setBackgroundColor(0, 0.24, 0) -- 背景を濃い緑色に
rsize = width / 25 -- 四角形のサイズ
x = 0 -- x 座標を0にする
y = 0 -- y 座標を0にする
speed = (width - rsize) / 3 -- 移動速度:1秒間に横幅の1/3
bullets = {} -- 弾の位置:{x, y} の配列
bsize = 3 -- 弾のサイズ
bspeed = (width - rsize) / 1.5 -- 弾の移動速度
lasttime = love.timer.getTime() -- 直前の時刻の初期値
end
-- ジョイスティックが接続された時に呼ばれるコールバック関数
function love.joystickadded(j)
joystick = j -- 接続されたジョイスティックオブジェクトを joystick に記録
end
-- 定期的に呼び出されるコールバック関数
function love.update(dt)
local dx, dy, xx, yy -- これらの変数はこの関数の中でしか使わない
dx = 0
dy = 0
if joystick and joystick:isConnected() then -- ジョイスティックが接続されているなら
local ax = joystick:getAxis(1) -- 左右方向
if ax > 0 then
dx = speed * dt
elseif ax < 0 then
dx = -speed * dt
end
ax = joystick:getAxis(2) -- 上下方向
if ax > 0 then
dy = speed * dt
elseif ax < 0 then
dy = -speed * dt
end
end
if love.keyboard.isDown("right") then -- 右矢印キーが押されている
dx = speed * dt
elseif love.keyboard.isDown("left") then -- 左矢印キーが押されている
dx = -speed * dt
elseif love.keyboard.isDown("up") then -- 上矢印キーが押されている
dy = -speed * dt
elseif love.keyboard.isDown("down") then -- 下矢印キーが押されている
dy = speed * dt
end
if (joystick and joystick:isDown(1)) or love.keyboard.isDown("space") then
-- Aボタン、またはスペースバーが押されているなら
local t = love.timer.getTime() -- 現在の時刻
if t - lasttime >= 0.125 then -- 0.125秒以上経過したか?
local b = {x + rsize / 2 + bsize, y + rsize / 2}
table.insert(bullets, b) -- 弾の位置の配列に新しく追加
lasttime = t -- 直前に撃った時刻を更新
end
else
-- 何も押されていなければ lasttime を「今より1秒前」にしておく
-- (次にボタンが押された時に確実に弾を出すため)
lasttime = love.timer.getTime() - 1.0
end
-- 新しい位置を計算する
xx = x + dx
yy = y + dy
-- 画面の中に収まっていれば x, y を新しい位置で置き換える
if xx >= 0 and xx < width - rsize then x = xx end
if yy >= 0 and yy < height - rsize then y = yy end
-- 弾の位置を更新する(テーブルの末尾から順番に)
for i = #bullets, 1, -1 do
local b = bullets[i]
xx = b[1] + bspeed * dt
if xx >= width then -- 画面の外に出た
table.remove(bullets, i) -- bullets から取り除く
else
b[1] = xx
end
end
end
-- 画面を書き換えるコールバック関数
function love.draw()
love.graphics.setColor(1, 0.5, 0.5) -- 淡い赤色
love.graphics.rectangle("fill", x, y, rsize, rsize) -- 塗りつぶした四角形を描く
love.graphics.setColor(1, 1, 1) -- 白
-- 弾を描く
for i = 1, #bullets do
local b = bullets[i]
love.graphics.circle("fill", b[1], b[2], bsize)
end
end
急にゲームっぽくなってきました。といっても、敵も出てこないし背景もないので、射撃練習みたいな雰囲気ですが。
目次