Автор статьи: Ростислав Мусиенко
Senior Unreal Engine Developer в District Zero
Все уже знакомы с игровым движком Unreal Engine 5. И экспериментировали или слышали о GPT chat. Я решил провести эксперимент и проверить насколько чат способен генерировать нестандартный код C++ для объектов в Unreal Engine
Решил начать с простого, сделать дверь которая будет открываться когда подходит персонаж, сделал небольшой запрос
В ответ мне было предложено следующее.
Откройте файл MyActorDoor.cpp
и добавьте следующий код:
#include "MyActorDoor.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/Actor.h"
AMyActorDoor::AMyActorDoor()
{
// Set this actor to call Tick() every frame.
PrimaryActorTick.bCanEverTick = true;
// Create the static mesh component for the door
DoorMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh"));
RootComponent = DoorMesh;
// Set the default door rotation
TargetRotation = FRotator(0.f, 90.f, 0.f);
}
void AMyActorDoor::BeginPlay()
{
Super::BeginPlay();
}
void AMyActorDoor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// If the door should be opened, interpolate the rotation
if (bShouldOpen)
{
FRotator CurrentRotation = DoorMesh->GetComponentRotation();
FRotator NewRotation = FMath::RInterpTo(CurrentRotation, TargetRotation, DeltaTime, 2.f);
DoorMesh->SetWorldRotation(NewRotation);
// Check if the door is fully opened
if (CurrentRotation.Equals(TargetRotation, 1.f))
{
bShouldOpen = false;
}
}
}
void AMyActorDoor::OpenDoor()
{
bShouldOpen = true;
}
Откройте файл MyActorDoor.h
и добавьте следующий код:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActorDoor.generated.h"
UCLASS()
class GPT_TEST_API AMyActorDoor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActorDoor();
// Called every frame
virtual void Tick(float DeltaTime) override;
// Function to open the door
UFUNCTION(BlueprintCallable, Category = "Door")
void OpenDoor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
private:
// Static mesh component for the door
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* DoorMesh;
// The target rotation for the door when it's fully opened
FRotator TargetRotation;
// Flag to determine if the door should be opened
bool bShouldOpen;
};
Увидев код, который мне проложил чат, я был воодушевлен. Он понимал конструкции и связи в движке а так же был знаком с синтаксисом.
Далее, переведя класс в блупринт и добавив меш двери я пошел проверять.
Очевидно он не работал потому, что нигде не применялось само событие для открытия двери.
Я понял что на этом вся простота и удобство закончились, и далее пытаясь починить код точечно это уже не давалось так легко.
Добавив измененный код после этого запроса все сломалось, и следующей целью было с помощью чата постараться починить код.
Он предлагал обрывочные решения, после которых появлялись новые ошибки.
После череды попыток код снова компилировался и пришло время его проверять.
Теперь он работал как требовалось. Открывал дверь после того как персонаж к ней подходил.
Далее задача была усложнена, сделать класс турели которая будет следить за игроком.
Первоначально был выслан только .cpp
код и даже в нем не хватало логики для итоговой реализации как логики стрельбы например (возможно потому что ранее небыло предусмотрено снарядов или способов того как будет производится стрельба)
#include "TurretActor.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "TimerManager.h"
// Sets default values
ATurretActor::ATurretActor()
{
// Set this actor to call Tick() every frame.
PrimaryActorTick.bCanEverTick = true;
// Create the base mesh component
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BaseMesh"));
RootComponent = BaseMesh;
// Create the turret mesh component
TurretMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("TurretMesh"));
TurretMesh->SetupAttachment(BaseMesh);
// Create the gun mesh component
GunMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("GunMesh"));
GunMesh->SetupAttachment(TurretMesh);
// Create the collision box for player detection
PlayerDetectionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("PlayerDetectionBox"));
PlayerDetectionBox->SetupAttachment(GunMesh);
// Set up initial rotation values
BaseRotationSpeed = 20.0f;
TurretRotationSpeed = 10.0f;
GunRotationSpeed = 5.0f;
// Set up the delay between shots and shooting range
ShootDelay = 2.0f;
ShootingRange = 1000.0f;
}
// Called when the game starts or when spawned
void ATurretActor::BeginPlay()
{
Super::BeginPlay();
// Set up the player character reference
ACharacter* PlayerCharacter = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);
if (PlayerCharacter)
{
PlayerCharacterRef = PlayerCharacter;
}
// Start the shooting timer
GetWorldTimerManager().SetTimer(ShootTimerHandle, this, &ATurretActor::Shoot, ShootDelay, true);
}
// Called every frame
void ATurretActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Rotate the turret base towards the player
if (PlayerCharacterRef)
{
FVector PlayerLocation = PlayerCharacterRef->GetActorLocation();
FVector TurretLocation = TurretMesh->GetComponentLocation();
FRotator LookAtRotation = (PlayerLocation - TurretLocation).Rotation();
FRotator BaseRotation = FRotator(0.f, LookAtRotation.Yaw, 0.f); // Only yaw rotation
FRotator NewBaseRotation = FMath::RInterpTo(BaseMesh->GetComponentRotation(), BaseRotation, DeltaTime, BaseRotationSpeed);
BaseMesh->SetWorldRotation(NewBaseRotation);
// Rotate the turret towards the player
FRotator NewTurretRotation = FMath::RInterpTo(TurretMesh->GetComponentRotation(), LookAtRotation, DeltaTime, TurretRotationSpeed);
TurretMesh->SetWorldRotation(NewTurretRotation);
// Rotate the gun towards the player
FRotator GunLookAtRotation = (PlayerLocation - GunMesh->GetComponentLocation()).Rotation();
FRotator NewGunRotation = FMath::RInterpTo(GunMesh->GetComponentRotation(), GunLookAtRotation, DeltaTime, GunRotationSpeed);
GunMesh->SetWorldRotation(NewGunRotation);
// Check if the turret is facing the player and within shooting range
FVector ToPlayer = PlayerLocation - GunMesh->GetComponentLocation();
float DotProduct = FVector::DotProduct(ToPlayer.GetSafeNormal(), GunMesh->GetForwardVector());
if (DotProduct > 0.99f && ToPlayer.Size() <= ShootingRange)
{
// Fire when facing the player
Shoot();
}
}
}
void ATurretActor::Shoot()
{
// Implement your shooting logic here
// You can spawn projectiles or apply damage to the player, for example
}
// Other functions and logic can be added as needed
Для того чтобы проверить работу хотя бы того когда, требовались файлы заголовков, и я достаточно просто их получил
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TurretActor.generated.h"
UCLASS()
class GPT_TEST_API ATurretActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATurretActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
private:
// Base mesh component
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* BaseMesh;
// Turret mesh component
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* TurretMesh;
// Gun mesh component
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* GunMesh;
// Collision box for player detection
UPROPERTY(VisibleAnywhere)
UBoxComponent* PlayerDetectionBox;
// Player character reference
ACharacter* PlayerCharacterRef;
// Rotation speeds
UPROPERTY(EditAnywhere)
float BaseRotationSpeed;
UPROPERTY(EditAnywhere)
float TurretRotationSpeed;
UPROPERTY(EditAnywhere)
float GunRotationSpeed;
// Shooting parameters
UPROPERTY(EditAnywhere)
float ShootDelay;
UPROPERTY(EditAnywhere)
float ShootingRange;
FTimerHandle ShootTimerHandle;
// Function to handle shooting
void Shoot();
};
Однако, в итоге, стоит отметить, что попытки решения проблемы с помощью чат-бота оказались неэффективными. Главной причиной этого оказалось ограничение по объему текста, так как целиком код .cpp
и .h
файлов не уместился в одном ответе. Это означает, что некоторые важные части кода и объявления в заголовочных файлах не были видны чат-боту, что делало невозможным диагностику и решение проблемы.
Чат-бот GPT имеет свои ограничения и он не способен самостоятельно создавать полностью работоспособный код даже для легких объектов. Разработка программного обеспечения требует глубокого технического понимания, архитектурного проектирования и знания специфических инструментов и библиотек.
Они не способны:
Создавать проекты с нуля;
Генерировать сложный и эффективный код;
Понимать специфические требования проекта;
Решать проблемы без контекста.
Несмотря на это есть и плюсы можно сделать вывод о полезности использования GPT в разработке, а именно:
Поиск документации и решений;
Исправление ошибок в коде;
Подсказки по оптимизации.
Следует помнить об ограничениях GPT и его текущих возможностях создания кода для Unreal Engine. Важно понимать, что GPT не является заменой опыта и компетенции разработчика. Он может быть полезным инструментом, но не всегда предоставляет исчерпывающие и точные ответы, особенно при сложных проблемах.
В будущем, с развитием искусственного интеллекта, возможно, GPT станет более мощным инструментом для разработчиков. Он сможет предоставлять более точные рекомендации и, возможно, даже генерировать части кода или ассеты для игр. Однако на данный момент основной путь к успешной разработке остается в обучении и практике.
Таким образом важно постоянно совершенствовать свои навыки и обучаться программированию, а также изучать работу движка Unreal Engine для достижения успешных результатов в разработке игр и приложений.
Для этого хотим предложить вам уникальную возможность углубить свои знания и навыки в разработке игр и виртуальных миров. OTUS предлагает вам посетить бесплатный вебинар, где погрузимся в мир Unreal Engine и научимся создавать эффект взрыва с использованием Niagara System. Разберем основы работы в Niagara и пройдем через процесс создания собственного эффекта взрыва, включая добавление собственных эмиттеров.
Комментарии (5)
Doman
15.09.2023 12:28+1Думаю, при большем усердии можно было бы добиться от ChatGPT лучших результатов, а потом переиспользовать систему промптов для других объектов. Но, в целом, я согласен, что пока контекст болтается в районе 4-16к токенов - для полноценного создания кода LLM-ки использовать не получится. Только как вспомогательный инструмент.
Возможно, авторы Copilot научатся эффективно работать с большими объемами кода в своем проекте GitHub Copilot for Your Codebase. Возможно, ML-инженеры смогут раздвинуть окно контекста у GPT без деградации способностей. Благо, есть много задач, где большой контекст или не нужен, или проблема решается эмбедингами, поэтому пока можно тренироваться на них. А потом - посмотрим.
nebsehemvi
15.09.2023 12:28Главной причиной этого оказалось ограничение по объему текста, так как целиком код
.cpp
и.h
файлов не уместился в одном ответе.Я могу порекомендовать поиграться с песочницей GPT-4, а не с самим чатом.
Выходит дешевле (можно закинуть всего 5$/5 евро например), выбрать максимальную длину ответа в токенах. 4 версия появляется в списке после вноса средств на баланс. Плюс доступ к API ключам, которые могут использоваться в сторонних приложениях (н. Cursor)
У меня вопрос - как влияет ограниченность GPT модели по датам? Датасет вроде бы был до 2021 года, 2 пропущенных года для движка - это же наверняка много?
diakin
То есть если снять это ограничение и выдать GPT подробное ТЗ, то задачу он решит?
Дальше надо чтобы он произвел отладку, тестирование и выдал готовый продукт на проверку человеком.
Так это же круто!
DrSavinkov
Это лишь одна из проблем бота, о чём автор и написал: