Считаю себя инди-разработчиком, моей самой серьёзной работой, является MMO Project zone 2d online (онлайн-версия игры “S.T.A.L.K.E.R.” в 2D). Когда я узнал про ChatGPT, решил, что обязательно надо сделать игру с его использованием. Первое, что приходит в голову, когда речь заходит о ChatGPT и играх, это работа с текстом: диалоги, квесты, сюжет. Но это банально, скучно и не интересно. Мне же хочется, что бы ИИ мог управлять буквально каждым аспектом геймплея.

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

Что под капотом?

Основная суть состоит из трех шагов.

  • Отправляем запрос в СhatGPT, получаем ответ в виде кода.

  • Выполняем этот код, получаем информацию.

  • Используем полученную информацию в своих целях.

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

Моя программа написана на языке java. Для подключения к СhatGPT API я использовал библиотеку openai-java by Theo Kanning. Для вывода интерфейса и графики, был использован фреймворк LibGDX. Так же, я использовал бесплатную версию ChatGPT "gpt-3.5-turbo-0613".

Подробнее

Изначально я планировал получать от ИИ готовые данные. Но это даёт куда меньше свободы, да и вообще не работает. ChatGPT не справляется с некоторыми задачами, если попросить его не писать код, но ту же задачу он решит правильно при помощи кода.

Пример

Я попросил ChatGPT представить, что у нас есть координатная плоскость размером 100 на 100 точек(игровая локация). У нас есть прямоугольник(дорога), размером, 100 точек по высоте и 30 точек по ширине, расположен он по середине координатной плоскости. Так же у нас есть 30 объектов(деревья), которые требуется расставить в случайных местах, но с условием, что их не должно было быть внутри прямоугольника. Я попросил ChatGPT прислать мне координаты каждого дерева, по оси x и y. Некоторые координаты деревьев постоянно находились внутри дороги, как бы я не менял условие и не просил его исправится. Если попросить ChatGPT прислать код с решением этой задачи, то он все сделает верно.

Java - компилируемый язык программирования. Это означает, что перед запуском программы весь код должен пройти стадию компиляции. Однако, когда мы получаем код от ChatGPT, программа уже запущена. Поэтому требуется компилировать код "на лету". За это отвечает библиотека компилятора JavaCompiler , которая встроена в java по дефолту. Вот полезная статья на эту тему. Вот пример из моего проекта: он компилирует класс с именем Main и запускает метод run().

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
    JavaFileObject fileObject = new JavaSourceFromString("Main", code);
    Iterable<? extends JavaFileObject> compilationUnits = Collections.singletonList(fileObject);
    JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
    boolean success = task.call();
    if (success) {
        System.out.println("Compilation succesful");
        URL[] urls = new URL[]{new File("").toURI().toURL()};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class<?> cls = classLoader.loadClass("Main");
        Object obj = cls.newInstance();
        cls.getMethod("run").invoke(obj);
        System.out.println("start function run");
    } else {
        for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
            System.out.println(diagnostic.getCode());
            System.out.format("Error on line %d in %s%n", diagnostic.getLineNumber(), diagnostic.getSource().toUri());
        }
    }
    fileManager.close();

Наш код ничего не знает о присланном коде от ChatGPT, и наоборот, код, присланный от ChatGPT ничего не знает о нашем коде. Поэтому, они не могут взаимодействовать друг с другом. Все, что остается, — это получить какой-то результат после выполнения нового кода и затем использовать этот результат в своем проекте. Конкретно в моем случае игровые объекты меняют значения своих координат в цикле. Поэтому я попросил ChatGPT каждую итерацию цикла записывать координаты каждого объекта в тестовый файл. Каждый класс, полученный от ChatGPT, содержит примерно такой код:

FileWriter writer = new FileWriter("coordinates.txt", false);
            for (int cycle = 0; cycle < numCycles; cycle++) {
                for (Object object : objects) {
                    object.move(width, height);
                    writer.write(object.toString());
                }
            }

Это и является связующим звеном между ChatGPT и нашим кодом.

Теперь у нас имеется файл coordinates.txt в котором хранятся, все координаты каждого объекта формате - Object "object number" at ("x","y"). Далее парсим этот файл, и записываем полученные данные в массивы "x" и "y". После чего, по сути воспроизводим, заранее записанный маршрут. Кстати парсер для файла coordinates.txt для меня написал ChatGPT, я бы справился с этим и сам, но на это бы, ушло какое-то время.

