Cheezy Retro 3D Crap

Anybody remember ‘DungeonMaster’ or ‘Eye of the Beholder’? 1988 or so? Probably before some of you were born. Of course, PONG was a wonder when I was a kid.

Everybody yawn audibly, now. One of the first things I wrote in Flash, looks like in March.

The one zip contains a Flash 8 project, the other contains the compiled result. If it comes up blank, mash on the arrow keys and/or QWE/ASD a bit to bring something near enough to see. Looking at the code, it looks like I hacked in 0/1 for changing map contents, just to be clever.

Anyway, one nice thing about this method is, it will run on ANY hardware and keep up a marvelously high frame rate. High enough to animate other Flash stuff. On the down-side, you’re locked into 90 degree turning and block-by-block movement…

Ultra-super-simple stuff. Just draw the blocks and whatever else in the right order, and up it pops, just like a cheezy 3D dungeon crawl of yore.

I wrote it as a demo. It doesn’t do anything the way it ought to, but it does illustrate how it’s done clearly enough. Really, it should have one set of blocks that are dynamically grown/shrunk to fit, but I was too lazy to do anything but sit like a vegetable and drag things around in the Flash UI the afternoon I hacked it up.

I had a version of this running on the Atari ST using Line-A calls, once upon a time. It allowed you to look up/down and rotate… all on 90 degree increments and bock boundaries.



// Do a little '3D' block dungeon crawl demo
// Up/Down -> Forward, Backward
// Left/Right -> Turn
// Make a map of chars.

//
// This doesn't work like it should.
// There should be a map, with details and collections of junk
// Instead of static blocks hidden/shown, this should maintain a set of MovieClip things, dynamically created, destroyed and replaced.
// Needs selection of art for things that are rotated versus always pointed the same way.
// 1-way (shrubs, columns), 2-way (some doors), 4-way (characters, switches on walls, hooks, decorations, signs, etc.)
// Things that do rotate need to rotate appropriately when player rotates
// 


//
// Define how we 'see' the world
//
var viewLutBlock:Array = new Array
(    // Block art in 'sequential' order
    _level0.BL02,_level0.BL01,_level0.BC0,_level0.BR01,_level0.BR02,
    _level0.BL12,_level0.BL11,_level0.BC1,_level0.BR11,_level0.BR12,
                _level0.BL21,_level0.BC2,_level0.BR21,
                _level0.BL31,             _level0.BR31
);
var viewLutN:Array = new Array
(    // Blocks facing North
    -2,-3, -1,-3, 0,-3, 1,-3, 2,-3,
    -2,-2, -1,-2, 0,-2, 1,-2, 2,-2,
         -1,-1, 0,-1, 1,-1,
          -1,0,        1,0
);
var viewLutE:Array = new Array
(    // Blocks facing East
    3,-2, 3,-1, 3,0, 3,1, 3,2,
    2,-2, 2,-1, 2,0, 2,1, 2,2,
         1,-1, 1,0, 1,1,
         0,-1,        0,1
);
var viewLutS:Array = new Array
(    // Blocks facing South
    2,3, 1,3, 0,3, -1,3, -2,3,
    2,2, 1,2, 0,2, -1,2, -2,2,
          1,1, 0,1, -1,1,
          1,0,        -1,0
);
var viewLutW:Array = new Array
(    // Blocks facing West
    -3,2, -3,1, -3,0, -3,-1, -3,-2,
    -2,2, -2,1, -2,0, -2,-1, -2,-2,
          -1,1, -1,0, -1,-1,
           0,1,       0,-1
);
var viewLut:Array = new Array
(     // Block addressing, per facing direction, clockwise
     viewLutN, 
    viewLutE, 
    viewLutS, 
    viewLutW 
);
var moveLut:Array = new Array
(
     0,-1,
    1,0,
    0,1,
    -1,0
);

var worldSize:Number = 32;     // Width and height of world
var world:Array = new Array(worldSize*worldSize); // Allocate an array to hold it.

//
// Where we stand in the world
//
var wpX:Number = 10;
var wpY:Number = 10;
var wpFace:Number = 0;

