Help shooting projectiles in multiple directions

hi there, i was wondering if i could get some help with my code
i’m trying to make a minimalist top down shooter for a game jam on itch.io
the bullets and target code i found it on a post here and tried to modify it so the bullets shoot on the direction i’m facing, but instead that i got to change the direction of all the bullets on screen

cooties.ino.hex (37.0 KB)

this is the part i modified

void moveBullets() {
  for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum) {
      
    if (bullet[bulletNum].x != bulletOff) { // If bullet in use
      
    if (facingRight){
        ++bullet[bulletNum].x; // move bullet right
      }
      if (facingLeft){
        --bullet[bulletNum].x; // move bullet left
      }
    }
    
    if (bullet[bulletNum].y != bulletOff) {
      if (facingDown){
        ++bullet[bulletNum].y; // move bullet down
      }
      if (facingUp){
        --bullet[bulletNum].y; // move bullet up
      }

      if (bullet[bulletNum].x >= arduboy.width() or (bullet[bulletNum].x == 0)
          or bullet[bulletNum].y >= arduboy.height() or (bullet[bulletNum].y == 0)) { // If off screen
        bullet[bulletNum].x = bulletOff;  // Set bullet as unused
      }
    }
  }
}

code here

(btw i asked the participants if they knew about the arduboy, and no one had idea about it, which really surprised me being over 2k participants)

Edit: it’s! finished!
but also, i lieedd!! maybe i’ll try to add some sounds, but over all, i’m really happy :smiley: and thanks to everyone who helped me with this, it was extremely useful and fun

That’s because you’re telling it to change the direction of all the bullets on the screen.

You need to do two things:

  • Firstly, you need to give all your bullets a direction or velocity, so that each bullet knows which direction it should be moving in.
  • Secondly, you need to separate your bullet moving code from your bullet firing code. They are two completely different jobs, so they should be handled by different functions. (This is known as separation of concerns.)

The first can be achieved with something like this:

enum class Direction : uint8_t
{
	Up, Right, Down, Left
};

struct Bullet
{
	int16_t x;
	int16_t y;
	uint8_t width;
	uint8_t height;
	Direction direction;
};

In case you haven’t see it before, the first thing is called an ‘enumeration’. It creates a datatype that can be one of several values. Internally it’s actually an integer (i.e. Up is 0, Right is 1, Down is 2, Left is 3), but the simple names make life a lot easier. (For more info, see here and here, or search the forum for mentions of it.)

If you then redefine Rect bullet[bullets]; as Bullet bullet[bullets] then you’ll be able to do:

bullet[bulletNum].direction = Direction::Up;

And you can change your movement code to be more like:

void moveBullets()
{
	for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum)
	{
		// If bullet in use
		if (bullet[bulletNum].x != bulletOff)
		{
			if(bullet[bulletNum].direction == Direction::Right)
			{
				// move bullet right
				++bullet[bulletNum].x;
			}
			
			if(bullet[bulletNum].direction == Direction::Left)
			{
				// move bullet left
				--bullet[bulletNum].x;
			}
			
			if(bullet[bulletNum].direction == Direction::Up)
			{
				// move bullet up
				--bullet[bulletNum].y;
			}
			
			if(bullet[bulletNum].direction == Direction::Down)
			{
				// move bullet down
				++bullet[bulletNum].y;
			}
			
			// If off screen
			if (bullet[bulletNum].x >= arduboy.width() || (bullet[bulletNum].x == 0)
			|| bullet[bulletNum].y >= arduboy.height() || (bullet[bulletNum].y == 0))
			{ 
				// Set bullet as unused
				bullet[bulletNum].x = bulletOff;
			}
		}
	}
}

(If you need diagonal movement, it will be a bit more complicated.)

Then for your firing code you just have to make sure that the bullet you create matches your player’s direction.

You even could replace your facingUp, facingDown et cetera variables with a single Direction direction; variable. After which you could easily do bullet[bulletNum].direction = direction; to make a bullet’s direction match the player’s direction.

