Sign in to follow this  

Simple ways to keep space between objects?

This topic is 4203 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I don't think that I should need serious collision detection just for this (though, I'm going to have to look into that for other reasons later on). Here's what I'm trying to do. I have a class of enemy that performs a multi sequence manuever where a leader basically circles the hero. To be more precise, here is the code for that
 if(!leader && myLeader == NULL){
    //If we aren't the leader who is the leader?
    for(int i=0; i<EnemyBase.enemies.size(); i++){
      if(EnemyBase.enemies[i]->myType == myType){
       /* if(EnemyBase.enemies[i]->myLeader !=NULL){
          myLeader = EnemyBase.enemies[i];
      }*/
        if(EnemyBase.enemies[i]->leader == true){
          myLeader = EnemyBase.enemies[i]; // Yay, a leader!
        }
      }
    }
    if(myLeader == NULL){ 
      // I'm ... the leader?   Oh crap, we're all gonna die
      leader = true;
    }
  }
  if(leader){
  switch(stage){
    case 0:
      if(stagedone){ stagedone = false; myAngle = getAngle(OurHero->myPosition.x+2.5f, OurHero->myPosition.y, myPosition.x, myPosition.y); }
      if(myPosition.y <= OurHero->myPosition.y){ stagedone = true; stage+=1; }
      break;
    case 1:
      if(stagedone){ stagedone = false; myAngle = getAngle(OurHero->myPosition.x, OurHero->myPosition.y-2.5f, myPosition.x, myPosition.y); }
      if(myPosition.x <= OurHero->myPosition.x){ stagedone = true; stage+=1; }
      break;
    case 2:
      if(stagedone){ stagedone = false; myAngle = getAngle(OurHero->myPosition.x-2.5f, OurHero->myPosition.y, myPosition.x, myPosition.y); }
      if(myPosition.y >= OurHero->myPosition.y){ stagedone = true; stage+=1; }
      break;
    case 3:
      if(stagedone){ stagedone = false; myAngle = getAngle(OurHero->myPosition.x, OurHero->myPosition.y+1.5f, myPosition.x, myPosition.y); }
      if(myPosition.x >= OurHero->myPosition.x){ stagedone = true; stage+=1; }
      break;
    case 4:
      if(stagedone){ stagedone = false; myAngle = getAngle(OurHero->myPosition.x+1.5f, OurHero->myPosition.y, myPosition.x, myPosition.y); }
      if(myPosition.y <= OurHero->myPosition.y){ stagedone = true; stage+=1; }
      break;
    case 5:
      if(stagedone){ stagedone = false; myAngle = getAngle(OurHero->myPosition.x, OurHero->myPosition.y-1.5f, myPosition.x, myPosition.y); }
      if(myPosition.x <= OurHero->myPosition.x){ stagedone = true; stage+=1; }
      break;
    case 6:
      if(stagedone){ stagedone = false; myAngle = getAngle(OurHero->myPosition.x-2.5f, OurHero->myPosition.y+4.0f, myPosition.x, myPosition.y); }
      if(myPosition.y >= OurHero->myPosition.y+4.0f){ stagedone = true; stage+=1; }
      break;
    case 7:
      if(stagedone){ stagedone = false; myAngle = getAngle(OurHero->myPosition.x+2.5f, OurHero->myPosition.y, myPosition.x, myPosition.y); }
      if(myPosition.y >= OurHero->myPosition.y){ stagedone = true; stage=0; }
      break;
    case 8: 
      stage = 0;
      break;
  };
  }else{
    myAngle = getAngle(myLeader->myPosition.x, myLeader->myPosition.y,myPosition.x,myPosition.y); //follow like a good little doggie
   // followAdjust = 0.90f;
  }
  restrictPosition(prevX, prevY);
The restrictPosition attempt is called at the end. I've also tried having each one be a leader and have them all fly behind one another in a line type of formation but that didn't work so well. What I get now is kind of close to that but they will stop due to getting too close. Also I have my concerns about the expense of the checking I'm doing .. not that on my machine it's slow but my machine is hardly average (3800+ X2) and I would prefer this run on considerably less. Ok, here is my basic restrictPosition function, which also handles keeping the unit positioned within the bounds of the screen maX/maxY
void Enemy::restrictPosition(GLfloat prevX, GLfloat prevY) {
  for(int i=0; i<EnemyBase.enemies.size(); i++){ // Keep some space between the enemies 
    if(EnemyBase.enemies[i] != this){ // Exclude ourself, how can we get away from ourself?
      if(::getDistance(static_cast<GameObject*>(this),static_cast<GameObject*>(EnemyBase.enemies[i])) < 0.5f){        
        if(fabs(::getAngle(EnemyBase.enemies[i]->myPosition.x, EnemyBase.enemies[i]->myPosition.y, myPosition.x, myPosition.y) - myAngle) < 90.0f){
          //If the angle difference isn't at least 90 then we are moving in their direction and we need to stop for the time being until we get further away
          myPosition.x = prevX;
          myPosition.y = prevY;
        }
          /*followAdjust = 0.5f;
          break;
        } ... failed method # 32.5 lol */
      }
      else{
        /* failed method # 32.5
        followAdjust = 1.0f; // run normally we aren't too close to anyone */
      }
    }
  }
  if(myPosition.x-mySize.w/2 < -5.0f) myPosition.x = -5.0f + mySize.w/2;
  if(myPosition.x+mySize.w/2 > 5.0f) myPosition.x = 5.0f - mySize.w/2;
}
I know this stuff is probably horribly ugly to anyone good so please keep in mind this is all coming from a total novice. I'm not good enough to even get basic collision detection right yet (I have the bounding box collision in another function and I've tried it, but it was working kind of goofy .. colliding when it shouldn't at times strangely and it also didn't account for object rotation (very important since I can rotate the ship.. odd for a top scroller but so far I think it's more fun with it). Just for interest sake here is that function along with the utility getAngle and getDistance (feel free to say if there are better ways to do these too).
GLfloat getAngle(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) {
  GLfloat x = x1-x2;
  GLfloat y = y1-y2;
  GLfloat angle;
  if(x == 0.0f) {
    if(y == 0.0f)
      angle = 0.0f;
    if(y < 0.0f)
      angle = PI;
    if(y > 0.0f)
      angle = 0.0f;
  } else if(y == 0.0f) {
    if(x < 0.0f)
      angle = PI/2;
    else
      angle = 3*PI/2;
  } else if(x < 0.0f) {
    angle = atan(y/x)+PI/2;
  } else {
    angle = atan(y/x)+3*PI/2;
  }
  angle = angle * 180.0 / PI;
  return angle;
}

bool collisionCheck(GameObject* g1, GameObject* g2){
  GLfloat left1, left2, right1, right2, top1, top2, bottom1, bottom2;
  
  left1 = g1->myPosition.x-((g1->mySize.w/2)*0.8f);
  left2 = g2->myPosition.x-((g2->mySize.w/2)*0.8f);
  right1 = g1->myPosition.x+((g1->mySize.w/2)*0.8f);
  right2 = g2->myPosition.x+((g2->mySize.w/2)*0.8f);
  
  bottom1 = g1->myPosition.y-((g1->mySize.h/2)*0.8f);
  bottom2 = g2->myPosition.y-((g2->mySize.h/2)*0.8f);
  top1 = g1->myPosition.y+((g1->mySize.h/2)*0.8f);
  top2 = g2->myPosition.y+((g2->mySize.h/2)*0.8f);
  
  if(bottom1 > top2){ return 0;}// we are above 2
  if(top1 < bottom2){ return 0;}// we are below 2
  if(right1 < left2){ return 0;} // we are off to the right of 2
  if(left1 > right2){ return 0;} // we are to the left of 2
  
  return 1; // if we aren't above, below, or to the right or left we must be colliding
}

GLfloat getDistance(GameObject* g1, GameObject* g2){
  return sqrt((g1->myPosition.x-g2->myPosition.x)*(g1->myPosition.x-g2->myPosition.x)+(g1->myPosition.y-g2->myPosition.y)*(g1->myPosition.y-g2->myPosition.y));
}
My current collision detection is VERY basic, and just checks the one objects bounding box taking the x/y position of the projectile.

Share this post


Link to post
Share on other sites
Without seeing it in action I couldn't really intuit what's going wrong, but I will help you clean up the "boring" code.

The boringness here primarily comes from useless repetition (and a little bit of wheel reinvention).

Some suggestions:

- Instead of having both a boolean 'leader' and a pointer to 'myLeader', just have the pointer. If the Enemy in question is the leader, have it point to itself.

- Compare null pointers as just 'ptr' or '!ptr'. With an appropriately named pointer, this often produces a quite idiomatic, easily-read result. It also avoids the whole 'null vs 0' debate (except for when you set the pointer).

- It looks like you have a container of Enemy pointers, using pointers because Enemy is a base class using polymorphism, and then you add a 'type' field to each Enemy subclass that indicates which type it is, so that Enemies of the same type within the container can communicate with each other.

*Don't do this*! The idea behind polymorphism is to avoid manually inspecting the type. Normally, if you need to "remember" the type of the Enemies, then you should put them in a type-specific container: doing otherwise is working at cross purposes with yourself. If you really can't avoid it (e.g. you iterate over the container using the general Enemy interface a fair bit as well), you should still not implement the type inspection yourself, but instead use dynamic_cast:


FooEnemy* f = dynamic_cast<FooEnemy*>(EnemyBase.enemies[i]);
if (f) { // i.e. not null
// the enemy is indeed a FooEnemy
}


Of course, in your case you don't know the type ahead of time but just want to check a "same type" - this is quite hairy x.x

- In the big switch statement, what we can do is take advantage of the similarity between the cases by building numeric arrays that hold the variant bits, and then apply a common algorithm, indexing into the arrays with the current state value. Meanwhile, we note that in every state we check to increment the state, but wrap it around at 7. We can implement that by just using a modulus operator. (Note that reaching the '8' state should be impossible.)


// WARNING: I am not responsible for any errors in transcription of the arrays ;)
const float targetXOffsets[8] = {
2.5f, 0.0f, -2.5f, 0.0f, 1.5f, 0.0f, -2.5f, 2.5f
};
const float targetYOffsets[8] = {
0.0f, -2.5f, 0.0f, 1.5f, 0.0f, -1.5f, 4.0f, 0.0f
};
// If the value in here is true, we want the value to be >= in order
// to move to the next state; otherwise we want it to be <=.
const bool checkGreater[8] = {
false, false, true, true, false, false, true, true
};
// In 'true' states, we compare y values; in 'false' states we compare x values.
const bool checkY[8] = {
true, false, true, false, true, false, true, true
};
// An offset added to the OurHero position value for the comparison.
const float checkOffset[8] = {
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.0f, 0.0f
};

if (leader) {
if (stagedone) {
stagedone = false;
myAngle = getAngle(OurHero->myPosition.x + targetXOffsets[stage],
OurHero->myPosition.y + targetYOffsets[stage],
myPosition.x, myPosition.y); }
}
int mypos = checkY[stage] ? myPosition.y : myPosition.x;
int itspos = checkY[stage] ? OurHero->myPosition.y : OurHero->myPosition.x;
itspos += checkOffset[stage];
bool check = checkGreater[stage] ? (mypos => itspos) : (mypos <= itspos);
if (check) {
stagedone = true;
stage = (stage + 1) % 8;
}
}
// Look ma, no cases



- Having made that transformation, it occurs to me that the offsets aren't quite as patterned as I might expect them to be. You might want to make sure that's intentional ;)

- Consider not tagging member variable names with "my". IMO it inhibits the readability, especially when you are looking at the members of *other* objects.

- You shouldn't need that static_cast for the pointers passed to getDistance(), if I'm thinking clearly. But maybe you should consider making it a GameObject member function instead of a free function.

- In general, you seem to be working with more pointers than are really called for. Consider your other options. In particular, why do you have *pointers* to whatever Position object you use in your Enemy etc. objects? Is a Position going to be polymorphic, or shared between objects?

- getAngle() basically reinvents the standard library atan2(), which normally is what you really want whenever you think you want atan() ;) But you might consider adjusting it to accept Position arguments, doing the unwrapping within the function. That simplifies the interface. Again, you could also make it a Position member function. (Many people will assert that making it a member function is poor C++, on the grounds that C++ isn't a pure OO language. You may want to try it both ways and see which appeals to you more.) HOWEVER, there are simpler ways to check if two direction vectors are within 90 degrees of each other, if that's all you want :) (google "2d cross product")

- g1->myPosition.x-((g1->mySize.w/2)*0.8f); <-- Here, the "1/2" and "0.8f" constants could be folded together. In any case the constant ought to be extracted and named:


