
Scripting
A TypeScript Game Engine
Mirror Engine is built on TypeScript, a superset of JavaScript - the most popular programming language in the world.1
If you're only familiar with JavaScript, don't worry! TypeScript is a superset of JavaScript, so you can use all your existing JavaScript knowledge - anything TypeScript-related is optional.
TypeScript helps us JS developers stay sane, and we hope you'll be quite pleased.
Your First Script
Select an Entity, click the Script button on the bottom-left toolbar, and click Add Script.
You'll see a basic script like this. Don't let it scare, this will all become second nature as you build.
import { MirrorScript } from '@mirror-engine/core'
export class NewEntity extends MirrorScript {
// Example property that can be referenced via this.rotateAmount
rotateAmount = 1
// Needed, but likely ignore. Use initialize() for init logic.
constructor(app) {
super(app)
}
// Called first when the script starts
initialize() {}
// Called every frame. dt, deltaTime, is the time since the last frame.
update(dt: number) {
this.entity.rotateLocal(0, this.rotateAmount, 0)
}
// This is called everytime the script is hot reloaded
swap() {}
}
After this script is added, you'll see the entity rotate around the Y-axis.
Key Things to Know
The 2 main methods to note are initialize()
and update()
.
initialize()
is called once when the script is initialized.update()
is called every frame, withdt
being thedeltaTime
since the last frame. Use this for frequent logic checks, but be cautious to not bottleneck performance.dt
can be used to smoothly interpolate, such as for rotation and movement as we do with rotation above.
You can safely ignore these for now. This is boilerplate code that's required for the script to work.
export class
extends MirrorScript
constructor(app){ super(app) }
swap()
The only thing you need to focus on is initialize()
and update()
.
Hot Reloading
swap()
is called every time the script is hot reloaded, such as when you or a contributor saves changes to the script. As of Alpha V5, we're still refining swap logic a bit, but it's useful to reininitialize any values as-needed since initialize()
is only called once, i.e. not called when the script changes via saving changes to the script.
This is Build Mode Only and will not occur in Play Mode.
Scripts Are Automatically Added to Entities
Behind the scenes, a ScriptComponent is automatically added to the entity for you when you add a Script via the UI. You'll never need to use the MirrorScript
class directly.
You only need to manually add a ScriptComponent if you're creating a new Entity via code, for example:
import { MirrorEntity, MirrorScript } from '@mirror-engine/core'
export class EntityCreator extends MirrorScript {
// don't use the constructor for initialization; the `initialize()` method waits until the engine is ready.
// However, the constructor with super(app) must still be called, so leave this boilerplate code as-is.
constructor(app: AppBase) {
super(app)
}
// called when the script first runs
initialize() {
const newEntity = new MirrorEntity(this.app)
// Add the ScriptComponent to the new Entity
newEntity.addComponent('script')
}
}
What's Mirror Engine built on?
Mirror Engine stands on the shoulders of giants with numerous open-source libraries and we especially owe a huge thank you to PlayCanvas, which powers key parts of Mirror Engine. We're incredibly grateful for the team's work.
API Similarities
Most functionality in the PlayCanvas API is available via scripting in-game. However, to help you build games quickly, Mirror Engine abstracts many low-level details, such as mirrored multiplayer, scaling, asset loading, real-time collaboration, runtime optimizations, TS compilation, player accounts, databases, publishing, and more.
API Differences
We're still polishing these Mirror Engine implementations:
- Events: These do work from a single-player perspective, but may have breaking changes as we polish mirrored multiplayer.
- Asset loading: This happens automatically behind-the-scenes. We'll be deprecating some exposed APIs on the engine core and streamlining Mirror Engine's implementation.
- UI: Since everything is mirrored, we're still fine-tuning sync. vs nosync entities, which UI is particularly something you don't want to sync across players.
- Dynamic rigidbody physics do work, but they're not fully synced, so the physics will look different on others players' game clients. Kinematic and static rigidbodies are synced.
Thank you for your support as we continue to fine-tune and add functionality in the alpha!
Footnotes
-
Stack Overflow Survey 2024. JavaScript is #1 and TypeScript comes in at #5. ↩