Автор статьи: Ростислав Мусиенко

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 имеет свои ограничения и он не способен самостоятельно создавать полностью работоспособный код даже для легких объектов. Разработка программного обеспечения требует глубокого технического понимания, архитектурного проектирования и знания специфических инструментов и библиотек.

Они не способны:

  1. Создавать проекты с нуля;

  2. Генерировать сложный и эффективный код;

  3. Понимать специфические требования проекта;

  4. Решать проблемы без контекста.

Несмотря на это есть и плюсы можно сделать вывод о полезности использования GPT в разработке, а именно:

  1. Поиск документации и решений;

  2. Исправление ошибок в коде;

  3. Подсказки по оптимизации.

Следует помнить об ограничениях GPT и его текущих возможностях создания кода для  Unreal Engine. Важно понимать, что GPT не является заменой опыта и компетенции разработчика. Он может быть полезным инструментом, но не всегда предоставляет исчерпывающие и точные ответы, особенно при сложных проблемах.

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

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

Для этого хотим предложить вам уникальную возможность углубить свои знания и навыки в разработке игр и виртуальных миров. OTUS предлагает вам посетить бесплатный вебинар, где погрузимся в мир Unreal Engine и научимся создавать эффект взрыва с использованием Niagara System. Разберем основы работы в Niagara и пройдем через процесс создания собственного эффекта взрыва, включая добавление собственных эмиттеров.

Комментарии (5)


  1. diakin
    15.09.2023 12:28

     Главной причиной этого оказалось ограничение по объему текста, так как целиком код .cpp и .h файлов не уместился в одном ответе. 

    То есть если снять это ограничение и выдать GPT подробное ТЗ, то задачу он решит?
    Дальше надо чтобы он произвел отладку, тестирование и выдал готовый продукт на проверку человеком.
    Так это же круто!


    1. DrSavinkov
      15.09.2023 12:28

      Это лишь одна из проблем бота, о чём автор и написал:

      Следует помнить об ограничениях GPT и его текущих возможностях создания
      кода для  Unreal Engine. Важно понимать, что GPT не является заменой
      опыта и компетенции разработчика. Он может быть полезным инструментом,
      но не всегда предоставляет исчерпывающие и точные ответы, особенно при
      сложных проблемах.


  1. Doman
    15.09.2023 12:28
    +1

    Думаю, при большем усердии можно было бы добиться от ChatGPT лучших результатов, а потом переиспользовать систему промптов для других объектов. Но, в целом, я согласен, что пока контекст болтается в районе 4-16к токенов - для полноценного создания кода LLM-ки использовать не получится. Только как вспомогательный инструмент.

    Возможно, авторы Copilot научатся эффективно работать с большими объемами кода в своем проекте GitHub Copilot for Your Codebase. Возможно, ML-инженеры смогут раздвинуть окно контекста у GPT без деградации способностей. Благо, есть много задач, где большой контекст или не нужен, или проблема решается эмбедингами, поэтому пока можно тренироваться на них. А потом - посмотрим.


    1. tantie
      15.09.2023 12:28

      У 4ки 32. Этого вполне хватает. Автор видимо 3,5 использовал.


  1. nebsehemvi
    15.09.2023 12:28

    Главной причиной этого оказалось ограничение по объему текста, так как целиком код .cpp и .h файлов не уместился в одном ответе.

    Я могу порекомендовать поиграться с песочницей GPT-4, а не с самим чатом.
    Выходит дешевле (можно закинуть всего 5$/5 евро например), выбрать максимальную длину ответа в токенах. 4 версия появляется в списке после вноса средств на баланс. Плюс доступ к API ключам, которые могут использоваться в сторонних приложениях (н. Cursor)

    У меня вопрос - как влияет ограниченность GPT модели по датам? Датасет вроде бы был до 2021 года, 2 пропущенных года для движка - это же наверняка много?