const float EFFECTIVE_HALFWIDTH_FACTOR = 0.4f;
g1->myPosition.x-(g1->mySize.w * EFFECTIVE_HALFWIDTH_FACTOR);


Also, consider moving that calculation into the GameObjects themselves as a member: give them an interface like


// Return a position resulting from subtracting the scaled width and height
// from the components of myPosition.
Position GameObject::getTopLeft();
// Similarly, adding them.
Position GameObject::getBottomRight();

Share this post


Link to post
Share on other sites
I would have posted a complete version with immediacy, except for awareness of the fact that most don't use linux here!

But, nevertheless, this shall be corrected when I go to work. During lunch I will compile up a windows version since that's what I have to use there (and they are nice enough to let me leave an external hard drive that I take back and forth for the weekends).

Anyway, I appreciate all the clean suggestions.

Some of it sounds applicable and I will use it.

But, as far as type goes let me explain that, and maybe you can tell me what you think.

I have an enemy class, then I have an enemies class, which manages the individual enemies. It calls their update functions, it deletes their reference in an enemy vector, etc.
It also loads userdefined enemy types

Then I have an "enemy_userdefined" class

This class inherits from the enemy class for it's basic functions & construction (also because I need to use the class objects in the enemies vector)

Anyway,I do this so that later I can define enemy types based on files. I would prefer to allow modification of the unit types by anyone without modifying the program itself.

