Last active
August 16, 2021 22:41
-
-
Save ZodmanPerth/fa5dd4b800f1237f3de8dfe58a4a5951 to your computer and use it in GitHub Desktop.
Touch Manipulation Capability Test on Godot. Blog post at http://www.redperegrine.net/2018/08/21/say-gday-to-godot/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Area2D | |
onready var collisionRect = $CollisionShape2D.shape | |
var sideLength = 128 | |
var lastSideLength = 0 | |
var borderWidth = 16 | |
var colour = Color("FF0000") | |
var borderColour = Color("800000") | |
var hit = false | |
var touchController | |
var outerRect | |
var innerRect | |
func _ready(): | |
var size = Vector2(sideLength, sideLength) | |
var sideLengthBy2 = sideLength / 2 | |
var sizeBy2 = size / 2 | |
var borderTimes2 = borderWidth * 2 | |
outerRect = Rect2(-sideLengthBy2, -sideLengthBy2, sideLength, sideLength) | |
innerRect = Rect2(-sideLengthBy2 + borderWidth, -sideLengthBy2 + borderWidth, sideLength - borderTimes2, sideLength - borderTimes2) | |
func _process(delta): | |
# recalculate if size has changed | |
if sideLength != lastSideLength: | |
var size = Vector2(sideLength, sideLength) | |
var sizeBy2 = size / 2 | |
collisionRect.extents = sizeBy2 | |
lastSideLength = sideLength | |
# To keep redrawing on every frame | |
update() | |
func _draw(): | |
draw_rect(outerRect, borderColour) | |
draw_rect(innerRect, colour) | |
func _input_event(viewport, event, shape_idx): | |
# check if touching with a single point | |
if (event is InputEventScreenTouch) and event.pressed and touchController.touchPointCount == 1: | |
hit = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[gd_scene load_steps=3 format=2] | |
[ext_resource path="res://touchInput/colourBox.gd" type="Script" id=1] | |
[sub_resource type="RectangleShape2D" id=1] | |
custom_solver_bias = 0.0 | |
extents = Vector2( 32, 32 ) | |
_sections_unfolded = [ "Resource" ] | |
[node name="box" type="Area2D" index="0"] | |
input_pickable = true | |
gravity_vec = Vector2( 0, 1 ) | |
gravity = 98.0 | |
linear_damp = 0.1 | |
angular_damp = 1.0 | |
audio_bus_override = false | |
audio_bus_name = "Master" | |
script = ExtResource( 1 ) | |
_sections_unfolded = [ "Transform" ] | |
__meta__ = { | |
"_edit_group_": true, | |
"_edit_horizontal_guides_": [ ], | |
"_edit_vertical_guides_": [ ] | |
} | |
[node name="CollisionShape2D" type="CollisionShape2D" parent="." index="0"] | |
shape = SubResource( 1 ) | |
_sections_unfolded = [ "Transform" ] | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Naming file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# manages a group of provided nodes to track which is selected via a mouse click. | |
# Nodes are processed for hit detection in the reverse order they are provided, which allows easy adding | |
# as the nodes are added to a SceneTree | |
extends Node | |
var touchController | |
var inputOrderedNodes = [] | |
var isHitPossible = false | |
# expose the selected node | |
var selectedNode = null | |
signal selectedNodeChanged(selectedNode) | |
func _input(event): | |
# check if touching with a single point | |
if (event is InputEventScreenTouch) and event.pressed and touchController.touchPointCount == 1: | |
isHitPossible = true | |
func _process(delta): | |
# process if any child was hit, and clear hit flags on all hit children | |
if isHitPossible: # input event happened | |
for n in inputOrderedNodes: | |
if n.hit: # this node was hit | |
if isHitPossible: # haven't found first hit yet | |
if !selectedNode == n: # handling first hit | |
selectedNode = n | |
emit_signal("selectedNodeChanged", selectedNode) | |
isHitPossible = false # stop looking for first hit | |
n.hit = false # clear hit flag on child | |
isHitPossible = false # clear hit possible in case no child was hit | |
# add a node to track for selection | |
func add(node): | |
inputOrderedNodes.insert(0, node) | |
node.touchController = touchController |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node | |
signal touchStarted | |
signal touchStopped | |
signal manipulationStarted(position) | |
signal manipulationChanged(data) | |
signal manipulationStopped | |
var isManipulating = false | |
var touchPointCount = 0 | |
var touchPoints = {} # the touch points for this pass | |
var lastTouchPoints = {} # the touch points for last pass | |
var touchPairs = {} # consecutive touch point pairs for this pass | |
var lastTouchPairs = {} # consecutive touch point pairs for last pass | |
var maxIndex = 0 # the index of the last touch point to process. Relies on the touch indexes being increasing numbers. | |
var indexesChanged = false # synchronises that at least one touch point has changed between maniptulation calculations | |
const baseVector = Vector2(1, 0) | |
func _input(event): | |
if event is InputEventScreenTouch: | |
# synchronisation flag | |
indexesChanged = true | |
if event.pressed: | |
# add point to register | |
var tp = { "index": event.index, "origin": event.position, "position": event.position } | |
touchPoints[event.index] = tp | |
touchPointCount = touchPoints.size() | |
# remember highest index for synchronising manipulation signals | |
maxIndex = event.index | |
# signals | |
emit_signal("touchStarted") | |
if touchPointCount == 1: | |
emit_signal("manipulationStarted", tp.position) | |
isManipulating = true | |
else: | |
# remove points | |
lastTouchPoints.erase(event.index) | |
touchPoints.erase(event.index) | |
# update maxIndex if necessary | |
touchPointCount = touchPoints.size() | |
if maxIndex == event.index: | |
if touchPointCount != 0: | |
maxIndex = touchPoints.keys()[touchPointCount - 1] | |
else: | |
maxIndex = -1 | |
# signals | |
emit_signal("touchStopped") | |
if touchPointCount == 0: | |
emit_signal("manipulationStopped") | |
isManipulating = false | |
elif event is InputEventScreenDrag: | |
# update position of this touch point | |
var tp = touchPoints[event.index] | |
tp.position = event.position | |
# Start manipulation loop if all points have been synchronised | |
if event.index == maxIndex: | |
handleManipulation() | |
# Manage the calculation of manipulations over all the touch points | |
func handleManipulation() : | |
# calculate centre points of relevant touch point pairs. | |
# centre point: average of all relevant points | |
# relevant: points in lastTouchPoints that are (still) present in touchPoints. | |
# The intersection of lastTouchPoints and TouchPoints | |
# calculate deltaPosition, the change in this manipulation point since the last pass. | |
# manipulation point: the centre point of all points that are relevant on this pass. | |
# This point is based on all values of points on any pass using only the | |
# relevant points from the current pass. | |
var deltaPosition = Vector2(0, 0) | |
if lastTouchPoints.size() != 0: | |
var lastManipulationCentre = Vector2(0, 0) # the last centre of all touch points | |
var thisManipulationCentre = Vector2(0, 0) # the new centre of all touch points | |
var tpCount = 0 | |
for tpKey in touchPoints: | |
if lastTouchPoints.has(tpKey): | |
tpCount += 1 | |
lastManipulationCentre += lastTouchPoints[tpKey].position | |
thisManipulationCentre += touchPoints[tpKey].position | |
# calculate the last and current manipulation point | |
lastManipulationCentre /= tpCount | |
thisManipulationCentre /= tpCount | |
# calculate delta position | |
deltaPosition = thisManipulationCentre - lastManipulationCentre | |
# calculate distances and angles between touch point pairs (if required) | |
var deltaAngle = 0 | |
var deltaScale = 0 | |
var touchPointCount = touchPoints.size() | |
touchPairs.clear() | |
if touchPointCount > 1: | |
var angle | |
var thisTotalAngle = 0 | |
var lastTotalAngle = 0 | |
var pairCount = 0 | |
var distance | |
var thisTotalDistance = 0 | |
var lastTotalDistance = 0 | |
var pairKey | |
var lastPair | |
var thisPoint | |
var nextPoint = touchPoints.values()[0] | |
for i in range(1, touchPointCount): | |
thisPoint = nextPoint | |
nextPoint = touchPoints.values()[i] | |
pairKey = Vector2(thisPoint.index, nextPoint.index) | |
# calculate touch pair data | |
angle = fposmod(baseVector.angle_to(nextPoint.position - thisPoint.position), 2 * PI) | |
distance = thisPoint.position.distance_to(nextPoint.position) | |
touchPairs[pairKey] = { angle = angle, distance = distance } | |
# check if pair is relevant (was present in last pass) | |
if lastTouchPairs.has(pairKey): | |
lastPair = lastTouchPairs[pairKey] | |
pairCount += 1 | |
# get angle details for calculating delta angle | |
thisTotalAngle += angle | |
lastTotalAngle += lastPair.angle | |
# add distance details for calculating delta scale | |
thisTotalDistance += distance | |
lastTotalDistance += lastPair.distance | |
# calculate delta angle | |
if pairCount != 0: | |
deltaAngle = (thisTotalAngle - lastTotalAngle) / pairCount | |
# calculate delta scale | |
if lastTotalDistance != 0: | |
deltaScale = (thisTotalDistance - lastTotalDistance) / lastTotalDistance | |
# set lastTouchPair for next pass | |
lastTouchPairs.clear() | |
for key in touchPairs: | |
lastTouchPairs[key] = touchPairs[key] | |
# set lastTouchPoints | |
if indexesChanged: | |
lastTouchPoints.clear() | |
for tp in touchPoints.values(): | |
lastTouchPoints[tp.index] = tp.duplicate() | |
# clear synchronisation flag | |
indexesChanged = false | |
# signal | |
emit_signal("manipulationChanged", { | |
"delta": { | |
"position": deltaPosition, | |
"scale": deltaScale, | |
"angle": deltaAngle | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node2D | |
onready var controller = get_node("../touchController") | |
var showTouchPoints = true | |
func _process(delta): | |
# To keep redrawing on every frame | |
update() | |
func _draw(): | |
if showTouchPoints: | |
for tp in controller.touchPoints.values(): | |
var colour = getColour(tp.index) | |
var radius = 80 | |
draw_circle(tp.origin, radius, colour) | |
draw_circle(tp.position, radius, colour) | |
draw_line(tp.origin, tp.position, colour, 20, true) | |
func getColour(id): | |
var x = (id % 7) + 1 | |
return Color(float(bool(x & 1)), float(bool(x & 2)), float(bool(x & 4)), 0.75) | |
#func _process(delta): | |
# # Called every frame. Delta is time since last frame. | |
# # Update game logic here. | |
# pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node | |
onready var touchController = $touchController | |
onready var debugger = $touchDebugger | |
onready var debugLabel = debugLabel | |
onready var selectionController = $selectionController | |
func _ready(): | |
selectionController.touchController = touchController | |
selectionController.connect("selectedNodeChanged", self, "_onSelectionChanged") | |
addBox("red", Color("60FF0000"), Color("60800000"), Vector2(300,300), PI/4) | |
addBox("green", Color("6000FF00"), Color("60008000"), Vector2(350,350)) | |
addBox("blue", Color("600000FF"), Color("60000080"), Vector2(200,200), PI/2) | |
# touchController.connect("touchStarted", self, "_on_touchChanged") | |
# touchController.connect("touchStopped", self, "_on_touchChanged") | |
## touchController.connect("manipulationStarted", self, "_on_manipulation", ["started"]) | |
## touchController.connect("manipulationStopped", self, "_on_manipulation", [null, "stopped"]) | |
touchController.connect("manipulationChanged", self, "_on_manipulationChanged") | |
debugger.showTouchPoints = false | |
# touchController.connect("touchStarted", debugger, "_on_touchStarted") | |
#func _on_touchChanged(): | |
# debugLabel.text = str(touchController.touchPoints.size()) | |
# | |
#func _on_manipulation(position, text): | |
# debugLabel.text = text | |
# | |
func _on_manipulationChanged(data): | |
var selectedNode = selectionController.selectedNode | |
if selectedNode == null: | |
return | |
$debugLabel.text = selectedNode.name | |
selectedNode.position += data.delta.position | |
selectedNode.scale += selectedNode.scale * data.delta.scale | |
selectedNode.rotation += data.delta.angle | |
$debugLabel.text = selectedNode.name | |
# print(data.delta.angle) | |
func _onSelectionChanged(selectedNode): | |
$debugLabel.text = selectedNode.name | |
func addBox(name, colour, borderColour, position, rotation = 0): | |
var sideLength = 200 | |
var borderWidth = 10 | |
var scene = load("res://touchInput/colourBox.tscn") | |
var box = scene.instance() | |
box.name = name | |
box.sideLength = sideLength | |
box.borderWidth = borderWidth | |
box.colour = colour | |
box.borderColour = borderColour | |
box.position = position | |
box.rotation = rotation | |
add_child(box) | |
selectionController.add(box) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[gd_scene load_steps=7 format=2] | |
[ext_resource path="res://touchInput/touchInput.gd" type="Script" id=1] | |
[ext_resource path="res://font/PressStart2P.ttf" type="DynamicFontData" id=2] | |
[ext_resource path="res://touchInput/selectionController.gd" type="Script" id=3] | |
[ext_resource path="res://touchInput/touchController.gd" type="Script" id=4] | |
[ext_resource path="res://touchInput/touchDebugger.gd" type="Script" id=5] | |
[sub_resource type="DynamicFont" id=1] | |
size = 20 | |
use_mipmaps = false | |
use_filter = true | |
font_data = ExtResource( 2 ) | |
_sections_unfolded = [ "Font", "Settings" ] | |
[node name="touchInput" type="Node" index="0"] | |
script = ExtResource( 1 ) | |
[node name="debugLabel" type="Label" parent="." index="0"] | |
anchor_left = 1.0 | |
anchor_top = 1.0 | |
anchor_right = 1.0 | |
anchor_bottom = 1.0 | |
margin_left = -28.0 | |
margin_top = -30.0 | |
margin_right = -8.0 | |
margin_bottom = -8.0 | |
grow_horizontal = 0 | |
rect_pivot_offset = Vector2( 0, 0 ) | |
rect_clip_content = false | |
mouse_filter = 2 | |
mouse_default_cursor_shape = 0 | |
size_flags_horizontal = 1 | |
size_flags_vertical = 4 | |
custom_fonts/font = SubResource( 1 ) | |
custom_colors/font_color = Color( 0.902654, 0.916033, 0.988281, 1 ) | |
text = "0" | |
align = 2 | |
percent_visible = 1.0 | |
lines_skipped = 0 | |
max_lines_visible = -1 | |
_sections_unfolded = [ "Anchor", "Grow Direction", "Margin", "custom_colors", "custom_fonts" ] | |
[node name="selectionController" type="Node" parent="." index="1"] | |
script = ExtResource( 3 ) | |
[node name="touchController" type="Node" parent="." index="2"] | |
script = ExtResource( 4 ) | |
[node name="touchDebugger" type="Node2D" parent="." index="3"] | |
script = ExtResource( 5 ) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment