Platform game written for the GHI/OutrageousCircuits RETRO game device. Navigate the caves collecting all the pickups and avoiding the creatures and haunted mine carts that patrol the caves. Oh and remember to watch out for the poisonous plants... This game demonstrates the ability to have multiple animated sprites where the sprites can overlap the background environment. See how the player moves past the fence and climbs the wall in the 3rd screen.

Dependencies:   mbed

RetroGameEngine/Scene.cpp

Committer:
taylorza
Date:
2015-02-16
Revision:
16:f9227904afc4
Parent:
14:b4884a31069e

File content as of revision 16:f9227904afc4:

#include "GameEngine.h"

Bitmap4bpp Scene::BitmapBuffer(24, 24);
    
Scene::Scene() : 
    _map(NULL),
    _x(0),
    _y(0),
    _objectsHeld(0)
{
   Game::Surface.setOrientation(LCD_ST7735::Rotate270, false);
   Game::Surface.clearScreen();
   
   for (int i = 0; i < MAX_GAMEOBJECTS; ++i)
   {
       _gameObjects[i] = NULL;
   } 
}

Scene::~Scene()
{
    
}

void Scene::restartScreen()
{
    _objectsHeld = 0;
    Game::Surface.clearScreen();
    drawMap();
}

void Scene::update()
{
    for (int i = 0; i < MAX_GAMEOBJECTS; ++i)
    {
        GameObject *o = _gameObjects[i];
        if (o != NULL) o->update();
    }
}

void Scene::draw()
{
    for (int i = 0; i < MAX_GAMEOBJECTS; ++i)
    {
        GameObject *o = _gameObjects[i];
        if (o != NULL) o->draw();
    }
}

void Scene::setPosition(uint8_t x, uint8_t y)
{
    if (x != _x || y != _y)
    {
        _x = x;
        _y = y;
        drawMap();
    }
}

void Scene::setMap(const uint8_t *map, uint8_t xCells, uint8_t yCells, const Block *blocks, Sprite *sprites)
{
    _map = map;
    _xCells = xCells;
    _yCells = yCells;
    _blocks = blocks;
    _sprites = sprites;
}

void Scene::addGameObject(GameObject *gameObject)
{
    for (int i = 0; i < MAX_GAMEOBJECTS; ++i)
    {
        if (_gameObjects[i] == NULL) 
        {
            _gameObjects[i] = gameObject;
            gameObject->setScene(this);
            break;
        }
    }
}

void Scene::removeGameObject(GameObject *gameObject)
{
    for (int i = 0; i < MAX_GAMEOBJECTS; ++i)
    {
        if (_gameObjects[i] == gameObject) 
        {
            gameObject->setScene(NULL);
            _gameObjects[i] = NULL;
            break;
        }
    }
}

const GameObject* Scene::detectCollision(GameObject *primary)
{
    for (int i = 0; i < MAX_GAMEOBJECTS; ++i)
    {
        GameObject *other = _gameObjects[i];
        if (other != NULL && other != primary)
        {
            if (detectCollision(primary, other))
            {
                return other;
            }
        }
    }
    return NULL;
}

const Block* Scene::detectBlock(GameObject *primary)
{
    Point &position = primary->getPosition();
    uint8_t cellX = position.X / 8;
    uint8_t cellY = position.Y / 8;    
    
    int my = cellY < _yCells ? 2 : 1;
    int mx = cellX < _xCells ? 2 : 1;
    
    int offset = (cellY * _xCells) + cellX;
    for (int y = 0; y < my; ++y, ++cellY)
    {        
        for (int x = 0; x < mx; ++x, ++cellX)        
        {
            uint8_t blockId = _map[offset++];
            const Block &block = _blocks[blockId];
            switch(block.getType())
            {
               case Block::Deadly : return &block;   
               case Block::Pickup : return !isHeld(cellX, cellY) ? &block : NULL;                 
            }                    
        }
        offset += _xCells - mx;
    }
    return NULL;       
}

#pragma push
//#pragma diag_suppress 4017
bool Scene::detectCollision(GameObject *o1, GameObject *o2)
{
    Rect r1 = o1->getCollisionRect();
    Rect r2 = o2->getCollisionRect();
    
    return r1.left < r2.right &&
     r2.left < r1.right &&
     r1.top < r2.bottom &&
     r2.top < r1.bottom;       
}
#pragma pop


void Scene::drawMap()
{
    if (_map == NULL) return;
        
    int yOffset = 0;
    
    for(int y = 0; y < _yCells; ++y, yOffset += _xCells)
    {        
        for (int x = 0; x < _xCells; ++x)
        {
            uint8_t blockId = _map[yOffset + x];
            if (blockId != 0)
            {                
                drawBlock(blockId, x * 8, y * 8);
            }
        }
    }      
}

bool Scene::canEnter(uint16_t x, uint16_t y)
{
    uint8_t cellX = x / 8;
    uint8_t cellY = y / 8;
    uint8_t blockId = _map[(cellY * _xCells) + cellX];
    const Block &block = _blocks[blockId];        
    Block::Type type = block.getType();
    
    switch(type)
    {
        case Block::Background : return true;
        case Block::Platform : return true;
        case Block::Solid : return false;
        case Block::Ladder : return true;
    }
    
    return true;
}

