Android で Lua 言語で LÖVE2d でマルチタッチ

# 序
　アンドロイドでマルチタッチのプログラムを python でやろうとするとアプリの pydroid3 だと、pygame2 のマルチタッチ機能のついたものでは、そのままできた。しかし、termux を使って python で作ると、x 転送しないと GUI へグラフィックスを表示できないので、なかなか面倒だ。でも、可能だが。（Android OS 中だけですべて完結させるという意味です。ついでに言うと、とても安くて入手しやすい 8 インチ以上のタブレットでクロスプラットフォームなプログラムは作って配布可能かという探索です。）

https://qiita.com/dauuricus/items/8b3c079b0cea067909d0

Lua ならマルチタップのゲームループを使ったプログラム（ゲームとは限りません）はどうなのか？

https://m.youtube.com/watch?v=RgEU60CwWM8&feature=

できそうではある。

というのが本題で、それはとても簡単だった。（pygame2 でマルチタッチのゲームコントローラーをゼロから作りたい場合参考になるか。よく似てるから。）

# テトリスのようなブロックのゲームで解説。

https://youtu.be/OVXleROVw5M

　テトリス（だと語弊があるので）のようなゲームの作り方は、むずかしいので解説しないですが、ゲームのコーディングの詳細はこちらのチュートリアルにすべてありますので参照してください。
とても丁寧にチュートリアルを作られているので、めちゃくちゃよいです。

https://simplegametutorials.github.io/love/blocks/

>https://simplegametutorials.github.io/about/
>About these tutorials

>Welcome!

>Here is how these tutorials work:

>First there is a download link to the complete game, and a description of the rules and controls of the game. I recommend playing the game before moving on to the next steps so you can have a good understanding of what the game does.

>Then there is a basic overview of the game's data and logic, like how you might think through how you're going to approach programming the game before you begin coding.

>Then the code is built up step-by-step. This includes intermediary steps that don't appear in the final code but help us get there. I recommend following along in your own code editor and running the code after ever step.

それ以前に LÖVE2d でハローワールドまでが大変そうという方は、こちら。

>

https://qiita.com/dauuricus/items/1fd6ec4b052f4cb88125

>たぶん想像よりは道のり短いです。pythonでマルチタッチについて疑問に思ってから、たどり着くまでに一年くらいかかりましたが、ここまではこれた生物はワンクリックでたどり着きます。

LÖVE2d について、ものすごく要約すると、main.lua、conf.lua、他に必要があれば用意したサウンドエフェクツやプラグインのようなプログラムのファイルをまとめて zip 圧縮したものを LÖVE2d で開いたらプログラムが動くということ。
そういうわけなので LÖVE2d が対応しているならば、そのプラットフォームでは同じ Lua のプログラムコードが動作するということです。

　ここでの本題は、上記のチュートリアルのテトリスのようなゲーム **block** はキーボードの入力を待ち受けているので、「**画面タッチにも対応させます**」ということになります。

Jigoku さんの作った LÖVE2d 用の Paddy というマルチタップ用のコントローラーを使います。

https://github.com/Jigoku/paddy/blob/master/paddy.lua

この記事では、`main.lua`,`paddy.lua`,`conf.lua`の 3 つのファイルだけでチュートリアルの**Block**というゲームを画面タッチコントロールにします。
この 3 つを ZIP でアーカイブして、LÖVE2d で開くとゲームとして遊べます。

つまり、３つファイルの内容をコピペして zip 圧縮してして、block.love というファイル名にして LÖVE2d インストールして block.love クリックしてみて内容確認して、から読んだ方がいいです。

Android OS の場合は、アプリストアか、LÖVE2d のホームページから LÖVE2d をダウンロードしてインストールします。

！ 10インチの画面サイズを想定しています。

**main.lua**

