Перейти к содержимому

Plasmid

Серверная библиотека для разработки мини-игр на Fabric.

FabricQuilt
Загрузчики
FabricQuilt
Скачиваний на Modrinth
5,2 тыс.
Подписчиков на Modrinth
24
версий
12
Обновлён
27 июня 202627 июн.
Опубликован
в 2023
Это библиотека — она нужна другим модам и сама ничего не добавляет в игруНужна 5 модам. Скачайте её, если этого требует другой мод.

Коротко о моде

Библиотека — нужна другим модам: её требуют 5
Работает на Fabric и Quilt
Для Minecraft 1.21.7–1.21.8, 26.1.2–26.2 …
Ставится только на сервер; игрокам ставить не нужно
Последняя версия — 0.7.2+26.2 от 27 июня 2026
Нужен Polymer и Fabric API
Где нужен
В игрене нуженНа сервереобязателен

Ставится только на сервер; игрокам ставить не нужно

Версии игры1.21.3–1.21.5, 1.21.7–1.21.8, 26.1.2–26.2 …
Описание переведено автоматическиПоказать оригинал (EN)

Plasmid

Plasmid — это библиотека для создания серверных мини-игр на Fabric. Plasmid берёт на себя всю скучную работу, связанную с реализацией мини-игр, чтобы можно было сосредоточиться непосредственно на самой игре.

Plasmid — это ядро Nucleoid Project, инициативы по созданию экосистемы мини-игр для серверного Minecraft с открытым исходным кодом. Множество примеров игр, реализованных с помощью Plasmid, можно посмотреть в Nucleoid GitHub Organisation. Возможно, вам даже будет интересно поиграть в некоторые из них на нашем тестовом Minecraft-сервере по адресу nucleoid.xyz! Также вы можете найти нас в нашем Discord, если у вас возникли проблемы или вопросы, или вы хотите присоединиться.

Использование

Это зеркало нашей вики-страницы Getting Started, дающее базовое представление о концепциях Plasmid. Более подробную информацию можно найти на нашем сайте вики.

Если вы хотите быстро начать работу с базовой настройкой игры, клонируйте репозиторий plasmid-starter, запустите init.py, а затем удалите .git, README.md и init.py. Либо, если вы ищете примеры уже реализованных игр, просмотрите репозитории Nucleoid Organisation.

Добавление в Gradle

Предполагая, что у вас уже настроено рабочее пространство Fabric, первым шагом к настройке Plasmid будет добавление его в ваш скрипт сборки gradle. Вам нужно будет добавить репозиторий maven, а также зависимость plasmid. PLASMID_VERSION следует заменить на последнюю версию с Maven.

Этот туториал на данный момент обновлён для Plasmid 0.5.x.

repositories {
  maven { url = 'https://maven.nucleoid.xyz/' }
}

dependencies {
  // ...
  modImplementation 'xyz.nucleoid:plasmid:PLASMID_VERSION'
}

Создание типа игры

"Тип игры" (GameType) — это точка входа для создания игры с Plasmid: он предоставляет уникальный идентификатор для вашей игры, а также всю информацию, необходимую для того, чтобы можно было вызвать ваш код при запуске игры.

Plasmid спроектирован так, чтобы поощрять игры, управляемые данными, и работает с концепцией "конфига игры". Конфиг игры — это, по сути, конкретная вариация типа игры! Это может подразумевать другую карту для игры или полностью другие игровые механики. Конфиг игры просто определяется как JSON-файл в датапаке, который ссылается на ваш GameType и передаёт любые дополнительные данные, которые могут быть полезны для настройки вашей игры. Хотя поначалу это может показаться чуть большей работой, это очень мощный подход, позволяющий гораздо проще настраивать игры или создавать несколько вариаций без дублирования кода. Подробнее о конфигах позже!

Чтобы зарегистрировать GameType, вам нужно вызвать GameType.register() в вашем классе ModInitializer. Вызов для регистрации GameType может выглядеть примерно так:

GameType.register(
        new Identifier("plasmid_example", "example"),
        ExampleGameConfig.CODEC,
        ExampleGame::open
);

