YACE64 Logo

The Commodore 64 Emulator with the extra dimension

Home Introduction Features Screenshots Download Links Help About
Commodore 64
www.icons8.com

Tutorials

Tutorial 08 - Sprite replacement and C64 program interaction

This script shows how to automate the setup and start of a program, so only a click is needed to run everything how it's needed.

This is the "Tutorial08" included in the Tutorial-Download.

Instructions

This is a complex tutorial, because it contains three things at once:

1) The replacement of a sprite with a real 3D model
2) Use the SID as a random number generator to control the 3D model
3) Use changes in C64 memory to control the 3D model

To 1)
This is an easy part; like replacing character, sprites can also replaced by 3D models. To replace the sprite set the modelIndex in the OnSprite() function to a value non zero.
The OnGetSprite() function will then be called, where the filename of the 3D model is provided for the given modelIndex.

To 2)
The SID is configured in the OnLoad() function to produce a noise waveform on oscillator three. In the OnFrameEnd() function the current wave-sample is read and used to set the rotation speed of our second ghost.

To 3)
This the fun part: In the OnLoad() function a condition (SetCondition()) is created, that will trigger the OnCondition() function of the script, if the condition is fullfilled.
Note: Observing a memory address or a register e.g. in the OnFrameEnd() function would maybe also work (like in 2)), but it might not hit a specific value change ("polling").
A condition is much faster and more reliable.
The used condition CPU.MWA (see Breakpoints) will trigger, when the 6502 CPU writes anything into the address 32768. So we can now read the value and do some logic with it.
In our case two conditions are create, to oberserve memory writing to address 32768 and 32769, which control the rotation (spriteRotationY1) and apperance (blueGhost) of the first sprite.

const int startProgramTimerId = 1;

float spriteRotationY1 = 180.0;
float spriteRotationY1Dir = -1.0;
float spriteRotationY2 = 180.0;
float spriteRotationY2Dir = -1.0;
float spriteRotationX = 0.0;
float spriteRotationXDir = -1.0;

float ghostScale = 0.020;

bool blueGhost = false;

// This is called, when we loaded the script-file
void OnLoad(bool reloaded)
{
    string scriptPath = APP.GetScriptPath();

    APP.ClearDepthMaps(); // Clear all loaded depth maps
    APP.ClearModels(); // Clear all loaded model

    // Set the view mode to the new 3D
    APP.ViewMode = ViewMode::Ext3D;

    // Set the ambient light color and brigtness
    APP.SetAmbientLight(0.3, 0.3, 0.3);
    // Set the direction of the light (from the middle off the scene (x), from top to bottom (y) and back to forth (z))
    APP.SetLightDir(0.0, -1.0, +1.0);

    // Position the scene, so we have the top/left in the middle of the screen
    E3D.SetScenePosition(0.44, -0.20, +0.01);
    // Rotate the scene a little bit, so we can see the 3D effect
    E3D.SetSceneOrientation(-25.0, -28.0, 0.0, 0.0); // yaw, pitch, roll in degree, speed

    // Setup the SID to produce random values on oscillator 3 without making noise (see OnFrameEnd())
    SID.Freq[2] = 5000;
    SID.Attack[2] = 0;
    SID.Decay[2] = 0;
    SID.Sustain[2] = 15;
    SID.Release[2] = 0;
    SID.Control[2] = SIDCtrl::NOISE | SIDCtrl::GATE;
    SID.OSC3Off = true;
    SID.Volume = 15;

    // Create a condition 1, that will notify us, when CPU writes into memory at 32768 (see OnCondition())
    C64.SetCondition(1, { "CPU.MWA=32768" }, "DataInputRot");
    // Create a condition 2, that will notify us, when CPU writes into memory at 32769 (see OnCondition())
    C64.SetCondition(2, { "CPU.MWA=32769" }, "DataInputType");

    if (!reloaded)
    {
        // Load our "game"
        APP.Load(scriptPath + "Sprites.prg", 0);
        SetTimer(startProgramTimerId, 120, false);
    }
}

