- Версия(и) Minecraft
- 1.7.10,1.12-1.19
АКТУАЛЬНО ДЛЯ GExt v.1.5.1.0-SNAPSHOT
Полезные ссылки:
GitHub
GitHub Wiki(English)
Здравствуйте. Раз вы забрели в мою скромную тему, значит, вас заинтересовало, что может предложить вам моя публичная разработка в области графических интерфейсов OpenGL. Итак, начнём.
Библиотека доступна в Maven. Пока что не было первого релиза, так что живём на снапшотах. Чтобы заполучить GExt в свой Gradle-проект, добавьте репозиторий снапшотов:
и зависимость:
Например,
Сразу отмечу, что, прежде чем читать этот тутор, стоит заглянуть в тему ресурса. Вероятно, это избавит вас от части вопросов.
Шаг первый - Сборка
Если вы скачали из maven, то смело переходите к следующему.
Клоним репозиторий и дожидаемся сборки Gradle проекта. Если у вас слабый компьютер и/или вам нужна библиотека "вот прям щас" и для одной-единственной версии - можно в файле
Шаг второй - Основной класс
В туториале будем рассматривать Minecraft GUI версии 1.12, на версиях ниже API почти не отличается. На версиях выше возможны большие отличия в API основных GUI классов Minecraft, которые влекут изменения API адаптеров библиотеки - но только их. Вы найдёте знакомые вашей версии методы в классах Extended*.
Несколько стандартных примеров размещено в классе
Для начала нам потребуется корневой контейнер - в библиотеке предусмотрены расширения для
Теперь, вероятно, мы хотим туда что-то добавить. Самое простое, что мы можем, когда у нас ничего нет - это добавить надпись. Так и сделаем. Добавим в
В итоге при открытии Gui получаем следующее:
Просто, не правда ли? Вот и я так думаю. Все компоненты собираются похожим образом. Сделаем что-то посложнее, например, ссылку.
По клику ссылка окрашивается белым и происходит переход по url.
Под конец построим сложный компонент с множеством настраиваемых параметров. Думаю, уже можно не комментировать каждую строчку:
Дальнейшие шаги необязательны для реализации, но тем не менее раскрывают потенциал системы и могут быть интересны пользователям для создания сложных функциональных графических интерфейсов.
Шаг третий - Инструменты композиции
Ради чего и создавалась библиотека - вашему вниманию представлены мощные(но, возможно, пока немногочисленные) инструменты композиции. Другими словами, это контейнеры - компоненты, занимающиеся организацией других. В частности, они особым для себя образом располагают добавляемые компоненты. Итак, на момент написания существуют:
Например, при добавлении к Gui кода
Весь контент, добавленный в контейнер
Более подробная информация по использованию содержится в коде методов фабрик и их джавадоках. Она слишком велика для размещения здесь. В любом случае, названия методов всегда носят декларативный характер и интуитивно понятный состав.
Относительная вёрстка
Component Binding
Кроме контейнеров и группировки по принципу иерархии, есть ещё один способ задать относительное позиционирование. Называется он
Alignments - привет из веба
И напоследок третий инструмент относительной вёрстки, пришедший к нам напрямую из веб-программирования - Alignments.
Грубо говоря, это способ позиционирования относительно родителя в терминах "сверху/снизу/сбоку/посередине". Обратите внимание - данная опция не совместима с Component Binding. Из двух инструментов для каждого компонента может быть применён лишь один(но при этом никто не мешает для одного компонента применить Component Binding, а для другого Alignment). Переходя к примерам, следующий код:
на выходе даёт сие:
Добавление прокрутки
Это сделано максимально доступным образом, по моему мнению. Все компоненты, реализующие интерфейс
В игре отображается вот так:
Шаг четвёртый - Создание своего компонента
Притаскивая библиотеку на свой проект, вам наверняка рано или поздно захочется больше возможностей, чем можно реализовать стандартными средствами GExt. В таком случае выход - написать свой компонент. Плюс библиотеки в том, что, реализовав в нём интерфейс
Также можно создать интерактивное контекстное меню для компонента. Для этого переопределите метод
Существует всего два вида компонентов меню - пункты и списки. У пункта можно задать иконку, надпись и действие, исполняемое при нажатии. У списка - иконку, надпись и ширину.
Ресурсы
С версией 1..5 в GExt приходит своя абстрактная ресурсная модель. Она представлена двумя интерфейсами -
Преобразование ResourceLocation в IResource/ITexture
Note: Здесь указан способ перевода
Ресурс-провайдеры
Кроме
Сейчас доступны:
В документации к пакету
Полезные ссылки:
GitHub
GitHub Wiki(English)
Здравствуйте. Раз вы забрели в мою скромную тему, значит, вас заинтересовало, что может предложить вам моя публичная разработка в области графических интерфейсов OpenGL. Итак, начнём.
Библиотека доступна в Maven. Пока что не было первого релиза, так что живём на снапшотах. Чтобы заполучить GExt в свой Gradle-проект, добавьте репозиторий снапшотов:
Gradle (Groovy):
repositories {
maven {
url = "https://oss.sonatype.org/content/repositories/snapshots/"
}
}
Gradle (Groovy):
implementation group: 'com.github.stannismod.gext', name: <имя_адаптера>, version: <версия>
Gradle (Groovy):
implementation group: 'com.github.stannismod.gext', name: 'forge112', version: '1.5.1.0-SNAPSHOT'
Сразу отмечу, что, прежде чем читать этот тутор, стоит заглянуть в тему ресурса. Вероятно, это избавит вас от части вопросов.
Шаг первый - Сборка
Если вы скачали из maven, то смело переходите к следующему.
Клоним репозиторий и дожидаемся сборки Gradle проекта. Если у вас слабый компьютер и/или вам нужна библиотека "вот прям щас" и для одной-единственной версии - можно в файле
settings.gradle
отключить ненужные версии. Для версий до 1.13 необходимо выполнить задачу setupDecompWorkspace
в Gradle. После этого выполните build
для каждой интересующей версии и заберите deobf-версию библиотеки из директории compiled
.Шаг второй - Основной класс
В туториале будем рассматривать Minecraft GUI версии 1.12, на версиях ниже API почти не отличается. На версиях выше возможны большие отличия в API основных GUI классов Minecraft, которые влекут изменения API адаптеров библиотеки - но только их. Вы найдёте знакомые вашей версии методы в классах Extended*.
Несколько стандартных примеров размещено в классе
com.github.stannismod.forge*version*.GuiTest
Для начала нам потребуется корневой контейнер - в библиотеке предусмотрены расширения для
GuiScreen
, GuiContainer
и Gui
. Унаследуйте интерфейс от одного из них, их работа с точки зрения библиотеки абсолютно одинакова. Я возьму GuiScreen, точнее, его Extended версию:
Основной класс:
public class GuiTest extends ExtendedGuiScreen {
@Override
public void init() {
// здесь мы будем создавать нашу структуру компонентов
}
}
Библиотека создаётся с целью дать максимальное удобство создания компонентов. За это отвечает класс
Graphics
, предоставляющий доступ к фабрикам компонентов. Создание компонентов всегда проходит следующим образом: вызов соответствующего метода у Graphics
, установка нужных параметров, доступных в фабрике, вызов build()
для завершения сборки и получения собранного компонента. После этого его с помощью методов add
можно добавить в любой контейнерТеперь, вероятно, мы хотим туда что-то добавить. Самое простое, что мы можем, когда у нас ничего нет - это добавить надпись. Так и сделаем. Добавим в
init()
:
Пишем hello world:
GLabel label = Graphics.label() // создаём надпись
.text("Hello, world!") // устанавливаем текст
.placeAt(50, 50) // размещаем на координатах (50, 50)
.build(); // собираем компонент
this.add(label); // добавляем собранный компонент в корневой контейнер
Просто, не правда ли? Вот и я так думаю. Все компоненты собираются похожим образом. Сделаем что-то посложнее, например, ссылку.
Оставляем ссылки:
GLink link = Graphics.link() // создаём ссылку
.text("GExt original source") // устанавливаем текст
.url("https://github.com/StannisMod/gext") // ссылку
.color(0xffffff, 0x121212) // активный и пассивный цвет
.scale(2.0F) // увеличение
.placeAt(250, 200) // размещаем на координатах (250, 200)
.build(); // собираем компонент
this.add(link); // добавляем собранный компонент в корневой контейнер
Под конец построим сложный компонент с множеством настраиваемых параметров. Думаю, уже можно не комментировать каждую строчку:
Собираем кнопку:
this.add(100, Graphics.button() // поставим глубину 100, чтобы кнопка была поверх всего остального
.label(Graphics.label().text("Primary text", 0xffffff).scale(2.0F).setCentered().build())
.action(button -> {
if (button.getLabel().getText().startsWith("P")) {
button.getLabel().setText("SecondaryText");
} else {
button.getLabel().setText("PrimaryText");
}
})
.size(150, 60)
.placeAt(800, 400)
.build());
Шаг третий - Инструменты композиции
Ради чего и создавалась библиотека - вашему вниманию представлены мощные(но, возможно, пока немногочисленные) инструменты композиции. Другими словами, это контейнеры - компоненты, занимающиеся организацией других. В частности, они особым для себя образом располагают добавляемые компоненты. Итак, на момент написания существуют:
- GPanel - обыкновенный контейнер, никак не преобразующий координаты компонентов. Можно добавить ScrollHandler - компонент, отвечающий за взаимодействие посредством прокрутки. Существуют варианты вертикальных и горизонтальных скроллбаров, а также прокрутка по колёсику мыши.
- GList - расширение GPanel, располагающее компоненты вертикально или горизонтально в ряд, а также дающее доступ к ним по индексу. По сути, является GUI-представлением списка.
- GTabList - расширение GList, имеющее своей целью управление составом целевого контейнера. То есть это стандартный таблист - нажатие на вкладку переключает контент целевого контейнера.
addComponent
независимо от типа контейнера.Например, при добавлении к Gui кода
Первый в жизни контейнер:
final GPanel<IGraphicsComponent> panel = Graphics.panel().size(500, 500).placeAt(100, 100).build();
this.add(panel);
this.add(Graphics.label().text("The first perfect text should be here").placeAt(300, 100).setCentered().build());
this.add(Graphics.label().text("The second perfect text should be here").placeAt(800, 100).setCentered().build());
panel.addComponent(Graphics.background().size(400, 200).build());
panel.addComponent(10, Graphics.label().text("This text should be rendered", 0xffffff).placeAt(200, 200).setCentered().build());
panel.addComponent(Graphics.label().text("But this shouldn't, because it's out of bounds", 0xffffff).placeAt(800, 200).setCentered().build());
panel
, будет сдвинут на (100, 100) и ограничен в размерах рамкой (500, 500):Более подробная информация по использованию содержится в коде методов фабрик и их джавадоках. Она слишком велика для размещения здесь. В любом случае, названия методов всегда носят декларативный характер и интуитивно понятный состав.
Относительная вёрстка
Component Binding
Кроме контейнеров и группировки по принципу иерархии, есть ещё один способ задать относительное позиционирование. Называется он
binding
(привязка). Вызовом метода setBinding
на компоненте либо bind
на фабрике вы осуществляете "привязку" - координаты, присвоенные вами компоненту, теперь считаются не от родителя, а от точки привязки. К примеру, таким кодом можно получить почти ту же картину, что и использованием контейнера GList
:
Создаём равномерную цепочку надписей:
final GPanel<GLabel> panel = Graphics.<GLabel>panel().size(500, 500).placeAt(500, 300).build();
GLabel prev = null;
for (int i = 0; i < 10; i++) {
panel.addComponent(prev = Graphics
.label()
.text("Label " + i, Color.WHITE.getRGB())
.placeAt(0, 10)
.bind(prev)
.build());
}
add(panel);
Alignments - привет из веба
И напоследок третий инструмент относительной вёрстки, пришедший к нам напрямую из веб-программирования - Alignments.
Грубо говоря, это способ позиционирования относительно родителя в терминах "сверху/снизу/сбоку/посередине". Обратите внимание - данная опция не совместима с Component Binding. Из двух инструментов для каждого компонента может быть применён лишь один(но при этом никто не мешает для одного компонента применить Component Binding, а для другого Alignment). Переходя к примерам, следующий код:
Java:
final GPanel<IGraphicsComponent> labels = Graphics.panel().size(400, 400).placeAt(100, 100).build();
// добавим фон шириной в весь контейнер для красоты
labels.addComponent(Graphics.background().size(400, 400).build());
// на самом деле этот код может быть короче, но он так отформатирован ради читаемости
labels.addComponent(Graphics
.label()
.text("Top", Color.WHITE.getRGB())
.placeAt(0, 0)
.alignment(Alignment.XCENTER, Alignment.TOP)
.padding(50, 50)
.build());
labels.addComponent(Graphics
.label()
.text("Bottom", Color.WHITE.getRGB())
.placeAt(0, 0)
.alignment(Alignment.XCENTER, Alignment.BOTTOM)
.padding(50, 50)
.build());
labels.addComponent(Graphics
.label()
.text("Left", Color.WHITE.getRGB())
.placeAt(0, 0)
.alignment(Alignment.LEFT, Alignment.YCENTER)
.padding(50, 50)
.build());
labels.addComponent(Graphics
.label()
.text("Right", Color.WHITE.getRGB())
.placeAt(0, 0)
.alignment(Alignment.RIGHT, Alignment.YCENTER)
.padding(50, 50)
.build());
labels.addComponent(Graphics
.label()
.text("Center", Color.WHITE.getRGB())
.placeAt(0, 0)
.alignment(Alignment.XCENTER, Alignment.YCENTER)
.padding(50, 50)
.build());
add(labels);
Добавление прокрутки
Это сделано максимально доступным образом, по моему мнению. Все компоненты, реализующие интерфейс
IScrollable
, поддерживают прокрутку контента. Без лишних слов, такой код:
Java:
@Override
public void initLayout() {
final GList<GLabel> panel = Graphics.<GLabel>list().size(50, 200).placeAt(500, 300).build(); // создали список
panel.setScrollHandler(Controls.verticalScroll().barWidth(8).scrollFactor(0.25F).build()); // установили ему вертикальный скролл
for (int i = 0; i < 1000; i++) { // добавили надписей
panel.addComponent(Graphics.label().text("Label " + i, Color.WHITE.getRGB()).build());
}
this.add(panel); // присоединили к интерфейсу
}
Шаг четвёртый - Создание своего компонента
Притаскивая библиотеку на свой проект, вам наверняка рано или поздно захочется больше возможностей, чем можно реализовать стандартными средствами GExt. В таком случае выход - написать свой компонент. Плюс библиотеки в том, что, реализовав в нём интерфейс
IGraphicsComponent
, вы сделаете свой компонент управляемым с точки зрения библиотеки. Все компоненты, в том числе контейнеры, смогут работать с вашим компонентом, как и с любым другим. Вы можете сосредоточиться на решении своей задачи в компоненте, не отвлекаясь на организацию GUI. Стандартная реализация базовых функций GUI-компонента представлена в классе GBasic
, крайне рекомендуется наследование от него или его потомков. Реализация впрямую интерфейса IGraphicsComponent
, конечно, возможна, но сложно представить ситуацию, в которой это необходимо.Также можно создать интерактивное контекстное меню для компонента. Для этого переопределите метод
constructMenu
в компоненте. За создание компонентов меню отвечает класс MenuBuilder
. Пример задания меню можно наблюдать ниже:
Java:
@Override
public IContextMenuList<? extends IContextMenuElement> constructMenu() {
return MenuBuilder.<GTextBox>create(80)
.point("Point", (c, p) -> System.out.println("Point"))
.point(Icon.APPROVE, "Point with item", (c, p) -> System.out.println("Point 1"))
.newList(Icon.DECLINE, "Just list 1", 70)
.point(Icon.CHECKBOX, "List 1 label 1", (c, p) -> System.out.println("Point 1/1"))
.point(Icon.CHECKBOX, "List 1 label 2", (c, p) -> System.out.println("Point 1/2"))
.endList()
.newList(Icon.DECLINE, "Just list 2", 70)
.point(Icon.CHECKBOX, "List 2 label 1", (c, p) -> System.out.println("Point 2/1"))
.point(Icon.CHECKBOX, "List 2 label 2", (c, p) -> System.out.println("Point 2/2"))
.newList(Icon.CHECKBOX, "List 2 sublist 1", 100)
.point(Icon.CHECKBOX, "List 2 sublist 1 label 1", (c, p) -> System.out.println("Point 2/2/1"))
.point(Icon.CHECKBOX, "List 2 sublist 1 label 2", (c, p) -> System.out.println("Point 2/2/2"))
.endList()
.endList()
.point(Icon.APPROVE, "Point with item", (c, p) -> System.out.println("Point 2"))
.build();
}
Ресурсы
С версией 1..5 в GExt приходит своя абстрактная ресурсная модель. Она представлена двумя интерфейсами -
IResource
и ITexture
.Преобразование ResourceLocation в IResource/ITexture
Java:
ResourceLocation loc = new ResourceLocation("textures/image.png");
IResource resource = GExt.resource(loc.toString());
ITexture tex = resource.toTexture();
Note: Здесь указан способ перевода
ResourceLocation
, ссылающегося на ресурсы мода. В других случаях универсального перевода нет, так как по факту ResourceLocation
даже не ресурс, а просто имя ресурса, и для использования ресурса его надо загрузить. Так что для использования в GExt придётся опираться на его ресурсную модель, либо же написать универсальный адаптер к Minecraft-ресурсам. Можно либо напрямую реализовать IResource
, либо же расширить ResourceImpl
и переопределить метод getInputStream
, отвечающий за доступ к ресурсу. Я же вижу такую ситуацию весьма экзотической, поэтому адаптеры под Minecraft ресурсы в поставке отсутствуют.Ресурс-провайдеры
Кроме
GExt.resource()
есть ещё масса способов получать ресурсы. Эти способы зовут ресурс-провайдерами.Сейчас доступны:
- AssetsResourceProvider - провайдер ассетов внутри JAR. Грубо говоря, то, что в
resources
мода лежит. Пример использования в пункте выше. - FilesystemResourceProvider - провайдер файловой системы. Можно загружать ресурсы из внешней файловой системы(не JAR). Пример:
Java:
IResourceProvider provider = new FilesystemResourceProvider("Test", Paths.get("resources"));
ITexture tex = provider.getResource("test", "image.png").toTexture();
this.add(Graphics.image() // отображается картинка, лежащая по пути /resources/test/image.png относительно папки запуска Minecraft
.texture(tex)
.uv(0, 0, 200, 50)
.size(200, 200)
.placeAt(200, 200)
.build());
- NetworkResourceProvider - TODO
В документации к пакету
com.github.stannismod.gext.api
можно найти исчерпывающую информацию о функциях API, средствах, гарантиях и прочем.Есть некоторые фичи, касающиеся некоторым образом модификации библиотеки.
1. Портирование библиотеки на любую платформу, использующую OpenGL
Модульная архитектура была создана для того, чтобы максимально облегчить встраивание библиотеки в любую графическую подсистему, использующую OpenGL. Таким образом, для портирования в любую игру(в том числе и на любую версию Minecraft) на самом деле нужно лишь портировать пакет любого адаптера в вашу платформу. Если платформа - не Minecraft(внезапно), то алгоритм создания адаптера с нуля следующий:
2. Создание корневых контейнеров
В библиотеке у каждого компонента есть родитель. Но по факту такого быть не может - у иерархии обязан быть какой-то корень. Так вот, интерфейс
1. Портирование библиотеки на любую платформу, использующую OpenGL
Модульная архитектура была создана для того, чтобы максимально облегчить встраивание библиотеки в любую графическую подсистему, использующую OpenGL. Таким образом, для портирования в любую игру(в том числе и на любую версию Minecraft) на самом деле нужно лишь портировать пакет любого адаптера в вашу платформу. Если платформа - не Minecraft(внезапно), то алгоритм создания адаптера с нуля следующий:
- Реализуем все классы из
com.github.stannismod.gext.api.adapter
- Реализуем все адаптеры к нужным классам GUI с помощью
IRootLayout
- При инициализации платформы вызываем конструктор класса
com.github.stannismod.gext.GExt
.
2. Создание корневых контейнеров
В библиотеке у каждого компонента есть родитель. Но по факту такого быть не может - у иерархии обязан быть какой-то корень. Так вот, интерфейс
IRootLayout
предоставляет возможность реализовать корневой контейнер с минимальными временными затратами. Отдельный интерфейс и реализация для корневого контейнера сделана специально, потому что фактически он является адаптером контейнера верхнего уровня к системе конечной реализации, которая, скорее всего, имеет свой класс для представления объектов GUI.