2D Projectiles Continued

Started by
18 comments, last by caldiar 17 years, 11 months ago
Well my old thread has since been retired unfortunately so I have to make a lousey link to it so people will know what Im talking about lol here ya go: old forum That's the link. The problem is still the same! Bullets don't function properly. New varation of the problem however. Now we have two things happening depending on what I do to the code: When the bullet goes past the visible left or right hand side of the screen it disappears naturally. However, upon hitting the left side the bullet freezes in animation but I can still move around. Second: If I switch | with & in this code

if(Bullet.getx()<0 | Bullet.getx()>640){


the bullet will just go past the screen with no problems. BUT! In both cases said bullet does not seem to delete itself from the linked list I have to track the bullets! I am not able to fire more than one bullet when I should be able to shoot up to 20. Here's the bullet code for your reading enjoyment:

#define BULLETS 20

Sprite Bullet[BULLETS];

struct bullet{
  float x;
  float y;
  float xvel;
  float yvel;
  int direction;
  bool dead;
  bullet *next;
  bullet *previous;
  };  
  bullet* first_bullet = NULL;
  bullet* current_bullet = NULL;
  
int bdraw[BULLETS];

bullet* NewBullet()
{
 for(int i=0;i<BULLETS;i++){
 if(bdraw == 0){
  if(first_bullet==NULL)
  {
    first_bullet=new bullet;
    current_bullet=first_bullet;
    current_bullet->direction = face_right;
    current_bullet->x = mario.getx();
    current_bullet->y = mario.gety();
    current_bullet->xvel = Bullet.getSpeed() * current_bullet->direction;
    Bullet.set(current_bullet->x += 1, current_bullet->y += 0);
    current_bullet->next=NULL;
    current_bullet->previous=NULL;
  }
  
  else
  {
    current_bullet->next=new bullet;
    current_bullet->next->direction = face_right;
    current_bullet->next->x = mario.getx();
    current_bullet->next->y = mario.gety();
    current_bullet->next->xvel = Bullet.getSpeed() * current_bullet->next->direction;
    current_bullet->next->previous=current_bullet->next;
    current_bullet=current_bullet->next;
    Bullet.set(current_bullet->x+=1, current_bullet->y+=0);
    current_bullet->next=NULL;
  }
  bdraw = 1;
  }
  }
  current_bullet->dead=false;
  return current_bullet;
  }
    
void DeleteAllBullets()
{
  bullet* this_bullet=first_bullet;
  bullet* dead_bullet=NULL;
  while(this_bullet!=NULL)
  {
    dead_bullet=this_bullet;
    this_bullet=this_bullet->next;
    delete dead_bullet;
  }
  first_bullet=NULL;
  current_bullet=NULL;
}

void RenderBullet()
{

  bullet* this_bullet=first_bullet;
  bullet* dead_bullet=NULL;
  while(this_bullet!=NULL)
  {
    if(this_bullet->dead)
    {
      dead_bullet=this_bullet;
      this_bullet=this_bullet->next;
      if(first_bullet==dead_bullet)
      {
        first_bullet=this_bullet;
        if(this_bullet!=NULL)
        {
          this_bullet->previous=NULL;
        }
      }
      else
      {
        dead_bullet->previous->next=this_bullet;
        if(this_bullet!=NULL)
        {
          this_bullet->previous=dead_bullet->previous;
        }
      }
      if(current_bullet==dead_bullet)
      {
        current_bullet=dead_bullet->previous;
      }
      delete dead_bullet;
    }
    else
    {
        for(int i=0;i<BULLETS;i++){
     if(bdraw == 1){
     Bullet.xadd(this_bullet->xvel);
     Bullet.yadd(0);
     Bullet.draw();
     if(Bullet.getx()<0 | Bullet.getx()>640){
     this_bullet->dead;
     bdraw=0;
     }
     }
     }
     this_bullet = this_bullet->next;
    }
}
}

void UpdateBullet()
{
  bullet* this_bullet=first_bullet;
  while(this_bullet!=NULL)
  {          
    this_bullet=this_bullet->next;
  }
  for (int i=0;i<BULLETS;i++){
  if(bdraw == 1){
  Bullet.updateBG();
  }
  }
}



I've gone through this over and over again and I think I just ended up complicating things by patching a bug here and there. Any help solving this problem would be greatly appreciated! (I think the bdraw=0 might be the reason my bullet animation freezes.... Ill check it out)
Advertisement
Hey Caldiar,

I use a somewhat similar approach to handling short-life projectiles, and thought I might throw out a suggestion.

If I understand you right, you delete a projectile from the list once it becomes inactive (according to whatever rules, offscreen etc.), correct?
I had problems with this same thing for a while so I finally switched to an uglier method that works for me.

Instead of using one list, I made two, and a key variable to tell me which one was the active list. When a projectile needs to be added, it is added to the list which is currently active. If the number of projectiles in the list becomes larger than some arbitrary number ( a max limit if you will), then the active list variable is switched and the previously inactive list is completely emptied and the projectile gets inserted into this list (I base insertion off of Y value for 2D rendering purposes).
The main difference is that a projectile is never deleted individually, just made inactive, which therefore prevents it from being updated or rendered anymore.
But, all of the projectiles in the inactive list are still updated and rendered as normal, until they become inactive or the inactive list is emptied in preparation for becoming the active list.
Like I said this approach is ugly, but I was going crazy.
Hopefully this made some kind of sense and I am willing to send you the source if you want to see it/are brave enough to decipher it.

Coby
that sounds interesting. So you have something like:
List A - Bullet 1, Bullet 2 - ACTIVE
List B - Empty - INACTIVE

List A has a max capacity of 2 bullets.
Bullet is added

List A = List B

List A - Bullet 1, Bullet 2 -INACTIVE
List B - Bullet 3 - ACTIVE

Add 2 Bullets

List A is emptied of all bullets.

List A - Bullet 5 - ACTIVE
List B - Bullet 3, Bullet 4 - INACTIVE

If this is the case and say you shoot 5 bullets in a short amount of time would two bullets suddenly disappear when they're cleared from the original list?

I would be extremely interested in seeing the source code =) my email is caldiar (at) eqguild (dot) net

