Как собрать паука в Godot, Unigine или PlayCanvas

С наступившим 21-м годом 21-го века.
В данной статье пробегусь по особенностям работы в трёх игровых движках, на примере написания кода для паукообразного средства передвижения.

Общая структура «паукомобиля» подразумевалась следующая — есть 6 лап, на каждой копия управляющего скрипта, который отслеживает перемещение основной конструкции и переставляет лапу в новую позицию, когда она отдаляется на определённое расстояние.
Для более точной посадки лап планировалось добавить и рейкасты, но, допустим, в том же Godot я хотел сделать паука для игры с видом сверху, при котором подобная точность не особо нужна.
Таким образом задача состояла в формировании нужной структуры внутри движка и написании кода отдельной лапки. Вот что получилось в разных средах разработки:

Godot

Здесь у меня уже был готов небольшой проект с машинками и паука я решил добавить в одну из сцен (префабов), содержащую в себе подкласс машинок, которые не имеют колёс.
Сама сцена specific_base устроена таким образом, что в основе узел-пустышка, который просто висит где-то в мире, без движения, а по миру перемещается kinematic body внутри него. Камера находится внутри сцены, но вне body, просто следуя за ним.

Для создания паука я добавил отдельный узел внутрь body, содержащий в себе точки-пустышки для расположения лап (место их постановки на землю, а не места крепления к телу).

Сами лапы для удобства разместил здесь же, в этой сцене, но вне body. Управляющий каждой из них скрипт, помимо переноса лапы, будет постоянно разворачивать её к центру «паука».


Код внутри редактора Godot

Пишем код. Я использую GDScript, потому как особого смысла писать именно на C# в Годо не вижу (не настолько фанат фигурных скобочек):

extends Spatial

export var distance = 2.5#максимальная дистанция, после которой случится перерасчёт
export var step = 1#переменная для дополнительного смещения лап

#ссылки на центр паука и одну из позиций лап, а также элементы для их хранения
export (NodePath) var spidercenter = null
var trg_center

export (NodePath) var spiderleg = null
var trg_leg

#переменные для расстояний по осям x и z
var x_dis = 0.0
var z_dis = 0.0

#переменная-таймер, а также флаг
var time_lag = -1.0

# инициализация
func _ready():
	self.hide()#скрыть лапу
	trg_center = get_node(spidercenter)#запомнить объекты
	trg_leg = get_node(spiderleg)
	LegPlace()#один раз вызвать установку лапки на позицию

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	#self.look_at(trg.global_transform.origin - self.global_transform.origin, Vector3(0,1,0)) 
	
	self.look_at(trg_center.global_transform.origin, Vector3(0,1,0))
	
	if self.visible == false: self.show() 
#	x_dis = abs(trg_center.global_transform.origin.x - self.global_transform.origin.x)
#	z_dis = abs(trg_center.global_transform.origin.z - self.global_transform.origin.z)

#	x_dis = abs(trg_leg.global_transform.origin.x - self.global_transform.origin.x)
#	z_dis = abs(trg_leg.global_transform.origin.z - self.global_transform.origin.z)
	
	if time_lag>=0:
		time_lag +=1*delta 
		if time_lag>0.06:
			time_lag = -1.0
			LegPlace()
	else:
		x_dis = abs(trg_leg.global_transform.origin.x - self.global_transform.origin.x)
		z_dis = abs(trg_leg.global_transform.origin.z - self.global_transform.origin.z)
	
	#if (x_dis + z_dis) > 5 or (x_dis + z_dis) < 2:
		if (x_dis + z_dis) > distance:
			#LegPlace()
			time_lag = 0.0
		#elif (x_dis + z_dis) > 0.5 and (x_dis + z_dis) < 1:
		#	time_lag = 0.0
		#	#LegPlace()	
	
	pass
	
func LegPlace():
	self.hide()
	step = step*(-1)
	self.global_transform.origin = trg_leg.global_transform.origin+Vector3(0,0,0.5*step)

Видеонарезка с тестом паука в Godot. Бонусом сюда же вошли моменты из отдельной игры с терраформированием воксельного мира, где тоже используется «паучок», но чисто анимированный, а также демка с машинками на playcanvas, до того как туда была добавлена форма паука.