bool Scene::canEnterFromTop(uint16_t x, uint16_t y)
{
    uint8_t cellX = x / 8;
    uint8_t cellY = y / 8;
    uint8_t blockId = _map[(cellY * _xCells) + cellX];
    const Block &block = _blocks[blockId];        
    Block::Type type = block.getType();
    
    switch(type)
    {
        case Block::Background : return true;
        case Block::Platform : return false;
        case Block::Solid : return false;
        case Block::Ladder : return true;
    }
    
    return true;
}

const Block& Scene::getBlock(uint16_t x, uint16_t y)
{
    uint8_t cellX = x / 8;
    uint8_t cellY = y / 8;
    uint8_t blockId = _map[(cellY * _xCells) + cellX];
    return _blocks[blockId];        
}

void Scene::animate(uint8_t spriteId)
{
    Sprite &sprite = _sprites[spriteId];
    sprite.animate();
}    

bool Scene::pickupObject(uint8_t cellX, uint8_t cellY)
{
    if (_objectsHeld == MAX_PICKUPS) return false;
    if (getBlock(cellX * 8, cellY * 8).getType() != Block::Pickup) return false;
        
    if (isHeld(cellX, cellY)) return false;         
        
    _pickups[_objectsHeld].X = cellX;
    _pickups[_objectsHeld].Y = cellY;
    ++_objectsHeld;
    drawBlock(0, cellX * 8, cellY * 8);
    return true;
}

bool Scene::isHeld(uint8_t cellX, uint8_t cellY)
{    
    Point pt(cellX, cellY); 
    for (int i = 0; i < _objectsHeld; ++i)
    {
        if (pt == _pickups[i]) return true;
    }
    return false; 
}

void Scene::compose(const Block &block, uint8_t x, uint8_t y)
{
    uint8_t fc = block.getForegroundColor();    
    uint8_t fch = (fc << 4) & 0xf0;
    uint8_t fcl = fc & 0x0f;
    
    uint8_t bc = block.getBackgroundColor();
    uint8_t bch = (bc << 4) & 0xf0; 
    uint8_t bcl = bc & 0x0f;
        
    uint8_t *bitmap = Scene::BitmapBuffer.getBitmapData();
    int offsetRow = (y * Scene::BitmapBuffer.getStride()) + (x >> 1);
        
    for (int iy = 0; iy < 8; ++iy, offsetRow += Scene::BitmapBuffer.getStride())
    {
        int offset = offsetRow;
        uint8_t b = *block.getBits(iy);
        bool highNibble = ((x & 0x01) == 0);
        for(int c = 0; c < 8; ++c, b <<= 1)
        {
            if (b & 0x80)
            {                                                
                if (highNibble) bitmap[offset] =  ((bitmap[offset] & 0x0f) | fch);
                else 
                {
                    bitmap[offset] =  ((bitmap[offset] & 0xf0) | fcl);
                    ++offset;
                }
            }
            else if (block.getType() == Block::Foreground)
            {
                if (!highNibble) ++offset;
            }   
            else
            {
                if (highNibble) bitmap[offset] =  ((bitmap[offset] & 0x0f) | bch);
                else 
                {
                    bitmap[offset] =  ((bitmap[offset] & 0xf0) | bcl);
                    ++offset;
                }
            }
            highNibble = !highNibble; 
        }               
    }    
}

void Scene::compose(const Sprite &sprite, uint8_t x, uint8_t y, bool flip)
{
    uint8_t fc = sprite.getForegroundColor();    
    uint8_t fch = (fc << 4) & 0xf0;
    uint8_t fcl = fc & 0x0f;
    
    uint8_t *bitmap = Scene::BitmapBuffer.getBitmapData();        
    int offsetRow = (y * Scene::BitmapBuffer.getStride()) + (x >> 1);
    
    if (!flip)
    {
        for (int iy = 0; iy < 16; ++iy, offsetRow += Scene::BitmapBuffer.getStride())
        {
            int offset = offsetRow;
            uint8_t *p = sprite.getBits(iy);
            bool highNibble = ((x & 0x01) == 0);
            for (int ix = 0; ix < 2; ++ix)
            {
                uint8_t b = *p++;
                for(int c = 0; c < 8; ++c, b <<= 1)
                {
                    if (b & 0x80)
                    {                                                
                        if (highNibble) bitmap[offset] = ((bitmap[offset] & 0x0f) | fch);
                        else 
                        {
                            bitmap[offset] = ((bitmap[offset] & 0xf0) | fcl);
                            offset++;
                        }
                    }
                    else if (!highNibble) 
                    {
                        offset++;
                    }   
                    highNibble = !highNibble; 
                }               
            }
        }    
    }
    else
    {
        for (int iy = 0; iy < 16; ++iy, offsetRow += Scene::BitmapBuffer.getStride())
        {
            int offset = offsetRow; 
            uint8_t *p = sprite.getBits(iy) + 1;
            bool highNibble = ((x & 0x01) == 0);
            for (int ix = 0; ix < 2; ++ix)
            {
                uint8_t b = *p--;
                for(int c = 0; c < 8; ++c, b >>= 1)
                {
                    if (b & 0x01)
                    {                            
                        if (highNibble) bitmap[offset] = ((bitmap[offset] & 0x0f) | fch);
                        else 
                        {
                            bitmap[offset] = ((bitmap[offset] & 0xf0) | fcl);
                            offset++;
                        }
                    }
                    else if (!highNibble)
                    {
                        offset++;
                    }   
                    highNibble = !highNibble; 
                }               
            }
        }    
    }
}

