In this commit I add a human character, a traveler that can move around a tiny world made of a single screen so far. :) Materials used so far: * 'HeartBeast' video lesons: https://goo.gl/3DtqPn * The very informative and rich documentation of Godot itself! For the Gamepad I used wonderful addon by fiaful: https://github.com/fiaful/Gamepad The beautiful arts are from "Open Pixel Project": https://openpixelproject.itch.io
408 lines
No EOL
15 KiB
GDScript
408 lines
No EOL
15 KiB
GDScript
###[ INFO ]######################################################################################################
|
|
|
|
# Component: GamepadPaddle
|
|
# Author: Francesco Iafulli (fiaful)
|
|
# E-mail: fiaful@hotmail.com
|
|
# Version: 1.0
|
|
# Last modify: 2018-07-20
|
|
|
|
# What is this:
|
|
# E' l'oggetto che consente di gestire paddle virtuali analogici (la paddle è un controller che ruota su se stesso,
|
|
# come ad esempio il volante di una automobile).
|
|
# Possono essere aggiunti nel contenitore quanti paddle si desideri (generalmente 1 o 2)
|
|
|
|
# Requirements:
|
|
# - il parent di questo nodo deve essere di tipo GamepadArea se si desidera utilizzare la proprietà show_dinamically
|
|
# per far apparire la paddle dinamicamente alla posizione della pressione del dito sullo schermo. Il suo parent
|
|
# può essere di tipo GamepadContainer se la paddle è sempre visibile sullo schermo ad una posizione fissa.
|
|
# - deve comunque essere contenuto (direttamente o indirettamente) in un nodo di tipo GamepadContainer, altrimenti
|
|
# non funzionerà
|
|
# - la texture di sfondo della paddle deve essere quadrata altrimenti si verificheranno problemi di visualizzazione
|
|
# a runtime (vedere le immagini di esempio nella cartella assets/Gamepad)
|
|
# - se la paddle deve essere sempre visibile in una posizione fissa, è necessario valorizzare questa posizione nella
|
|
# proprietà static_position.
|
|
|
|
# Changelog:
|
|
#
|
|
#
|
|
|
|
###[ BEGIN ]#####################################################################################################
|
|
|
|
tool
|
|
extends Control
|
|
|
|
###[ CONSTS ]####################################################################################################
|
|
|
|
# contiene un valore per definire un angolo non valido
|
|
const INVALID_ANGLE = -99
|
|
|
|
###[ INTERNAL OBJECTS ]##########################################################################################
|
|
|
|
# texture di sfondo della paddle
|
|
onready var bg = $PaddleBackground
|
|
|
|
# texture del centro della paddle
|
|
onready var paddle = $Paddle
|
|
|
|
# animazione di visualizzazione/nascondimento della paddle
|
|
onready var fader = $ShowHideAnimation
|
|
|
|
onready var timer = $Timer
|
|
|
|
###[ EXPORTED VARIABLES ]########################################################################################
|
|
|
|
# indica se la paddle deve essere disabilitata (se true, la paddle non riceverà i tocchi dell'utente)
|
|
export var disabled = false
|
|
|
|
# indica se la paddle deve essere staticamente sempre visualizzata (false) o se questa deve apparire nascosta e
|
|
# mostrarsi (true) quando l'utente tocca la sua area (in questo caso deve essere contenuta in un oggetto di tipo
|
|
# GamepadArea)
|
|
export var show_dynamically = false setget _set_show_dynamically
|
|
|
|
# questa proprietà contiene il nome dell'oggetto (che viene restituito nell'oggetto finger)
|
|
export var gamepad_type = "PADDLE 0"
|
|
|
|
# texture di sfondo della paddle
|
|
export(Texture) var background_texture setget _set_bg_texture, _get_bg_texture
|
|
|
|
# texture del centro della paddle
|
|
export(Texture) var paddle_texture setget _set_texture, _get_texture
|
|
|
|
# scala della texture del centro della paddle (la dimensione dello sfondo è data dal rect_size dell'oggetto,
|
|
# quindi per impostare la dimensione del centro della paddle si usa questa proprietà)
|
|
export(Vector2) var paddle_scale setget _set_scale, _get_scale
|
|
|
|
# contiene la reale posizione della paddle
|
|
export var static_position = Vector2(0, 0)
|
|
|
|
# questa proprietà indica la forza minima da imporre alla paddle per iniziare a considerare validi
|
|
# i valori (es. con un valore = 0.5, la paddle inizierà a ruotare solo se l'utente toccherà l'oggetto
|
|
# su oltre la metà della distanza tra il centro della paddle ed il bordo)
|
|
export var valid_threshold = 0.2
|
|
|
|
# se impostato a true, il rilascio della paddle resetterà i valori e la posizione della paddle, mentre
|
|
# se impostato a false, al rilascio i valori e la posizione resteranno gli ultimi validi
|
|
export var reset_on_release = true
|
|
|
|
# impone un limite inferiore alla rotazione della paddle (limite inferiore e superiore possono essere invertiti)
|
|
export var low_limit = 0
|
|
|
|
# impone un limite alto alla rotazione della paddle (limite inferiore e superiore possono essere invertiti)
|
|
export var high_limit = 0
|
|
|
|
# per utilizzare uniformemente gli oggetti anche in presenza di tastiera, consento di associare
|
|
# direttamente degli input map per ruotare la paddle in senso antiorario e orario
|
|
export var simulate_counter_clockwise = "ui_left"
|
|
export var simulate_clockwise = "ui_right"
|
|
|
|
# in caso di simulazione con la tastiera, indica lo step di incremento/decremento dell'angolo
|
|
export var simulation_increment = 0.05
|
|
|
|
# in caso di simulazione con la tastiera, indica la velocità di incremento/decremento dell'angolo
|
|
export var simulation_delay = 0.01
|
|
|
|
###[ PRIVATE AND PUBLIC VARIABLES ]##############################################################################
|
|
|
|
# centro della paddle (ovvero dello sfondo della paddle)
|
|
var center_point = Vector2(0,0)
|
|
|
|
# forza calcolata dal centro della paddle (serve per calcolare l'angolo)
|
|
var current_force = Vector2(0,0)
|
|
|
|
# metà della dimensione dello sfondo della paddle
|
|
var half_size = Vector2()
|
|
|
|
# area del rettangolo costituito da metà delle dimensioni dello sfondo
|
|
var squared_half_size_length = 0
|
|
|
|
# indica se l'angolo di rotazione della paddle si trova all'interno o all'esterno dei limiti imposti
|
|
var into_limits = false
|
|
|
|
# i dati del tocco (in modo che possano essere recuperati negli eventi)
|
|
var finger_data = null
|
|
|
|
# angolo di rotazione della paddle
|
|
var angle = -1
|
|
|
|
# indica se sto ruotando la paddle con i tasti della tastiera oppure no
|
|
var simulation = false
|
|
|
|
# ultimo angolo calcolato (serve per emettere i segnali solo se l'angolo corrente è diverso da quello precedente)
|
|
var last_angle = INVALID_ANGLE
|
|
|
|
# mantiene lo stato della visualizzazione dinamica
|
|
var shown = true
|
|
|
|
# indica la direzione di rotazione nel caso di simulazione con la tastiera
|
|
var direction = 0
|
|
|
|
###[ SIGNALS ]###################################################################################################
|
|
|
|
# viene emesso quando la paddle ruota, restituendo l'angolo di rotazione e l'oggetto paddle stesso (in modo da
|
|
# poter recuperare altre informazioni (qualsiasi proprietà dell'oggetto)
|
|
signal angle_changed(current_angle, sender)
|
|
|
|
# viene emesso quando l'utente rilascia il dito dalla paddle (l'angolo sarà sempre invalido, pertanto è inutile
|
|
# passare il sender)
|
|
signal paddle_released
|
|
|
|
###[ METHODS ]###################################################################################################
|
|
|
|
# costruisce l'albero dei nodi necessari all'oggetto prendendoli dal template
|
|
func _init():
|
|
# se non sono già stati caricati
|
|
if get_child_count() > 0: return
|
|
# carico e istanzio il template
|
|
var gamepad_paddle_template = load("res://addons/Gamepad/GamepadPaddleTemplate.tscn").instance()
|
|
# quindi se ci sono oggetti nel template (ovviamente si)
|
|
if gamepad_paddle_template.get_child_count() > 0:
|
|
# prendo ogni oggetto nel template
|
|
for child in gamepad_paddle_template.get_children():
|
|
# se l'oggetto è il timer
|
|
if child is Timer:
|
|
# ne creo il duplicato
|
|
var tmr = child.duplicate()
|
|
# lo aggiungo al mio nodo
|
|
add_child(tmr)
|
|
tmr.wait_time = simulation_delay
|
|
# connetto il suo segnale timeout allo script
|
|
tmr.connect("timeout", self, "_on_timer_timeout")
|
|
else:
|
|
# aggiungo un duplicato al mio nodo
|
|
add_child(child.duplicate())
|
|
|
|
func _ready():
|
|
# se l'oggetto deve essere visualizzato dinamicamente (ovvero solo quando l'utente tocca lo schermo) lo nascondo
|
|
if show_dynamically:
|
|
_hide_paddle()
|
|
# imposto la sua posizione statica (non ha senso se visualizzato dinamicamente in quanto la sua posizione
|
|
# varierà in base al tocco dell'utente)
|
|
rect_position = static_position
|
|
# ricavo i restanti valori che mi serviranno più avanti per fare i calcoli
|
|
half_size = bg.rect_size / 2
|
|
center_point = half_size
|
|
paddle.position = half_size
|
|
squared_half_size_length = half_size.x * half_size.y
|
|
|
|
# emula la paddle tramite i tasti
|
|
func handle_input(event):
|
|
if event is InputEventKey:
|
|
if !((simulate_counter_clockwise and event.is_action(simulate_counter_clockwise)) or \
|
|
(simulate_clockwise and event.is_action(simulate_clockwise))): return
|
|
else:
|
|
return
|
|
# verifica quale tasto è stato premuto
|
|
var cnt = simulate_counter_clockwise and Input.is_action_pressed(simulate_counter_clockwise)
|
|
var clk = simulate_clockwise and Input.is_action_pressed(simulate_clockwise)
|
|
simulation = false
|
|
# se nessuna delle 2 direzioni è premuta, azzero l'angolo e sollevo l'evento di rilascio
|
|
if !cnt and !clk:
|
|
# fermo il timer che si occupa di far ruotare la paddle
|
|
timer.stop()
|
|
handle_up_event(null, null)
|
|
else:
|
|
# imposto la direzione di rotazione
|
|
if cnt:
|
|
clk = false
|
|
direction = -simulation_increment
|
|
elif clk:
|
|
cnt = false
|
|
direction = simulation_increment
|
|
# ed avvio il timer che si occuperà di far ruotare la paddle
|
|
timer.start()
|
|
|
|
func _on_timer_timeout():
|
|
# inizializza la posizione del'oggetto
|
|
var ev = InputEventScreenTouch.new()
|
|
ev.position = get_parent().rect_global_position + static_position + half_size
|
|
simulation = true
|
|
|
|
# incrementa/decrementa l'angolo
|
|
angle += direction
|
|
|
|
# se l'angolo è diverso dal precedente
|
|
if angle != last_angle:
|
|
last_angle = angle
|
|
# simulo la rotazione della paddle
|
|
handle_down_event(ev, null)
|
|
|
|
# l'utente ha toccato lo schermo in corrispondenza della paddle o dell'area che contiene la paddle
|
|
func handle_down_event(event, finger):
|
|
# se la paddle è disabilitata esco senza fare nulla (prima però resetto i dati interni)
|
|
if disabled:
|
|
reset()
|
|
return
|
|
# altrimenti imposto i dati del tocco in modo che possano essere recuperati da fuori
|
|
finger_data = finger
|
|
# se la paddle deve essere visualizzata dinamicamente vuol dire che in questo momento non è visibile e quindi la mostro
|
|
if show_dynamically:
|
|
_show_paddle(event)
|
|
|
|
# se il tocco è avvenuto nella zona dello sfondo della paddle
|
|
if simulation or bg.get_global_rect().has_point(event.position):
|
|
# calcolo la forza e l'angolo, aggiorno la rotazione della paddle ed emetto il segnale
|
|
calculate(event)
|
|
else:
|
|
# altrimenti resetto tutti i dati e esco
|
|
reset()
|
|
|
|
# l'utente ha sollevato il dito con cui aveva toccato la paddle o la sua area
|
|
func handle_up_event(event, finger):
|
|
# se la paddle è disabilitata esco senza fare nulla (prima però resetto i dati interni)
|
|
if disabled:
|
|
reset()
|
|
return
|
|
# altrimenti imposto i dati del tocco in modo che possano essere recuperati da fuori
|
|
finger_data = finger
|
|
# se la paddle deve essere visualizzata dinamicamente vuol dire che in questo momento è visibile e quindi la nascondo
|
|
if show_dynamically:
|
|
_hide_paddle()
|
|
|
|
# resetto i dati interni
|
|
reset()
|
|
# quindi emetto il segnale per comunicare che la paddle è stata rilasciata
|
|
emit_signal("paddle_released")
|
|
|
|
# l'utente ha spostato il dito con cui aveva toccato la paddle o la sua area
|
|
func handle_move_event(event, finger):
|
|
# se la paddle è disabilitata esco senza fare nulla (prima però resetto i dati interni)
|
|
if disabled:
|
|
reset()
|
|
return
|
|
# altrimenti imposto i dati del tocco in modo che possano essere recuperati da fuori
|
|
finger_data = finger
|
|
# calcolo la forza, l'angolo, aggiorno la rotazione della paddle ed emetto il segnale
|
|
calculate(event)
|
|
|
|
# calcolo la forza, l'angolo, aggiorno la rotazione della paddle ed emetto il segnale
|
|
func calculate(event):
|
|
# ricalcolo la posizione dell'evento in modo che lo 0,0 coincida con lo 0,0 dell'oggetto
|
|
if !simulation:
|
|
var pos = event.position - rect_global_position
|
|
calculate_force(pos)
|
|
update_paddle_pos()
|
|
emit()
|
|
|
|
func calculate_force(pos):
|
|
# print ("pos: ", pos, " - center_point: ", center_point, " - half_size: ", half_size)
|
|
# calcolo la forza in relazione alla posizione del mouse e il centro della paddle, e la normalizzo
|
|
current_force.x = (pos.x - center_point.x) / half_size.x
|
|
current_force.y = (pos.y - center_point.y) / half_size.y
|
|
if current_force.length_squared() > 1:
|
|
current_force = current_force / current_force.length()
|
|
# quindi se la forza è minore della soglia di validità, resituisco 0,0 (per comunicare che
|
|
# non è stato effettuato uno spostamento valido del centro della paddle)
|
|
if (current_force.length() < valid_threshold):
|
|
current_force = Vector2(0,0)
|
|
|
|
# aggiorno la rotazione della paddle in modo che graficamente sia coerente
|
|
func update_paddle_pos():
|
|
var new_angle
|
|
if !simulation:
|
|
var x = center_point.x + half_size.x * current_force.x
|
|
var y = center_point.y + half_size.y * current_force.y
|
|
new_angle = Vector2(x, y).angle_to_point(center_point)
|
|
else:
|
|
new_angle = angle
|
|
# quindi verifico che il nuovo angolo sia nei limiti
|
|
into_limits = false
|
|
var deg_angle = rad2deg(new_angle) + 180
|
|
# print ([deg_angle, low_limit, high_limit])
|
|
# se low_limit e high_limit sono uguali non devo imporre limiti, ovvero sono sempre nei limiti
|
|
if low_limit != high_limit:
|
|
if low_limit > high_limit:
|
|
if deg_angle <= high_limit:
|
|
into_limits = true
|
|
if deg_angle >= low_limit:
|
|
if deg_angle >= high_limit:
|
|
into_limits = true
|
|
else:
|
|
if deg_angle <= high_limit and deg_angle >= low_limit:
|
|
into_limits = true
|
|
else:
|
|
into_limits = true
|
|
|
|
# se sono nei limiti, imposto il nuovo angolo ed aggiorno la rotazione della paddle
|
|
if into_limits:
|
|
angle = new_angle
|
|
paddle.rotation = angle
|
|
|
|
# solo se reset_on_release = true (vedi commento) effettuo un reset dei dati interni,
|
|
# imposto l'angolo della paddle ad un valore invalido e ne aggiorno graficamente la rotazione
|
|
func reset():
|
|
if !reset_on_release: return
|
|
calculate_force(center_point)
|
|
update_paddle_pos()
|
|
angle = INVALID_ANGLE
|
|
last_angle = angle
|
|
# emit()
|
|
|
|
# emette il segnale per comunicare il cambiamento dell'angolo della paddle
|
|
func emit():
|
|
if into_limits:
|
|
emit_signal("angle_changed", angle, self)
|
|
# print (angle / PI * 180)
|
|
# print (rad2deg(angle) + 180)
|
|
|
|
# mostra la paddle
|
|
func _show_paddle(event):
|
|
# se event è diverso dal null (nel caso in l'utente tocca la paddle o la sua area) calcolo la posizione
|
|
# in base a quella passata nell'evento
|
|
if shown: return
|
|
shown = true
|
|
if event:
|
|
rect_global_position = event.position - center_point
|
|
else:
|
|
# altrimenti la posizione della paddle è quella statica impostata in static_position
|
|
rect_position = static_position
|
|
# avvio l'animazione di visualizzazione
|
|
if fader:
|
|
reset()
|
|
fader.stop()
|
|
fader.play("fade_in", -1, 10)
|
|
|
|
# nasconde la paddle
|
|
func _hide_paddle():
|
|
if !shown: return
|
|
shown = false
|
|
# avvia l'animazione di nascondimento
|
|
if fader:
|
|
fader.stop()
|
|
fader.play("fade_out", -1, 10)
|
|
|
|
###[ SETTER/GETTER ]#############################################################################################
|
|
|
|
func _get_scale():
|
|
return $Paddle.scale
|
|
|
|
func _set_scale(value):
|
|
# if !has_node("Paddle"): return
|
|
$Paddle.scale = value
|
|
$Paddle.position = $PaddleBackground.rect_size / 2
|
|
|
|
func _get_bg_texture():
|
|
return $PaddleBackground.texture
|
|
|
|
func _set_bg_texture(value):
|
|
# if !has_node("PaddleBackground"): return
|
|
$PaddleBackground.texture = value
|
|
$Paddle.position = $PaddleBackground.rect_size / 2
|
|
|
|
func _get_texture():
|
|
return $Paddle.texture
|
|
|
|
func _set_texture(value):
|
|
# if !has_node("PaddleBackground"): return
|
|
$Paddle.texture = value
|
|
$Paddle.position = $PaddleBackground.rect_size / 2
|
|
|
|
func _set_show_dynamically(value):
|
|
show_dynamically = value
|
|
# se sono nell'editor non faccio nulla (altrimenti mi verrebbe nascosto l'oggetto anche dall'editor)
|
|
if Engine.editor_hint: return
|
|
if value:
|
|
_hide_paddle()
|
|
else:
|
|
_show_paddle(null)
|
|
|
|
###[ END ]####################################################################################################### |