Unigine

После того, как была готова реализация для Godot, я решил перенести это решение и в Unigine engine. Там у меня тоже имелся проект с машинками, правда чтобы не перегружать его я сделал отдельный «паучий» форк, чтобы впоследствии, наверное, вовсе убрать из него колёса и развивать как-то отдельно.

Тут имеется вся сцена игрового мира, в котором располагается корпус машинки игрока. В начале игры к этому корпусу пристыковываются отдельно расположенные колёса.

Завожу пустышку внутри корпуса, внутри которой будут находится точки, задающие позиции лап. Лапы размещаются просто в игровом мире. Само движение оставил реализованным через колёса, но их визуальное представление отключено.


Unigine для редактирования кода запускает внешнюю среду

Код:

using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "5a8dd6f85781adf7567432eae578c5414581ddac")]
public class theLegBehavior : Component
{

	[ShowInEditor][Parameter(Tooltip = "CenterSpider")]//указатель на центр паука
	private Node spiderCenter = null;
	[ShowInEditor][Parameter(Tooltip = "Target Leg Point")]//указатель на точку крепления
	private Node legPoint = null;

	//переменные для вычислений дистанций по осям
	private float x_dis= 0.0f;
	private float z_dis= 0.0f;

    private float old_dist = 0.0f;
	private float new_dist = 0.0f;
	private float velocity = 0.0f;

	private float ifps;
	private float time_lag = -1.0f;

	private void Init()
	{
		node.Enabled = false;
		LegPlace();
		
	}
	
	private void Update()
	{
		ifps = Game.IFps;

		if (time_lag>=0.0f){
			time_lag += 1.0f*ifps;

			if (time_lag>=0.6f) { // || (velocity>0.2f)) {
				time_lag = -1.0f;
				LegPlace();
			}
		
		}else{
			x_dis = MathLib.Abs(legPoint.WorldPosition.x - node.WorldPosition.x);
			z_dis = MathLib.Abs(legPoint.WorldPosition.z - node.WorldPosition.z);
            
			new_dist = x_dis + z_dis;
			velocity = MathLib.Abs(old_dist - new_dist);
			old_dist = new_dist;
			
			if (x_dis + z_dis > 0.8f){
				time_lag = 0.0f;
			}
			
			//vec3 dist = node.WorldPosition*node.WorldPosition;//) - (legPoint.WorldPosition*legPoint.WorldPosition));
			//vec3 dist2 = legPoint.WorldPosition*legPoint.WorldPosition;
			//dist = di
		}


		//LegPlace();
		
	}

	private void LegPlace()
	{
		node.Enabled = false;

        //vec3 currentDirection = body.GetWorldDirection(MathLib.AXIS.Y);
		vec3 targetDirection = vec3.ZERO;
		targetDirection = (legPoint.WorldPosition - node.WorldPosition);
		//targetDirection.z = 0;
		//targetDirection.Normalize();		

		quat targetRot = new quat(MathLib.LookAt(vec3.ZERO, targetDirection, vec3.UP, MathLib.AXIS.Y));

		quat delta = MathLib.Inverse(targetRot);
		delta.z = 0;
		delta.Normalize();

		node.WorldPosition = legPoint.WorldPosition;

        targetDirection = (spiderCenter.WorldPosition - node.WorldPosition);
		node.SetWorldDirection(targetDirection, vec3.UP, MathLib.AXIS.Y);

		node.Enabled = true;		
	}
}

Видеонарезка паучьего теста в Unigine

PlayCanvas

PlayCanvas — игровой движок под webGL, использующий javascript. Недавно начал в нём разбираться. Напоминает нечто среднее между Unity и Godot, но с разработкой онлайн — редактор открывается в браузере.

В данном случае я переделал один из предлагаемый данной платформой примеров, добавив туда пару своих моделек машин, немного дополнительного функционала вроде прыжков, дописав камеру и поигравшись с материалами/настройками.