//
// Build a little world randomly
//
function initWorld()
{
    var index:Number;
    var numCels:Number = worldSize*worldSize;
    while( numCels-- )
    {
        if( random(3) < 1 )
        {
            world[numCels] = 1;
        }
        else
        {
            world[numCels] = 0;
        }
    }
    // Make sure the player isn't buried
    worldSet( wpX-1, wpY-1, 0 );
    worldSet( wpX, wpY-1, 0 );
    worldSet( wpX+1, wpY-1, 0 );
    worldSet( wpX-1, wpY, 0 );
    worldSet( wpX+1, wpY, 0 );
    worldSet( wpX, wpY, 0 );
    worldSet( wpX-1, wpY+1, 0 );
    worldSet( wpX, wpY+1, 0 );
    worldSet( wpX+1, wpY+1, 0 );
}
// Set a map value in the world, return previous value
function worldSet( cx:Number, cy:Number, value:Number ) : Number
{
    // Out of bounds is 'solid wall', and won't be changed.
    if( cx < 0 || cx >= worldSize || cy < 0 || cy >= worldSize )
        return 1;
    var index:Number = (cy * worldSize) + cx;
    var ret:Number = world[index];
    world[index] = value;
    return ret;
}
// Get a map value from the world
function worldGet( cx:Number, cy:Number ) : Number
{
    // Out of bounds is 'solid wall'.
    if( cx < 0 || cx >= worldSize || cy < 0 || cy >= worldSize )
        return 1;
    var index:Number = (cy * worldSize) + cx;
    var ret:Number = world[index];
    return ret;
}
initWorld();
Key.addListener(this); // Listen for keydown events


// Each frame, look at world array around current location and hide/show blocks accordingly
// Eventually needs doors, decorations, characters, 
function onEnterFrame()
{
    // Check for keyboard input, change world position accordingly
    var cBlocks:Number = viewLutBlock.length;
    var ilut:Number = 0;
    var iblock:Number = 0;
    while( iblock < cBlocks )
    {
        var vLut:Array = viewLut[wpFace];
        var xc:Number = wpX + vLut[ilut++];
        var yc:Number = wpY + vLut[ilut++];
        var bVisible = worldGet( xc, yc ) == 1;
        viewLutBlock[iblock++]._visible = bVisible;
    }
    // The one we're standing on is always invisible (delete it?)
    //_level0.BR31._visible = false;
    _level0.StatusLine.text = "Rotation: " + wpFace + " Location: " + wpX + "," + wpY;
}

// Be all interactive 'n stuff.
function onKeyDown()
{
    var code:Number = Key.getCode();
    if( code == Key.RIGHT || code == 69 ) //'E'
    {    // Turn right
        if( ++wpFace > 3 )
            wpFace = 0;
    }
    else if( code == Key.LEFT || code == 81 ) // 'Q'
    {    // Turn left
        if( --wpFace < 0 )
            wpFace = 3;
    }
    else if( code == Key.UP || code == 87 ) // 'W'
    {    // Go forward
        var iDir:Number = wpFace*2;
        wpX += moveLut[iDir];
        wpY += moveLut[iDir+1];
        if( worldGet( wpX, wpY ) != 0 )
        {
            wpX -= moveLut[iDir];
            wpY -= moveLut[iDir+1];

        }
    }
    else if( code == Key.DOWN || code == 83 ) // 'S'
    {    // Go backward
        var iDir:Number = wpFace*2;
        wpX -= moveLut[iDir];
        wpY -= moveLut[iDir+1];
        if( worldGet( wpX, wpY ) != 0 )
        {
            wpX += moveLut[iDir];
            wpY += moveLut[iDir+1];
        }
    }
    else if( code == Key.DELETEKEY || code == 65 ) // 'A'
    {    // Go left
        var iDir:Number = ((wpFace+1)&3)*2;
        wpX -= moveLut[iDir];
        wpY -= moveLut[iDir+1];
        if( worldGet( wpX, wpY ) != 0 )
        {
            wpX += moveLut[iDir];
            wpY += moveLut[iDir+1];
        }
    }
    else if( code == Key.PGDN || code == 68 ) // 'D'
    {    // Go right
        var iDir:Number = ((wpFace-1)&3)*2;
        wpX -= moveLut[iDir];
        wpY -= moveLut[iDir+1];
        if( worldGet( wpX, wpY ) != 0 )
        {
            wpX += moveLut[iDir];
            wpY += moveLut[iDir+1];
        }
    }
    else if( code >= 48 && code <= 57 ) // '0'~'9'
    {    // Change map
        var iDir:Number = wpFace*2;
        worldSet( wpX + moveLut[iDir], wpY + moveLut[iDir+1], code-48 );
    }
}