Thanks for the reply =)
Yep, that is the idea. To avoid the situation you mentioned, you just have to set the limit to a number that is slightly higher than roughly half of the maximum amount of projectiles that can be on screen, but is reasonable enough so that it doesn't eat up a lot of memory. For my RPG, there are never more than 50-70 projectiles on the screen at one time, so I set the arbitray limit for each list to 50 to make sure there will never be projectiles that suddenly vanish. This also makes sure that there will only be a maximum of 100 projectiles in memory at any given time.

I'll send the source now.
EDIT: I'll just post it here.

bool Game::AddNewSpell(CPoint* p,int x,int y,int typ,int flag){	CUtil u;	bool valid;	if(SpellArrayKey==1)								//If First List is Active	{		if(CurrentSpellSlot<=50)					//If Capacity has not been reached		{			valid=Spells1[CurrentSpellSlot].SetSpell(typ,x,y,p,flag); //Addition Successful			CurrentSpellSlot++;		}		else																	//Capacity has been reached		{			Spells2.DecrementDimen(50);					//Empty the Second List			Spells2.IncrementDimen(50);					//Re-establish Maximum Allocation for Second List			for(int az=1;az<=50;az++)				Spells2[az].SetGamePointer(this); //Re-establish Game pointers			SpellArrayKey=2; 										//Second List is now Active			CurrentSpellSlot=1;									//One Spell in Second List			valid=Spells2[CurrentSpellSlot].SetSpell(typ,x,y,p,flag); //Addition Successful			CurrentSpellSlot++;		}	}	else if(SpellArrayKey==2) //If Second List is Active	{		if(CurrentSpellSlot<=50)				//If Capacity has not been reached		{			valid=Spells2[CurrentSpellSlot].SetSpell(typ,x,y,p,flag);	//Addition Successful			CurrentSpellSlot++;				}		else													//Capacity has been reached		{			Spells1.DecrementDimen(50);								//Empty the First list			Spells1.IncrementDimen(50);								//Re-establish Maximum Allocation for First list			for(int azl=1;azl<=50;azl++)				Spells1[azl].SetGamePointer(this);			//Re-establish Game Pointers			SpellArrayKey=1;													//First List is now Active			CurrentSpellSlot=1;												//One Spell in First List			valid=Spells1[CurrentSpellSlot].SetSpell(typ,x,y,p,flag);	//Addition Successful			CurrentSpellSlot++;						}	}	else		u.ProcessTerminalError("Game::AddSpell-Invalid SpellArrayKey!!");	//Error catch	return valid;}
if((Bullet.getx()<0) || (Bullet.getx()>640)){ Bullet->dead = true;}


| and & do bitwise calculations on ints. || is what you want in this case. And you need to assign to dead!
Shadow - Thank you very much for the source code. Ill take a look at it right now!

Bob - I thought the | operator meant "or"? Ill have to look that one up hehe =)
Well, I tried it out anyways and I get a new access violation =) I smell progress.
Oh. the bullet->dead doesn't work for my code because bullet is referring to a class while this_bullet is the node in the linked list referring to a struct that initilizes a bullet based on the class settings. That might have come out a bit confusing hehe.

