Design choice to render bullets?

Started by abstauber, Tue 27/04/2010 20:08:46

Previous topic - Next topic

abstauber

Who would I be without your open source attitude...

Not the copycat I am now ;D

Shane 'ProgZmax' Stevens

I find it interesting how you used guis to do what most people would use dynamic surfaces for, and I mean that in a good way.  I never would have thought of messing about with a gui for a shooter, but you seem to have the logic down for it well so I'd say stick to it if it works.  The only thing that confuses me is what you said about making the bullets part of the gui.  That would then make them buttons with animated sprites, I gather?  How can you make them leave the gui limits and continue to a target, or are the hero and enemy guis just full screen overlapping at all times (that sounds a bit sloppy to me)?  I like the concept of using a gui for a big boss ship with things to destroy (buttons), but I definitely think there are advantages to using dynamic sprites for, say, bullets and pickups and minor enemies.  With aeronuts, were the enemies just buttons floating on a huge gui?

abstauber

Yes - I simply used GUIs as Layers. 2 Bullet layers, and one enemy layer. The GUIs are full screen and you're right - all that's moving are floating buttons :)

Well, this GUI button concept in AeroNuts worked great before I realised that pixel perfection is not possible with GUI buttons. So I had to convert the player's plane to a character and that resulted in a nasty z-level problem since GUI appear in front of characters. It ended in converting the carrier into a room object, but the turrets are still GUI buttons.
At that stage I decided it's better not to release the source anymore ;)

