Hello everyone,
Over the last few months I’ve been working on a scripting language that feels friendly with C++ so I can use it in my own projects and also learn more about programming languages. My main focus has been on building script bindings for C++ engines.
After testing several scripting languages, I often felt frustrated because something was always missing. So I decided to create my own language and VM. This is an example of binding Ogre3D to my script system. It lets me expose C++ classes in a simple and direct way and then control the scene from scripts.

Video:
Project/VM:
https://github.com/akadjoker/buOgre
If anyone is curious or wants to share feedback, I’d love to hear it.
Code: Select all
import math;
include "keys.bu";
include "constants.bu";
class Player
{
var node;
var entity;
var animations;
var currentState;
var currentBase;
var currentTop;
// Animações indexadas
var idleBase;
var idleTop;
var runBase;
var runTop;
var dance;
var sliceVertical;
var sliceHorizontal;
// Propriedades de movimento
var moveSpeed;
var rotationSpeed;
var isAttacking;
var attackCooldown;
var attackTimer;
// Espadas
var swordLeft;
var swordRight;
var trailLeft;
var trailRight;
var swordNodeLeft;
var swordNodeRight;
def init(scene,root)
{
self.node = root.createChild();
self.entity = scene.createEntity("Sinbad.mesh");
self.entity.castShadows = true;
self.node.attachObject(self.entity);
self.node.setPosition(0, 5, -30);
self.node.setScale(1, 1, 1);
// Configurar animações
self.idleBase = self.entity.getAnimationState("IdleBase");
self.idleTop = self.entity.getAnimationState("IdleTop");
self.runBase = self.entity.getAnimationState("RunBase");
self.runTop = self.entity.getAnimationState("RunTop");
self.dance = self.entity.getAnimationState("Dance");
self.sliceVertical = self.entity.getAnimationState("SliceVertical");
self.sliceHorizontal = self.entity.getAnimationState("SliceHorizontal");
// Configurar loops
self.idleBase.loop = true;
self.idleTop.loop = true;
self.runBase.loop = true;
self.runTop.loop = true;
self.sliceVertical.loop = false;
self.sliceHorizontal.loop = false;
self.dance.loop = true;
self.dance.enabled = false;
// Estado inicial: idle
self.currentState = "idle";
self.idleBase.enabled = true;
self.idleTop.enabled = true;
// Propriedades de gameplay
self.moveSpeed = 50.0;
self.rotationSpeed = 5.0;
self.isAttacking = false;
self.attackCooldown = 0.8; // 800ms entre ataques
self.attackTimer = 0.0;
// Criar espadas
self.swordLeft = scene.createEntity("Sword.mesh");
self.swordRight = scene.createEntity("Sword.mesh");
// Attach espadas aos bones das mãos
self.entity.attachObjectToBone("Handle.L", self.swordLeft);
self.entity.attachObjectToBone("Handle.R", self.swordRight);
// Criar nodes para os trails (na ponta das espadas)
self.swordNodeLeft = root.createChild();
self.swordNodeRight = root.createChild();
}
def setState(newState)
{
if (self.currentState == newState)
{
return;
}
// Desabilitar todas as animações
self.idleBase.enabled = false;
self.idleTop.enabled = false;
self.runBase.enabled = false;
self.runTop.enabled = false;
self.sliceVertical.enabled = false;
self.sliceHorizontal.enabled = false;
self.dance.enabled = false;
// Ativar animações do novo estado
if (newState == "idle")
{
self.idleBase.enabled = true;
self.idleTop.enabled = true;
}
elif (newState == "running")
{
self.runBase.enabled = true;
self.runTop.enabled = true;
}
elif (newState == "attacking")
{
self.sliceVertical.enabled = true;
self.sliceVertical.setTimePosition(0); // Reinicia a animação
self.isAttacking = true;
self.dance.enabled = false;
} elif (newState == "dance")
{
self.dance.enabled = true;
}
self.currentState = newState;
}
def handleInput(deltaTime)
{
// Não processar movimento se estiver atacando
if (self.isAttacking)
{
return;
}
var moveForward = 0.0;
var turnDirection = 0.0;
// UP/DOWN = mover para frente/trás
if (isKeyDown(KEY_UP))
{
moveForward = 1.0;
}
if (isKeyDown(KEY_DOWN))
{
moveForward = -1.0;
}
// LEFT/RIGHT = rodar
if (isKeyDown(KEY_LEFT))
{
turnDirection = 1.0;
}
if (isKeyDown(KEY_RIGHT))
{
turnDirection = -1.0;
}
// Rotacionar o player
if (turnDirection != 0.0)
{
var currentYaw = self.node.getYaw();
var newYaw = currentYaw + turnDirection * self.rotationSpeed * deltaTime;
self.node.setYaw(newYaw);
}
// Mover o player
if (moveForward != 0.0)
{
self.setState("running");
// Mover na direção que o player está a olhar
var currentYaw = self.node.getYaw();
var moveX = sin(currentYaw) * moveForward;
var moveZ = cos(currentYaw) * moveForward;
var pos = self.node.getPosition();
var newX = pos.x + moveX * self.moveSpeed * deltaTime;
var newZ = pos.z + moveZ * self.moveSpeed * deltaTime;
self.node.setPosition(newX, pos.y, newZ);
}
else
{
self.setState("idle");
}
// Ataque
if (isKeyDown(KEY_P) && self.attackTimer <= 0.0)
{
self.setState("attacking");
self.attackTimer = self.attackCooldown;
}
if (isKeyDown(KEY_O))
{
self.setState("dance");
}
}
def update(deltaTime)
{
// Atualizar timer de ataque
if (self.attackTimer > 0.0)
{
self.attackTimer = self.attackTimer - deltaTime;
}
// Processar input
self.handleInput(deltaTime);
// Atualizar posição dos nodes dos trails (aproximação simples)
var pos = self.node.getPosition();
var yaw = self.node.getYaw();
// Offset para as mãos (esquerda e direita)
var leftX = pos.x + cos(yaw) * 1.0 - sin(yaw) * 0.5;
var leftZ = pos.z + sin(yaw) * 1.0 + cos(yaw) * 0.5;
var rightX = pos.x + cos(yaw) * 1.0 + sin(yaw) * 0.5;
var rightZ = pos.z + sin(yaw) * 1.0 - cos(yaw) * 0.5;
self.swordNodeLeft.setPosition(leftX, pos.y + 1.5, leftZ);
self.swordNodeRight.setPosition(rightX, pos.y + 1.5, rightZ);
// Atualizar animações ativas
if (self.idleBase.enabled)
{
self.idleBase.addTime(deltaTime);
}
if (self.idleTop.enabled)
{
self.idleTop.addTime(deltaTime);
}
if (self.runBase.enabled)
{
self.runBase.addTime(deltaTime);
}
if (self.runTop.enabled)
{
self.runTop.addTime(deltaTime);
}
if (self.dance.enabled)
{
self.dance.addTime(deltaTime);
}
if (self.sliceVertical.enabled)
{
self.sliceVertical.addTime(deltaTime);
// Verificar se animação de ataque terminou
if (self.sliceVertical.hasEnded())
{
self.isAttacking = false;
self.setState("idle");
}
}
}
}
def load_resources()
{
addResourceLocation("./media/Main", "FileSystem", "OgreInternal");
addResourceLocation("./media/RTShaderLib", "FileSystem", "OgreInternal");
addResourceLocation("./media/Terrain", "FileSystem", "OgreInternal");
addResourceLocation("./media/packs/SdkTrays.zip", "Zip", "Essential");
addResourceLocation("./media/packs/profiler.zip", "Zip", "Essential");
addResourceLocation("./assets/PBR", "FileSystem","General");
addResourceLocation("./assets/PBR/filament", "FileSystem","General");
addResourceLocation("./assets/materials/programs/GLSL", "FileSystem","General");
addResourceLocation("./assets/materials/programs/GLSL120", "FileSystem","General");
addResourceLocation("./assets/materials/programs/GLSL150", "FileSystem","General");
addResourceLocation("./assets/materials/programs/GLSL400", "FileSystem","General");
addResourceLocation("./assets/materials/programs/GLSLES", "FileSystem","General");
addResourceLocation("./assets/materials/programs/SPIRV", "FileSystem","General");
addResourceLocation("./assets/materials/scripts", "FileSystem","General");
addResourceLocation("./assets/materials/textures", "FileSystem","General");
addResourceLocation("./assets/materials/textures/terrain", "FileSystem","General");
addResourceLocation("./assets/models", "FileSystem","General");
addResourceLocation("./assets/particle", "FileSystem","General");
addResourceLocation("./assets/DeferredShadingMedia", "FileSystem","General");
addResourceLocation("./assets/DeferredShadingMedia/DeferredShading/post", "FileSystem","General");
addResourceLocation("./assets/PCZAppMedia", "FileSystem","General");
addResourceLocation("./assets/materials/scripts/SSAO", "FileSystem","General");
addResourceLocation("./assets/materials/textures/SSAO", "FileSystem","General");
addResourceLocation("./assets/volumeTerrain", "FileSystem","General");
addResourceLocation("./assets/CSMShadows", "FileSystem","General");
addResourceLocation("./assets/packs/Sinbad.zip", "Zip", "General");
addResourceLocation("./assets/packs/skybox.zip", "Zip", "General");
addResourceLocation("./assets/packs/skybox.zip", "Zip", "General");
addResourceLocation("./assets/packs/cubemapsJS.zip", "Zip", "General");
addResourceLocation("./assets/packs/filament_shaders.zip", "Zip", "General");
initialiseAllResourceGroups();
}
CreateEngine(1024, 720, "FPS Controller", 1, false, true);
load_resources();
CreatePlane("OceanSurface", 1000, 1000, 50, 50, true, 1, 1.0, 1.0);
var scene = CreateScene();
scene.setSkyBox(true, "SkyBox", 1000.0);
var rootNode = scene.getRoot();
var camera = rootNode.createChild();
// ========== CONFIGURAÇÃO INICIAL ==========
scene.setAmbientLight(0.2, 0.2, 0.2);
var sun = scene.createLight("Sun", 1); // 1 = directional
sun.setDiffuse(1.0, 0.95, 0.9);
sun.setSpecular(0.5, 0.5, 0.5);
var sunNode = rootNode.createChild();
sunNode.attachObject(sun);
sunNode.setDirection(0.3, -0.75, 0.5,TS_LOCAL,0,0,-1); // Aponta para baixo
var water = scene.createEntity("OceanSurface");
water.setMaterial("OceanHLSL_GLSL");
var waterNode = rootNode.createChild();
waterNode.attachObject(water);
waterNode.setPosition(0, 10, 0); // altura da água
scene.setFog(3, 0.9, 0.7, 0.5, 0.001, 100.0, 1800.0);
var tg = scene.createTerrainGroup();
tg.configureDefaults(sun);
tg.loadHeightmap(0, 0, "terrain.png");
tg.loadAllTerrains();
tg.freeTemporaryResources();
// CÂMERA
var cam = scene.createCamera("MainCam");
cam.setNearClip(0.1);
camera.attachObject(cam);
camera.setPosition(0, 30, 80);
camera.lookAt(0, 5, 0, TS_WORLD);
var lf = scene.createLensFlare(cam, 0,60,50);
var viewport = CreateViewPort(cam);
viewport.setBackgroundColor(0.2, 0.3, 0.4);
viewport.setDimensions(0,0,1,1);
var currentShadowType = 0;
def setupShadows(type)
{
scene.setShadowTechnique(type);
if (type == 3 || type == 4) // TEXTURE shadows
{
scene.setShadowTextureSize(2048);
scene.setShadowTextureCount(4);
scene.setShadowFarDistance(1500.0);
scene.setShadowDirLightTextureOffset(0.6);
scene.setShadowTextureSelfShadow(true);
scene.setShadowCasterRenderBackFaces(false);
scene.setShadowColour(0.5, 0.5, 0.5);
}
}
currentShadowType = 2;
setupShadows(currentShadowType);
var player = Player(scene,rootNode);
var cam2 = scene.createCamera("OgreCam");
cam2.setNearClip(0.1);
var q = Quaternion(0, 0, 1, 0); // identidade (sem rotação)
var p = Vector3(0, 0.25,-10.6); // offset na frente do rosto
player.entity.attachObjectToBoneEx("Head", cam2, q, p);
// camera2.lookAt(0, 5, 0, TS_WORLD);
var viewport2 = CreateViewPort(cam2,1);
viewport2.setBackgroundColor(0.2, 0.3, 0.4);
viewport2.setDimensions(0,0,0.2,0.2);
var tpController = ThirdPersonController(camera, player.node);
tpController.setOffset(0, 8.0, 50);
// Create FPS controller
var fpsController = FPSController(camera);
fpsController.moveSpeed = 150.0; // velocidade de movimento
fpsController.mouseSensitivity = 0.15; // sensibilidade do mouse
// Mouse tracking
var lastMouseX = getMouseX();
var lastMouseY = getMouseY();
var time=0.0;
while(EngineCanUpdate())
{
var deltaTime = getDeltaTime();
var fps = getFPS();
time = time + deltaTime;
fpsController.setMoveForward(isKeyDown(KEY_W));
fpsController.setMoveBackward(isKeyDown(KEY_S));
fpsController.setMoveLeft(isKeyDown(KEY_A));
fpsController.setMoveRight(isKeyDown(KEY_D));
fpsController.setMoveUp(isKeyDown(KEY_Q));
fpsController.setMoveDown(isKeyDown(KEY_Z));
if (isKeyDown(KEY_1)) { currentShadowType = 0; setupShadows(currentShadowType); }
if (isKeyDown(KEY_2)) { currentShadowType = 1; setupShadows(currentShadowType); }
if (isKeyDown(KEY_3)) { currentShadowType = 2; setupShadows(currentShadowType); }
if (isKeyDown(KEY_4)) { currentShadowType = 3; setupShadows(currentShadowType); }
if (isKeyDown(KEY_5)) { currentShadowType = 4; setupShadows(currentShadowType); }
tpController.update(deltaTime, 0,0);
lf.update();
// Calculate mouse delta
var mouseX = getMouseX();
var mouseY = getMouseY();
var mouseDeltaX = 0;
var mouseDeltaY = 0;
if (isMouseButtonDown(MOUSE_LEFT))
{
mouseDeltaX = mouseX - lastMouseX;
mouseDeltaY = mouseY - lastMouseY;
}
lastMouseX = mouseX;
lastMouseY = mouseY;
// Update controller
//fpsController.update(deltaTime, -mouseDeltaX, mouseDeltaY);
player.update(deltaTime);
var pos = player.node.getPosition();
var h = tg.getHeightAtWorldPosition(pos.x, 0, pos.z);
player.node.setPosition(pos.x, h + 4.8, pos.z);
UpdateEngine(deltaTime);
}
ReleaseEngine();