```lua:main.lua
function love.load()
	require('paddy') -- [[ここでpaddyを読み込みます]]
	press = false
	time = 0
	timeA = 0
--	music = love.audio.newSource("All_is_full_of_love1.mp3", "stream") --[[BGMがほしければ、なにかいい感じに曲を作って読み込むといい。musescoreとかで譜面で書いてmp3でダウンロードするなど。]]
--	music:play()
    love.graphics.setBackgroundColor(.43,.43,.43)

    pieceStructures = {
        {
            {
                {' ', ' ', ' ', ' '},
                {'i', 'i', 'i', 'i'},
                {' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'i', ' ', ' '},
                {' ', 'i', ' ', ' '},
                {' ', 'i', ' ', ' '},
                {' ', 'i', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {' ', 'o', 'o', ' '},
                {' ', 'o', 'o', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {'j', 'j', 'j', ' '},
                {' ', ' ', 'j', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'j', ' ', ' '},
                {' ', 'j', ' ', ' '},
                {'j', 'j', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {'j', ' ', ' ', ' '},
                {'j', 'j', 'j', ' '},
                {' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'j', 'j', ' '},
                {' ', 'j', ' ', ' '},
                {' ', 'j', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {'l', 'l', 'l', ' '},
                {'l', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'l', ' ', ' '},
                {' ', 'l', ' ', ' '},
                {' ', 'l', 'l', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', ' ', 'l', ' '},
                {'l', 'l', 'l', ' '},
                {' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {'l', 'l', ' ', ' '},
                {' ', 'l', ' ', ' '},
                {' ', 'l', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {'t', 't', 't', ' '},
                {' ', 't', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 't', ' ', ' '},
                {' ', 't', 't', ' '},
                {' ', 't', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 't', ' ', ' '},
                {'t', 't', 't', ' '},
                {' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 't', ' ', ' '},
                {'t', 't', ' ', ' '},
                {' ', 't', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {' ', 's', 's', ' '},
                {'s', 's', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {'s', ' ', ' ', ' '},
                {'s', 's', ' ', ' '},
                {' ', 's', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {'z', 'z', ' ', ' '},
                {' ', 'z', 'z', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'z', ' ', ' '},
                {'z', 'z', ' ', ' '},
                {'z', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
    }

    gridXCount = 10
    gridYCount = 18

    pieceXCount = 4
    pieceYCount = 4

    timerLimit = 0.5

    function canPieceMove(testX, testY, testRotation)
        for y = 1, pieceYCount do
            for x = 1, pieceXCount do
                local testBlockX = testX + x
                local testBlockY = testY + y

                if pieceStructures[pieceType][testRotation][y][x] ~= ' ' and (
                    testBlockX < 1
                    or testBlockX > gridXCount
                    or testBlockY > gridYCount
                    or inert[testBlockY][testBlockX] ~= ' '
                ) then
                    return false
                end
            end
        end

        return true
    end

    function newSequence()
        sequence = {}
        for pieceTypeIndex = 1, #pieceStructures do
            local position = love.math.random(#sequence + 1)
            table.insert(
                sequence,
                position,
                pieceTypeIndex
            )
        end
    end

    function newPiece()
        pieceX = 3
        pieceY = 0
        pieceRotation = 1
        pieceType = table.remove(sequence)

        if #sequence == 0 then
            newSequence()
        end
    end

    function reset()
        inert = {}
        for y = 1, gridYCount do
            inert[y] = {}
            for x = 1, gridXCount do
                inert[y][x] = ' '
            end
        end

        newSequence()
        newPiece()

        timer = 0
    end

    reset()
end

function love.update(dt)
    if not music:isPlaying( ) then
        love.audio.play( music )
    end
    timer = timer + dt
    timeA = timeA + dt
    paddy.update(dt)
    if press ~= true then
        if paddy.isDown("b") then
	    time = timeA
            local testRotation = pieceRotation + 1
            if testRotation > #pieceStructures[pieceType] then
                testRotation = 1
            end

            if canPieceMove(pieceX, pieceY, testRotation) then
                pieceRotation = testRotation
                press = true
            end

        elseif paddy.isDown("x") then
	    time = timeA
            local testRotation = pieceRotation - 1
            if testRotation < 1 then
                testRotation = #pieceStructures[pieceType]
            end

            if canPieceMove(pieceX, pieceY, testRotation) then
                pieceRotation = testRotation
                press = true
            end

        elseif paddy.isDown("left") then
	    time = timeA
            local testX = pieceX - 1

            if canPieceMove(testX, pieceY, pieceRotation) then
                pieceX = testX
                press = true
            end

        elseif paddy.isDown("right") then
	    time = timeA
            local testX = pieceX + 1

            if canPieceMove(testX, pieceY, pieceRotation) then
                pieceX = testX
                press = true
            end

        elseif paddy.isDown("down") or paddy.isDown("a") then
	    time = timeA
            press = true
            while canPieceMove(pieceX, pieceY + 1, pieceRotation) do
                pieceY = pieceY + 1
                timer = timerLimit
            end
	end
    else
	if time + 0.2  < timeA then
		press = false
	end
    end

    if timer >= timerLimit then
        timer = 0



        local testY = pieceY + 1
        if canPieceMove(pieceX, testY, pieceRotation) then
            pieceY = testY
        else
            -- Add piece to inert
            for y = 1, pieceYCount do
                for x = 1, pieceXCount do
                    local block =
                        pieceStructures[pieceType][pieceRotation][y][x]
                    if block ~= ' ' then
                        inert[pieceY + y][pieceX + x] = block
                    end
                end
            end

            -- Find complete rows
            for y = 1, gridYCount do
                local complete = true
                for x = 1, gridXCount do
                    if inert[y][x] == ' ' then
                        complete = false
                        break
                    end
                end

                if complete then
                    for removeY = y, 2, -1 do
                        for removeX = 1, gridXCount do
                            inert[removeY][removeX] = inert[removeY - 1][removeX]
                        end
                    end

                    for removeX = 1, gridXCount do
                        inert[1][removeX] = ' '
                    end
                end
            end

            newPiece()

            if not canPieceMove(pieceX, pieceY, pieceRotation) then
                reset()
            end
        end
    end
end

function love.keypressed(key)
    if key == 'x' then
        local testRotation = pieceRotation + 1
        if testRotation > #pieceStructures[pieceType] then
            testRotation = 1
        end

        if canPieceMove(pieceX, pieceY, testRotation) then
            pieceRotation = testRotation
        end

    elseif key == 'z' then
        local testRotation = pieceRotation - 1
        if testRotation < 1 then
            testRotation = #pieceStructures[pieceType]
        end

        if canPieceMove(pieceX, pieceY, testRotation) then
            pieceRotation = testRotation
        end

    elseif key == 'left' then
        local testX = pieceX - 1

        if canPieceMove(testX, pieceY, pieceRotation) then
            pieceX = testX
        end

    elseif key == 'right' then
        local testX = pieceX + 1

        if canPieceMove(testX, pieceY, pieceRotation) then
            pieceX = testX
        end

    elseif key == 'c' then
        while canPieceMove(pieceX, pieceY + 1, pieceRotation) do
            pieceY = pieceY + 1
            timer = timerLimit
        end
    end
end

function love.draw()
    local function drawBlock(block, x, y)
        local colors = {
            [' '] = {.57, .57, .57},
           -- i = {.47, .76, .94},
           -- j = {.93, .91, .42},
           -- l = {.49, .85, .76},
           -- o = {.92, .69, .47},
           -- s = {.83, .54, .93},
           -- t = {.97, .58, .77},
           -- z = {.66, .83, .46},
            i = {.99, .99, .99},
            j = {.99, .99, .99},
            l = {.99, .99, .99},
            o = {.99, .99, .99},
            s = {.99, .99, .99},
            t = {.99, .99, .99},
            z = {.99, .99, .99},
            preview = {.75, .75, .75},
        }
        local color = colors[block]
        love.graphics.setColor(color)

        local blockSize = 46
        local blockDrawSize = blockSize - 1
        love.graphics.rectangle(
            'fill',
            (x - 1) * blockSize,
            (y - 1) * blockSize,
            blockDrawSize,
            blockDrawSize
        )
    end

    local offsetX = 4
    local offsetY = 1

    for y = 1, gridYCount do
        for x = 1, gridXCount do
            drawBlock(inert[y][x], x + offsetX, y + offsetY)
        end
    end

    for y = 1, pieceYCount do
        for x = 1, pieceXCount do
            local block = pieceStructures[pieceType][pieceRotation][y][x]
            if block ~= ' ' then
                drawBlock(block, x + pieceX + offsetX, y + pieceY + offsetY)
            end
        end
    end

    for y = 1, pieceYCount do
        for x = 1, pieceXCount do
            local block = pieceStructures[sequence[#sequence]][1][y][x]
            if block ~= ' ' then
                drawBlock('preview', x + 7, y + 20 )
            end
        end
    end

    paddy.draw()
end
```