Визуальное представление базовой машины в исходном примере было задано цельной моделью, которую я частично отключил, оставив пока только колёса, которые изнутри модели достаёт её управляющий скрипт (то есть они не заведены отдельными узлами в иерархии сцены). Это визуальное представление во время игрового процесса цепляется к движущейся физической колёсной модели.
Для реализации «паука» внутри данного проекта я завёл «паучий центр» внутри визуального представления машинки. «Тело паука» для удобства вложено внутрь него же, так как хотелось сразу сделать переключение на «форму паука» и обратно.

Таким образом, местный «паук» тоже сделан поверх езды на колёсах, а сами колёса скрыты. Возможно, правильнее было бы крепить паучий каркас к узлу физики, но переключение между формами уже расположено в скрипте узла с визуалом машинки, и прочие модели находятся внутри этого узла, поэтому для простоты выбрал такой вариант. На деле же получается, что визуал по своему скрипту перемещается за физикой, а скрипты лап уже смотрят на паучий каркас, перемещающийся вместе с визуалом. По идее тут может возникать некоторая рассинхронизация, по крайней мере я заметил, что на слабых устройствах некоторые лапки не успевают вычислять позиции и перемещаются лишь некоторые.

Лапы расположены в самой сцене, но для удобства их включения/отключения собраны внутри отдельного узла, чтобы можно было просто отключать его, а не каждую по отдельной ссылке.


В playcanvas редактор кода запускается вновой вкладке браузера

Код:

var TheLegBehavior = pc.createScript('theLegBehavior');

//ссылка на центр паука
TheLegBehavior.attributes.add('N_spiderCenter', { type: 'entity' });
//ссылка на точку постановки этой лапки
TheLegBehavior.attributes.add('N_legPoint', { type: 'entity' });

this.x_dis = 0.0;
this.z_dis = 0.0;

this.time_lag = -1.0;


// initialize code called once per entity
TheLegBehavior.prototype.initialize = function() {
    

    
};

// update code called every frame
TheLegBehavior.prototype.update = function(dt) {
    if (this.N_spiderCenter) {
        this.entity.lookAt(this.N_spiderCenter.getPosition());
    }
};

// постапдейт
TheLegBehavior.prototype.postUpdate = function(dt) {
    
//    if (this.N_spiderCenter) {
//        this.entity.lookAt(this.N_spiderCenter.getPosition());
//    }

    if (time_lag>=0.0){
        time_lag+=1.0*dt;
        if (time_lag>=0.06){
            time_lag=-1.0;
            this.LegUpdate();
        }
        
    } else {
        
        x_dis = Math.abs(this.entity.getPosition().x-this.N_legPoint.getPosition().x);
        z_dis = Math.abs(this.entity.getPosition().z-this.N_legPoint.getPosition().z);
        
        if ((x_dis+z_dis)>3.0){
         time_lag=0.0;
        }
        
        
    }
};


TheLegBehavior.prototype.LegUpdate = function() {
    
    if (this.N_legPoint) {
        this.entity.setPosition(this.N_legPoint.getPosition());
    }
    
//    if (this.N_spiderCenter.enabled === false) {
//        this.entity.enabled = false;
//    }
//    if (this.N_spiderCenter.enabled === true) {
//        this.entity.enabled = true;
//    }
    
};

В целом, пока получилась заготовка паучка с четырьмя лапками и не совсем оптимальными расчётами.

Потестить получившегося на текущий момент кадавра можно здесь:
https://playcanv.as/p/rOebDLem/

Я лично пробовал запускать на не слишком мощном смартфоне через Chrome и Dolphin, графика становится похожей на PsOne и вычисления для некоторых лап не работают, пока на экране виден уровень, у края карты лапы проявляются. На слабом ноутбуке наблюдались очень сильные тормоза в Chrome, но в Firefox всё катается нормально. На ноутбуке с дискретной видеокартой и на стационарнике летает в обоих этих браузерах.

На ПК, в отличие от смартфонов в этой демке работает прыжок (по кнопке пробел), заготовка стрейфа (Q и E) и перезагрузка уровня (на R).

Итог

Как видите, языки и движки разные, но тем не менее многие вещи в семействе скриптовых языков делаются довольно похожим образом. А как часто вы осваивали новые для себя игровые движки и фреймворки, с какими основными трудностями столкнулись?

Let’s block ads! (Why?)

Read More

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *