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

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

На этой странице собраны практические рекомендации по написанию скриптов 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 только для рендеринга
  • проверяйте консоль перед слепой отладкой