Need critics for my external DSL
⚓ Rust 📅 2026-06-12 👤 surdeus 👁️ 1Hello fellas.
I'm planning to go on a long journey with creating an external DSL for my narrative game engine binary. It takes lots of inspiration from rust with some bits of unique stuff specifically for creating a narrative game.
This is my initial draft of some of the core syntax. What do you guys think?
// Syntax draft for .fizz file
// Characters.fizz
// Uses game-project/assets/sprites/ for character assets.
use Assets::Sprites::{ // Just a bunch of static images, but could be animated through sprite looping or something
Jonathan::{ // game-project/assets/sprites/Jonathan/ for Jonathan assets
Jonathan, JonathanShocked, // game-project/assets/sprites/Jonathan/Jonathan.png,JonathanShocked.png
JonathanIdle as SpriteGroup, // game-project/assets/sprites/Jonathan/JonathanIdle/ Reserved keyword "SpriteGroup" to indicate JonathanIdle as a folder containing multiple frames for animation.
JonathanRun as SpriteGroup, // game-project/assets/sprites/Jonathan/JonathanRun/ Frames inside Spritegroup are natively accessed using name order convenstions, e.g. 0-9, a-z, etc. for easy looping and referencing.
JonathanWalk as SpriteGroup, // game-project/assets/sprites/Jonathan/JonathanWalk/
} as JonathanAssets, // The "as" syntax is used to locally group assets together under a custom name
}
use Assets::sounds::{ // game-project/assets/sounds/ for sound assets
Shock as ShockSound, // Static sound file shock.wav
}
// Like other languages, imports cannot be of same name, so we can either use them inside as sounds::Shock, or ShockSound as above.
// In this language, imports have higher heirarchy than global declarations then local usage, so it would cause error unless you reimport them to another name.
use Engine::{ // built-in engine module
Character,
Actions,
}
// actions and entity declarations are called Components here. They can be used globally with GLOBAL prefix.
// Actions type inside actions is not yet fully fleshed out, but i will leave it there for now to give an idea of how it would look.
GLOBAL actions Walk(frames: SpriteGroup) {
Actions::Frameloop(frames, 0.1) // Actions::Frameloop indefinitely loops frames for 0.1s per frame
}
// There can only be one "Action" inside this action component, use Orchestrate to combine multiple actions together
// Since there can only be one Action, Action types may attach methods back to the main component like Jonathan.Walk.stop()
GLOBAL actions ShockFace(frames: Sprite) { // By default, single sprites inherit Sprite type automatically, can be expressed explicitly during import for clarity
Actions: Orchestrate(
PlaySound(ShockSound), // Plays shock.wav sound effect
ShowSprite(frames) and ShakeScreen(0.5, 5), // "and" operator allows multiple actions to be executed simultaneously, so the shock sprite appears on screen while the screen shakes for 0.5 seconds with intensity 5
Wait(0.5), // Waits for 0.5 seconds
HideSprite(frames), // Hides the shock sprite from screen
)
}
// This is a more complex demonstration with of Orchestrate.
// Since Orchestrate executes actions sequentially by default, loop actions must have an end condition.
// for such cases, the "until" keyword can be used to specify an end condition for the loop, such as a key press, timer, or custom condition.
GlOBAL actions Something(gsprite: SpriteGroup, sound: Sound) { // Single sound files are also automatically inferred as Sound type, but can be explicitly expressed for clarity like above
Actions: Orchestrate(
Frameloop(JonathanIdle, 0.1) until KeyPress("W"),
PlaySound(ShockSound)
)
}
GLOBAL entity Jonathan: Character { //character acts like a rust Trait here
pose: JonathanAssets, // or the entire import above without "as JonathanAssets" inside {}
actions: [Default(Jonathan),Walk(JonathanWalk), ShockFace(JonathanShocked)], // This significantly reduces refactoring time, as you can just change the frames in the action definition and it will automatically update for all characters using that action.
}
// Here, Default is a reserved keyword that indicates a static pose and the fallback. It only accepts a single frame.
// sample usage
Jonathan.Walk.start() // Starts Jonathan's walk animation
Jonathan.ShockFace.start() // Jonathan's face changes to shocked expression, shock sound plays, screen shakes, then after 0.5 seconds the shocked expression disappears
Jonathan.Walk.stop() // Stops Jonathan's walk animation, reverting to idle pose
// or with keybind (makeup syntax for keybinds, not fully fleshed out yet)
onKeyPress("W", Jonathan.Walk.start()) // When "W" key is pressed, Jonathan starts walking
onKeyRelease("W", Jonathan.Walk.stop()) // When "W" key is released, Jonathan stops walking
This is just the initial draft after an hour of brainstorming, things here are not fully fleshed out (can't count how many times I've said it)
Let me know your views, suggestions on what to improve or add or just change altogehter. It'd help a lot!
3 posts - 2 participants
🏷️ Rust_feed