Thanks a bunch for all the help guys! =)
Oh, yeah, Bullets is a Sprite not a Bullet. Damn, that's confusing. Well, you need to find the Bullet object corresponding to Bullets and set its dead to true, otherwise it won't get removed by Update. I think this_bullet->dead = true will do the trick, actually, looking at the context of that code.

| is bitwise or, i.e. 4 | 3 is 7, 6 | 2 is 6 ... it sets all bits in the answer that are in one of the arguments. || is logical or, which is almost always what you want in if tests.
hmm well, this_bullet->dead = true does this:
Something like a Memory Access Violation at this line of code:
this_bullet=this_bullet->next;
in the Update Bullet function.

EDIT: Ah, its an Access Violation (Segmentation Fault)
ok moved the this_Bullet=this_bullet->next; to the spot where it really belonged and I received my original error that I was trying to get back again.

bullet* NewBullet(){ for(int i=0;i<BULLETS;i++){ if(bdraw == 0){  if(first_bullet==NULL)  {    first_bullet=new bullet;    current_bullet=first_bullet;    current_bullet->direction = face_right;    current_bullet->x = mario.getx();    current_bullet->y = mario.gety();    current_bullet->xvel = Bullet.getSpeed() * current_bullet->direction;    Bullet.set(current_bullet->x += 1, current_bullet->y += 0);    current_bullet->next=NULL;    current_bullet->previous=NULL;  }    else  {    current_bullet->next=new bullet;    current_bullet->next->direction = face_right;    current_bullet->next->x = mario.getx();    current_bullet->next->y = mario.gety();    current_bullet->next->xvel = Bullet.getSpeed() * current_bullet->next->direction;    current_bullet->next->previous=current_bullet->next;    current_bullet=current_bullet->next;    Bullet.set(current_bullet->x+=1, current_bullet->y+=0);    current_bullet->next=NULL;  }  bdraw = 1;  }  }  current_bullet->dead=false; ----THIS IS THE AFFECTED LINE  return current_bullet;  }


Access Violation (Segmentation Fault) and current_bullet->dead=false; is highlighted.

Im thinking since this_bullet is originally the first bullet then moves on until its at the current bullet Im actually setting current_bullet-> dead to true AND false at the same time? I wonder if that really is the problem...
Okay, well I suspect that your bullet creation code is borked, not the killing code, in that case. What exactly are you trying to do? ... fill out the first unused bullet in the array? If so you ought to break out of your for loop when you've done one (i.e. inside the if(draw == 1) section).

It also strikes me that you are duplicating about 10 lines of code near the top of that function, and that smells of a refactoring opportunity:
bullet* NewBullet(){ // Save the previous 'current bullet' so we can  // link the new one to it bullet* last_bullet = current_bullet; // Depending on your sorting techniques etc, this // might not be necessary while(last_bullet->next == NULL) last_bullet = last_bullet->next; for(int i=0;i<BULLETS;i++){  if(bdraw == 0){   if(first_bullet==NULL)   {    first_bullet=new bullet;    current_bullet=first_bullet;    MakeBullet(current_bullet, NULL, NULL, i);   }      else   {     current_bullet->next=new bullet;     MakeBullet(current_bullet->next, current_bullet, NULL, i);   }   bdraw = 1;   break;  // made one now, so don't do any more  } } current_bullet->dead=false; ----THIS IS THE AFFECTED LINE last_bullet->next = current_bullet; // link to the rest of the list return current_bullet;}void MakeBullet(Bullet* target, Bullet prev, Bullet next, int index){ // New function to fill out a bullet // NB syntax may be wrong, I don't write C++ code target->direction = face_right; target->x = mario.getx(); target->y = mario.gety(); target->xvel = Bullet[index].getSpeed() * target->direction; Bullet[index].set(target->x += 1, target->y += 0); target->next=prev; target->previous=next;}


You're also mixing things up by storing some of your data in the linked list of Bullet structs, and some in a fixed array of Sprites. I think you need to pick one of those two and stick with it. At the minute the array is limiting the number of Bullets you can have, so why bother with the linked list?

You also had a bug in the second part of the creation code where you set the prev pointer of the new bullet to itself, and you don't link in the new bullet at all. I've added that functionality in.

This topic is closed to new replies.

Advertisement