But if you like, I can upload the source somewhere (be warned, it's MAGS code).

Gilbert

It's actually quite straight forward to code bullets moving in any direction. You may check this quick mess that I made in a couple of minutes (sorry, no module or comments present, check script in room1 and compile with V3.1.2+). In fact the bullet layer DynamicSprite can even be used as a collision map! (If bullets are not in single pixels and you want the collision maps to be in single pixels just paint on another sprite at the same time to create the collision map.)

abstauber

Hehe, that totally reminds me at the early demo scene :D

Thanks for the help!

Wyz

A minor optimization when you end up firing bullets in the same direction often: Use unit vectors to do the calculations. In that way you can just multiply the vector by any distance.

Code: ags

int sourceX, sourceY; // player position
int destX, destY;        // mouse pointer or something

float vx = IntToFloat(destX - sourceX);
float vy = IntToFloat(destY - sourceY);
float n = Maths.Sqrt(vx*vx + vy*vy);

vx /= n;
vy /= n;

now you can move a bullet like this:
float distance = ...; // Distance the bullet has traveled in pixels

int bulletX = sourceX + FloatToInt(vx * distance);
int bulletY = sourceY + FloatToInt(vy * distance);



Yay math! :D
Life is like an adventure without the pixel hunts.

Khris

Actually one would store a movement vector (directional vector * initial speed) for each bullet and add that to the bullet's position each frame. It's faster (additions only) and the speed can directly be modified my changing the movement vector. One can also incorporate reflections this way.

Also, one should use only floats until the object is drawn.

abstauber


One could also post a code sample :P


@Wyz: Yay :D

Calin Leafshade

Basically a unit vector is simply a measurement of direction. It has no speed value associated with it.

so (1,1) is 45 degrees up-right, (-1,1) is 45 degrees up left (using Cartesian cords)

Wyz suggests storing this vector and then multiplying it by a scalar (just a number) speed.

so you'd have something like:

Code: ags


vector.x = 1;
vector.y = 1;
scalarspeed = 8;

bullet.x += vector.x * scalarspeed; // i.e 1 * 8 = 8;
bullet.y += vector.y * scalarspeed;



Khris suggests just save the vector as a product of the unit vector and the scalar speed so you dont have to do it every loop.. thus less math.




Khris

Actually, the length of (1,1) is Sqrt(2) ~ 1.414. A unit vector pointing up-right would be (Sqrt(2)/2, -Sqrt(2)/2).

In actual code one would use a vector class.

Code: ags
float vector_class::Length() {
  return Maths.Sqrt(this.x*this.x + this.y*this.y);
}

void vector_class::Normalize() {
  float l = this.Length();
  if (l == 1.0 || l == 0.0) return;
  this.x = this.x/l;
  this.y = this.y/l;
}


Now I can do this:

Code: ags
vector_class v;

// inside function

  v.x = IntToFloat(mouse.x - player.x);
  v.y = IntToFloat(mouse.y - player.y);

  v.Normalize();
  v.x = v.x*speed;
  v.y = v.y*speed;

DoorKnobHandle

#30
An even faster way to normalize a vector would be this:

Code: ags

void Vector::Normalize ( )
{
   float l = this.Length ( );

   if ( l == 1.0 || l == 0.0 )
      return;

   float tmp = 1.0 / l;

   this.x = this.x * tmp;
   this.y = this.y * tmp;
}


This substitutes one additional division by two multiplications which is worth it speed-wise.

:D

Smiley is there because, unless you are doing something very fancy, you're not going to notice a difference. Especially in AGS.

GarageGothic

And the float tmp does what exactly in that piece of code, dkh?

DoorKnobHandle

EDIT: OOps. Typo fixed in my function above.

abstauber

haha.. okay I think I really have a lot of choices now :D

Thanks a bunch, guys!

Monsieur OUXX

I'd recomand to work as much as possible with integers. That's a miracle solution for all time-consuming tasks.
That's how they made Doom so incredibly fast and good-looking in the first place, compared to Wolfenstein.

Integer arithmetics can of course not replace float arithmetics completely, but it should be used strictly every time you can exclude scenarios from your code - scenarios that would require you to calculate things repeatedly and with high accuracy.

Exclude scenarios. All the time. In every circumstances. Make your code straight-forward. Never mind if the result lacks accuracy : if it looks like it's correct, then for the player it's correct.

Example 1: As it has been mentionned before, calculating a displacement vector (using floats, if necessary) once and for all (at the time you set the direction of a moving object - i.e. when you create it or when it rebounds), and then always refer to that constant vector, expressed in integer pixels offest (e.g. "move 3 pixels to the right" - not 2.5, not 3.5, not 3.0, but strictly 3).

Example 2: pixel-perfect collision detection is very rarely needed. Use a box or a circle or a "vertical lozenge". Even for bullets.
 

abstauber

#35
Okay, you may now start laughing. Even with all this helpI can't get it to work  :'(

Right now bullets can fly to 0° ,45° to the left  90° left and get stuck but render at 90° to the right.

I knew it wasn't just simply copy'n paste .

Anyhow here's the code.

Code: ags

//here's the bullet struct
struct cBullets {
  int x;
  int y;
  bool active;
  
  float vector_x;
  float vector_y;
  int speed; 
  int gravity;
  int sprite;

  import float Length();
  import void Normalize();
};


float cBullets::Length() {
  return Maths.Sqrt(this.vector_x * this.vector_x + this.vector_y * this.vector_y);
}

void cBullets::Normalize ()
{
   float l = this.Length ();

   if ( l == 1.0 || l == 0.0 )
      return;

   float tmp = 1.0 / l;
   this.vector_x = this.vector_x * tmp;
   this.vector_y = this.vector_y * tmp;
}

// here's shooting
// TENG.get_free_bullet works fine btw.

static function TENG::shoot_player_bullet() 
{
  int xpos;
  int ypos;
  int modifier;
  
  int nextFree;
  
  if (weapon[ego_stats.active_weapon].bullet_sprite >-1) {
    
    nextFree = TENG.get_free_bullet (true);
    
    if (nextFree > -1) {
      
      if (ego_stats.SpriteDirection == 4) {
        xpos = player.x + (TENG.get_char_width(player)/2);
        modifier = 1;
      }
      else {
        xpos = player.x - (TENG.get_char_width(player)/2);
        modifier = -1;        
      }
      ypos = player.y - (TENG.get_char_height(player)/2);
      
      player_bullet[nextFree].x = xpos;
      player_bullet[nextFree].y = ypos;
      player_bullet[nextFree].speed = weapon[ego_stats.active_weapon].speed * modifier ;
      
      player_bullet[nextFree].sprite = weapon[ego_stats.active_weapon].bullet_sprite;
      player_bullet[nextFree].active = true;
      
      if (weapon[ego_stats.active_weapon].type == eWT_Freeway) {
        player_bullet[nextFree].vector_x = IntToFloat(mouse.x - player.x);
        player_bullet[nextFree].vector_y = IntToFloat(mouse.y - player.y);
        player_bullet[nextFree].Normalize();
        
      }

    }
  }
}


// And here's the drawing, called by rep_exec
static function TENG::draw_bullets() 
{
 
  DrawingSurface *surface = Room.GetDrawingSurfaceForBackground();
  
  int counter = 0;
  
  // draw player_bullets
  while (counter < MAX_BULLETS) {
    if (player_bullet[counter].active) {
      player_bullet[counter].x += FloatToInt(player_bullet[counter].vector_x) * weapon[ego_stats.active_weapon].speed;
      player_bullet[counter].y += FloatToInt(player_bullet[counter].vector_y) * weapon[ego_stats.active_weapon].speed;
 
      surface.DrawImage(player_bullet[counter].x, player_bullet[counter].y, player_bullet[counter].sprite);
    }
    counter ++;
  }
  
  surface.Release();
  
}



"Argh" says

n00bstauber


@Monsieur OUXX
As soon as this works I'll store the vector as an integer ;)

Monsieur OUXX

Quote from: abstauber on Fri 30/04/2010 13:47:10
Okay, you may now start laughing. Even with all this helpI can't get it to work  :'(

Yeah but you didn't say what actually doesn't work...
 

abstauber

Quote
Right now bullets can fly to 0° ,45° to the left  90° left and get stuck but render at 90° to the right.

That's about what's not working ;) Those bullets should fly in all directions.

Kweepa

The problem is you're storing the x and y positions of the bullets as ints, which loses all the precision of the bullet direction. You should store those (bullet.x and bullet.y) as floats, and only convert to int temporarily to draw the bullet.

EDIT: Or never, if you use the DrawAntialiased module! [/shameless plug off]
Still waiting for Purity of the Surf II

abstauber

Oh dear, thanks Steve!

Next time I'll only listen to Monsieur OUXX, when I understand what I write.  :-[


SMF spam blocked by CleanTalk