void Scene::drawBlock(uint8_t blockId, int16_t x, int16_t y)
{
    const Block &block = _blocks[blockId];
    uint8_t fc = block.getForegroundColor();
    uint8_t fch = (fc << 4) & 0xf0;
    uint8_t fcl = fc & 0x0f;
    
    uint8_t bc = block.getBackgroundColor();
    uint8_t bch = (bc << 4) & 0xf0; 
    uint8_t bcl = bc & 0x0f;
    
    uint8_t *bitmap = Scene::BitmapBuffer.getBitmapData();
    
    int offset = 0;   
    for (int iy = 0; iy < 8; ++iy)
    {
        uint8_t b = *block.getBits(iy);
        
        bitmap[offset] = b & 0x80 ? ((bitmap[offset] & 0x0f) | fch) : ((bitmap[offset] & 0x0f) | bch); 
        bitmap[offset] = b & 0x40 ? ((bitmap[offset] & 0xf0) | fcl) : ((bitmap[offset] & 0xf0) | bcl);
        ++offset;
        
        bitmap[offset] = b & 0x20 ? ((bitmap[offset] & 0x0f) | fch) : ((bitmap[offset] & 0x0f) | bch); 
        bitmap[offset] = b & 0x10 ? ((bitmap[offset] & 0xf0) | fcl) : ((bitmap[offset] & 0xf0) | bcl);
        ++offset;
        
        bitmap[offset] = b & 0x08 ? ((bitmap[offset] & 0x0f) | fch) : ((bitmap[offset] & 0x0f) | bch); 
        bitmap[offset] = b & 0x04 ? ((bitmap[offset] & 0xf0) | fcl) : ((bitmap[offset] & 0xf0) | bcl);
        ++offset;
        
        bitmap[offset] = b & 0x02 ? ((bitmap[offset] & 0x0f) | fch) : ((bitmap[offset] & 0x0f) | bch); 
        bitmap[offset] = b & 0x01 ? ((bitmap[offset] & 0xf0) | fcl) : ((bitmap[offset] & 0xf0) | bcl);
        //++offset;       
        offset += Scene::BitmapBuffer.getStride() - 3;
    }

    Game::Surface.drawBitmap(_x + x, _y + y, Scene::BitmapBuffer, 0, 0, 8, 8);    
}

void Scene::drawSprite(uint8_t spriteId, int16_t x, int16_t y, int16_t dx, int16_t dy, bool flip)
{
    uint8_t cellX = x / 8;
    uint8_t cellY = y / 8;
    uint8_t rx = x % 8;
    uint8_t ry = y % 8;
    
    if (rx == 0 && dx > 0 && cellX > 0) { --cellX; rx += 8; }    
    if (ry == 0 && dy > 0 && cellY > 0) { --cellY; ry += 8; }
    
    Scene::BitmapBuffer.clear();
    
    // Compose blocks, except foreground blocks
    for (int cy = 0; cy < 3; ++cy)
    {
        int yOffset = (cellY + cy) * _xCells;
        for (int cx = 0; cx < 3; ++cx)
        {
            uint8_t blockId = _map[yOffset + cellX + cx];
            const Block &block = _blocks[blockId];            
            
            if (blockId != 0 && block.getType() != Block::Foreground)
            {                
                if (!(block.getType() == Block::Pickup && isHeld(cellX + cx, cellY + cy)))                
                {
                    compose(block, cx * 8, cy * 8);
                }
            }
        }
    }
    
    // Compose sprite        
    const Sprite &sprite = _sprites[spriteId];
    compose(sprite, rx, ry, flip);
    
    // Compose foreground blocks
    for (int cy = 0; cy < 3; ++cy)
    {
        int yOffset = (cellY + cy) * _xCells;
        for (int cx = 0; cx < 3; ++cx)
        {
            uint8_t blockId = _map[yOffset + cellX + cx];
            const Block &block = _blocks[blockId];
            if (blockId != 0 && block.getType() == Block::Foreground)
            {
                compose(block, cx * 8, cy * 8);
            }
        }
    }
    
    // Render the composed image
    //Screen.drawBitmap(cellX * 8, cellY * 8, Scene::BitmapBuffer, 0, 0, 24, 24);
  
    Game::Surface.drawBitmap(
        _x + (dx > 0 ? x - dx : x), 
        _y + (dy > 0 ? y - dy : y),
        Scene::BitmapBuffer, 
        dx > 0 ? rx - dx : rx,
        dy > 0 ? ry - dy : ry,
        16 + abs(dx), 16 + abs(dy));
      
}