Godotで複数指が触っている物体の検出をする

# 最初に
あまりにもGodotのスクリーンタッチに関する情報が少なくて苦戦したので、これが助けになるかはわかりませんが記事を書こうと思いました。

### 環境
+ Godot 4.2.1
+ Windows 11

# 経緯
GarageBandのピアノや、音ゲーのようなものを作りたいと思いGodotを勉強していました。しかし、マウスがそのオブジェクトを触っている状態かどうかという情報を入手する簡単な関数がないので、「鍵盤が押されている」「レーンを触っている」状態を入手することが非常に困難でした。
その状態を入手する方法を頑張って作ったので記していきます。


# 実際に作ったもの
動画の載せ方を知らないのでツイッターからになってしまいますが、このようなデモを作ってみました。

https://twitter.com/i/status/1779894477748076596

GitHubにもデモを載せておきました。初めてGitHub使ったので間違っているかもしれません。

https://github.com/stuckedmochi/GodotMultiTouch

# 作成
## 設定変更
コードに移る前にセッティングやノードなどを配置していきます。
まず`Project Settings >> Input Devices`に行き、
`Emulate Mouse From Touch`を無効にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669884/9b51cd97-17fa-1f71-08f2-e5ca2acacc10.png)

## タッチ判定ノードを作成
タッチ判定で色が変わるノードを作成していきます。
ノード構成はこのようになっています。形は好きな形で大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669884/38ffeeb2-08bb-c1a6-fc2b-13aa241b5678.png)

`MeshInstance3D`については、色を変えないといけないので以下のように`Surface Material Override`に`Standard Material 3D`を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669884/8897c4c2-7b81-ce64-eb42-78185d085961.png)

ここで注意なのですが、`Standard Material 3D`の`Resource`の`Local to Scene`をONにしないと`CollideObject`の色が連動して変わってしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669884/68b30113-3556-33e8-4895-0e0a3e806ceb.png)


外観はこのようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669884/31d0d743-d65d-43fc-2e34-a53a023435d4.png)
白い[]は`touchingIndicesLabel`です。


## メインノードを作成
ノード構成はこのようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669884/d469c54f-cf08-eedf-db82-fa6edba57327.png)
上で作ったタッチ判定ノードの`CollideObject`を`CollideObjects`という`Node3D`でひとまとめにします。
`CollideObject`が一つ外にあるのは、最終的に`CollideObjects`子ノードでないと判定されないかどうかを確認するためです。

配置はこのようにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669884/19d1fce1-5891-4920-0ee1-7d09c00edf83.png)

# スクリプトをアタッチ
CollideObjectに以下のスクリプトをアタッチします。

```GDScript:collide_object.gd
extends StaticBody3D

@onready var mesh_instance_3d = $MeshInstance3D
@onready var touching_indices_label = $touchingIndicesLabel
@onready var material = mesh_instance_3d.get_surface_override_material(0)

const OFF_COLOR = Color(0, 0, 0)
const ON_COLOR = Color(255, 255, 255)

var touchingIndices = {}
var was_touching = false

func _ready():
	changeColor(OFF_COLOR)
	
func changeColor(color):
	material.albedo_color = color
	mesh_instance_3d.set_surface_override_material(0, material)

func screenTouchUpdate(event, colliding:bool):
	var val = event.index
	
	if colliding:
		touchingIndices[val] = event.position
	else:
		touchingIndices.erase(val)
		
	var is_touching = not touchingIndices.is_empty()
	if was_touching != is_touching:
		if is_touching:#just pressed / entered
			changeColor(ON_COLOR)
		else:#just released / left
			changeColor(OFF_COLOR)
			
	was_touching = is_touching
	touching_indices_label.text = str(touchingIndices.keys())
 ```

 メインノードの`CollideObjects`に以下のスクリプトをアタッチします。
 

 ```GDscript:CollideObjects.gd
extends Node3D

@onready var main_camera = $"../MainCamera"


func getIntersect(pos):
	var worldspace = get_world_3d().direct_space_state
	var start = main_camera.project_ray_origin(pos)
	var end = main_camera.project_position(pos, 1000)
	var parameters = PhysicsRayQueryParameters3D.create(start, end)
	var result = worldspace.intersect_ray(parameters)
	
	return result
	
func _input(event):
	#mouse debug
	if event is InputEventMouse:
		if event.is_pressed():
			print(getIntersect(event.position))
	
	elif event is InputEventScreenTouch or event is InputEventScreenDrag:
		get_viewport().set_input_as_handled()
		var collider = null
		if event is InputEventScreenTouch and event.is_released():#touch released
			collider = null
		else:#touch pressed / entered
			var intersect = getIntersect(event.position)
			if not intersect.is_empty():
				collider = intersect["collider"]
				
			
		for collideObject in get_children():
			var colliding = collider == collideObject
			collideObject.screenTouchUpdate(event, colliding)
 ```

 これで完成です。

 # 参考にした情報
 上記の`getIntersect`関数に関してはこの動画を参考にしました。
 
 https://youtu.be/42q6vZSvtxc?si=hFc7WJF4QnV_A5dK

 複数指のトラッキングについてはこのサイトを参考にしました。
 
 https://godotengine.org/asset-library/asset/142

 その他
 
 https://docs.godotengine.org/en/latest/classes/class_viewport.html#class-viewport-method-set-input-as-handled