Projectile shooting enemy

Projectile enemies are enemies that toss objects, such as arrows and fireballs, at the player if the player comes close enough. For the projectile to hit the player, there is a base from which the projectile will be generated. Depending on the distance from the player, the base will launch the projectile at a calculated angle and speed so that the projectile can reach the player. Sometimes, to increase the chance of hitting the player, more than one projectile is shot. In this example, we will shoot just one projectile, but we will make sure that we give the player a tough time.

Getting ready

I included the assets for the base and the rocket in the resource folder, so make sure that they are included in the project.

We will create two classes. The first one will be the shooter base, which will be responsible for checking the distance between the player and base. If the distance is less than a certain amount, then the projectile will be launched at the player. The projectile will be a separate class, which will handle the behavior of the projectile.

How to do it…

First, let's create the ShooterBase class. Create a new file called ShooterBase, and in the interface file, add the following:

#import "CCSprite.h"

@interface ShooterBase : CCSprite{

  CCSprite* _hero;
  CGPoint startPos;
  int xDirection;
  float xSpeed;

  //float xAmplitude;

  int shootCounter;
  
}

-(id)initWithFilename:(NSString *) filename Hero:(CCSprite*) hero pos: (CGPoint) position Direction: (int)Direction;

@end

This is exactly same as the PatrolAI class implementation. However, once again, we won't include the amplitude variable here as it is not required.

Next, let's take a look at the implementation file, which is as follows:

#import "ShooterBase.h"
#import "ProjectileAI.h"

@implementation ShooterBase


-(id)initWithFilename:(NSString *) filename Hero:(CCSprite*) hero pos: (CGPoint) position Direction: (int)Direction
{
  if (self = [super initWithImageNamed:filename]) {

    _hero = hero;
    xDirection = Direction;
    xSpeed = 10.0f;
    shootCounter = 0;

    [self setPosition:position];

    startPos = self.position;
  }

  return self;
}

In the init function, we will set the initial values as usual by executing the following code:

- (void)update:(CCTime)delta {

  shootCounter -- ;
  if(shootCounter <= 0)
    shootCounter = 0;

    float dist = ccpDistance(self.position, _hero.position);


  if(dist < 300){

    if(shootCounter == 0){

      shootCounter = 30;

      [self shoot];
    }
  }


  if(self.position.x - _hero.position.x < 0)
    self.scaleX = -1.0;
  else if(self.position.x - _hero.position.x > 0)
    self.scaleX = 1.0;

}

As in the case of PatrolAI, we will decrement the counter. We will check whether it is less than zero; if it is, then we will set its value to zero.

As the shooter base is stationary, we won't change the position of the base. However, if you want the shooter base to move, you can add this functionality to it as we did with the PatrolAI class.

We will then get the distance between the player and the shooter base. If it is less than 300 then we call the shoot function and set the counter to 30.

The shooter base is also scaled in the X direction so that it faces the player while shooting:

-(void)shoot{

  ProjectileAI* projectileRocket = [[ProjectileAI alloc]initWithFilename:@"ProjectileRocket.png"
    Hero:_hero
    pos:self.position
    Direction:self.scaleX * -1.0];
    [[self parent]addChild:projectileRocket];

}


@end

In the shoot function, we will create an instance of ProjectileAI and pass in the image, the reference to the hero object, the position from which you want the projectile to be launched, and the direction in which the projectile needs to be launched.

Finally, we will add the projectile to the parent of the current class.

That is all for the ShooterBase class! Let's add the ProjectileAI class next.

First, let's take a look at the interface file. Add the following to the file:

#import "CCSprite.h"

@interface ProjectileAI : CCSprite{

  CCSprite* _hero;
  CGPoint startPos;
  int xDirection;
  float speed;

  bool isAlive;

  CGPoint velocity;
  CGPoint jumpForce;
  CGPoint gravity;
  CGPoint drag;

}

-(id)initWithFilename:(NSString *) filename Hero:(CCSprite*) hero pos: (CGPoint) position Direction: (int)Direction;

@end

You will immediately see that apart from the usual, this time, we also have a few other variables, such as velocity, jumpForce, gravity, and drag.

To make sure that the projectile does a good job of hitting the player, we will make use of some math and physics. We will use the projectile equation to calculate with how much force the projectile should be launched to get a successful target hit.

To get the exact force, we will set the angle of attack, gravity, and drag. So, let's go into the implementation and set the values, as follows:

#import "ProjectileAI.h"

@implementation ProjectileAI

