Skip to content

Noc2D Getting Started

Follow the steps below to run your first game.

Download the Binary

Download the latest Noct2D binary from the Releases Page.

Run the Game

Double-click the noct2d.exe file to start the game.

Edit the Game

Open the main.noct file.

Export the Game

Select your game files on Explorer and drop them to the noct2dpack.exe file, then the product.img file will be created.

Place the product.img file alongside the noct2d.exe file.


Noct2D Tutorial Game

Bouncer

This is the simplest sample. See the samples/bouncer folder in the SDK.

//
// ==== bouncer ====
//

func setup() {
    return {
        width: 800,
        height: 450,
        title: "Bouncer"
    };
}

func start() {
    W = 800;
    H = 450;
    BLOCK_W = 48;      // pixels
    BLOCK_H = 48;      // pixels
    SPEED_X = 180.0;   // pixels/sec
    SPEED_Y = 140.0;   // pixels/sec
    MAX_DT  = 0.1;     // sec

    // Create a texture.
    tex = Engine.createColorTexture({
        width: BLOCK_W,
        height: BLOCK_H,
        r: 0,
        g: 128,
        b: 255,
        a: 255
    });

    // Set the initial block position.
    x = (W - BLOCK_W) * 0.5;
    y = (H - BLOCK_H) * 0.5;

    // Set the initial speed.
    vx = SPEED_X;
    vy = SPEED_Y;

    last_ms = Engine.millisec;
}

func frame() {
    var now_ms = Engine.millisec;
    var dt = (now_ms - last_ms) * 0.001;
    last_ms = now_ms;

    // Process the input.
    if (Engine.isMouseLeftPressed) {
        x = (W - BLOCK_W) * 0.5;
        y = (H - BLOCK_H) * 0.5;
    }

    // Update the position
    x = x + vx * dt;
    y = y + vy * dt;

    // Reflect.
    if (x < 0) {
        // Left to right.
        x = -x;
        vx = -vx;
    } else if (x > W - BLOCK_W) {
        // Right to left.
        x = 2*(W - BLOCK_W) - x;
        vx = -vx;
    }
    if (y < 0) {
        // Up to down.
        y = -y;
        vy = -vy;
    } else if (y > H - BLOCK_H) {
        // Down to up.
        y = 2*(H - BLOCK_H) - y;
        vy = -vy;
    }

    // Draw.
    Engine.draw({
        texture: tex,
        x: x,
        y: y
    });
}

DON'T RUSH IN COMIKET

This is a simple sample game. See the samples/rush folder in the SDK.

//
// DON'T RUSH IN COMIKET
//

func setup() {
    screenW = 640;
    screenH = 320;
    return {
        width:  screenW,
        height: screenH,
        title:  "DON'T RUSH IN COMIKET"
    };
}

func start() {
    // Constants.
    true = 1;
    false = 0;
    null = 0;

    // baseline for the ground
    groundY = screenH - 20;

    // Globals
    playerTex = null;
    obTex = null;
    fontReady = false;
    scoreTex = null;
    msgTex = null;

    player = {
        x: 60,
        y: 0,
        w: 48,
        h: 48,
        vy: 0,
        onGround: true
    };

    obstacles = [];

    // Tuning
    gravity = 2000.0;
    jumpVel = -700.0;
    scrollSpeed = 380.0;
    spawnInterval = 1.10; // seconds
    spawnTimer = 0.0;

    score = 0;
    lastTime = 0;
    gameOver = false;

    // Load the textures.
    playerTex = Engine.loadTexture({
        file: "player.png"
    });
    obTex = Engine.loadTexture({
        file: "obstacle.png"
    });
    floorTex = Engine.createColorTexture({
        width: 1, height: 1,
        r: 255, g: 255, b: 0, a: 255
    });

    // Load the font.
    Engine.loadFont({
        slot: 0,
        file: "PixelifySans-VariableFont_wght.ttf"
    });
    fontReady = true;

    // Place player on ground
    player.y = groundY - player.h;
    lastTime = Engine.millisec;

    updateScoreTex();
}

func frame() {
    var now = Engine.millisec;
    var dt = (now - lastTime) * 0.001;
    if (dt > 0.05) { dt = 0.05; } // clamp to avoid huge steps
    lastTime = now;

    update(dt);
    render();
}

