Skip to content

Commit 0d7f711

Browse files
committed
Rewritten animating elements tutorial, lint and fixes
1 parent 28c28f2 commit 0d7f711

8 files changed

Lines changed: 277 additions & 278 deletions

File tree

docs/.markdownlint.json

Lines changed: 0 additions & 3 deletions
This file was deleted.
File renamed without changes.
File renamed without changes.

i18n/ru/docusaurus-plugin-content-docs/current/blocks/renders/conditioning.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,24 @@
88

99
## Соседние блоки
1010

11+
```mdx-code-block
12+
<NotImplemented />
13+
```
14+
1115
## Рандомизация
1216

17+
```mdx-code-block
18+
<NotImplemented />
19+
```
20+
1321
## Совмещаем условия
1422

23+
```mdx-code-block
24+
<NotImplemented />
25+
```
26+
1527
## Тонкая настройка
28+
29+
```mdx-code-block
30+
<NotImplemented />
31+
```
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Анимация элементов
2+
3+
Анимация позволяет сделать игровые интерфейсы более живыми и динамичными: будь то плавное появление и исчезновение окон, перемещение отдельных элементов или изменение их свойств с течением времени. В этой статье мы рассмотрим базовые подходы к созданию анимаций, работу с элементами вне основного потока и некоторые подводные камни.
4+
5+
## Инструменты для анимации
6+
7+
Для реализации анимаций можно использовать встроенные средства Android (например, `android.animation.ValueAnimator`), события обновления (`Updatable`) или создание отдельных фоновых потоков (`Threading`). В данном руководстве мы остановимся на использовании потоков, так как этот способ предоставляет наибольший контроль над процессом обновления элементов интерфейса и часто применяется на практике (дополнительно ознакомьтесь со статьей о потоках, если вы еще этого не сделали).
8+
9+
## Обновление элементов
10+
11+
Для достижения плавной анимации необходимо быстро и многократно изменять параметры элементов (позицию, размер, прозрачность), избегая полной перерисовки всего окна.
12+
13+
### Прямое обращение к элементам
14+
15+
Исторически параметры элементов изменялись через прямое обращение к объекту описания интерфейса с последующим вызовом функции обновления, например:
16+
17+
```js
18+
окно.content.elements.название_элемента.x = новое_значение;
19+
```
20+
21+
В потоке для анимаций такой способ работает недостаточно быстро и может приводить к заметному мерцанию или просадке кадров.
22+
23+
Вместо этого рекомендуется использовать методы интерфейса `UI.Element`, позволяющие манипулировать отрисованным элементом напрямую нативными средствами.
24+
25+
### Методы интерфейса UI.Element
26+
27+
Для начала необходимо получить объект самого элемента из окна:
28+
29+
```js
30+
const elements = окно.getElements();
31+
const element = elements.get("идентификатор_элемента");
32+
```
33+
34+
После этого мы можем применять специальные методы для изменения его состояния, которые не требуют перерисовки:
35+
36+
```js
37+
// Изменение позиции элемента (работает быстро, идеально для анимации движения)
38+
element.setPosition(x, y);
39+
40+
// Установка значения для конкретного свойства элемента
41+
element.setBinding("название_ключа", значение);
42+
43+
// Получение текущего значения свойства
44+
const value = element.getBinding("название_ключа");
45+
```
46+
47+
Для работы с самим окном, например, для изменения его прозрачности, используется объект компоновки (`layout`):
48+
49+
```js
50+
// Установка прозрачности окна (число от 0.0 до 1.0)
51+
окно.layout.setAlpha(0.5);
52+
53+
// Получение текущей прозрачности
54+
const alpha = окно.layout.getAlpha();
55+
```
56+
57+
:::warning Исключения в `setBinding`
58+
59+
Не все значения элементов можно изменить через `setBinding`. Если какое-то свойство не реагирует на изменения таким способом, используйте классический вариант с изменением ключа в объекте окна, но старайтесь не делать этого каждый кадр анимации.
60+
61+
:::
62+
63+
## Анимация траты средств
64+
65+
В качестве практики реализуем анимацию списания денег: текст с суммой и иконка будут появляться, плавно опускаться вниз и постепенно исчезать.
66+
67+
### Подготовка интерфейса
68+
69+
Определим объект, который будет хранить состояние анимации, настройки и сам интерфейс:
70+
71+
```js
72+
const animator = {
73+
// Константы для позиционирования
74+
MAX_HEIGHT: 270, // Путь, который проделают элементы по оси Y
75+
TEXT_X: 812, TEXT_Y: 210,
76+
ICON_X: 858, ICON_Y: 192,
77+
78+
// Переменные состояния
79+
isRunning: false,
80+
queue: []
81+
};
82+
83+
animator.window = new UI.Window({
84+
drawing: [{
85+
type: "background",
86+
color: android.graphics.Color.TRANSPARENT
87+
}],
88+
elements: {
89+
balanceIcon: {
90+
type: "image",
91+
x: animator.ICON_X, y: animator.ICON_Y,
92+
width: 40, height: 40,
93+
bitmap: "default_icon"
94+
},
95+
balanceText: {
96+
type: "text",
97+
x: animator.TEXT_X, y: animator.TEXT_Y,
98+
text: "",
99+
font: { size: 15, color: android.graphics.Color.LTGRAY }
100+
}
101+
}
102+
});
103+
104+
// Настраиваем свойства окна
105+
animator.window.setDynamic(true);
106+
animator.window.setTouchable(false); // Пропускаем клики сквозь окно
107+
animator.window.setAsGameOverlay(true); // Отображаем как игровой оверлей
108+
```
109+
110+
### Вспомогательные методы
111+
112+
Добавим в объект `animator` методы для инкапсуляции работы с прозрачностью и позициями. Обратите внимание на проверки того, открыто ли окно — это защитит от ошибок, если анимация запустится в неожиданный момент или игрок внезапно выйдет в меню.
113+
114+
```js
115+
animator.setAlpha = function(alpha) {
116+
if (this.window.isOpened()) {
117+
this.window.layout.setAlpha(alpha);
118+
}
119+
};
120+
121+
animator.getAlpha = function() {
122+
if (this.window.isOpened()) {
123+
return this.window.layout.getAlpha();
124+
}
125+
return 0;
126+
};
127+
128+
animator.setHeightOffset = function(offset) {
129+
const elements = this.window.getElements();
130+
const balanceText = elements.get("balanceText");
131+
const balanceIcon = elements.get("balanceIcon");
132+
133+
if (balanceText && balanceIcon) {
134+
balanceText.setPosition(this.TEXT_X, this.TEXT_Y + offset);
135+
balanceIcon.setPosition(this.ICON_X, this.ICON_Y + offset);
136+
}
137+
};
138+
139+
animator.reset = function() {
140+
this.setAlpha(1);
141+
this.setHeightOffset(0);
142+
};
143+
```
144+
145+
### Логика потока анимации
146+
147+
При написании потока для анимации всегда необходима задержка (`Thread.sleep()`). Для приемлемой плавности (около 60 кадров в секунду) достаточно задержки в 16 миллисекунд. Меньшие значения (например, 2-3 мс) могут перегрузить процессор устройства и привести к нестабильной работе игры.
148+
149+
```js
150+
animator.update = function() {
151+
let offset = 0;
152+
while (true) {
153+
java.lang.Thread.sleep(16);
154+
155+
const alpha = this.getAlpha();
156+
157+
// Если анимация завершена или окно было закрыто извне
158+
if ((offset >= this.MAX_HEIGHT && alpha <= 0) || !this.window.isOpened()) {
159+
this.isRunning = false;
160+
161+
// Проверяем очередь на наличие новых анимаций
162+
if (this.queue.length > 0) {
163+
this.play(this.queue.shift());
164+
return; // Завершаем текущий поток, play() запустит новый
165+
}
166+
167+
this.window.close();
168+
return;
169+
}
170+
171+
// Начинаем плавно уменьшать прозрачность, когда прошли половину пути
172+
if (offset >= (this.MAX_HEIGHT / 2) && alpha > 0) {
173+
this.setAlpha(Math.max(0, alpha - 0.05));
174+
}
175+
176+
// Опускаем элементы вниз (на 5 юнитов за кадр)
177+
if (offset < this.MAX_HEIGHT) {
178+
offset += 5;
179+
this.setHeightOffset(offset);
180+
}
181+
}
182+
};
183+
```
184+
185+
### Запуск анимации
186+
187+
Теперь напишем публичный метод для инициализации анимации. Мы будем сохранять значения в очередь, чтобы при нескольких быстрых вызовах они воспроизводились последовательно, а не накладывались друг на друга или ломали поток.
188+
189+
```js
190+
animator.play = function(amount) {
191+
if (this.isRunning) {
192+
this.queue.push(amount);
193+
return;
194+
}
195+
196+
this.isRunning = true;
197+
this.window.content.elements.balanceText.text = "-" + amount;
198+
199+
if (!this.window.isOpened()) {
200+
this.window.open();
201+
}
202+
// Важно вызывать сброс после открытия окна, иначе элементы могут быть не инициализированы
203+
this.reset();
204+
205+
Threading.initThread("mod_animatorThread", function() {
206+
animator.update();
207+
});
208+
};
209+
```
210+
211+
Проверим наш код: при любом клике в мире будет появляться наша анимация. Если кликнуть несколько раз подряд, они проиграются по очереди.
212+
213+
```js
214+
Callback.addCallback("ItemUseLocal", function() {
215+
animator.play(10);
216+
});
217+
```
218+
219+
## Рекомендации и частые ошибки
220+
221+
1. **Не анимируйте позицию самого окна**. Изменение координат всего окна через `window.getLocation().set(...)` в потоке нередко приводит к сильному мерцанию фона и медленной отрисовке. Вместо этого двигайте элементы внутри окна.
222+
2. **Контролируйте потоки**. Убедитесь, что для одной и той же анимации не запускается несколько потоков одновременно. В нашем примере это решено проверкой переменной `isRunning` и использованием очереди.
223+
3. **Обеспечивайте безопасное завершение**. Игрок может закрыть мир или меню в любой момент, пока поток всё ещё работает. Регулярная проверка `window.isOpened()` гарантирует, что код не будет пытаться обращаться к несуществующим элементам, что в противном случае привело бы к вылету игры.
224+
4. **Комбинируйте подходы при необходимости**. Бывают ситуации, когда позиции элементов внезапно сбрасываются (например, при изменении размеров растягивающихся фреймов). В таком случае можно комбинировать старый и новый подходы или принудительно обновлять макет через `window.forceRefresh()`.
225+
226+
Создание качественных анимаций часто требует экспериментов с таймингами, задержками и шагами изменения значений. Но результат стоит того: интерфейс становится значительно отзывчивее и приятнее для пользователя.
227+
228+
## Готовые примеры анимаций
229+
230+
Хотя написание собственных анимаций позволяет лучше понять работу потоков, никто не заставляет делать их с нуля. Вы можете воспользоваться [библиотекой Notification](https://github.com/ArtemKot4/libraries/tree/main/release/Notification) для более простого создания анимаций, или [ScrutinyAPI](https://github.com/Reider745/libs/tree/main/ScrutinyAPI) для создания анимаций, подобным изучениям и квестам.

i18n/ru/docusaurus-plugin-content-docs/current/ui/extending-components.md renamed to i18n/ru/docusaurus-plugin-content-docs/current/ui/advanced/extending-components.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ UI.TextureSource.put("innercore_background", source);
6060

6161
Не думаю, что стоит обсуждать то, какое влияние такой подход окажет на производительность. Прямое рисование на этом листе интерфейса — наилучший вариант во всех смыслах. Помимо канваса, параметр `scale` здесь определяет масштаб юнита относительно пикселя.
6262

63-
Это самый обычный объект описания компонента. Для фоновой компоновки предусмотрен только метод `onDraw(canvas, scale)`, рассмотрите [жизненный цикл](drawings.md#жизненный-цикл) для получения подробностей. В остальном, базовое взаимодействие с холстом описано в [обработке ресурсов](../storage/processing-resources.md).
63+
Это самый обычный объект описания компонента. Для фоновой компоновки предусмотрен только метод `onDraw(canvas, scale)`, рассмотрите [жизненный цикл](../drawings.md#жизненный-цикл) для получения подробностей. В остальном, базовое взаимодействие с холстом описано в [обработке ресурсов](../../storage/processing-resources.md).
6464

6565
## Элементы
6666

67-
Начиная с реализации собственного элемента, важно изучить [общие свойства](elements.md#общие-свойства) для каждого из них. Помимо свойств, каждый элемент придерживается общего [жизненного цикла](elements.md#жизненный-цикл). Рассмотрев события, можно изучить и объект описания:
67+
Начиная с реализации собственного элемента, важно изучить [общие свойства](../elements.md#общие-свойства) для каждого из них. Помимо свойств, каждый элемент придерживается общего [жизненного цикла](../elements.md#жизненный-цикл). Рассмотрев события, можно изучить и объект описания:
6868

6969
```js
7070
{
@@ -210,11 +210,11 @@ UI.TextureSource.put("innercore_background", source);
210210

211211
## Встроенные реализации
212212

213-
Как было сказано в статье о [элементах](elements.md), некоторые из компонентов имеют схожие друг с другом реализации. Они наследуются от других компонентов, имея те же свойства.
213+
Как было сказано в статье о [элементах](../elements.md), некоторые из компонентов имеют схожие друг с другом реализации. Они наследуются от других компонентов, имея те же свойства.
214214

215215
### Кнопка закрытия
216216

217-
Как и следует из названия, закрывает интерфейс. Совершенно ничем не отличается от [обычной кнопки](elements.md#кнопки), за исключением того что не обрабатывает события из объекта описания, так как уже настроена на свое действие.
217+
Как и следует из названия, закрывает интерфейс. Совершенно ничем не отличается от [обычной кнопки](../elements.md#кнопки), за исключением того что не обрабатывает события из объекта описания, так как уже настроена на свое действие.
218218

219219
```js
220220
{
@@ -225,7 +225,7 @@ UI.TextureSource.put("innercore_background", source);
225225

226226
### Слот инвентаря
227227

228-
В отличии от [обычного слота](elements.md#слоты), также как и кнопка, не обрабатывает события, но добавляет и изменяет некоторые стандартные свойства. Требует открытия интерфейса через контейнер.
228+
В отличии от [обычного слота](../elements.md#слоты), также как и кнопка, не обрабатывает события, но добавляет и изменяет некоторые стандартные свойства. Требует открытия интерфейса через контейнер.
229229

230230
```js
231231
{
@@ -241,7 +241,7 @@ UI.TextureSource.put("innercore_background", source);
241241

242242
### Счетчик кадров
243243

244-
Представляет из себя [обычный текст](elements.md#текст), обновляемый с некоторым промежутком времени. Изменение объекта шрифта не принесет никакого эффекта.
244+
Представляет из себя [обычный текст](../elements.md#текст), обновляемый с некоторым промежутком времени. Изменение объекта шрифта не принесет никакого эффекта.
245245

246246
```js
247247
{

0 commit comments

Comments
 (0)