(Again, if you need diagonals it would be a little bit more complicated, but could still be done with a single variable.)


Ordinarily I’d be more thorough, but it’s very late here so I’ve had to cobble an answer together. Hopefully I’ve given you enough to work with for now. If you need more information feel free to ask and hopefully I’ll be around to answer tomorrow.

3 Likes

Your bullets tied with player. Hold Ctrl and move directions. I think its a bug.

1 Like

not sure if you know what the game jam i’m in theme is, or was just a coincidence xD
but you just gave me the best idea ever

really thanks for the help! i got it working, but i found another problem after doing those changes
i’m using arduboy.collide to check collisions, and after changing to struct, that stop working
how can that be fixed

either way, i’m going back to the bullets not working (since the theme of this game jam is “bug” and… i completely forgot about that hahaha, but still wanna learn how to work with that

also, for checking the collision with an enemy, i’m drawing a rect, and on top of it the sprite, and i was thinking on moving them together, this is correct? or there’s a better way for this?

There’s an easy way that you won’t understand unless you know what inheritance is, or there’s a harder way that you are more likely to understand.

This is the ‘harder’ way:

bool areColliding(const Bullet & a, const Bullet & b)
{
	int16_t aLeft = a.x;
	int16_t aRight = (a.x + a.width);

	int16_t bLeft = b.x;
	int16_t bRight = (b.x + b.width);

	if(aRight < bLeft)
		return false;

	if(aLeft > bRight)
		return false;

	int16_t aTop = a.y;
	int16_t aBottom = (a.y + a.height);

	int16_t bTop = b.y;
	int16_t bBottom = (b.y + b.height);

	if(aBottom < bTop)
		return false;

	if(aTop > bBottom)
		return false;

	return true;
}

Essentially, it’s easier to check if the bullets aren’t overlapping.

(If you have round bullets then there’s a more accurate way to test bullet collisions.)

If you also need to check collisions between the player and the bullets then the easier way is to change Bullet's definition to:

struct Bullet : Rect
{
	Direction direction;
};

And then arduboy.collide will work with bullets.

But you won’t understand why unless you know what ‘inheritance’ is and how it works.

Drawing and collisions are two entirely separate things.
You could draw a square and have it collide like a circle if you really wanted to.

But if you mean “does drawing a square and a sprite in the same place make the square and the sprite move at the same rate” then yes, it does.

just to make sure i understand correctly, inheritance is giving a class, the characteristics from another class

like here, you’re giving the struct Bullet, the characteristics of a Rect
that’s why i don’t need to declare x, y, w and h on it, and only the direction

i thought that for arduboy.collide to work, i needed to actually draw the rect under the sprite of my enemy, so i can check if the bullets collide with it
but after reading one of your answers on another post i understood better how it works :smiley:

now there’s a thing i need a little of guidance, how can i make enemies spawn?
my idea is having them spawn on waves coming from the edges of screen, increasing in amount over time

Currently you have a structure Characters that is used for both the hero and the cootie itself. If you want the characters to start off screen, you will probably want to change the x and y variables to be unsigned to support negative numbers and 16 bit to handle values larger than 128.

Turn the line 'Characters cootie into an array like this 'Characters cootie[10]; (5, 10, 20?). Add an extra field called enabled or similar to the Characters structure to indicate whether the cootie is being used or not.

Then you can respawn a new character by looping through the array to find an ‘unused’ character and spawn it in a random location off-screen.

for (uint8_t i = 0; i < 10; i++) {
    if (!cooties[i].enabled) {
        cooties[i].enabled = true;  
        cooties[i].x = { a position off-screen }  
        cooties[i].y = { a position off-screen }  
        break;
    }
}

Of course when resting whether the player has killed a cootie, you will also need to iterate through the array of cooties.

If you want the cooties to appear off screen but in a position where they are only just off screen, you could have an array of known starting positions and just select from it randomly.

const int16_t xStart[48] = {  0,  8, 16, 24, 32, 40, 48, 56, 64, 72,  // Top 
                             80, 88, 96, 104, 112, 120, 128,          // Top 
                             130, 130, 130, 130, 130, 130, 130,       // RHS
                             128, 120, 112, 104, 96, 88, 80, 72,      // Bot
                             64, 56, 48, 40, 32, 24, 16, 8, 0,        // Bot
                             -8, -8, -8, -8, -8, -8, -8 };            // LHS

const int16_t yStart[48] = { -8, -8, -8, -8, -8, -8, -8, -8, -8, -8,  // Top 
                             -8, -8, -8,  -8,  -8,  -8,  -8,          // Top 
                              8, 16, 24, 32, 40, 48, 56,              // RHS
                             64, 64, 64, 64, 64, 64, 64, 64, 64,      // Bot
                             64, 64, 64, 64, 64, 64, 64, 64,          // Bot
                             56, 48, 40, 32, 24, 16, 8 };             // LHS

Then :

for (uint8_t i = 0; i < 10; i++) {
    if (!cooties[i].enabled) {
        uint8_t pos = random(0, 48);
        cooties[i].enabled = true;  
        cooties[i].x = xStart[pos]; 
        cooties[i].y = yStart[pos]; 
        break;
    }
}

If by edges, you mean to spawn from the side of the screen but be visible when spawning you can adjust the starting positions as needed. You can also ignore the comment about changing the x / y values to unsigned, 16 bit integers.

2 Likes

A quick correction:

if (cooties[i].enabled == false)

(Or better yet, if (!cooties[i].enabled) - less opportunity for bugs.)

1 Like

Ah … thanks. As you can see its strictly untested!

2 Likes

Thanks for the help! i’ll start working on this now

just one thing i’d like to understand more

here, what’s the meaning of Top, RHS, Bot, LHS?

1 Like

That’s the general idea. There’s actually a bit more to it than that though.

It means that Bullet is now a ‘child class’ (or ‘subtype’) of Rect, which means that a function expecting a Rect can accept a Bullet instead.

(Note that in C++ struct and class mean almost the same thing. The only difference is whether the contents default to public or private.)

There’s other aspects to inheritance too (i.e. virtual functions), but you don’t really need to know about those for what you’re doing.

Ah, no. Like I say, drawing and collisions are completely different things.

Drawing involves putting pixels on the screen.
Collision checking just involves doing some geometry calculations.

Hence a computer without any kind of output (e.g. no screen, no lights) could check collisions.


Top of the screen, right hand side (RHS) of the screen, bottom of the screen, left hand side (LHS) of the screen.

Note that you can also do this:

constexpr Point enemyStart =
{
	// Top of the screen
	{ 0, -8 }, { 8, -8 }, { 16, -8 }, { 24, -8 },
	{ 32, -8 }, { 40, -8 }, { 48, -8 }, { 56, -8 },
	{ 72, -8 }, { 80, -8 }, { 88, -8 }, { 96, -8 },
	{ 104, -8 }, { 112, -8 }, { 120, -8 }, { 128, -8 },
	
	// Right of the screen
	{ 130, 8 }, { 130, 16 }, { 130, 24 }, { 130, 32 },
	{ 130, 40 }, { 130, 48 }, { 130, 56 },

	// Bottom of the screen
	{ 128, 64 }, { 120, 64 }, { 112, 64 }, { 104, 64 },
	{ 96, 64 }, { 88, 64 }, { 80, 64 }, { 72, 64 },
	{ 64, 64 }, { 56, 64 }, { 48, 64 }, { 40, 64 },
	{ 32, 64 }, { 24, 64 }, { 16, 64 }, { 8, 64 },
	{ 0, 64 },

	// Left of the screen
	{ -8, 56 }, { -8, 48 }, { -8, 40 }, { -8, 32 },
	{ -8, 24 }, { -8, 16 }, { -8, 8 },
};

Point is defined in the Arduboy2 library with an x and y:

So then you can use enemyStart[index].x and enemyStart[index].y instead of xStart[index] and yStart[index], so the grouping between the x and y is more obvious, and you’re less likely to make the mistake of accidentally deleting an entry.

(constexpr allows you to use the array at compile time. Most of the time it’s not an advantage, but it’s usually worth using constexpr instead of const when you can because there’s no downside to doing so. Sometimes you can’t use constexpr though, e.g. it won’t work for Arduboy2.)

1 Like

i think i understand how this works, but i’ll have to play a little bit with it to see it better

i’m trying now to make the enemies not overlap, i thought on checking if they collide with each other, not let them move
testing with

if (!arduboy.collide(target,target))

and then use my movement code, but if i do that the movement never happen

i think i know why this happens, basically they detect themself right?

Are you literally testing target against target (ie the same cootie instance)? In which case, yes the test will always fail.

I am guessing that you are looping through the cooties[] array and for each cootie, you then loop through the collection of cooties a second time looking for collisions. If this is how you are doing it you need to skip the instances where the cootie is the same.

for (uint8_t i = 0; i < 10; i++) {
    for (uint8_t j = 0; j < 10; j++) {
        if (i != j) {
            if (!arduboy.collide(cooties[i], cooties[j])) {
                .. do stuff
            }
        }
    }
}

oooohhh! yes, i was checking on the same instances and couldn’t think on a way to do this
but at least i’m happy that i wasn’t so off :smiley:

edit:
well after trying this i ran into another problem
this is my code for checking collisions, and i’m declaring the rects inside it

void checkCollisions() {

  Rect Hero(hero.x, hero.y, hero.w, hero.h);

  for (uint8_t i = 0; i < 10; i++){
     Rect target(cootie[i].x, cootie[i].y, targetWidth, targetHeight);

  //check if enemy touch the hero
    if (arduboy.collide(target,Hero)&& hero.iframe==0){
      ++enemyHit;
      hero.iframe=50;
    }

  

  //check if bullet hit the enemy
    for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum) {
      if (arduboy.collide(bullet[bulletNum], target)) {
        ++hitCount;
        bullet[bulletNum].x = bulletOff;  // Set bullet as unused
      }
    }
  } // here ends the for loop

}