Here is the current loading function until I put in an I/O structure to load it (which I have to learn how to do, haven't really messed with i/o in C++ yet)


void Enemies::loadDefinedTypes(){
// Eventually we will load this data from files, but for now we are just going to define it directly
definedTypes.push_back(new EnemyDefinition());
definedTypes[0]->Name="Tank";
definedTypes[0]->textureID=8;
definedTypes[0]->velocity=0.75f;
definedTypes[0]->RotateRate=0.0f;
definedTypes[0]->Angle=180.0f;
definedTypes[0]->Size.w=1.0f;
definedTypes[0]->Size.h=1.0f;
definedTypes[0]->health=300;
definedTypes[0]->gunports.push_back(new GunPortDefinition());
definedTypes[0]->gunports[0]->Name="gp1";
definedTypes[0]->gunports[0]->OffSetAngle = 0.0f;
definedTypes[0]->gunports[0]->OffSetPosition.x = 0.0f;
definedTypes[0]->gunports[0]->OffSetPosition.y = 0.6f;
definedTypes[0]->gunports[0]->OffSetPosition.z = 0.0f;
definedTypes[0]->gunports[0]->range_right = 0.0f;
definedTypes[0]->gunports[0]->range_left = 0.0f;
definedTypes[0]->gunports[0]->fire_rate = 1500;
definedTypes[0]->gunports[0]->ammoType = tankProjectile;

definedTypes.push_back(new EnemyDefinition());
definedTypes[1]->Name="Gnat";
definedTypes[1]->textureID=7;
definedTypes[1]->velocity=12.0f;
definedTypes[1]->RotateRate=90.0f;
definedTypes[1]->Angle=180.0f;
definedTypes[1]->Size.w=0.2f;
definedTypes[1]->Size.h=0.2f;
definedTypes[1]->health=10;
definedTypes[1]->gunports.push_back(new GunPortDefinition());
definedTypes[1]->gunports[0]->Name="gp1";
definedTypes[1]->gunports[0]->OffSetAngle = 0.0f;
definedTypes[1]->gunports[0]->OffSetPosition.x = 0.0f;
definedTypes[1]->gunports[0]->OffSetPosition.y = 0.0f;
definedTypes[1]->gunports[0]->OffSetPosition.z = 0.0f;
definedTypes[1]->gunports[0]->range_right = 180.0f;
definedTypes[1]->gunports[0]->range_left = 180.0f;
definedTypes[1]->gunports[0]->fire_rate = 1500;
definedTypes[1]->gunports[0]->ammoType = enemyProjectile;

definedTypes.push_back(new EnemyDefinition());
definedTypes[2]->Name="Alpha";
definedTypes[2]->textureID=9;
definedTypes[2]->velocity=0.75f;
definedTypes[2]->RotateRate=0.0f;
definedTypes[2]->Angle=180.0f;
definedTypes[2]->Size.w=1.0f;
definedTypes[2]->Size.h=1.0f;
definedTypes[2]->health=100;
definedTypes[2]->gunports.push_back(new GunPortDefinition());
definedTypes[2]->gunports[0]->Name="gp1";
definedTypes[2]->gunports[0]->OffSetAngle = 0.0f;
definedTypes[2]->gunports[0]->OffSetPosition.x = 0.0f;
definedTypes[2]->gunports[0]->OffSetPosition.y = 0.6f;
definedTypes[2]->gunports[0]->OffSetPosition.z = 0.0f;
definedTypes[2]->gunports[0]->range_right = 0.0f;
definedTypes[2]->gunports[0]->range_left = 0.0f;
definedTypes[2]->gunports[0]->fire_rate = 10;
definedTypes[2]->gunports[0]->ammoType = tankProjectile;

definedTypes.push_back(new EnemyDefinition());
definedTypes[3]->Name="Beta";
definedTypes[3]->textureID=10;
definedTypes[3]->velocity=3.0f;
definedTypes[3]->RotateRate=0.0f;
definedTypes[3]->Angle=180.0f;
definedTypes[3]->Size.w=1.0f;
definedTypes[3]->Size.h=1.0f;
definedTypes[3]->health=100;
definedTypes[3]->gunports.push_back(new GunPortDefinition());
definedTypes[3]->gunports[0]->Name="gp1";
definedTypes[3]->gunports[0]->OffSetAngle = 0.0f;
definedTypes[3]->gunports[0]->OffSetPosition.x = 0.0f;
definedTypes[3]->gunports[0]->OffSetPosition.y = 0.6f;
definedTypes[3]->gunports[0]->OffSetPosition.z = 0.0f;
definedTypes[3]->gunports[0]->range_right = 0.0f;
definedTypes[3]->gunports[0]->range_left = 0.0f;
definedTypes[3]->gunports[0]->fire_rate = 1500;
definedTypes[3]->gunports[0]->ammoType = enemyProjectile;

definedTypes.push_back(new EnemyDefinition());
definedTypes[4]->Name="Delta";
definedTypes[4]->textureID=11;
definedTypes[4]->velocity=0.75f;
definedTypes[4]->RotateRate=0.0f;
definedTypes[4]->Angle=180.0f;
definedTypes[4]->Size.w=1.0f;
definedTypes[4]->Size.h=1.0f;
definedTypes[4]->health=100;
definedTypes[4]->gunports.push_back(new GunPortDefinition());
definedTypes[4]->gunports[0]->Name="gp1";
definedTypes[4]->gunports[0]->OffSetAngle = 0.0f;
definedTypes[4]->gunports[0]->OffSetPosition.x = 0.0f;
definedTypes[4]->gunports[0]->OffSetPosition.y = 0.6f;
definedTypes[4]->gunports[0]->OffSetPosition.z = 0.0f;
definedTypes[4]->gunports[0]->range_right = 45.0f;
definedTypes[4]->gunports[0]->range_left = 45.0f;
definedTypes[4]->gunports[0]->fire_rate = 10;
definedTypes[4]->gunports[0]->ammoType = enemyProjectile;

definedTypes.push_back(new EnemyDefinition());
definedTypes[5]->Name="Gamma";
definedTypes[5]->textureID=12;
definedTypes[5]->velocity=3.5f;
definedTypes[5]->RotateRate=0.0f;
definedTypes[5]->Angle=180.0f;
definedTypes[5]->Size.w=1.0f;
definedTypes[5]->Size.h=1.0f;
definedTypes[5]->health=100;
definedTypes[5]->gunports.push_back(new GunPortDefinition());
definedTypes[5]->gunports[0]->Name="gp1";
definedTypes[5]->gunports[0]->OffSetAngle = 0.0f;
definedTypes[5]->gunports[0]->OffSetPosition.x = 0.0f;
definedTypes[5]->gunports[0]->OffSetPosition.y = 0.6f;
definedTypes[5]->gunports[0]->OffSetPosition.z = 0.0f;
definedTypes[5]->gunports[0]->range_right = 0.0f;
definedTypes[5]->gunports[0]->range_left = 0.0f;
definedTypes[5]->gunports[0]->fire_rate = 1500;
definedTypes[5]->gunports[0]->ammoType = enemyProjectile;

definedTypes.push_back(new EnemyDefinition());
definedTypes[6]->Name="Omicron";
definedTypes[6]->textureID=13;
definedTypes[6]->velocity=0.75f;
definedTypes[6]->RotateRate=0.0f;
definedTypes[6]->Angle=180.0f;
definedTypes[6]->Size.w=1.0f;
definedTypes[6]->Size.h=1.0f;
definedTypes[6]->health=100;
definedTypes[6]->gunports.push_back(new GunPortDefinition());
definedTypes[6]->gunports[0]->Name="gp1";
definedTypes[6]->gunports[0]->OffSetAngle = 0.0f;
definedTypes[6]->gunports[0]->OffSetPosition.x = 0.0f;
definedTypes[6]->gunports[0]->OffSetPosition.y = 0.0f;
definedTypes[6]->gunports[0]->OffSetPosition.z = 0.0f;
definedTypes[6]->gunports[0]->range_right = 180.0f;
definedTypes[6]->gunports[0]->range_left = 180.0f;
definedTypes[6]->gunports[0]->fire_rate = 1500;
definedTypes[6]->gunports[0]->ammoType = lightningProjectile;

definedTypes.push_back(new EnemyDefinition());
definedTypes[7]->Name="Omega";
definedTypes[7]->textureID=14;
definedTypes[7]->velocity=0.35f;
definedTypes[7]->RotateRate=0.0f;
definedTypes[7]->Angle=180.0f;
definedTypes[7]->Size.w=3.0f;
definedTypes[7]->Size.h=2.0f;
definedTypes[7]->health=500;
definedTypes[7]->gunports.push_back(new GunPortDefinition());
definedTypes[7]->gunports[0]->Name="gp1";
definedTypes[7]->gunports[0]->OffSetAngle = 0.0f;
definedTypes[7]->gunports[0]->OffSetPosition.x = 0.0f;
definedTypes[7]->gunports[0]->OffSetPosition.y = 0.6f;
definedTypes[7]->gunports[0]->OffSetPosition.z = 0.0f;
definedTypes[7]->gunports[0]->range_right = 15.0f;
definedTypes[7]->gunports[0]->range_left = 15.0f;
definedTypes[7]->gunports[0]->fire_rate = 1500;
definedTypes[7]->gunports[0]->ammoType = lightningProjectile;
definedTypes[7]->gunports.push_back(new GunPortDefinition());
definedTypes[7]->gunports[1]->Name="gp2";
definedTypes[7]->gunports[1]->OffSetAngle = 0.0f;
definedTypes[7]->gunports[1]->OffSetPosition.x = -0.5f;
definedTypes[7]->gunports[1]->OffSetPosition.y = 0.6f;
definedTypes[7]->gunports[1]->OffSetPosition.z = 0.0f;
definedTypes[7]->gunports[1]->range_right = 0.0f;
definedTypes[7]->gunports[1]->range_left = 0.0f;
definedTypes[7]->gunports[1]->fire_rate = 1000;
definedTypes[7]->gunports[1]->ammoType = enemyProjectile;
definedTypes[7]->gunports.push_back(new GunPortDefinition());
definedTypes[7]->gunports[2]->Name="gp3";
definedTypes[7]->gunports[2]->OffSetAngle = 0.0f;
definedTypes[7]->gunports[2]->OffSetPosition.x = 0.5f;
definedTypes[7]->gunports[2]->OffSetPosition.y = 0.6f;
definedTypes[7]->gunports[2]->OffSetPosition.z = 0.0f;
definedTypes[7]->gunports[2]->range_right = 15.0f;
definedTypes[7]->gunports[2]->range_left = 15.0f;
definedTypes[7]->gunports[2]->fire_rate = 1000;
definedTypes[7]->gunports[2]->ammoType = enemyProjectile;
definedTypes[7]->gunports.push_back(new GunPortDefinition());
definedTypes[7]->gunports[3]->Name="gp2";
definedTypes[7]->gunports[3]->OffSetAngle = 0.0f;
definedTypes[7]->gunports[3]->OffSetPosition.x = -1.4f;
definedTypes[7]->gunports[3]->OffSetPosition.y = 0.3f;
definedTypes[7]->gunports[3]->OffSetPosition.z = 0.0f;
definedTypes[7]->gunports[3]->range_right = 45.0f;
definedTypes[7]->gunports[3]->range_left = 0.0f;
definedTypes[7]->gunports[3]->fire_rate = 1000;
definedTypes[7]->gunports[3]->ammoType = tankProjectile;
definedTypes[7]->gunports.push_back(new GunPortDefinition());
definedTypes[7]->gunports[4]->Name="gp3";
definedTypes[7]->gunports[4]->OffSetAngle = 0.0f;
definedTypes[7]->gunports[4]->OffSetPosition.x = 1.4f;
definedTypes[7]->gunports[4]->OffSetPosition.y = 0.3f;
definedTypes[7]->gunports[4]->OffSetPosition.z = 0.0f;
definedTypes[7]->gunports[4]->range_right = 0.0f;
definedTypes[7]->gunports[4]->range_left = 45.0f;
definedTypes[7]->gunports[4]->fire_rate = 1000;
definedTypes[7]->gunports[4]->ammoType = tankProjectile;
}


Share this post


Link to post
Share on other sites
Aha, then 'myType' is a pointer into definedTypes vector (or other sequential container), or something? That's quite good design-wise, actually (you create "virtual type" by object composition, using pointers in order to alias "prototype" composed objects). But implementation-wise... if you added types later (say you wanted to dynamically load enemy types per level) and the vector resized, the pointers would be invalidated. Not to mention what would happen (with any sequential container) if you removed a type ;)

You may find it more robust to take the Name field out of the EnemyDefinition, and use it as a key instead, storing definedTypes as a std::map<std::string, EnemyDefinition>.

And again, stop it with the pointers already :)

