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

Как писать хорошие скрипты ​

На этой странице собраны практические рекомендации по написанию скриптов Melonity, которые проще поддерживать, отлаживать и безопасно расширять.

Это не абстрактные правила TypeScript. Это рекомендации, основанные на реальном процессе разработки скриптов для Melonity.


Храните логику сценария внутри пространства имен ​

Не распыляйте логику скрипта по глобальной области видимости. Храните переменные, вспомогательные функции и callback-обработчики внутри отдельного пространства имён.

Если другому скрипту действительно нужен доступ к части вашей логики, экспортируйте только то, что должно быть общедоступным.

Хороший
ts
let MyScript: ScriptDescription = {};

namespace myScript {
	let enabled = false;

	export function IsEnabled(): boolean {
		return enabled;
	}

	MyScript.OnScriptLoad = () => {
		enabled = true;
	};

	RegisterScript(MyScript, 'Example');
}
Избегать
ts
let MyScript: ScriptDescription = {};
let enabled = false;

function isEnabled(): boolean {
	return enabled;
}

MyScript.OnScriptLoad = () => {
	enabled = true;
};

RegisterScript(MyScript, 'Example');

Сохраняйте состояние меню простым ​

Избегайте разделения одной части состояния на несколько переменных, когда достаточно одного четкого значения.

Хороший
ts
let enabled = Menu.AddToggle(PATH, 'Enable', false)
	.OnChange(state => {
		enabled = state.newValue;
	})
	.GetValue();
Избегать
ts
let enabled = Menu.AddToggle(PATH, 'Enable', false)
	.OnChange(state => {
		enabledValue = state.newValue;
	});

let enabledValue = enabled.GetValue();

В первой версии состояние сценария легко читается. Вторая версия работает, но усложняет понимание логики.

Совет

Если вам нужны и объект пункта меню, и его текущее значение, используйте понятные имена, например enabledOption и enabled.


Масштабировать значения рендеринга до текущего разрешения ​

Жестко закодированные значения рендеринга обычно выглядят корректно только на том мониторе, на котором они были созданы.

Размеры шрифта, позиции, отступы и размеры элементов должны масштабироваться относительно текущего разрешения экрана.

Пример
ts
const ratio = screenHeight / 1080;
const fontSize = Math.floor(14 * ratio);
const offsetX = 20 * ratio;
const offsetY = 12 * ratio;

Это делает макет более последовательным при разных разрешениях.


Пересчитать значения, зависящие от разрешения, в OnScreenSizeChange ​

Если ваш скрипт зависит от размера экрана, не думайте, что значения останутся правильными навсегда.

При изменении разрешения пересчитывайте шрифты, смещения и значения кэшированного макета внутри OnScreenSizeChange.

Пример
ts
let font: Font;
let ratio = 1;

let MyScript: ScriptDescription = {};

namespace myScript {
	MyScript.OnScreenSizeChange = (_width, height) => {
		ratio = height / 1080;
		font = Renderer.LoadFont('Tahoma', Math.floor(14 * ratio), Enum.FontWeight.BOLD);
	};

	RegisterScript(MyScript, 'Example');
}

Важно

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


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

Не вызывайте EntitySystem.GetLocalHero() каждый OnUpdate, если можете закэшировать результат при запуске игры.

Лучший подход
ts
let MyScript: ScriptDescription = {};

namespace myScript {
	let myHero: Hero | null = null;
	let enabled = true;

	MyScript.OnGameStart = MyScript.OnScriptLoad = () => {
		myHero = EntitySystem.GetLocalHero();
	};

	MyScript.OnGameEnd = () => {
		myHero = null;
	};

	MyScript.OnUpdate = () => {
		if (!enabled || !myHero) {
			return;
		}

		// Script logic
	};

	RegisterScript(MyScript, 'Example');
}

Это делает сценарий более понятным и позволяет избежать ненужных повторных поисков.


Держать OnDraw сосредоточился на рендеринге ​

OnDraw вызывается каждый кадр. При более высоком FPS он срабатывает чаще.

Из-за этого:

  • держите код рендеринга внутри OnDraw
  • переносите тяжёлую логику в OnUpdate
  • кэшировать значения, когда это возможно

Это снижает вероятность падения FPS и упрощает отладку пути рендеринга.


Отдавайте предпочтение оговоркам о ранней защите ​

Защитные условия делают callback-обработчики скрипта более читаемыми и упрощают их дальнейшее расширение.

Хороший
ts
MyScript.OnUpdate = () => {
	if (!enabled || !myHero) {
		return;
	}

	// Main logic
};

Обычно это проще, чем оборачивать всё тело callback-обработчика в несколько вложенных блоков if.


Сначала проверьте консоль, если пользовательский интерфейс или рендеринг прерываются. ​

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

Использовать:

txt
F10

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


Краткое содержание ​

Хорошие сценарии Melonity обычно следуют нескольким простым правилам:

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