as you can see, my Rect target is there, and i’m grabbing the x and y coords from the characters array from cooties

if i do this directly with the array it says i can’t use a character type it for collide, and i have no idea how to change my Rect target

i also updated my code on my github so it’s easier to take a look
here is the code

Yes, because a Characters is not a Rect or a subtype of Rect.

This looks like it would probably work as long as you add a check to make sure the enemy is enabled and disable it when appropriate.

1 Like

oh god i completely forgot about this
well! i made characters a subtype of Rect, with that i was able to work a little better my code for the collisions and finally get the enemies to colide with each other :smiley:
ArduboyRecording(6)

i wanted them to bump each other, still not looking the best, but i’m really happy with it for now :smiley:

1 Like

i think i’m almost done at least for what i wanted to do, the only thing that bothers me is that when i disable and enable some enemies, they lose completely their hitbox
and i really have no idea why that’s happening

cooties.ino.hex (30.2 KB)

still, beisdes that, on my to do list

  • clean the code and make it more organized
  • make a states machine
  • tittle screen
  • game over screen

I’m not sure what you mean by that.

This is another good use for an enum class.

You may discover why as you reorganise the code.

I had a quick look, but couldn’t see anything obvious.

I’d have a closer look, but considering you’re entering this in a game jam I’m not sure how much help it’s fair to provide (until the jam is over at least).

by this i mean them to push each other when they touch themself

and yes! really thanks for all the help up to this point, and definetely after the jam is over i wanna keep working on this game
literally i learned way more on these last 20 days than from making courses

2 Likes