Anyway, quick example of reading from a file. This does no error checking, and expects the file in a simple text (human-readable) format whereby blank lines separate the enemy definitions, the first line of each enemy definition gives the field values in the order you've been initializing them, and subsequent lines give gunport data in order (by the way, I think you will find it not terribly useful to name the gunports, but I kept that in):

(Spec by example)

Omicron 13 .75 0 180 1 1 100
gp1 0 0 0 0 180 180 1500 lightning


And implementation:

// I'm showing you code that assumes the extra pointers are already cleaned up,
// because I'm lazy :P

istream& operator>>(istream& is, EnemyDefinition& ed) {
return is >> ed.Name >> ed.textureId >> ed.velocity >> ed.RotateRate >>
ed.Angle >> ed.Size.w >> ed.Size.h >> ed.health;
}

istream& operator>>(istream& is, GunPortDefinition& gpd) {
// To translate the string name in the file into an enumerated type (or
// Projectile* or whatever it is that you have), we'll have to build a map
// somewhere else - e.g. "lightning" -> lightningProjectile.
// Although you might want to take the advice I offered earlier and just
// store the string in the object and use it as a map key :)
is >> gpd.Name >> gpd.OffsetAngle >> gpd.OffsetPosition.x
>> gpd.OffsetPosition.y >> gpd.OffsetPosition.z >> gpd.range_right
>> gpd.range_left >> gpd.fire_rate;
string ammoTypeAsString;
is >> ammoTypeAsString;
gpd.ammoType = theLookupTable[ammoTypeAsString];
return *is;
}