Настраиваем ChatGPT

Для взаимодействия с ChatGPT API нам требуется получить ключ от OpenAI. Получить его можно по адресу OpenAI API в личном кабинете. Далее я советую сохранить этот ключ, как переменную окружения(windows) и вызывать его в коде при помощи строки:

System.getenv("OPENAI_TOKEN");

Перед работой с ChatGPT его можно "настроить", используя системное сообщение. Моё системное сообщение выглядит примерно так:

System Message

Привет, тебе следует придерживаться следующих инструкций.

1. Когда я попрошу тебя написать код, тебе следует использовать язык java. Тебе следует прислать мне класс с названием Main, в этом классе должен отсутствовать метод public static void main(String args[]), его следует заменить методом run(), всю логику стоит помещать внутрь этого метода. Так же мне не требуется код, связанный с отрисовкой графики, мне нужна только логика.

2. Когда я попрошу изменить цвет текста для чата, мне не нужен код, мне потребуется ответ из трех чисел, r,g и b. Требуется вызывать метод в коде.

3. Во всех остальных случаях, веди себя как обычный помощник.

ChatGPT не хранит у себя историю переписки. Когда вы пишите что-то в чат, вы всегда присылаете ему все прошлые сообщения. И он каждый раз анализирует всю историю, чтобы выдать вам правильный ответ. Зная это, я сделал еще одну очень важную вещь. После отправки системного сообщения, я записываю в массив переписки еще два сообщения. Первое от имени пользователя:

ChatMessageRole.USER

Мне нужен java код который создаст 7 объектов, на координатной плоскости размером 1920 на 1025 пикселей. Каждый объект имеет размер 50 на 50 пикселей. Расставь их в случайных местах. Затем они должны начать движение влево, пока не достигнут границ, после чего следует изменить направление. 1000 циклов выполнения будет достаточно. Каждую итерацию цикла требуется записывать координаты каждого объекта в текстовый файл с именем coordinates.txt в формате Object numberObject at (x, y), где numberObject - это номер объекта. При создании объекта FileWriter значение параметра boolean следует указать как false, для того что бы файл перезаписывался.

Второе от имени ChatGPT с примером правильного ответа на запрос выше:

ChatMessageRole.ASSISTANT
import java.io.FileWriter;
import java.io.IOException;
import java.util.Random;

public class Main {
    public static void run() {
        int width = 1920;
        int height = 1025;
        int objectSize = 50;
        int numObjects = 7;
        int numCycles = 1000;

        Random random = new Random();
        Object[] objects = new Object[numObjects];

        for (int i = 0; i < numObjects; i++) {
            int x = random.nextInt(width - objectSize);
            int y = random.nextInt(height - objectSize);
            objects[i] = new Object(i, x, y);
        }

        try {
            FileWriter writer = new FileWriter("coordinates.txt", false);
            for (int cycle = 0; cycle < numCycles; cycle++) {
                for (Object object : objects) {
                    object.move(width, height);

                    writer.write(object.toString());
                }
            }
            writer.close();
        } catch (IOException e) {
            System.out.println("Ошибка записи в файл.");
        }
    }

    public static void main(String[] args) {
        run();
    }
}

class Object {
    private int number;
    private int x;
    private int y;
    private int direction;

    public Object(int number, int x, int y) {
        this.number = number;
        this.x = x;
        this.y = y;
        this.direction = 1;
    }

    public void move(int maxX, int maxY) {
        if (x + direction * 5 >= maxX - 50 || x + direction * 5 <= 0) {
            direction *= -1;
        }
        x += direction * 5;
    }

    @Override
    public String toString() {
        return "Object " + number + " at (" + x + ", " + y + ")\n";
    }
}

Тем самым, перед каждым запросом в ChatGPT, мы уже будем иметь "правильную" историю переписки, проанализировав которую ChatGPT выдаст "правильный" ответ.

ChatGPT умеет вызывать ваши методы (функции)

До того момента, как я начал разбираться с библиотекой взаимодействия с ChatGPT API, я даже не подозревал о таком функционале. Это довольно интересная особенность. Она была использована для смены цвета текста в чате. Также я хотел использовать эту функцию для того, что бы принимать код, но с этим возникли проблемы, которые опишу чуть далее.

Вот код получения цвета, для изменения текста в чате:

СhangeСolor
    public static class СhangeСolorText {
        @JsonPropertyDescription("Измени цвет текста для чата,число r, значение должно быть от 0 до 0.999")
        public float r;
        @JsonPropertyDescription("Измени цвет текста для чата,число g, значение должно быть от 0 до 0.999")
        public float g;
        @JsonPropertyDescription("Измени цвет текста для чата, число b, значение должно быть от 0 до 0.999")
        public float b;
    }

    public static class СhangeСolorTextResponse {
        public float r;
        public float g;
        public float b;

        public СhangeСolorTextResponse(float r, float g, float b, MyGdx mygdx) {
            this.r=r;
            this.g=g;
            this.b=b;
            mygdx.chat.changeTextColor(r,g,b);
        }
    }

FunctionExecutor functionExecutor = new FunctionExecutor(Collections.singletonList(ChatFunction.builder()
                .name("get_color")
                .description("Измени цвет текста для чата, мне требуется ответ из трех чеслел r g b, значение каждой переменной " +
                        "должно варьироваться от 0 до 0.999")
                .executor(СhangeСolorText.class, w -> new СhangeСolorTextResponse(w.r,w.g, w.b,mygdx))
                .build()));

ChatFunctionCall functionCall = responseMessage.getFunctionCall();
        if (functionCall != null) {
            System.out.println("Trying to execute " + functionCall.getName() + "...");
            functionExecutor.execute(functionCall);
        }

Изначально ChatGPT присылал значения переменных RGB от 0 до 256. Я думал, что придётся писать метод для конвертации значений, так как в LibGDX используется диапазон от 0 до 1.0. Но я просто попросил ChatGPT использовать нужный диапазон, и он все понял.

С вызовом метода для получения java-кода, у меня возникли некие трудности. Был написан метод в параметры к которому, должен приходить код от ChatGPT. Когда требуется вызов метода, ChatGPT присылает ответ в JSON-обёртке. В моём случае ответом был java-код. Java-код, обернутый в JSON... При парсингде ответа в 50% случаев все было в порядке, в остальных 50% я получал вот такие ошибки:

java.lang.RuntimeException