-(id)initWithFilename:(NSString *) filename Hero:(CCSprite*) hero pos: (CGPoint) position Direction: (int)Direction
{
  if (self = [super initWithImageNamed:filename]) {

    _hero = hero;
    xDirection = Direction;
    speed = 10.0f;

    [self setPosition:position];

    startPos = self.position;

    gravity = ccp(0, -120);
    drag = ccp(1.0, 1.0);
    isAlive = true;

    float distX = hero.position.x - self.position.x;
    float distY = hero.position.y - self.position.y;

    distX = fabsf(distX);

    // ** angle constant
    float shootAngle = 45.0f;
    shootAngle =  - CC_DEGREES_TO_RADIANS(shootAngle);

    float angle2= sin(2 * shootAngle);
    float force = sqrtf((distX * gravity.y )/(angle2));

    CCLOG("force: %f", force);

    shootAngle =   - CC_RADIANS_TO_DEGREES(shootAngle);
    jumpForce = ccp(xDirection * force * cosf(shootAngle), force * sinf(shootAngle));
    velocity = ccpAdd(velocity, jumpForce);


  }

  return self;
}

In the init function, we will set the usual variables, such as speed, direction, and position. Then, we will set gravity and drag.

As gravity always acts downwards, we only set the Y direction. The value of gravity needs to be tinkered with to make sure we get an exact hit on the player. The drag accounts for air friction, and the values are set to 1 in the x and y directions to ignore any air friction. This can be modified according to the needs of your game.

Then, we will set the isAlive variable to true.

We will then calculate the distance in the x and y directions separately. Then, we will get the absolute value in the x direction as we are only concerned with the distance and don't care whether it is positive or negative.

To calculate the force, we will first set the shooting angle to 45 degrees. Then, we will calculate it in radians and multiply it by -1.

A new variable called value will be created, which stores the value of sin(2 * angle). Then, the formulae for calculating the force will be given by the square root of the distance in the x direction multiplied by the gravity in the y direction and divided by the sin(2 * angle) value. As we already calculated the value of sin(2 * angle), we will just divide it by this to make it easier.

Now, to actually apply the force to the projectile, we have to first convert the angle back to degrees and multiply it by -1.

Now, the jump force or initial velocity of the projectile in the x direction is the direction in which the projectile needs to be launched multiplied by the force, further multiplied by the cosine of the angle. The force in the y direction is given by the force multiplied by the sine of the angle. Finally, for the resultant initial velocity the calculated jump force is added to it. Take a look at the following code:

- (void)update:(CCTime)delta {


  CGPoint mGravityStep = ccpMult(gravity, delta);
  velocity = ccpAdd(velocity, mGravityStep);

  CGPoint moveSpeed = ccp(xDirection * speed,  1 * speed);
  CGPoint moveSpeedStep = ccpMult(moveSpeed, delta);

  velocity = ccpAdd(velocity, moveSpeedStep);

  CGPoint mVelocityStep = ccpMult(velocity, delta);

  CGPoint initpos = self.position;

  CGPoint finalpos = self.position = ccpAdd(self.position, mVelocityStep);


  float dx = finalpos.x - initpos.x;
  float dy = finalpos.y - initpos.y;

  float rotAngle = atan2f(dy, dx);

  rotAngle = CC_RADIANS_TO_DEGREES( - rotAngle);

  [self setRotation:rotAngle];


  if(self.position.y < 75 && isAlive){

    isAlive = false;
    [self removeFromParent];
  }


  if(isAlive){
    if(CGRectIntersectsRect(self.boundingBox, _hero.boundingBox)){

      isAlive = false;
      [self removeFromParent];
    }
  }


}



@end

In the update function, to make the gravity independent of the processor speed, we will calculate gravityStep, which is the multiplication of gravity by delta time. The gravityStep variable is added to the velocity.

Then, the current movement speed is calculated. For the x direction, we will multiply it by the direction in which we want the projectile to move. Then, the moveStep variable will be calculated by multiplying the speed by the delta time. The moveStep variable is then added to the velocity, and velocityStep is calculated.

Finally, the velocity is added to the current position to get the final position of the projectile.

In order for the projectile to rotate properly and face the direction of movement, we have to do a little more calculation.

To calculate the rotation angle, we will first get the change in distance between the previous frame and the next frame in both the x and y directions. Then, we will calculate the slope by calculating the tan inverse of the distance in the y direction divided by the distance in the x direction.

We will multiply the angle by -1 and then convert the value from radians to degrees.

Finally, we will set the current rotation of the object to the newly calculated value.

After this, we will first check whether the current y position is less than 75; if it is, then we will set the isAlive Boolean to false and remove the self from the parent.

Next, we will check for the collision between the player and projectile. If true, then we will again set the isAlive Boolean to false and remove the current class from the parent.

How it works…

To see our projectile in action, we have to import the ShooterBase.h header into the MainScene.h header.

Now, after PatrolAI, we will add the following and comment out the PatrolAI code as it is not required any more:

        CGPoint startPos = CGPointMake(winSize.width * .5 , winSize.height/4);

        //PatrolAI* pEnemy = [[PatrolAI alloc] initWithFilename:|       @"enemy.png" :hero :startPos];
        //[self addChild: pEnemy];

        ShooterBase* shooterBase = [[ShooterBase alloc]initWithFilename:@"RocketBase.png" Hero:hero pos:startPos Direction: 1.0];
        [self addChild:shooterBase];

The projectile is ready to attack the player.

How it works…
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.187.210