bool OnSprite(int mode, SpriteInfo& spriteInfo, BlockManipulation& spriteManipulation)
{
    spriteManipulation.ambient = 0.35;

    spriteManipulation.xScale = ghostScale;
    spriteManipulation.yScale = ghostScale;
    spriteManipulation.zScale = ghostScale;

    spriteManipulation.rotationAxis1 = Rotation::Y; // some animated rotation (see OnFrameEnd())
    spriteManipulation.rotationAxis2 = Rotation::X;
    spriteManipulation.rotation2 = spriteRotationX;

    if (spriteInfo.index == 0)
    { // Ghost 1
        spriteManipulation.modelIndex = blueGhost ? 3 : 1;
        spriteManipulation.rotation1 = spriteRotationY1;
    }
    else if (spriteInfo.index == 1)
    { // Ghost 2
        spriteManipulation.modelIndex = 2; // When modelIndex is not zero, then a 3D model file is request in the OnGetModel() function
        spriteManipulation.rotation1 = spriteRotationY2;
    }

    spriteManipulation.zTranslation -= 0.03; // translate the sprites a little mit more in foreground

    return true;
}

void OnCondition(int device, int conditionId, string data)
{
    // Check which condition fired and then read the data written to memory
    if (conditionId == 1)
    {
        int pokedValue = C64.Read(32768);
        //GUI.ShowInfoMessage("Poked value $32768 = " + formatInt(pokedValue));

        spriteRotationY1 = 90.0 + pokedValue; // rotate the sprite to the "poked" angle
    }
    else if (conditionId == 2)
    {
        int pokedValue = C64.Read(32769);
        //GUI.ShowInfoMessage("Poked valuee $32769 = " + formatInt(pokedValue));

        blueGhost = (pokedValue != 0); // enable the blue ghost, if "poked" value is not zero
    }
}

float extraSpeed = 0.0;

void OnFrameEnd()
{
    int oscChannel3 = SID.OSC3; // Read the oscillator 3 wave sample (noise should produce a "random" value)
    if (oscChannel3 < 4)
    {
        float s = oscChannel3;
        extraSpeed = (s / 2.0); // Some simple logic to let the ghost rotate more randomly (extraSpeed)
    }

    // Just some simple rotation around x and y axis
    if (spriteRotationY2 > 225.0 && spriteRotationY2Dir > 0.0)
        spriteRotationY2Dir = -0.7 - extraSpeed;
    else if (spriteRotationY2 < 110.0 && spriteRotationY2Dir < 0.0)
        spriteRotationY2Dir = +0.7 + extraSpeed;

    spriteRotationY2 += spriteRotationY2Dir;

    if (spriteRotationX < -10.0 && spriteRotationXDir < 0.0)
        spriteRotationXDir = 0.15;
    else if (spriteRotationX > 20.0 && spriteRotationXDir > 0.0)
        spriteRotationXDir = -0.15;

    spriteRotationX += spriteRotationXDir;
}

bool OnGetModel(int mode, int index, ModelDescription& modelDescription)
{
    modelDescription.renderSides = RenderSides::RenderSidesFront;

    string scriptPath = APP.GetScriptPath();
    switch (index)
    {
        case 1: modelDescription.objFile = scriptPath + "3D\\Ghosts\\Ghost3-8-3.obj"; return(true);
        case 2: modelDescription.objFile = scriptPath + "3D\\Ghosts\\Ghost3-8-4.obj"; return(true);
        case 3: modelDescription.objFile = scriptPath + "3D\\Ghosts\\Ghost3-7-2.obj"; return(true); // Panic Ghosts
        default: break;
    }
    return(false);
}

void OnTimer(int timerId)
{
    if (timerId == startProgramTimerId)
    {
        APP.SendASCII("RUN\n");
    }
}