0
\$\begingroup\$

So like the title says I want my Enemies to move on a Grid like in Space Invaders. Say I have 8 rows and 12 columns. Now there are 10 Enemies in each row and there are 5 rows filled. In total I have 50 Enemies. Since I am using OOP I got 50 Objects. Now I want to shift all Enemies from left to right as far as possible. So if all Enemies are alive I can shift them 2 to the right and 2 to the left. If I destroy all Enemies in a column the Enemies can shift 3 to the left and to the right.

How do I check efficiently how many times I can shift over / check if a column is empty. How should I initialize the grid and shift all Enemies by one? For me this looks like it could get pretty laggy if I don't make it efficiently. Note: All Objects are dynamically allocated and I am using C++.

I hope I could make it clear. If you guys don't understand it, I could try to explain it a bit better.

\$\endgroup\$
1
  • 4
    \$\begingroup\$ With just 50 objects, you could do this in the absolute worst way possible and still get a blazingly fast framerate. You simply do not have enough content here for that kind of optimization to make a significant impact. Just do it in a way that's clear to you, then profile it to see if you need anything more than that. \$\endgroup\$ Commented Feb 28, 2020 at 23:32

2 Answers 2

0
\$\begingroup\$

Moving the invaders as one

Something that @Krangogram has alluded to in their answer is that the individual invaders don't actually move by themselves, so one approach to efficiently move the invaders is to treat them as a single invader formation and the move the formation as a whole. You could define this similar to:

class Invader {
   // x,y offset within the formation. Each invader should have a unique offset from
   // (0,0), the top left, to (FORMATION_WIDTH-1,FORMATION_HEIGHT-1), the bottom right.
   int offsetX, offsetY;

   // Whether this invader is alive.
   bool isAlive;
};
class InvaderFormation {
   // The invaders within the formation, includes dead invaders
   std::vector<Invader *> invaders;

   // The location of the top left invader in the formation. 
   int x, y;
};

Since every invader's location is defined as an offset from the formation as a whole, you can move every invader at the same time with invaderFormation->x += DELTA or invaderFormation->y += DELTA.

Checking for the edge

As for how to determine whether the invaders can continue moving in a direction or if they've reached the edge, with 50 invaders you're probably safe to enumerate them all without running into any performance issues. But maybe in the future the screen will be bigger/scroll and there'll be a million invaders, so let's look how you can optimize this check.

The first thing to notice about this problem is that the only invaders you actually care about are the ones on the sides of the formation. The ones in the middle will never touch an edge as long as there's an invader on the side of the formation. But wait, you don't even need to figure out a way to track and enumerate those on the sides, because another thing to notice about this problem is that it doesn't matter which invader is on the side, all that matters is there's at least one.

So you could define your invader formation closer to something like this:

class Invader {
   int offsetY;
   bool isAlive;

   // The column of the formation this invader is in. The invader's offset within the
   // formation can now be defined as (column.offsetX, offsetY). It's fine to keep offsetX
   // in this class too though.
   InvaderFormationColumn* column;
};
class InvaderFormationColumn {
   // offsetX is also in the index into InvaderFormation::columns
   int offsetX;

   // How many invaders are still alive in this column
   int aliveCount;
};
class InvaderFormation {
   // All of the columns...
   std::vector<InvaderFormationColumn *> columns;

   // ...And more specifically, shortcuts to the leftmost and rightmost columns
   // that have at least 1 living invader
   InvaderFormationColumn* leftmostColumn;
   InvaderFormationColumn* rightmostColumn;

   int x, y;
};

Now you can know if the invader formation has reached the edge with a single check:

invaderFormation->x + invaderFormation->leftmostColumn->offsetX == LEFT_EDGE or

invaderFormation->x + invaderFormation->rightmostColumn->offsetX == RIGHT_EDGE.

But you'll need to update these leftmostColumn and rightmostColumn pointers whenever an invader dies in order to keep them up-to-date:

// When an invader is killed:
invader->isAlive = false;
invader->column->aliveCount--;

// Assuming there is still at least 1 living invader in the formation:
if (invader->column->aliveCount == 0) {
   // All invaders are dead in this column, so check if the formation needs to point to
   // a new leftmost/rightmost column
   if (invader->column->offsetX == invaderFormation->leftmostColumn->offsetX) {
      // Look for a new leftmost column, starting from the current one
      // You can stop at the rightmost column because you know there's no invaders still
      // alive in any columns past that.
      for (int i = invaderFormation->leftmostColumn->offsetX; i <= invaderFormation->rightmostColumn->offsetX; i++) {
         InvaderFormationColumn* column = invaderFormation->columns[i];
         if (column->aliveCount > 0) {
            invaderFormation->leftmostColumn = column;
            break;
         }
      }
   }
   // And similarly for the rightmost column moving towards the leftmost column
}
\$\endgroup\$
3
  • \$\begingroup\$ Thank you for your answer. I think I understand that, but the problem I have is. How do I draw each texture for the invader and how do I check if they got it / define the position if they shoot for the bullet. Because I now got a defined x and y position for each invader I can just check that all the things with a GetPosition method. I am using the raylib engine if that matters. But the thing with your solution is that there i can only let the lowest invader in a column shoot. I guess I have to think this a it more through with more time. \$\endgroup\$ Commented Feb 29, 2020 at 7:39
  • \$\begingroup\$ Why would only the lowest invader in the column be able to shoot? (I'm not familiar with the raylib engine if that's factoring into this somehow). For drawing the invaders you still need to enumerate them all and draw with something like drawSprite(invader->sprite, (invaderFormation->x + invader->offsetX) * TILE_WIDTH, (invaderFormation->y + invader->offsetY) * TILE_HEIGHT) (if in the future there can be more then you can optimize the subset that you enumerate by including logic to track the left/right-most visible columns, similar to my suggestion for detecting when they reach the edges). \$\endgroup\$ Commented Mar 4, 2020 at 2:01
  • \$\begingroup\$ The real space invaders game has this mechanic aswell. So I wanted to implement it too. I haven't had time to go on with this project in the last week so I am just now thinking about it again, but I think I will try to implement it as you said. Would you actually mind to review my code I got so far? [This is my repo] (github.com/HenningCode/Space_Invaders) \$\endgroup\$ Commented Mar 5, 2020 at 19:41
0
\$\begingroup\$

When i created a Space Invaders clone, I moved the 'row' and then the enemy just had a fixed offset in X from that 'row' object. I think in the end I used just one object to cover all enemies with offset of X and Y.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.