Adventure Game Studio

AGS Support => Beginners' Technical Questions => Topic started by: Duckbutcher on Sun 16/09/2018 06:53:39

Title: Continuous shooting with spacebar
Post by: Duckbutcher on Sun 16/09/2018 06:53:39
Hey people, hope you're all well.

I'm playing about with a keyboard controlled top down shooter sort of game. Arrow keys to move and space to fire so far.

I'm using a character (cfire) as the projectile, and have this character binded behind the player character using FOLLOW_EXACTLY.

Upon pressing the space bar, the game checks which direction the character is facing and moves the projectile 50 degrees away from the character in the appropriate direction. There is a short timer which allows the projectile to achieve this distance before respawning behind the player character for the next shot. It works pretty good.

My problem is I have to repeatedly hammer the space bar in order for this to work - holding the spacebar produces some odd effects, ie the projectile slowing to aãâ,¬â,¬snail's pace before stopping suspended in the air. Ideally I don't want to give my players broken wrists, so if there's a simple way to have the projectile fire at a steady rate while holding the spacebar down, I'd be really grateful if you could let me know! I suspect the problem may be something to do with the timer but I'm not sure what to do.

Here's the code, from globalscript function on keypress.

Code (ags) Select


  // FUNCTION KEYS AND SYSTEM SHORTCUTS
 
  if (keycode == eKeySpace){
   cfire.FollowCharacter(null);
   if (player.Loop==2){
   cfire.FaceDirection(eDirectionRight);
   cfire.Move(cego.x+50, cego.y+0, eNoBlock, eAnywhere);
   SetTimer(1, 6);
   }
   if (player.Loop==1){
   cfire.FaceDirection(eDirectionLeft);
   cfire.Move(cego.x-50, cego.y-0, eNoBlock, eAnywhere);
   SetTimer(1, 8);
   }
   if (player.Loop==0){
   cfire.FaceDirection(eDirectionDown);
   cfire.Move(cego.x+0, cego.y+50, eNoBlock, eAnywhere);
   SetTimer(1, 8);
   }
   if (player.Loop==3){
   cfire.FaceDirection(eDirectionUp);
   cfire.Move(cego.x-0, cego.y-50, eNoBlock, eAnywhere);
   SetTimer(1, 8);
    }
  }


And here is the timer check in global rep- ex.

Code (ags) Select

  if (IsTimerExpired(1)){
   cfire.x=cego.x;
   cfire.y=cego.y;
   cfire.FollowCharacter(cego, FOLLOW_EXACTLY, 1);
   return;
  }
Title: Re: Continuous shooting with spacebar
Post by: Slasher on Sun 16/09/2018 08:40:58
For a start i would increase the bullets speed... and 6 on a timer is slow, 40 is a second,...
I generally go about this scenario differently....
Title: Re: Continuous shooting with spacebar
Post by: Duckbutcher on Sun 16/09/2018 08:50:44
I'm open to suggestions, if there's a better way to tackle this situation I'm all ears. I've never attempted to do something like this before and as ags isn't really set up for this kind of game I kind of just threw stuff at the wall to see what would stick..!
Title: Re: Continuous shooting with spacebar
Post by: Mandle on Sun 16/09/2018 09:53:30
There's something like "IsKeyPressed"
Title: Re: Continuous shooting with spacebar
Post by: Khris on Sun 16/09/2018 18:27:34
Quote from: Duckbutcher on Sun 16/09/2018 08:50:44as ags isn't really set up for this kind of game
Not quite true; the engine is quite flexible and can easily deal with held down keys.
All you need to do is use repeatedly_execute and IsKeyPressed() instead of on_key_press().
Title: Re: Continuous shooting with spacebar
Post by: Monsieur OUXX on Mon 17/09/2018 18:54:45
What people said. There's some interesting ideas mixed with some clunky aspects in the solution.
I defintely don't get what FollowCharacter has to do with it.

I'd go like this :
(not compiled, not tested)

Code (ags) Select

#define MAX_PROJECTILES 6 //if you want to be able to fire repeatedly
#define COOLDOWN 30 //we allow the player to shoot once every 30 frames, which is approximately 500ms at 60FPS
#define PROJECTILE_SPEED 1.0 //how much the projectile moves during each frame
#define PROJECTILE_LIFESPAN 240 //A projectile can live up to 240 game cycles, which is 4 seconds at 60FPS

struct ProjectileStruct {
    bool inUse;
    Character* c;
    float x; //coordinates in float to free ourselves from moving the projectiles at speeds that must match exactly screen pixels
    float y;
    float angleInRadians; //Reminder : 360 degrees = 2*Pi
    int age; //how many game cycles have elapsed since it was created
};
ProjectileStruct projectiles[MAX_PROJECTILES];
int coolDown;




void MakeProjectile(int p)
{
  projectiles[p].inUse = true;
 
   int direction = -1;
   float angle = 0.0;
   int xOffset = 0; int yOffset = 0;
   
   switch (player.Loop){
case 0 :
direction = eDirectionDown; xOffset = 0; yOffset = 50; angle = 3.0*Maths.Pi/2.0; // (3/2)Pi = 270 degrees = down
break;
case 1 :
direction = eDirectionLeft; xOffset = -50; yOffset = 0; angle = 2.0*Maths.Pi; // 2Pi = 180 degrees = left
break;
case 2 :
direction = eDirectionRight; xOffset = 50; yOffset = 0; angle = 0.0; // 0 = 0 degrees = right
break;
case 3 :
direction = eDirectionUp; xOffset = -50; yOffset = 0; angle = Maths.Pi/2.0; // Pi/2 = 90 degrees = up
break;
   }
   projectiles[p].c.Visible = true;
   projectiles[p].c.Move(cego.x+xOffset, cego.y+yOffset, eNoBlock, eAnywhere);
   projectiles[p].c.FaceDirection(direction);
   projectiles[p].x = IntToFloat(projectiles[p].c.x);
   projectiles[p].y = IntToFloat(projectiles[p].c.y);

   projectiles[p].age = 0;
}


//Update projectiles location at each screen refresh
void MoveProjectiles()
{

  for (int p=0; p<MAX_PROJECTILES; p++) {
if(projectiles[p].inUse) { //in use
  //Our accurate locations, in float
  projectiles[p].x+=Maths.Cos(projectiles[p].angle)*PROJECTILE_SPEED;
  projectiles[p].y+=Maths.Sin(projectiles[p].angle)*PROJECTILE_SPEED;
 
  //Our on-screen locations, in pixels (rounded from the float value)
  projectiles[p].c.x = FloatToInt(projectiles[p].x);
  projectiles[p].c.y = FloatToInt(projectiles[p].y);
}
  }

}

//Kill projectiles that have been around for too long
void KillProjectiles()
{

  for (int p=0; p<MAX_PROJECTILES; p++) {
    if (projectiles[p].inUse) {
  projectiles[p].age++;
  if (projectiles[p].age > PROJECTILE_LIFESPAN) {
  projectiles[p].inUse = false;
  projectiles[p].c.Visible = false;
  projectiles[p].age = 0;
  }
}
  }

}

void ManageShooting()
{
  if (IsKeyPressed(eKeySpace)) {
    if(coolDown==0) {
      coolDown = COOLDOWN; //reset cooldown
      //Find a projectile that's not in use
      for (int i=0; i<MAX_PROJECTILES; i++) {
        if(!projectiles[i].inUse) { //found!
  MakeProjectile(i);
  break; //stop looking immediately
}
      }
    }
  }
}

void repeatedly_execute()
{
  if (coolDown > 0)
    coolDown--;

  ManageShooting();
  MoveProjectiles();
  KillProjectiles();

}


//Bind your in-game projectiles with some actual characters from your game, that you created in the editor
void InitProjectiles()
{
  projectiles[0].c = cFire0;
  projectiles[1].c = cFire1;
  projectiles[2].c = cFire2;
  projectiles[3].c = cFire3;
  projectiles[4].c = cFire4;
  projectiles[5].c = cFire5;
}

void game_start()
{
  SetGameSpeed(60); //600FPS is probably better than AGS' weird native 40FPS
  InitProjectiles();
}

Title: Re: Continuous shooting with spacebar
Post by: Duckbutcher on Wed 19/09/2018 14:31:11
Monsieur, looks like you put in a lot of work there, thanks. And regarding the follow character command, this was how I was masking the projectile - hiding it behind the player character ready to be called to move. I guess it does seem a bit daft but I was making it up as I went along.

Regarding your code, it's way beyond my current level of understanding and while I can understand the general gist, I've put it into my game and got a couple of errors. Float angle = 0 under void makeprojectile throws up a “can't convert int to float” error, and after pulling bits of the code apart to see how they work in isolation I also get an error that “math” is undefined. At the moment everything is at the top of global script, so if it needs to be anywhere specific please let me know.

Thanks for your time, appreciate it.
Title: Re: Continuous shooting with spacebar
Post by: dayowlron on Wed 19/09/2018 16:22:36
in line 25 change it to:
float angle=0.0;
and in lines 30,33, and 39 add an s onto Math to
... Maths.Pi
Title: Re: Continuous shooting with spacebar
Post by: Monsieur OUXX on Thu 20/09/2018 09:49:57
OK, if FollowCharacer was used to position the projectile at the same location as the player at the beginning of the shooting, then it makes sense, but I have a tendency not to trust an elaborate function to just teleport my object one time to a given location. I prefer to change .x and .y and be done with it. FollowCharacter might force the projectile to keep following the character or some unforeseen side effects of the same sort.

My code works as follows :

1) A bunch of #define to create some constants (that's some oddity inherited from ye olde C language).

2) Create 6 projectiles that I put in an array

3) the projectiles are "objects" (structs) where I store all the variables needed (x, y, angle, etc.). Each projectile also contains one Character* c, which is an actual AGS Character used to represent the projectile on-screen and move it around, just like you did yourself.

4) At game start, I bind Character* c of each projectile in the array to an actual AGS character that you must have created beforehand in the editor. It's the same idea as your dummy "cFire" character, except now there's 6 of them.

5) At each game loop, we check if the Space key is down and if the cooldown has elapsed (to prevent infinite instant firing). If yes, then we check if one of the 6 characters is "not in use" already, and if yes we place it at the same screen position as the player (+ or - 50 pixels, to make it start from the player's gun location) and give it an angle corresponding to the player's orientation.

6) At each game loop, we update the location of every projectile that is currently in use. that's your typical x = x+cos(angle); y=y+sin(angle)

7) At each game loop, we delete every projectile that is in use but has become too old. It's a way of recycling them to allow new projectiles to be created.
Title: Re: Continuous shooting with spacebar
Post by: Duckbutcher on Thu 20/09/2018 16:17:09
So I had a bit of a tinker with this again today. Thanks for the corrections dayolron and thanks again Monsieur for the clarification, though I was more apologising for my inevitable questions! That makes everything a lot clearer, appreciated.

I've changed a couple of things, including changing a projectiles[p].c.Visible=true; to projectiles[p].c.on=1; as (I think) characters have to be turned on and off like this rather than with .Visible=.

I've also hit another snag, regarding this bit:

Code (ags) Select

//Update projectiles location at each screen refresh
void MoveProjectiles()
{

  for (int p=0; p<MAX_PROJECTILES; p++) {
        if(projectiles[p].inUse) { //in use
          //Our accurate locations, in float
          projectiles[p].x+=Maths.Cos(projectiles[p].angle)*PROJECTILE_SPEED;
          projectiles[p].y+=Maths.Sin(projectiles[p].angle)*PROJECTILE_SPEED;
         
          //Our on-screen locations, in pixels (rounded from the float value)
          projectiles[p].c.x = FloatToInt(projectiles[p].x);
          projectiles[p].c.y = FloatToInt(projectiles[p].y);
        }
  }

}


Error is '.angle' is not a public member of 'ProjectileStruct'.
Title: Re: Continuous shooting with spacebar
Post by: Monsieur OUXX on Fri 21/09/2018 15:53:00
Quote from: Duckbutcher on Thu 20/09/2018 16:17:09
Error is '.angle' is not a public member of 'ProjectileStruct'.

Well as you can see here :
Code (ags) Select

struct ProjectileStruct
{
   ...
   float angleInRadians;
}


The member is named angleInRadians, not angle. Just change the name accordingly everywhere I made the mistake.

Since we're there, I suggest you change this :
#define PROJECTILE_SPEED 1.0
to this :
#define PROJECTILE_SPEED 5.0

This will make projectiles move 5 pixels during each game loop, instead of only 1 (they would be super slow and probably overlap)