Към съдържанието
PN Scripts

Игри

Управление с докосване за игра на Godot 4, което работи и с клавиатура и геймпад

  • PN Scripts
  • 9 мин четене

Игра на Godot, която се играе добре с клавиатура и геймпад, обикновено стига до телефоните последна. Тогава преносът се превръща във втора игра: скрипт за джойстик, който мести героя направо, бутони, които сами извикват jump(), урок, който още казва „Натиснете Space“, и телефон, на който бутонът за скок е под изреза на екрана. Тук показваме друг път. Бутоните на екрана натискат същите действия, които натискат клавиатурата и геймпадът, така че кодът на играта не се променя, а подсказките следват това, което играчът държи в ръцете си. Всеки пример на GDScript тук е пуснат в Godot 4.7.1.

#Натискайте действия, не извиквайте кода на играта

Мястото за свързване е Input Map на Godot. Героят ви вече чете Input.get_vector() или Input.is_action_pressed(&"jump"); клавиш, стик и спусък са просто различни събития, вързани към тези действия. Управлението с докосване трябва да е още един източник на същите действия. Input.action_press() приема сила между 0 и 1, така че виртуалният джойстик може да натисне move_right със сила 0,53 точно като истински стик:

extends Node

const DEADZONE := 0.15

func stick_to_actions(raw: Vector2) -> void:
	var v := Vector2.ZERO
	if raw.length() > DEADZONE:
		var t := clampf((raw.length() - DEADZONE) / (1.0 - DEADZONE), 0.0, 1.0)
		v = raw.normalized() * t
	_set_action(&"move_left", maxf(-v.x, 0.0))
	_set_action(&"move_right", maxf(v.x, 0.0))
	_set_action(&"move_up", maxf(-v.y, 0.0))
	_set_action(&"move_down", maxf(v.y, 0.0))

func _set_action(action: StringName, strength: float) -> void:
	if strength > 0.0:
		Input.action_press(action, strength)
	else:
		Input.action_release(action)

raw е отместването на копчето от центъра, разделено на радиуса на джойстика, така че дължината му е от 0 до 1. Извиквайте функцията при всяко плъзгане и веднъж с Vector2.ZERO, когато пръстът се вдигне. Понеже действията се отпускат, щом силата падне до нула, нищо не остава натиснато след пръста.

#Двойната мъртва зона

Ето изненадата, на която попаднахме, когато го пуснахме. Всяко действие има своя мъртва зона, а действията, създадени в кода или в Input Map, започват с 0,2. Input.get_vector() ползва средната мъртва зона на четирите си действия и преоразмерява това, което е над нея. Нашият джойстик вече е махнал своята мъртва зона, така че двете се събират:

  • отместване 0,3 дава сила 0,18, а get_vector() я превръща в нищо;
  • отместване 0,6 дава 0,53, а get_vector() връща 0,41;
  • с Input.get_vector(&"move_left", &"move_right", &"move_up", &"move_down", 0.0) или с мъртва зона 0 на действията героят получава 0,18 и 0,53.

Играчът го усеща като джойстик, който не прави нищо в първата третина от хода си. Дръжте една мъртва зона, на едно място. Ако искате мъртвата зона на Input Map за истинските стикове, нека джойстикът на екрана праща сурови стойности; ако джойстикът има своя, подайте нулева мъртва зона там, където четете вектора.

#Бутони за няколко пръста

На телефона левият палец държи джойстика, докато десният натиска скок. Button и другите контроли на Godot реагират на емулирани събития от мишката, а мишката е една, така че два пръста върху две контроли си пречат. Събитията от докосване носят index за всеки пръст. Бутон, който помни кой пръст го е натиснал, работи до джойстика:

extends Control

@export var action: StringName = &"jump"
var finger := -1

func _input(event: InputEvent) -> void:
	if not event is InputEventScreenTouch:
		return
	if event.pressed and finger < 0 and get_global_rect().has_point(event.position):
		finger = event.index
		Input.action_press(action)
		get_viewport().set_input_as_handled()
	elif not event.pressed and event.index == finger:
		release_all()

func release_all() -> void:
	if finger >= 0:
		finger = -1
		Input.action_release(action)