func resetGame() {
    obstacles = [];
    player.x = 120;
    player.y = groundY - player.h;
    player.vy = 0;
    player.onGround = true;
    score = 0;
    spawnTimer = 0.0;
    gameOver = false;
    updateScoreTex();
    if (msgTex != null) {
        Engine.destroyTexture({ texture: msgTex });
        msgTex = 0;
    }
}

func update(dt) {
    if (gameOver) {
        if (Engine.isMouseLeftPressed) {
            resetGame();
        }
        return 1;
    }

    // Input: jump on mouse/tap
    if (Engine.isMouseLeftPressed && player.onGround) {
        player.vy = jumpVel;
        player.onGround = false;

        Engine.playSound({ stream: 0, file: "jump.ogg" });
    }

    // Physics
    player.vy = player.vy + gravity * dt;
    player.y  = player.y + player.vy * dt;

    // Ground clamp
    var floorY = groundY - player.h;
    if (player.y >= floorY) {
        player.y = floorY;
        player.vy = 0;
        player.onGround = true;
    }

    // Spawn obstacles
    spawnTimer = spawnTimer + dt;
    if (spawnTimer >= spawnInterval) {
        spawnTimer = spawnTimer - spawnInterval;
        spawnObstacle();
    }

    // Move & prune obstacles, scoring & collisions
    var newObs = [];
    for (ob in obstacles) {
        ob.x = ob.x - scrollSpeed * dt;

        // score when fully passed player
        if (!ob.counted && (ob.x + ob.w) < player.x) {
            ob.counted = true;
            score = score + 1;
            updateScoreTex();
        }

        // AABB collision
        if (rectOverlap(player.x, player.y, player.w, player.h,
                        ob.x, ob.y, ob.w, ob.h)) {
            onGameOver();
        }

        // keep if on screen
        if ((ob.x + ob.w) >= -64) {
            newObs->push(ob);
        }
    }
    obstacles = newObs;
}

func onGameOver() {
    if (gameOver) {
        return 1;
    }

    gameOver = true;

    if (fontReady) {
        msgTex = Engine.createTextTexture({
            slot: 0,
            text: "GAME OVER - click to retry",
            size: 32,
            r: 255,
            g: 64,
            b: 64,
            a: 255
        });
    }
}

func spawnObstacle() {
    // Simple single-block obstacle near ground
    var w = 49;
    var h = 70;  // jump over this
    var x = screenW + 20;
    var y = groundY - h;
    obstacles->push({
        x: x,
        y: y,
        w: w,
        h: h,
        counted: false
    });
}

func render() {
    // Draw ground as a long rectangle
    drawRect(0, groundY, screenW, 8, floorTex);

    // Draw obstacles
    for (ob in obstacles) {
        Engine.draw({
            texture: obTex,
            x: ob.x,
            y: ob.y
        });
    }

    // Draw player
    Engine.draw({
        texture: playerTex,
        x:       player.x,
        y:       player.y
    });

    if (fontReady) {
        if (scoreTex != null) {
            Engine.draw({ texture: scoreTex, x: 12, y: 10 });
        }
        if (gameOver && msgTex != null) {
            Engine.draw({ texture: msgTex, x: (screenW - msgTex.width) / 2, y: 120 });
        }
    }
}

func drawRect(x, y, w, h, tex) {
    Engine.renderTexture({
        dstLeft:   x,
        dstTop:    y,
        dstWidth:  w,
        dstHeight: h,
        texture:   tex,
        srcLeft:   0,
        srcTop:    0,
        srcWidth:  tex.width,
        srcHeight: tex.height,
        alpha:     255
    });
}

func rectOverlap(ax, ay, aw, ah, bx, by, bw, bh) {
    var aRight = ax + aw;
    var bRight = bx + bw;
    var aBottom = ay + ah;
    var bBottom = by + bh;

    if (aRight <= bx) {
        return false;
    }
    if (bRight <= ax) {
        return false;
    }
    if (aBottom <= by) {
        return false;
    }
    if (bBottom <= ay) {
        return false;
    }

    return true;
}

func updateScoreTex() {
    if (!fontReady) {
        return 0;
    }

    if (scoreTex != null) {
        Engine.destroyTexture({
            texture: scoreTex
        });
    }

    scoreTex = Engine.createTextTexture({
        slot: 0,
        text: "Score: " + score,
        size: 28,
        r: 255,
        g: 255,
        b: 255,
        a: 255
    });
}