Skip to content

Instantly share code, notes, and snippets.

@ZodmanPerth
Last active August 16, 2021 22:41
Show Gist options
  • Save ZodmanPerth/fa5dd4b800f1237f3de8dfe58a4a5951 to your computer and use it in GitHub Desktop.
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/
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
[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" ]
# 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
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
}
})
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
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)
[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