LÖVE (Love2D) 入門編:12.マウス・タッチスクリーンを使う

(2018.8.7. 公開) (2018.8.26. 対象をバージョン 11 系列に変更)

1. マウスクリックを検知する

 マウスやタッチスクリーンが「押された」ことを知るには、love.mousepressed() というコールバックを使えばよい。

  function love.mousepressed(x, y, button, istouch)
    -- (x, y): マウスの座標
    -- button: ボタンの番号 (1: 左、2: 右、3: 真ん中)
    -- istouch: タッチスクリーンなら true, そうでなければ false、
  end

 さっそくプログラム例。画面を円が移動していく。マウスでクリックするたびに、その円が2つに割れて、小さい円になる。どうってことなさそうだけど、案外ハマるよ。

-- サンプルプログラム 12-01 main.lua
function love.load()
  love.graphics.setBackgroundColor(1, 1, 1)
  width = love.graphics.getWidth()
  height = love.graphics.getHeight()
  c = {{width / 2, height / 2, 64, 100, 1.0}}  -- x座標、y座標、半径、スピード、方向
end

-- 点 (x1, y1) と (x2, y2) の間の距離を返す
function distance(x1, y1, x2, y2)
  local xx = x1 - x2
  local yy = y1 - y2
  return math.sqrt(xx * xx + yy * yy)
end

function love.mousepressed(x, y, button, istouch)
  local ii
  for i, a in ipairs(c) do   -- 配列 c の各要素について調べる
    if distance(x, y, a[1], a[2]) < a[3] then  -- ヒットしたか
      table.remove(c, i)
      if a[3] > 2 then
        -- 新しい小さい円を2つ作る
        table.insert(c, {a[1], a[2], a[3] / 1.414, a[4] * 1.2, a[5] + love.math.random()})
        table.insert(c, {a[1], a[2], a[3] / 1.414, a[4] * 1.2, a[5] - love.math.random()})
      end
      break
    end
  end
end

function love.update(dt)
  for i, a in ipairs(c) do
    local x, y = a[1], a[2]
    local dx = a[4] * dt * math.cos(a[5])
    local dy = a[4] * dt * math.sin(a[5])
    x = x + dx
    y = y + dy
    if x < a[3] or x > width - a[3] then
      a[5] = 3.1415927 - a[5] -- 左右反転
      x = x - 2 * dx
    end
    if y < a[3] or y > height - a[3] then
      a[5] = -a[5] -- 上下反転
      y = y - 2 * dy
    end
    a[1], a[2] = x, y
  end
end
  
function love.draw()
  love.graphics.setColor(0, 0, 1)
  for i, a in ipairs(c) do
    love.graphics.circle("fill", a[1], a[2], a[3])
  end
end

2. マウスボタンが離されるまで処理を続ける

 上のようにリアルタイム性の高い場合は、「クリックした瞬間」に処理すればよいが、一般的には「マウスで何かを押すと、その部分の色が変わる」→「ボタンを離した瞬間に処理が行われる」という手順が普通である。このため、「押された」→「まだ押されてる」→「離された」という一連の流れをプログラムに組み込んでみる。

 例として、画面にボタンを表示して、それを「押す」→「離す」という操作で何かメッセージを出してみる。水色の四角がボタン。押されると緑色になり、離されると元に戻って「呼んだ?」というメッセージが表示される。

 マウスボタンを離す操作、およびマウスポインタを動かす操作は、以下のコールバックで処理する。

  function love.mousemoved(x, y, dx、dy, istouch)
    -- (x, y): マウスの座標
    -- (dx, dy): 直前に呼ばれた時からの移動量
    -- istouch: タッチスクリーンなら true, そうでなければ false、
  end
  function love.mousereleased(x, y, button, istouch)
    -- (x, y): マウスの座標
    -- button: ボタンの番号 (1: 左、2: 右、3: 真ん中)
    -- istouch: タッチスクリーンなら true, そうでなければ false、
  end

 処理は次のように進める。まず、ボタンが押されているかどうかを示す変数を1つ決める(ここでは pressed とする)。値の意味は次のように決めておく。

意味
0マウスボタンは押されていない
1マウスボタンが押されている
ポインタは画面ボタンの枠の中
2マウスボタンが押されている
ポインタは画面ボタンの枠の外

 love.mousepressed() が呼ばれたら、マウスポインタの座標を調べて、画面ボタンの枠の中にあれば、pressed を 1 にする。pressed が 0 でない間は、love.mousemoved() で、ポインタが画面ボタンの枠から外れれば pressed を 2 にし、枠に入っていれば 1 に戻す。love.mousereleased() では、pressed が 1 の時に限って、「ボタンが離された」時の処理を行う。

-- サンプルプログラム 12-02 main.lua
-- ipag.ttf (IPAゴシックフォント) が必要
function love.load()
  love.graphics.setBackgroundColor(1, 1, 1)
  font = love.graphics.newFont("ipag.ttf", 24)     -- IPAゴシック、24ポイントのフォントを作る
  love.graphics.setFont(font)                      -- そのフォントを設定
  rect = { 20, 20, 80, 40 }  -- ボタンの枠
  pressed = 0                -- 押されていない
  yonda = nil                -- ボタンが押されたら、押された時刻が入る
end

function point_in_rect(x, y, rect)
  return x >= rect[1] and x <= rect[1] + rect[3]
  and y >= rect[2] and y <= rect[2] + rect[4]
end

function love.mousepressed(x, y, button, istouch)
  if button == 1 and point_in_rect(x, y, rect) then
    -- マウスがボタンの枠の中で押された
    pressed = 1  -- 「押された」状態にする
  end
end

function love.mousemoved(x, y, dx, dy, istouch)
  if pressed ~= 0 then
    if point_in_rect(x, y, rect) then
      pressed = 1  -- 押されている
    else
      pressed = 2  -- まだ押されているがボタンの枠から外れた
    end
  end
end

function love.mousereleased(x, y, button, istouch)
  if pressed == 1 then  -- 枠の中で押されていた
    yonda = love.timer.getTime()  -- メッセージ表示のためのタイマーをセット
  end
  pressed = 0
end

function love.draw()
  if pressed == 1 then
    love.graphics.setColor(0, 1, 0)
  else
    love.graphics.setColor(0, 1, 1)
  end
  love.graphics.rectangle("fill", rect[1], rect[2], rect[3], rect[4], 10, 10)
  if yonda then
    if love.timer.getTime() - yonda < 5 then
      -- ボタンが離されてから5秒間メッセージを表示
      love.graphics.setColor(0, 0, 0)
      love.graphics.print("呼んだ?", 120, 20)
    else
      -- 5秒たったら yonda を nil に戻す
      yonda = nil
    end
  end
end

3. ラズパイでタッチスクリーンを使う

 ラズパイでタッチスクリーンを使いたいと思ったのだが、どうも素のままのビルドではだめらしい。私が使っているのは Elecrow の5インチディスプレイで、ADS7846 をコントローラとする抵抗式タッチスクリーンがついている。Raspbian に tslib を入れれば X window なしでも利用できるのだが、LÖVE ではうまくいかない。下のようなエラーメッセージが出てしまう。

INFO: The key you just pressed is not recognized by SDL. To help get this fixed, please report this to the SDL forums/mailing list <https://discourse.libsdl.org/> EVDEV KeyCode 330

 SDL がタッチスクリーンを認識していないらしい。環境変数などをいろいろいじってみたが、結局 SDL2 にパッチを当てて再ビルドする必要があった。「インストール」のページに改訂版を置いておきましたので、お試しください。パッチは SDL-2.0.8 専用。作成には Cedric Paille さんの SDL2-PI を参考にした。

 なお、このバージョンでは、タッチスクリーンはマウスとして認識される。また、他のタッチスクリーン(特に純正の7インチタッチスクリーン)での動作は未確認。いろいろと不完全だが、SDL2, LÖVE がきちんと対応するまでは辛抱するしかない。純正スクリーンについては、入手できれば動作確認したいと思っています。

目次