lmbs-core
v1.0.0
Published
A real-time 2D Linear Motion Battle System core simulation engine.
Maintainers
Readme
⚔️ Linear Motion Battle System (LMBS) Core
A highly modular, engine-agnostic, real-time 2D fighting/RPG battle simulation engine written in TypeScript. This package isolates pure physics, kinematics, collision math, and action queues from rendering and platform dependencies, allowing it to be ported easily into various games or game engines.
The engine is divided into four core domains.
lmbs-core/
├── src/
│ ├── types.ts (State, configuration, and data structures)
│ ├── BattlerState.ts (The individual fighter state and 1-to-1 properties)
│ ├── ActionPipeline.ts (The action initialization, notes parsing, and queuing)
│ └── EngineCore.ts (The central match state, physics ticks, and collision loops)
└── index.ts (The public API wrapper for external game engines)Usage
Initialize the Core Engine To start a battle encounter, instantiate the main facade class by passing the viewport bounds, fallback configuration parameters, and your game's static data databases.
import { LinearMotionBattleSystem, LMBSConfig } from "./src/index";
// Define environmental parameters and patches
const battleConfig: LMBSConfig = {
turnDuration: 240,
gravityPower: 10,
groundHeightOffset: 0, // Baseline floor modification
damageReaction: false, // If false, grants invulnerability during hitstun
agilityMultiplier: 0, // Scale move speed with character speed stats
normalAttackRate: 60,
deadPoseYOffset: 24,
shadowYOffset: -4,
// Unofficial Knockback Patch Fallbacks
fallbackKnockbackX: 2,
fallbackKnockbackY: 2,
fallbackKnockbackDuration: 15,
fallbackHitstun: 30
};
// Simulated static databases containing raw skill note string data
const skillsDatabase = [
{ id: 0, repeats: 1, scope: 1, note: "" }, // Basic Attack Placeholder
{
id: 1,
repeats: 3,
scope: 1,
note: "LMBS Duration: 40\nLMBS Hit Delay: 10\nLMBS Area: [20,20,60,20]\nLMBS KnockbackX: 8\nLMBS KnockbackY: 4"
}
];
const itemsDatabase: any[] = [];
// Instantiate the battle system environment
const lmbs = new LinearMotionBattleSystem({
screenWidth: 800,
screenHeight: 600,
config: battleConfig,
skillsDatabase: skillsDatabase,
itemsDatabase: itemsDatabase
});👥 Spawning Battle Participants
Fighters (actors or enemies) are defined by pairing their current statistical values and raw metadata strings into an ExternalBattlerData object, then injecting them straight into the simulation pool.
import { LmbsBattlerState, ExternalBattlerData } from "./src/index";
// 1. Configure the Player Fighter
const playerData: ExternalBattlerData = {
actorOrEnemyId: 1,
isActor: true,
agi: 45,
attackSkillId: 0,
skills:, // Shortcut slots matrix mapping
groundHeightOffset: 0, // Character structural floor baseline tweak
note: "LMBS Sprite Name: Hero\nLMBS Move Speed: 6\nLMBS Jump Height: 22\nLMBS Double Jump: true\nLMBS Air Dash: true"
};
const hero = new LmbsBattlerState(playerData, battleConfig.gravityPower, battleConfig.deadPoseYOffset);
lmbs.spawnParticipant(hero);
// 2. Configure an Enemy Target
const goblinData: ExternalBattlerData = {
actorOrEnemyId: 10,
isActor: false,
agi: 20,
attackSkillId: 0,
skills: [],
groundHeightOffset: 0,
note: "LMBS Sprite Name: Goblin\nLMBS Move Speed: 3\nLMBS Action Rate: 120"
};
const goblin = new LmbsBattlerState(goblinData, battleConfig.gravityPower, battleConfig.deadPoseYOffset);
lmbs.spawnParticipant(goblin);🔄 Running the Core Game Loop
To progress velocities, compute gravity drag curve changes, evaluate AABB overlapping hitboxes, and tick internal state countdown variables, bind the .tick() method straight into your global frame rendering update timeline.
function gameLoop() {
// 1. Advance the mathematical battle engine states by 1 frame step
lmbs.tick();
// 2. Query data out of the engine state to update visual assets/sprites
const actors = lmbs.core.getActors();
actors.forEach(actor => {
const visualSprite = getYourVisualSpriteReference(actor.externalData.actorOrEnemyId);
// Smoothly update position coordinates mapping 1-to-1 from simulation memory
visualSprite.x = actor._lmbs_X;
visualSprite.y = actor._lmbs_Y;
// Check action states to resolve visual sheets/poses dynamically
if (actor._lmbs_Knockback.duration > 0) {
visualSprite.playPose("Damage");
} else if (actor._lmbs_Walking) {
visualSprite.playPose("Walk");
} else if (actor._lmbs_ActionSkill.length > 0) {
// Index 21 houses parsed custom pose strings ("lmbs pose name")
const activePose = actor._lmbs_ActionSkill[21] || "Attack";
visualSprite.playPose(activePose);
} else {
visualSprite.playPose("Idle");
}
});
// Loop processing step mapping
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);🎮 Processing Combat Commands & Input Hooks
The package keeps input polling completely abstract. Your rendering interface tracks key presses or controller gestures, changing the flags or immediately pushing executions down-funnel.
Direct Action Commands:
// Command the hero to use skill slot shortcut index 1 targeting the goblin
if (myInputManager.isButtonPressed("SkillKey")) {
lmbs.triggerAction(hero, 1, false, goblin);
}Manual Movement Manipulation:
// Update locomotion states natively to allow the physics engine loop to translate positions
if (myInputManager.isKeyPressed("LeftArrow")) {
hero._lmbs_Direction = 2; // Face Left constant configuration
hero._lmbs_Walking = true;
} else if (myInputManager.isKeyPressed("RightArrow")) {
hero._lmbs_Direction = 4; // Face Right constant configuration
hero._lmbs_Walking = true;
} else {
hero._lmbs_Walking = false; // Entity returns cleanly back to neutral idle
}
// Intercept jumping triggers natively
if (myInputManager.isButtonPressed("JumpKey") && !hero._lmbs_Jump.isActive) {
hero._lmbs_Jump.isActive = true;
// Apply upward kinetic impulse subtracting vectors away from floor line lines
hero._lmbs_Y -= hero._lmbs_Jump.power;
}📑 Supported Notetag Metadata Reference
When configuring raw asset string packages (ExternalBattlerData.note or skill payloads), use standard metadata syntax formatting:
Character Traits
- LMBS Sprite Name: [String] – Defines target file visual identifier tracking structures.
- LMBS Move Speed: [Number] – Sets the base pixel shifting step increment.
- LMBS Jump Height: [Number] – Peak impulse ceiling boundary adjustments.
- LMBS Double Jump: [Boolean] – Authorizes extra mid-air upward trajectory commands.
- LMBS Air Dash: [Boolean] – Authorizes fast horizontal air slides.
- LMBS Fly Height: [Number] – Enforces hover buffers above floor lines, disabling jumping states natively.
Ability Traits
- LMBS Duration: [Number] – Life-span window of the collider execution frames.
- LMBS Hit Delay: [Number] – Wait frames required before resetting active hit triggers for multi-hit capabilities.
- LMBS Area: [Up, Down, Left, Right] – Hitbox dimension arrays calculated outwards from user coordinates.
- LMBS Pose Name: [String] – Changes character visual frames while active.
- LMBS KnockbackX: [Number] – Transferred horizontal kinetic impulse speed.
- LMBS KnockbackY: [Number] – Transferred vertical kinetic push-up speed.
- LMBS Hitstun: [Number] – Frames target remains locked out of action state execution layers.
- LMBS Skill Chain: [Number] – Auto-injects follow-up skill IDs instantly upon termination.
Layout
Module 1: The Shared Blueprints - types.ts
This file holds configurations and strict TypeScript versions
Module 2: 1-to-1 Property Mapping - BattlerState.ts
This class completely replaces Game_Battler.prototype. Every lmbs variable is preserved here as a clean class property. This is where functions like lmbsSetParametersTags() or movement gating logic will live.
Where methods updating this._lmbs_Y, that exact code can be ported inside this class without changing the property names. It accepts an abstract externalStats provider so it can read agi or item notes without knowing if they come from RPG Maker, Unity, or Godot.
Module 3: Action & Combat Execution - ActionPipeline.ts
This module handles everything from parsing note tags ("lmbs knockbackx") to running combo timing windows, chaining skills, and managing target scopes. It isolates the heavy string parsing and the knockbackCounter math we saw in Section 6. It calculates the raw data and bundles it into an ActionPayload to be executed.
Module 4: The Tick & Physics Loop - EngineCore.ts
This class replaces Game_Temp and Game_System. It maintains the global list of active BattlerState instances, monitors the boundaries of the scene, and exposes a single .update(deltaTime) or .tick() method. Your external game loop (like a requestAnimationFrame in JS or a _process loop in Godot) just calls this tick method. The core then loops through all battlers, applies gravity vectors, processes active projectiles, and handles collision math.