func _notification(what: int) -> void:
	if what in [NOTIFICATION_PAUSED, NOTIFICATION_EXIT_TREE, NOTIFICATION_APPLICATION_FOCUS_OUT]:
		release_all()
	elif what == NOTIFICATION_VISIBILITY_CHANGED and not is_visible_in_tree():
		release_all()

При нашето изпълнение вдигането на друг пръст остави скока натиснат, а скриването на бутона или паузата на играта го отпуснаха. Втората част е по-важна, отколкото изглежда. Меню за пауза, което се отваря, докато пръстът е на бутона, или обаждане, което праща приложението на заден план, иначе оставят действието задържано: играчът се връща и героят сам тича към стената.

Godot има и вграден TouchScreenButton. Той е Node2D, а не Control, така че не следва контейнерите и котвите, но натиска действие, следи своя пръст и може да се показва само на сензорни екрани. За един или два бутона в проста подредба е добро начало.

#Разберете какво държи играчът

Управлението с докосване трябва да се вижда само от играчите с докосване, а подсказките трябва да назовават верния клавиш. И за двете трябва устройството, което играчът е ползвал последно. Автоматично зареден възел (autoload), който следи всяко събитие, може да го каже, стига да пренебрегва два вида шум:

extends Node
## Autoload "Device": the kind of device the player used last.
signal device_changed(kind: String)

var kind := "keyboard"

func _input(event: InputEvent) -> void:
	var next := kind
	if event is InputEventScreenTouch or event is InputEventScreenDrag:
		next = "touch"
	elif event is InputEventJoypadButton and event.pressed:
		next = "gamepad"
	elif event is InputEventJoypadMotion and absf(event.axis_value) >= 0.5:
		next = "gamepad"
	elif event is InputEventKey and event.pressed:
		next = "keyboard"
	elif event is InputEventMouseButton and event.pressed \
			and event.device != InputEvent.DEVICE_ID_EMULATION:
		next = "keyboard"
	if next != kind:
		kind = next
		device_changed.emit(kind)

Първият шум е отклонението на стика: износен контролер, оставен на бюрото, праща малки стойности по осите през цялото време и без прага 0,5 подсказките за клавиатура биха прескачали към подсказки за геймпад. При нашето изпълнение отклонение 0,12 не промени нищо. Вторият е собствената емулация на мишка в Godot. „Emulate Mouse From Touch“ е включено по подразбиране, така че всяко докосване пристига и като щракване с мишката с номер на устройството InputEvent.DEVICE_ID_EMULATION. Без тази проверка всяко докосване би върнало играча на телефон към „клавиатура“ и би скрило бутоните, които току-що е докоснал.

За да пробвате управлението на компютър, включете „Emulate Touch From Mouse“ в Project Settings, Input Devices, Pointing. Тогава плъзгането с мишката става плъзгане с пръст. Полезна проверка е, но е само един пръст; случаите с няколко пръста искат истинско устройство или докосвания, изпратени като събития от тест.

#Подсказки според контролера

„Натиснете A за скок“ е вярно на геймпад на Xbox и грешно на геймпад на Nintendo, където долният бутон е с надпис B. Godot отчита бутоните по положение (JOY_BUTTON_A е долният на всеки геймпад), а името на контролера казва семейството:

extends RefCounted

static func family_of(joy_name: String) -> String:
	var n := joy_name.to_lower()
	for word in ["xbox", "xinput"]:
		if n.contains(word):
			return "xbox"
	for word in ["playstation", "dualsense", "dualshock", "ps4", "ps5"]:
		if n.contains(word):
			return "playstation"
	for word in ["nintendo", "pro controller", "joy-con"]:
		if n.contains(word):
			return "nintendo"
	return "generic"

static func face_label(button: JoyButton, family: String) -> String:
	var labels := {
		"nintendo": ["B", "A", "Y", "X"],
		"playstation": ["south", "east", "west", "north"],
	}
	return labels.get(family, ["A", "B", "X", "Y"])[button]