Давайте разберём, что здесь происходит:

  • new Identifier("plasmid_example", "example")

    • объявляет уникальный идентификатор для этого типа игры, на который будут ссылаться JSON-файлы конфигов игр
  • ExampleGameConfig.CODEC

    • Codec, который будет использоваться для загрузки конфига игры из JSON-файла (подробнее об этом позже!)
  • ExampleGame::open

    • ссылка на метод для функции, которая будет использоваться для запуска вашей игры, когда игрок её запросит

Это, естественно, пока не скомпилируется: ни ExampleGame , ни ExampleGameConfig не существуют! Давайте это исправим.

Создаём конфиг в коде

Сначала создадим класс ExampleGameConfig, который будет хранить поле String, используемое как сообщение, отправляемое игроку при входе. Новые Records из Java идеально подходят для конфигов, но это не обязательно!

public record ExampleGameConfig(String greeting) {
}

Достаточно просто! Но мы упустили поле CODEC, на которое ссылались ранее. В чём дело?

Codec — очень полезный инструмент, реализованный в библиотеке Mojang DataFixerUpper, который по сути позволяет удобно сериализовать и десериализовать Java-объект в JSON-файл. Более подробное объяснение Codec от Drullkus можно найти здесь, но для простых целей достаточно знать шаблон их сборки.

По сути, Codec описывает, как объект сериализуется и десериализуется. Проще говоря, их можно создавать из списка полей и того, как эти поля должны сериализоваться. Выглядит это так:

public record ExampleGameConfig(String greeting) {
    public static final Codec<ExampleGameConfig> CODEC = RecordCodecBuilder.create(instance -> {
        return instance.group(
                Codec.STRING.fieldOf("greeting").forGetter(ExampleGameConfig::greeting)
        ).apply(instance, ExampleGameConfig::new);
    });
}

Это будет соответствовать JSON-файлу примерно такого вида:

{
  "greeting": "Hello World!"
}