Exception in thread "main" java.lang.RuntimeException: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('+' (code 43)): was expecting comma to separate Object entries
at [Source: (String)"{
"sCode": "String code = "import java.io.FileWriter;\n" +\n" +
" "import java.io.IOException;\n" +\n" +
" "\n" +\n" +
" "public class Main {\n" +\n" +
" "\n" +\n" +
" " public static void main(String[] args) {\n" +\n" +
" " int numObjects = 5;\n" +\n" +
" " int width = 1920;\n" +\n" +
" " int height = 1080;\n" +\n" +
" "[truncated 1119 chars]; line: 2, column: 67]
at com.theokanning.openai.service.FunctionExecutor.execute(FunctionExecutor.java:87)
at example.OpenAiApiFunctionsExample.main(OpenAiApiFunctionsExample.java:85)

java.lang.RuntimeException

Exception in thread "main" java.lang.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: Illegal unquoted character ((CTRL-CHAR, code 10)): has to be escaped using backslash to be included in string value

Это происходило, из-за того что где-то попадался лишний слеш. Я боролся с этой проблемой два дня, но так и не смог её исправить. Просто плюнул и удалил функционал, связанный с вызовом методов для java-кода, и стал вырезать код из обычных сообщений.

Делается это довольно просто. Иногда ChatGPT присылает код, без каких-либо комментариев. В этом случае сообщение начинается с импорта нужных классов. А конкретно с ключевого слова "import" и если оно стоит первым, значит это java-код.

 String simport=sCode.substring(0,6);
 if(simport.equals("import"))code=sCode;

А иногда ChatGPT даёт некие комментарии к коду (хоть я и просил его этого не делать). В этом случае код заключается в кавычки.

int startPosition=sCode.indexOf("```java")+7;
int endPosition=sCode.lastIndexOf("```");
code=sCode.substring(startPosition,endPosition);

Если ничего из вышеперечисленного не найдено, значит это обычное сообщение.

О ChatGPT и играх

На самом деле, придумать концепцию игры, куда бы органично вписывался ИИ, довольно не простая задача. И у меня даже нет конкретных идей на этот счет. Если у кого-то есть интересные мысли по этому поводу, буду рад прочесть их в комментариях. Следующий шаг, которым я хочу заняться - это генерация тайловых карт по запросу. Я думаю с этим не возникнет особых трудностей. Так же, ChatGPT мог бы генерировать квесты, не текст для квестов, а именно создавать саму структуру заданий. Это вполне реализуемо.

Есть такая шутка, что в идеальном движке для разработки игр должна быть всего одна кнопка - "Сделать крутую игру". ChatGPT серьёзно приблизил нас к этому. Я уверен, что в скором времени, появится игровой движок, в котором, можно будет сделать игру, просто описывая все обычными предложениями. Возможно, я этим как-нибудь и займусь, единственная проблема, которая может помешать этому - это ограничение в количестве токенов.

Немного лирики

ИИ - это очень перспективная технология, которая со временем все глубже и глубже будет проникать в нашу жизнь. Создание AGI можно будет сравнить с изобретением электричества или интернета. С развитием ИИ, грань между человеческими языками и языками программирования будет стираться. Последние будут использоваться только узкоспециализированными специалистами или и вовсе будут забыты - это вопрос времени. Моя программа, небольшое тому доказательство, мы можем программировать движение объектов не используя код.

Пара слов напоследок

Я только начал разбираться с ChatGPT, поэтому думаю некоторые вещи можно было сделать гораздо лучше, код проекта тоже не идеален. Но надеюсь, моя статья оказалась полезной. Возможно, она натолкнёт кого-нибудь на интересную мысль. Весь код проекта на github.

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


  1. aka352
    13.04.2024 05:38

    Проект Sora - первый шаг к динамически генерируемым играм. Так что в этом вы правы, относительно скоро создание игры будет сводиться к условному нажатию одной кнопки. Равно как и создание фильмов, музыки. Хотя с играми несколько сложнее - существующие генерационные модели линейные и нужна серьезная доработка архитектуры, чтобы сделать процесс итерационным для взаимодействия с пользователем в реальном времени.

    Если интересно углубиться в эту тему, то рекомендую использовать не API моделей, а локальные модели на 2-7B параметров. Да, они меньше, возможности их скромнее, но их можно дообучать под свои локальные задачи, не придумывая промптовых костылей. Тем более, что большинство практических задач достаточно узкоспециализированы и небольшая доообученная модель будет справляться лучше, чем универсальная.


    1. max-warlock Автор
      13.04.2024 05:38

      Ну Sora к играм никакого отношения не имеет. Да и мне кажется openAI не будут заниматься играми, это не их профиль. Для разработки движка, требуется проделать огромную работу, не зависящую от ИИ. Но для концепции такого движка возможностей ИИ уже хватает.


      1. aka352
        13.04.2024 05:38

        Sora - это технология генерации. А что именно она будет генерировать - клипы, фильмы или визуальный ряд игрового процесса, дело второе. При должном быстродействии ничего не мешает получать промпт в виде действий пользователя и генерировать соответствующую картинку. OpenAI может сама не заниматься играми, а просто предоставить доступ к API, как это реализовано с ChatGPT.


        1. max-warlock Автор
          13.04.2024 05:38

          Мысль интересная, но все таки это очень далёкое будущее, на такое дело, требуются нереальные вычислительные мощности. Обрабатывать ввод игрока, и генерировать картинку в реалтайме, учитывая игровую механику. Это что-то из области фантастики) Но кто знает, наш мир развивается быстрыми темпами. Я все же смотрю на игры с ИИ, немного под другим углом.


          1. aka352
            13.04.2024 05:38

            Sora уже неплохо моделирует физику и окружение, при том, что она ещё в бета-версии. За год-два, учитывая текущую динамику это может оформиться в годную технологию. Применительно к играм это может дополняться мощностью локального GPU, генерирующего промежуточные кадры на базе опорных от Sora по типу DLSS или FSR, для обеспечения плавности игрового процесса.


  1. Blackthorn
    13.04.2024 05:38
    +1

    Интересная статья использования языка java и ИИ.


  1. paroletatel
    13.04.2024 05:38

    Здравствуйте.

    Используйте английский язык. Английский язык повышает точность, скорость (за счёт того, что кол-во токенов снижено в сравнении с русским), уменьшает шанс галлюцинации.

    Одним словом, когда просите gpt выполнить инструкции, всегда используйте английский. (Speak <language> only! — решает задачу о языке оутпута)


    1. max-warlock Автор
      13.04.2024 05:38

      Я знаю об этом, но мне все же было интересно, как ChatGPT, работает конкретно с русским. И проблем не было никаких, кроме повышенного количества токенов. Если делать коммерческий продукт, должна быть возможность использовать любой язык.