Подайте ѝ Input.get_joy_name(event.device). За „Nintendo Switch Pro Controller“ долният бутон излиза B; за „DualSense Wireless Controller“ е „south“ (долу). Символите по бутоните на PlayStation са търговски марки, затова много игри показват тези бутони по положение или с неутрална икона; това означават имената по положение в примера. Непознат геймпад остава с A B X Y, което е отпечатано на повечето контролери на други производители.

На телефона няма клавиш, който да назовете, затова трябва да се смени самият текст: „Плъзнете стика, за да вървите“ вместо „Натиснете WASD, за да вървите“. Пазете отделна версия за докосване на всеки ред от урока, вместо да пъхате знак в нея.

#Изрези и заоблени ъгли

Бутон в долния десен ъгъл на съвременен телефон може да попадне под заоблен ръб или лентата за жестове. DisplayServer.get_display_safe_area() връща частта от екрана, която системата оставя свободна. Сложете управлението в контейнер на целия екран с полета, равни на безопасната зона плюс удобен минимум, и обновявайте полетата при промяна на размера на прозореца, защото завъртането на телефона мести изреза.

#Добавки, които го правят

Събрахме този подход в Pipalo, добавка за Godot 4: фиксиран, плаващ или следващ пръста джойстик, който натиска вашите действия с аналогова сила, бутони за няколко пръста с плъзгане от бутон на бутон, жестове, които пренебрегват докосванията, вече ползвани от бутоните, безопасна зона, следене на устройството със семействата контролери и подсказки, които сменят клавиатура, геймпад и докосване. Тествана е с 19 теста без графика в Godot 4.7.1 на Linux и със симулирани докосвания, още не на истински телефон или с истински геймпади. Самите знаци са в Podkanka, 161 изображения за подсказки за клавиатура, мишка, геймпади и докосване в три стила, с помощник за Godot, който избира знака за действие и устройство. И двете са обявени като „Очаквайте скоро“.

#Често задавани въпроси

Трябва ли джойстикът да мести героя направо?

Не. Натискайте същите действия, които натискат клавиатурата и геймпадът, така че кодът за движение, мъртвата зона и пренастройката на клавишите да са на едно място и версията за докосване да не се разминава с другите.

Защо виртуалният ми джойстик не реагира близо до центъра?

Обикновено защото се събират две мъртви зони: тази на джойстика и 0,2 на действията, която прилага Input.get_vector(). Оставете само едната.

Защо бутоните на екрана се скриват веднага след като ги докосна?

Godot превръща всяко докосване и в емулирано щракване с мишката. Ако проверката ви за устройство приема всяко щракване като „клавиатура“, тя превключва от докосване. Пренебрегвайте събитията, чието устройство е InputEvent.DEVICE_ID_EMULATION.

Мога ли да тествам управлението без телефон?

Отчасти. „Emulate Touch From Mouse“ ви дава един пръст на компютъра; за два пръста пращайте от тест събития InputEventScreenTouch с различни индекси и пробвайте на истинско устройство преди издаването.

#Източници

Коментари

Коментари

Напишете първия коментар.

Оставете коментар

Следваща стъпка

pnscripts.com/bg/contact

Пишете на екипа

Попитайте за статия или ни разкажете за проект, който искате да изградим. Отговаряме до един работен ден.

Пишете ни

Семейството PN Scripts

Другите сайтове на PN Scripts

Хостингът, игрите и блогът имат свои сайтове, поддържани от същата компания.

  • pnscripts.com

    PN Scripts

    Софтуерно инженерство

    Уеб и мобилни приложения, API и игри по поръчка, плюс нашите продукти и плъгини с отворен код.

  • games.pnscripts.com

    Игри

    Игри и игрови сървъри

    Домът на игрите на PN Scripts: безплатни браузърни игри, които се играят веднага, а игровите сървъри предстоят.

  • hosting.pnscripts.com

    Хостинг

    Хостинг и инфраструктура

    Споделен хостинг, KVM VPS, самостоятелни сървъри и домейни от същата компания, която изработва проекта ви.

  • blog.pnscripts.com

    Блог

    Статии и бележки

    Практични статии за разработка на софтуер, хостинг, отворен код и игри, писани от хората, които ги създават.

    Вие сте тук