Большую часть здесь можно игнорировать: на самом деле нужно обращать внимание только на то, что находится в вызове instance.group(...), и на generic у Codec. Разберём каждую важную часть подробнее:

  • `Codec`

    • Тип класса, в который выполняется десериализация, передаётся как generic-параметр в Codec.
  • Codec.STRING.fieldOf(...).forGetter(...)

    • Это добавляет поле с заданным именем и типом, которое будет читаться из JSON.
    • Вы заметите, что Codec.STRING сам по себе является `Codec! Каждое объявляемое поле требует Codec, описывающий, как с этим полем следует обращаться. В данном случае мы указываем, что поле greeting должно загружаться с помощью Codec.STRING`. Точно так же мы могли бы сослаться на любой другой созданный нами codec, чтобы добавить его как поле! Это очень полезно, поскольку позволяет комбинировать codec для создания сложных структур!

      • Совет по Codec: большинство сериализуемых типов Minecraft содержат статическое поле CODEC для использования (например, BlockPos.CODEC или Identifier.CODEC). Если его нет, мы поставляем тип MoreCodecs, который предоставляет некоторые распространённые, не включённые в ванильную кодовую базу (например, MoreCodecs.TEXT).
    • Параметр .fieldOf() задаёт имя поля (в JSON), из которого будет читаться это значение.
    • .forGetter() задаёт, как значение поля должно считываться обратно из нашего объекта конфига. Это полезно, поскольку codec допускают как сериализацию, так и десериализацию, а getter нужен для превращения объекта обратно в данные. Здесь можно использовать ссылку на метод, так как мы используем record.
  • ExampleGameConfig::new

    • Это указывает codec, как создать объект после десериализации всех полей. Требуется ссылка на метод конструктора для данного объекта со всеми полями в том порядке, в котором они были указаны!.
    • Например, если бы мы передали Codec.STRING.fieldOf("foo"), а затем Codec.INT.fieldOf("bar), конструктор принимал бы (String, int).
    • Но здесь мы принимаем одно поле String, и конструктор, на который мы ссылаемся, тоже принимает один параметр String.

Конечный результат всей этой работы с Codec — когда мы создаём игровой конфиг, все эти данные автоматически парсятся из нашего JSON-файла и передаются в игровой код!

Создание конфига

Теперь, когда мы знаем, какие данные должен хранить наш конфиг, мы можем создать настоящий игровой конфиг JSON, который загрузит Plasmid.

Все игровые конфиги должны находиться в ресурсах вашего мода (или в датапаке!) по пути `data//games/.json. Для мода namespace— это просто id вашего мода, аid` может быть любым уникальным именем, которое позже будет использоваться для ссылки на ваш игровой конфиг из Minecraft.

Plasmid требует только 1 поле JSON из конфига, остальное загружается согласно кодеку конфига, который вы настроили. Однако есть также несколько дополнительных необязательных полей, которые могут быть полезны. Единственное обязательное поле — type, которое ссылается на GameType, созданный вами ранее, в формате namespace:path (например, в нашем случае plasmid_example:example).

Для наших целей наш игровой конфиг по пути data/plasmid_example/games/hello_world_example.json будет выглядеть так:

{
  "type": "plasmid_example:example",
  "greeting": "Hello, World!"
}

Мы также можем добавить несколько дополнительных встроенных полей в наш JSON, таких как name, short_name, description и icon. Это может выглядеть так:

{
  "type": "plasmid_example:example",
  "name": "Hello World Example!",
  "description": ["Look at my cool game!", "It greets you when you join."],
  "icon": "minecraft:apple"
  // ...
}

name и description также могут ссылаться на ключи перевода, поскольку являются JSON Text Components. Например, это может быть вместо этого: "name": {"translation": "game.plasmid_example.hello_world_example"}.

Замечание о переводах

Переводы в Plasmid немного нестандартны из-за того, что он полностью серверный! Обычно переводы хранятся на клиенте игры, а сервер просто отправляет ключи перевода, которые затем превращаются в читаемый текст на стороне клиента. Здесь же нам нужно вместо этого обрабатывать переводы, изменяя пакеты, отправляемые игрокам, так, чтобы они были корректно переведены до того, как клиент их получит. Это большая работа! К счастью, этим занимается Server Translations, и нам не нужно об этом беспокоиться!

Всё, что это значит для вас на практике, — ваши языковые файлы должны находиться в папке data, а не в папке assets (например, `data//lang/en_us.json`).

Есть несколько стандартных языковых ключей, о которых стоит позаботиться, если мы не определяем имя вручную: `gameType..иgame..`. Эти ключи применяются для игровых типов и игровых конфигов соответственно. При определении читаемого имени для игрового конфига проверяются как перевод конфига, так и перевод типа, причём тип используется как запасной вариант. Это значит, что строго необходим только перевод игрового типа.

Например, мы можем определить наш data/plasmid_example/lang/en_us.json так:

{
  "gameType.plasmid_example.example": "Plasmid Example!",
  "game.plasmid_example.hello_world_example": "Hello World Example!"
}

Пишем код для запуска нашей игры

Теперь, когда мы настроили конфиг и рассказали Plasmid, как из него читать, мы наконец можем написать код для фактического запуска нашей игры.

Для целей этого примера давайте создадим класс ExampleGame. Мы будем использовать этот класс для хранения состояния игры, а также нашего загруженного ExampleGameConfig. Пока же нам нужно только создать эту функцию open, на которую мы ссылались в GameType.

Это должно выглядеть так:

public class ExampleGame {
    public static GameOpenProcedure open(GameOpenContext<ExampleGameConfig> context) {
        // get our config that got loaded by Plasmid
        ExampleGameConfig config = context.config();

        // create a very simple map with a stone block at (0; 64; 0)
        MapTemplate template = MapTemplate.createEmpty();
        template.setBlockState(new BlockPos(0, 64, 0), Blocks.STONE.getDefaultState());

        // create a chunk generator that will generate from this template that we just created
        TemplateChunkGenerator generator = new TemplateChunkGenerator(context.server(), template);

        // set up how the world that this minigame will take place in should be constructed
        RuntimeWorldConfig worldConfig = new RuntimeWorldConfig()
                .setGenerator(generator)
                .setTimeOfDay(6000);

        return context.openWithWorld(worldConfig, (activity, world) -> {
            // to be implemented
        });
    }
}

Тут много всего, но всё не так сложно, если разобрать по частям. Наш open будет вызываться каждый раз, когда игрок запускает эту игру. Функция принимает GameOpenContext, который хранит данные из нашего JSON-конфига (context.config()), и должна вернуть GameOpenProcedure, который указывает Plasmid, как дальше настраивать игру. Стоит отметить, что эта функция выполняется асинхронно в пуле потоков, так что здесь безопасно запускать любой медленный код перед стартом игры.

GameOpenProcedure создаётся из функции GameOpenContext.openWithWorld и принимает RuntimeWorldConfig, а также лямбду, которая принимает GameActivity и ServerWorld. Runtime world — это концепция внутри Plasmid, представляющая полностью изолированный и временный мир, в котором проходит игра. Он автоматически удаляется, когда игра заканчивается. Когда игрок присоединяется к игре, его инвентарь очищается, а когда покидает — возвращается обратно. Game activity — это определённый набор логики, работающий внутри игры: именно его мы будем настраивать, чтобы менять поведение игры. Мы можем переключать activity внутри игры в любой момент.

RuntimeWorldConfig описывает, как должен создаваться этот мир. Самое важное, что здесь настраивается — это chunk generator: он говорит игре, как должен генерироваться мир. Можно было бы, например, передать сюда chunk generator оверворлда, но для нашей цели мы создаём пустой мир с одним каменным блоком. Это делается через удобный TemplateChunkGenerator: он принимает MapTemplate, который представляет собой просто очень базовый мир, содержащий несколько блоков! Затем generator загружает из него данные в сам мир.

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

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

Например:

return context.openWithWorld(worldConfig, (activity, world) -> {
    activity.deny(GameRuleType.FALL_DAMAGE);

    activity.listen(GamePlayerEvents.ADD, player -> {
        // a player has been added!
    });
});

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

Однако! Прежде чем мы дадим функциональность нашей блестящей примерной игре, нам нужно ответить на слушатель события предложения игрока. Он вызывается до того, как любой игрок присоединится к игре, и может принять или отклонить этот запрос на присоединение. Самое главное, слушатель определяет, как и где игрок должен появиться в нашем игровом мире.

Пример слушателя предложения может выглядеть так:

activity.listen(GamePlayerEvents.OFFER, offer -> {
    ServerPlayerEntity player = offer.player();
    return offer.accept(world, new Vec3d(0.0, 64.0, 0.0))
            .and(() -> {
                player.changeGameMode(GameMode.ADVENTURE);
            });
});

Это много! Давайте разберём это:

  • Мы регистрируем слушатель для GamePlayerEvents.OFFER, который принимает параметр offer.
  • Мы получаем экземпляр игрока, который пытается присоединиться, из предложения.
  • Мы вызываем offer.accept(...), чтобы принять игрока в игру.

    • Мы передаём функции accept мир и позицию, куда игрок должен быть телепортирован. Мир был передан нам выше Plasmid!
  • Затем мы вызываем .and(...) на результате .accept(...), чтобы прикрепить дополнительную логику появления, которая будет выполнена, когда игрок присоединится. В данном случае это установка игроку режима игры на приключение при присоединении.

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

GameSpace gameSpace = activity.getGameSpace();
activity.listen(GamePlayerEvents.ADD, player -> {
    LiteralText message = new LiteralText(config.greeting);
    gameSpace.getPlayers().sendMessage(message);
});

Итак, мы добавили логику для отправки сообщения внутри слушателя, но что такое GameSpace? GameSpace — это концепция, введённая Plasmid, которая, как следует из названия, представляет пространство, в котором происходит игра. Для всех наших целей это пространство — просто это одно измерение, в котором играет игра. GameSpace полезен для нас тем, что он отслеживает всех игроков внутри него, а также ServerWorld, в котором происходит игра. Здесь мы получаем доступ к GameSpace через GameActivity.getGameSpace().

Работа с игроками дополнительно идёт через другой API Plasmid: PlayerSet. PlayerSet представляет просто список игроков, и его можно перебирать или запрашивать, но дополнительно предоставляет утилиты для выполнения массовых операций над многими игроками. Например, отправка сообщения! Здесь мы используем PlayerSet.sendMessage(), чтобы отправить наше приветствие каждому игроку в игре.

Тада! 🎉 У нас есть работающая игра! Но прежде чем мы её протестируем, давайте сделаем небольшую реорганизацию. Со всеми этими обработчиками и лямбдами наш код внутри createOpenProcedure будет становиться довольно длинным очень быстро! Было бы неплохо, если бы мы могли поместить все слушатели событий на наш объект ExampleGame вместо этого.

Оказывается, это работает просто отлично, и у нас остаётся наша финальная настройка ExampleGame:

public final class ExampleGame {
    private final ExampleGameConfig config;
    private final GameSpace gameSpace;
    private final ServerWorld world;

    public ExampleGame(ExampleGameConfig config, GameSpace gameSpace, ServerWorld world) {
        this.config = config;
        this.gameSpace = gameSpace;
        this.world = world;
    }

    public static GameOpenProcedure open(GameOpenContext<ExampleGameConfig> context) {
        // get our config that got loaded by Plasmid
        ExampleGameConfig config = context.config();

        // create a very simple map with a stone block at (0; 64; 0)
        MapTemplate template = MapTemplate.createEmpty();
        template.setBlockState(new BlockPos(0, 64, 0), Blocks.STONE.getDefaultState());

        // create a chunk generator that will generate from this template that we just created
        TemplateChunkGenerator generator = new TemplateChunkGenerator(context.server(), template);

        // set up how the world that this minigame will take place in should be constructed
        RuntimeWorldConfig worldConfig = new RuntimeWorldConfig()
                .setGenerator(generator)
                .setTimeOfDay(6000);

        return context.openWithWorld(worldConfig, (activity, world) -> {
            ExampleGame game = new ExampleGame(config, activity.getGameSpace(), world);

            activity.deny(GameRuleType.FALL_DAMAGE);
            activity.listen(GamePlayerEvents.OFFER, game::onPlayerOffer);
            activity.listen(GamePlayerEvents.ADD, game::onPlayerAdd);
        });
    }

    private PlayerOfferResult onPlayerOffer(PlayerOffer offer) {
        ServerPlayerEntity player = offer.player();
        return offer.accept(this.world, new Vec3d(0.0, 64.0, 0.0))
                .and(() -> {
                    player.changeGameMode(GameMode.ADVENTURE);
                });
    }

    private void onPlayerAdd(ServerPlayerEntity player) {
        LiteralText message = new LiteralText(this.config.greeting);
        this.gameSpace.getPlayers().sendMessage(message);
    }
}

### Тестируем игру!
Как только всё скомпилируется, мы наконец сможем запустить Minecraft. Если наш `GameType` настроен правильно и игровой конфиг JSON на месте, то, открыв мир, мы сможем начать нашу игру, выполнив: `/game open <id>`. (Помни, здесь указывается имя JSON-файла, а не GameType!)

В нашем случае: `/game open plasmid_example:hello_world_example`
...и мы должны попасть в наш пустой мир с каменным блоком и милым приветствием!

Теперь любой другой игрок тоже может присоединиться к нам, выполнив `/game join` или кликнув по ссылке, которая появится в чате.

Вот и всё! 🎉

Частые вопросы

Как установить Plasmid?

Установите Fabric под свою версию Minecraft, скачайте Plasmid под ту же версию и загрузчик, положите файл в папку mods и запустите игру с профилем загрузчика.

Для каких версий Minecraft есть Plasmid?

У Plasmid есть сборки для Minecraft 1.20.1, 1.21.3–1.21.5, 1.21.7–1.21.8, 26.1.2–26.2.

Нужен ли Plasmid на сервере?

Ставится только на сервер; игрокам ставить не нужно

Лицензия и источник
GNU Lesser General Public License v3.0 onlyможно использовать и распространять; изменённые версии — под той же лицензией и с исходным кодом
Оригинал — Modrinth↗Файлы — без изменений, проверены по SHA-512Это ваш мод? Правообладателям

Сайт использует cookie: необходимые — для входа и темы, а также счётчики посещений Яндекс Метрика и LiveInternet — по ним мы видим, как пользуются сайтом. Рекламных cookie у нас нет. Подробнее