Question

I have a 2D Array of Objects that move synchronously on the screen and I need to detect when they hit the edge of the screen so that they change direction (think Space Invaders). So far, I had it working with a Rectangle pre-defined to be the size and position of the objects in the Array but the Objects can be hit with missiles and are no longer drawn so when all of them on one side are destroyed, the rectangle stays the same size and they change direction too early.

Is there a better way to do what I want to do? This is my code for the functionality at the moment:

(In LoadContent Method)

invaderRect = new Rectangle(
    0, 0,
    invadersWide * (invaderImage.Width + 15) - 15,
    invadersHigh * (invaderImage.Height + 15) - 15);

(In Update Method)

if ((invaderRect.X + invaderRect.Width) >= screenRectangle.Width - 15)
    invadersHitWall = true;
else if (invaderRect.X <= 15)
    invadersHitWall = false;

if (!invadersHitWall)
    invaderRect.X += 2;
else if (invadersHitWall)
    invaderRect.X -= 2;
Was it helpful?

Solution

It's all a matter of organizing your code. You should use an object oriented approach. Create a class that represents your game objects. I would use an abstract base class defining the basic properties and methods. Then derive concrete game objects from this base class. The concrete game objects' constructors have the task to initialize the game objects.

public abstract class GameObject
{
    protected BlockType[,] _buildingBlocks; // The 2-d array. Replace "BlockType" 
                                            // by the type you are using.
    protected int _x0, _y0; // Indexes of the first non empty block.
    protected int _x1, _y1; // Indexes of the last  non empty block + 1.

    // Pixel coordinates of upper left corner of the intact game object.
    public Point Location { get; set; }

    // Represents the current position and size of the possibly diminished
    // game object in pixels.
    public Rectangle BoundingBox
    { 
        get {
            return new Rectangle(
                Location.X + BlockSize * _x0,
                Location.Y + BlockSize * _y0,
                BlockSize * (_x1 - _x0),
                BlockSize * (_y1 - _y0)
            );
        } 
    }

    public void Draw(Graphics g, int x, int x)
    {
        for (int i = _x0; i < _x1; i++) {
            for (int j = _y0; j < _y1; j++) {
                BlockType block = _buildingBlocks[i, j];
                if (block != null) {
                    // Replace by the appropriate drawing methods for XNA.
                    g.FillRectangle(block.Brush,
                                    x + BlockSize * i, y + BlockSize * j,
                                    BlockSize, BlockSize);
                }
            }
        }
    }

    // Call this after changes have been made to the arrray which may affect the
    // apparent size of the game object, e.g. after the object was hit by a bomb.
    protected void CalculateBounds()
    {
        _x0 = _buildingBlocks.GetLength(0);
        _y0 = _buildingBlocks.GetLength(1);
        _x1 = 0;
        _y1 = 0;
        for (int i = 0; i < _buildingBlocks.GetLength(0); i++) {
            for (int j = 0; j < _buildingBlocks.GetLength(1); j++) {
                if (buildingBlocks[i, j] != null) {
                   _x0 = Math.Min(_x0, i);
                   _y0 = Math.Min(_y0, j);
                   _x1 = Math.Max(_x1, i + 1);
                   _y1 = Math.Max(_y1, j + 1);
                }
            }
        }
    }

    public void DestroyBlocksAt(IEnumerable<Point> points)
    {
        //TODO: destroy hit blocks.

        CalculateBounds();
    }
}

After an object has been hit by a bomb, call CalculateBounds(); in order to recalculate the real size of the object. The idea is that instead of using invaderRect you would be using the BoundingBox property that reflects the real extents of the gaming object. BoundingBox takes into account the position of the game object on the screen (Location) and the position within the array (_x0, _x1, _y0, _y1).

This is a raw sketch. You may have to refine it and adapt it to your current logic. Don't use magical numbers like 15. Define constants like public const int BlockSize = 15;. This makes it easier to change the numbers later and also to understand the code.

Here is an example of an invader class

public class Invader : GameObject
{
    private const int WIDTH = 10, HEIGHT = 7; // Width and height of invader in blocks.

    public Invader()
    {
        _buildingBlocks = new BlockType[WIDTH, HEIGHT];
        _x1 = WIDTH;
        _y1 = HEIGHT;
        _buildingBlocks[0, 0] = ...
        ...
    }
}

UPDATE

As I understood your post, every movable “thing” is stored in its own 2D array storing the 15 x 15 pixel blocks it is made of. GameObject is the base class of all the visible movable things, like invaders, space ships, bombs and so on and is a wrapper around the 2D arrays (named _buildingBlocks in my code examples). It adds the logic needed to determine the real bounds of objects after they have been hit by bombs. CalculateBounds recalculates the position and size of the remaining object after a bomb hit within the 2D array (of course you must call it every time the shape of the objects changes). BoundingBox moves these internal 2D array bounds (stored in _x0, _x1, _y0 and _y1) to real screen positions (stored in the Location property) by multiplying with the block size (the 15 pixels) and adding the screen location.

For every game object type (i.e. movable shape type) you have to derive a class (e.g. a class Invader for invaders). Then create an invader object with Invader invader = new Invader(); for every single invader. GameObject is not the main class. The main class includes the game loop and game logic. It creates game objects and calculates their new positions (stored in the Location property) as they move around. Instead of working with invaderRect the wall-hitting logic would now work with BoundingBox which returns the real size and positions of the objects.

Rectangle invaderBounds = invader.BoundingBox;
bool isLeftWallHit = invaderBounds.Left <= 0;
bool isRightWallHit = invaderBounds.Right >= screenRectangle.Width;
bool isUpperWallHit = invaderBounds.Top <= 0;
bool isLowerWallHit = invaderBounds.Bottom >= screenRectangle.Height;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top