vector<EnemyDefinition> loadDefinedTypes(const string& filename) {
vector<EnemyDefinition> result;
// you may consider passing the filename as a const char* directly instead,
// if you don't need to "calculate" it.
ifstream f(filename.c_str());
// To simplify things, I will read the file a line at a time into a temporary
// buffer, and then re-parse the lines.
string fileLine;
while (getline(f, fileLine)) {
EnemyDefinition ed;
stringstream parser(fileLine);
parser >> ed;
// Yeah, a nested while loop on the same condition. We're using it mostly
// for the side effect :)
while (getline(f, fileLine) && !fileLine.empty()) {
GunPortDefinition gpd;
stringstream parser(fileLine);
parser >> gpd;
ed.gunports.push_back(gpd);
}
result.push_back(ed);
}
return result;
}

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I use the same method (a vector) for storage of projectiles and it seems to work fine.

I just have a deleteProjectile function which does..


void Projectiles::deleteProjectile(int projectileID){
delete projectiles[projectileID]; // this calls the deconstructer and frees instance memory
projectiles.erase(projectiles.begin()+projectileID);
}



So I think using similar methods for other vectors will work fine.

Thanks very much for the suggestions. I worked on cleaning up the code as much as possible to release it here. It's not got all of your suggestions, but I definitely did keep in mind your words on changing the "my" variable naming set. I changed all of that, finally got it all converted over and didn't have time for much else after work sadly. I just compiled up a windows version.

It's a debug version so all it will show is the 1 single enemy class that I'm working on (I haven't yet modified the routine as you suggested, or decided if the behavior is correct). Currently this enemy type isn't even coming towards my hero unit, but I may have accidentally done something weird. It was "kind of" working before today.. so I'm sure I missed something.

Anyway, you can download it from http://matrixcentral.net/xanas/nextstep.exe and it includes the windows exe, sdl.dll, all the "art" assets (and I use that term EXTREMELY loosely, as my artistic skills are beyond poor)

Anyway, if anyone would like to help me debug a bit more I'd appreciate it. All the routines I'm referencing here are in the enemy.cpp/nextstep.cpp files




Share this post


Link to post
Share on other sites

This topic is 4203 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this