Writing good scripts
This page collects practical guidelines for writing Melonity scripts that are easier to maintain, easier to debug, and safer to extend later.
These are not abstract TypeScript rules. They are recommendations based on real Melonity scripting workflow.
Keep script logic inside a namespace
Do not scatter script logic across the global script scope. Keep variables, helpers, and callbacks inside a dedicated namespace.
If another script really needs access to part of your logic, export only what should be public.
let MyScript: ScriptDescription = {};
namespace myScript {
let enabled = false;
export function IsEnabled(): boolean {
return enabled;
}
MyScript.OnScriptLoad = () => {
enabled = true;
};
RegisterScript(MyScript, 'Example');
}let MyScript: ScriptDescription = {};
let enabled = false;
function isEnabled(): boolean {
return enabled;
}
MyScript.OnScriptLoad = () => {
enabled = true;
};
RegisterScript(MyScript, 'Example');Keep menu state simple
Avoid splitting one piece of state across multiple variables when a single clear value is enough.
let enabled = Menu.AddToggle(PATH, 'Enable', false)
.OnChange(state => {
enabled = state.newValue;
})
.GetValue();let enabled = Menu.AddToggle(PATH, 'Enable', false)
.OnChange(state => {
enabledValue = state.newValue;
});
let enabledValue = enabled.GetValue();The first version keeps the script state easy to read. The second version works, but makes the logic harder to follow.
TIP
If you need both the menu option object and the current value, use clear names such as enabledOption and enabled.
Scale rendering values to the current resolution
Hardcoded rendering values usually look correct only on the monitor on which they were created.
Font sizes, positions, padding, and element sizes should be scaled relative to the current screen resolution.
const ratio = screenHeight / 1080;
const fontSize = Math.floor(14 * ratio);
const offsetX = 20 * ratio;
const offsetY = 12 * ratio;This makes the layout behave more consistently on different resolutions.
Recalculate resolution-dependent values in OnScreenSizeChange
If your script depends on screen size, do not assume the values will stay correct forever.
When the resolution changes, recompute fonts, offsets, and cached layout values inside OnScreenSizeChange.
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');
}IMPORTANT
If you cache resolution-dependent values and never update them, the result may look correct only until scripts are reloaded with F7.
Cache stable references instead of requesting them every tick
Do not call EntitySystem.GetLocalHero() every OnUpdate if you can cache the result when the game starts.
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');
}This makes the script clearer and avoids unnecessary repeated lookups.
Keep OnDraw focused on rendering
OnDraw is called every frame. At higher FPS, it runs more often.
Because of that:
- keep rendering code inside
OnDraw - move heavy logic to
OnUpdate - cache values whenever possible
This reduces the chance of FPS drops and makes the rendering path easier to debug.
Prefer early guard clauses
Guard clauses make script callbacks easier to read and easier to extend.
MyScript.OnUpdate = () => {
if (!enabled || !myHero) {
return;
}
// Main logic
};This is usually clearer than wrapping the entire callback body in several nested if blocks.
Check the console first when UI or rendering breaks
If the menu looks broken, rendering behaves incorrectly, or something visual stops working, open the console first.
Use:
F10In many cases, the error is easier to identify from the console than by guessing from the visible symptoms.
Summary
Good Melonity scripts usually follow a few simple rules:
- keep logic organized inside a namespace
- keep state simple and readable
- scale UI and rendering to resolution
- update cached screen-dependent values when the screen size changes
- cache stable game objects instead of requesting them every tick
- use
OnDrawonly for rendering work - check the console before debugging blindly