Paddy の方のコードを調整してコントローラーの部分を画面のいい位置にしないといけない。

**paddy**
<details><sumarry>paddy.lua code :the GNU General Public License</sumarry><div>

```lua:paddy.lua
--[[
 paddy - an onscreen controller display for touch enabled devices


 * Copyright (C) 2017 Ricky K. Thomson
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * u should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 --]]

paddy = {}
paddy.debug = true

-- The size of the buttons which can be pressed.
paddy.buttonw = 80
paddy.buttonh = 80


-- This lists any buttons which are currently being pressed
paddy.touched = {}

-- Create a dpad widget
paddy.dpad = {}

-- The properties of the canvas to draw
paddy.dpad.w = paddy.buttonw*3
paddy.dpad.h = paddy.buttonh*3
paddy.dpad.x = 10
paddy.dpad.y = love.graphics.getHeight()-20-paddy.dpad.h + 450
paddy.dpad.canvas = love.graphics.newCanvas(paddy.dpad.w,paddy.dpad.h)

-- These just make things look prettier
paddy.dpad.opacity = 200
paddy.dpad.padding = 5

-- Setup the names for the buttons, and their position on the canvas
paddy.dpad.buttons = {
	{ name="up",   x=paddy.buttonw, y=0 },
	{ name="left", x=0, y=paddy.buttonh },
	{ name="right",x=paddy.buttonw*2, y=paddy.buttonh },
	{ name="down", x=paddy.buttonw, y=paddy.buttonh*2 },
}


-- Create a buttons widget
paddy.buttons = {}

-- The properties of the canvas to draw
paddy.buttons.w = paddy.buttonw*3
paddy.buttons.h = paddy.buttonh*3
--paddy.buttons.x = love.graphics.getWidth()-20-paddy.buttons.w
paddy.buttons.x = 555
paddy.buttons.y = love.graphics.getHeight()-20-paddy.buttons.h + 450
paddy.buttons.canvas = love.graphics.newCanvas(paddy.buttons.w,paddy.buttons.h)

-- These just make things look prettier
paddy.buttons.opacity = 200
paddy.buttons.padding = 5

-- Setup the names for the buttons, and their position on the canvas
paddy.buttons.buttons = {
	{ name="y", x=paddy.buttonw, y=0 },
	{ name="x", x=0, y=paddy.buttonh },
	{ name="b", x=paddy.buttonw*2, y=paddy.buttonh },
	{ name="a", x=paddy.buttonw, y=paddy.buttonh*2 },
}

-- Stores any widgets containing interactive buttons
paddy.widgets = { paddy.dpad, paddy.buttons }


function paddy.draw()
	-- Draw the control pad
	for _,widget in ipairs(paddy.widgets) do
		love.graphics.setColor(0.607,0.607,0.607,0.196)
		love.graphics.circle("fill", widget.x+widget.w/2,widget.y+widget.h/2,widget.w/2)

		love.graphics.setCanvas(widget.canvas)
		love.graphics.clear()
	
		love.graphics.setColor(0.607,0.607,0.607,1)
	

		for _,button in ipairs(widget.buttons) do
			if button.isDown then
				love.graphics.setColor(0.607,0.607,0.607,1)
				love.graphics.rectangle("fill", 
					button.x+widget.padding, 
					button.y+widget.padding, 
					paddy.buttonw-widget.padding*2, 
					paddy.buttonh-widget.padding*2,
					10
				)
			else
				love.graphics.setColor(0.607,0.607,0.607,0.784)	
				love.graphics.rectangle("line", 
					button.x+widget.padding, 
					button.y+widget.padding, 
					paddy.buttonw-widget.padding*2, 
					paddy.buttonh-widget.padding*2,
					10
				)
			end
			
			
			-- Temporary code until  button naming can be improved
			if paddy.debug then
				love.graphics.setColor(1,1,1,1)
				
				local font = love.graphics.newFont(10)
				love.graphics.setFont(font)
				local str = button.name
				

				
				love.graphics.printf(
					button.name, 
					button.x+paddy.buttonw/2,
					button.y+paddy.buttonh/2, 
					font:getWidth(str),
					"center"
				)
			end
		end
	
		love.graphics.setCanvas()
		love.graphics.setColor(1,1,1,widget.opacity)
		love.graphics.draw(widget.canvas, widget.x, widget.y)
	end
	
	
	-- debug related
	if paddy.debug then
		for _,id in ipairs(paddy.touched) do
			local x,y = love.touch.getPosition(id)
			love.graphics.circle("fill",x,y,20)
		end
	end

end

function paddy.isDown(key)
	-- Check for any buttons which are currently being pressed
	for _,widget in ipairs(paddy.widgets) do
		for _,button in ipairs(widget.buttons) do
			if button.isDown and button.name == key then return true end
		end
	end
end

function paddy.update(dt)
	-- Decide which buttons are being pressed based on a 
	-- simple collision, then change the state of the button

	paddy.touched = love.touch.getTouches()
	
 	for _,widget in ipairs(paddy.widgets) do
		for _,button in ipairs(widget.buttons) do
			button.isDown = false
			for _,id in ipairs(paddy.touched) do	
				local tx,ty = love.touch.getPosition(id)
				if  tx >= widget.x+button.x 
				and tx <= widget.x+button.x+paddy.buttonw 
				and ty >= widget.y+button.y 
				and ty <= widget.y+button.y+paddy.buttonh then
					button.isDown = true
				end
			end
		end
	end
end


return paddy
```

</div></details>

オリジナルの paddy.lua を書き換えたのはボタンの表示位置に関する部分。だけだったと記憶していますが、deff で確認していません。オリジナルの場合は右の方に表示させたいボタンが消えますから、画面の幅にあわせた位置にボタンがくるようにしたいとポジションに関する箇所のコードを書き換えました。
それと paddy.lua は update でボタンの入力状況を判断するコードなので、press　（ボタンを押した） と release （ボタンを離した） の判断をなんとかしないといけないので、main.lua の方で timer を使ってなんとかしました。それは `timeA` が現れる箇所になります。他にも方法はあるとおもいます。

```
paddy.dpad.x = 10
paddy.dpad.y = love.graphics.getHeight()-20-paddy.dpad.h + 450
```

```
paddy.buttons.x = 555
paddy.dpad.y = love.graphics.getHeight()-20-paddy.dpad.h + 450
```

**conf.lua**

```lua:conf.lua
function love.conf(t)
    t.window.width = 40 * 14
    t.window.height = 40 * 25
end
```

スーパーコライダーでゲームのサウンドエフェクトを作れれば楽しそう。
https://supercollider.github.io/

https://qiita.com/dauuricus/items/bacad647e681ba30f3fb