HomeForumsGeneral DiscussionAdding a Bullet Class

This topic has 2 voices, contains 18 replies, and was last updated by  joeb 117 days ago.

Viewing 19 posts - 1 through 19 (of 19 total)
Author Posts
Author Posts
December 15, 2011 at 3:21 am #580

GPP

So I’d like to add a bullet class and bullet cache class to Quexlor, but how should I go about organizing them?

December 15, 2011 at 4:24 am #2202

joeb

There’s already a projectile class in the items.m you can start there. You’ll have to speed up the travel distance per delta by at least 3x, maybe more.

December 15, 2011 at 4:51 am #2203

GPP

Hey Joeb, the Items.m file I am looking does not have a projectile class, but I have code for writing both a Bullet and a Bullet Cache class. I created both files, one titled Bullet.h/m and one BulletCache.m Because Quexlor is importing the iphoneGameKit, which in turn is importing Foundations & the UIkit and Cocos2d files, I think it should be safe to just import Quexlor.h into both files right?

So just for clarity here’s my structure so far:

Character.h
#import "Quexlor.h"
#import "Bullet.h"
//code

In the Bullet.h class

#import "Quexlor.h"
@interface Bullet : CCSprite
{
CGPoint velocity;
float outsideScren;
}
//..code
-(void) shootBulletFromPlayer: (Player*) playerGun;

Bullet.m

@interface Bullet (PrivateMethods)
-(id) initWithBulletImage;
@end
@implementation Bullet
//code
-(void) shootBulletFromPlayer: (Player*) playerGun
{
float spread = (CCRANDOM_0_() -0.5f) * 0.5f;
velocity = CGPointMake (1, spread);
 outsideScreen = [[CCDirector sharedDirector] winSize].width;
self.position = CGPointMake(ship.position.x + ship.contentSize.width * 0.5f, ship.position.y);
self.visible = YES;
[self scheduleUpdate];
}
-(void) update: (ccTime) delta
{
self.position = ccpAdd (self.position, velocity);
if (self.position.x > outsideScreen)
{
self.visible = NO;
[self unscheduleAllSelectors];
}
}
@end

The -(void) update:(ccTime)delta is an override method, I believe, because it’s not declared in the interface, but please correct me if I am wrong.

My next step is figuring out:
1) how to get the player sprite to play the “attack sword animation” while shooting out bullets
2) To get those bullets to hit the enemy
3) To integrate the classes with everything else.
4) To make sure those bullets are tied to the appropriate sprites.

December 15, 2011 at 4:50 pm #2204

joeb

It’s going to be a little tougher than you might have initially planned. Collision detection has to play a part as well. Yes you’re right about the update being declared in a parent.

1) how to get the player sprite to play the “attack sword animation” while shooting out bullets

This one isn’t so bad and can be solved by using the code from the attack method in the character.m
You can basically ignore the pointtoward part and just run the animation section when you shoot.

2) To get those bullets to hit the enemy

Think about this one and how you want it to behave. Bullet’s don’t target the enemy per se, things (enemies) just happen to be standing where the vector directs the bullets to. Unless of course you develop a gun item that fires ‘smart’ bullets that seek the enemy. But don’t worry, firing ‘dumb’ bullets is easy. Just create them, send them on their way, and let collision detection do the rest.

3) To integrate the classes with everything else.

You’re object oriented, so once you create the bullet object, it should ‘know’ what it’s doing as far as where it’s going and when it hits something. This means your update delta method in the bullet class will have to have collision detection.

4) To make sure those bullets are tied to the appropriate sprites.

Here is the shotarrow class which is a child of the projectile class.

Replace the arrow.png with your bullet.png
adjust the speed, damage, and maxlife. Using the maxlife instead of trying to detect when it leaves the screen is a much slicker way of dealing with the object. No need to reinvent the wheel. Note the can hit player in the update that checks to see if it hit an enemy. You’ll need to change this to a look that checks the enemies in the level to see if it has found/hit any of them. This is where you will likely want to check to see if it hit other solid objects also (like walls or other obstacles)
And don’t forget to change the vector creation to direct the bullet object in the direction your player is facing instead of a vector toward where the player is.

@implementation ShotArrow
	-(id) init
	{
		return [super initWithImage:@"arrow.png"];
	}
	-(void) onEnter
	{
		[super onEnter];
		if( vector.x == 0.0f && vector.y == 0.0f )
		{
			// establish a vector toward player
			float angle = ccpToAngle(ccpSub([self getPlayer].position, self.position));
			angle += (CCRANDOM_MINUS1_1() * M_PI * 0.125f);
			vector = ccpForAngle(angle);
			// rotate arrow
			sprite.rotation = ((-angle + (M_PI / 2.0f)) / M_PI) * 180.0f;
			sprite.scale = 0.8f;
		}
		if( speed == 0.0f )
		{
			speed = 300.0f;
			damage = 1.5f;
			maxLife = life = 5.0f; // long life means it will travel further and be more visible
		}
	}
	-(void) update:(ccTime)delta
	{
		// use Projectile's update method to move
		[super update:delta];
		// can hit player?
		if( [self isNearPlayer:35.0f] )
		{
			[[self getPlayer] changeLife:-damage quiet:NO];
			[self destroy];
		}
	}
@end

December 15, 2011 at 8:20 pm #2205

GPP

Hey joeb, thanks for the awesome response. Unfortunately, my gamekit version does not have either a projectile or arrows subclass– at least not in the Items.h/m file.

Player.h:

#import "Quexlor.h"
BulletCache* playerCache;
@interface Player : Character
	{
		int swordLevel,experience,usableExperience,bones;
		NSMutableDictionary* misc;
		CCZSprite* effectClock;
		HudLevelSprite* effectAura,*effectFog;
		CCAnimation* effectAnimClock,*effectAnimAura;
	}
	@property int swordLevel,experience,usableExperience,bones;
	-(void) saveState;
	-(void) swingSword;
	-(void) destroyTile;
	-(void) playerShoot;
	-(void) levelUp:(NSString*)gid;
	-(void) animateClock;
	-(void) animateAura:(float)scale;
	-(void) setMisc:(NSString*)key value:(id)value;
	-(id) getMisc:(NSString*)key;
	-(BOOL) hasMisc:(NSString*)key;
	-(int) countSecrets;
	+(Player*) get;
	+(void) setDefaultState:(GameState*)state;
@end

Player.m

#import “Quexlor.h”
@interface Player () //private methods
-(Character*) findAttackee;
-(void) grow;
@end
@implementation Player
//code
-(void) playerShoot
	{
		playerCache.shootBulletFrom:(CGPoint)startPosition velocity:(CGPoint)velocity frameName:(NSString*)frameName isPlayerBullet:(bool)isPlayerBullet;
	}
//more code
@end

The definition for the method called on the playerCache comes from the BulletCache class:

-(void) shootBulletFrom:(CGPoint)startPosition velocity:(CGPoint)velocity frameName:(NSString*)frameName isPlayerBullet:(bool)isPlayerBullet
{
	CCArray* bullets = [batch children];
	CCNode* node = [bullets objectAtIndex:nextInactiveBullet];
	NSAssert([node isKindOfClass:[Bullet class]], @"not a Bullet!");
	Bullet* bullet = (Bullet*)node;
	[bullet shootBulletAt:startPosition velocity:velocity frameName:frameName isPlayerBullet:isPlayerBullet];
	nextInactiveBullet++;
	if (nextInactiveBullet >= [bullets count])
	{
		nextInactiveBullet = 0;
	}
}

The BulletCache class also has a -(bool) isPlayerBulletCollindgWithRect(CGRect)rect
method as well, so it looks like slowly, a collision detection system is being setup.

Quick question, these classes are cocos2d centric classes, is it enough that have these classes #import “Quexlor.h”
(and have Quexlor.h import these classes)?

I’m assuming Player.h should also import BulletCache.h as well as Quexlor.h. Am I wrong?

December 16, 2011 at 8:07 pm #2207

GPP
@interface BulletCache : CCNode
{
	CCSpriteBatchNode* batch;
	int nextInactiveBullet;
}

I keep getting an error on the CCSpriteBatchNode* batch; compile error says
[quote]expected specifier-qualifier-list before ‘CCSpriteBatchNode’[/quote]

Am I incorrectly importing the Bullet and BulletCache classe into the Quexlor files?

Here’s my Quexlor.h file:

#import "iPhoneGameKit.h"
#import "GameState.h"
#import "Game.h"
#import "LevelObject.h"
#import "Character.h"
#import "Bullet.h"
#import "BulletCache.h"
#import "Player.h"
#import "Enemies.h"
#import "Items.h"
#import "Triggers.h"
#import "Hud.h"
#import "MainMenu.h"
#import "CharacterMenu.h"
#import "VictoryMenu.h"
#import "AboutMenu.h"
#import "Level.h"

My Bullet.h file

#import "Quexlor.h"
@interface Bullet : CCSprite
{
	CGPoint velocity;
	float outsideScreen;
}
//more code

my BulletCache

#import "Bullet.h"
#import BulletCache.h"
//code
December 17, 2011 at 7:19 am #2208

joeb

What’s the difference between the bullet and the bullet cache?

December 17, 2011 at 7:29 pm #2209

GPP

The author writes that the BulletCache class is a “an in-between” CCNode. The BulletCache keeps an array of bullets instead of creating and releasing bullet objects over and over again.

BulletCache also has the shootBulletAt method which was modified to take three parameters.

-(BulletCache*) bulletCache
{
CCNode* node = [self getChildByTag:GameSceneNodeTagBulletCache];
NSAssert([node isKindOfClass:[BulletCache class]], @”not a BulletCache”);
return (BulletCache*)node;
}

However, in trying to integrate this code into the Quexlor project, I may have forgotten a key piece of code. The author writes that I also need to add a bulletCache accessor so other classes can access the BulletCache instance through the GameScene:

-(BulletCache*) bulletCache
{
CCNode* node = [self getChildByTag:GameSceneNodeTagBulletCache];
NSAssert([node isKindOfClass:[BulletCache class]], @"not a BulletCache");
return (BulletCache*)node;
}

I didn’t implement that yet, so that may be the next piece of the puzzle.

Although the author writes that his BulletCache is a child of the GameScene class, and mine is not. Is there a particular way you do that?

His BulletCache interface says:

@interface BulletCache : CCNode

which I thought just means its a child of the CCNode class.

December 17, 2011 at 11:25 pm #2213

joeb

Hm I suppose you could keep an array of a dozen bullets or so, make them invisible after use, and reset the location when they become a ‘new’ bullet; I guess if you go that way you’ll need to make sure to re-initialize it when it’s called upon for use, and you’ll also likely need some sort of bullet manager class (aha, that bullet cache looks to fit the description.)
Thinking it through, the job of the bullet cache doesn’t seem manage the bullets themselves as much as it looks through the array to determine if there are any that have ‘returned’ from use and can be used over again, then maybe calls an init method to make it ‘new’ for use again. You can go that route sure, but if you’re using dealloc on a bullet item before [self destroy] it should come out about the same. Just create em, let them do their work, then throw them away after use..

Remember, programmers are notoriously lazy, that’s why we like computers. They do all the work. I have encountered situations where I over-thought something and started adding conditions and token variables and timers etc until I sat back and realized I’d made it way more complicated than it had to be.

Personally, I’d create a new item class in the items.h and give it any necessary methods to govern its behavior after it is created, then let the player.attack trigger the creation, set the vector, and the bullet methods should know how to handle the rest.

Also keep in mind that the best way that something should be done is the way that makes sense to you, and most importantly, works! If you get it working, you can tweak and optimize until rapture – but when it comes down to it there’s working and not working; Not much of a gray line.

December 18, 2011 at 12:13 am #2214

GPP

joeb, thanks for the feedback, I am going to try this. I guess my only real hangup is conceptualizing, apart from my incredible newness to the material. I did read and complete Kochan’s book on objec-c so I have some background, but still, it can get very dizzying.

So I deleted both the bulletCache and Bullet Class References, and the game compiles successfully. However, when I swing the sword, the destroyTile method is called, and if there is no enemy on that tile, the game crashes, but only if there is no enemy. The console error says
[Player destroyTileCallback:]: unrecognized selector sent to instance 0x6887590'

Any help or suggestions?

Here’s the destroyTile definition:

-(void) destroyTile
	{
		// start animating the swing, when finished try to destroy a tile
		CCAction* action = [CCSequence actions:
			[CCAnimate actionWithAnimation:[profile getAnimation:@"attack" index:currentDir]],
			[CCCallFuncN actionWithTarget:self selector:@selector(destroyTileCallback:)],
			nil];
		action.tag = kActionAttack;
		[sprite runAction:action];
	}
December 18, 2011 at 11:20 pm #2215

joeb

Yes sometimes it’s a game of follow-the-breadcrumbs until you find the method/object that’s causing the issue. Take a look at the destroyTileCallback method to see if it’s looking for something that’s not there.

December 19, 2011 at 1:02 am #2216

GPP

It is odd though. All I did was delete the Bullet/BulletCache class references and then all of the sudden, this completely random method goes haywire on me. Wonder why that is.

December 20, 2011 at 5:17 am #2224

GPP

edited for readability.

December 21, 2011 at 12:15 am #2227

GPP

I’m also looking for the player.attack method, but I can’t find the damn thing. I looked in the Hud, Character, and Player classes, but neither has that method explicitly stated.

In the Character class I found the -(void) attack:(Character*)attackee method:

-(void) attack:(Character*)attackee
	{
		// get direction for animation
		[self pointToward:attackee.position];
		// create attack sequence (animation + gotHit callback)
		CCAction* action = [CCSequence actions:
			[CCAnimate actionWithAnimation:[profile getAnimation:@"attack" index:currentDir]],
			[CCCallFuncND actionWithTarget:attackee selector:@selector(gotHit:data:) data:self],
			nil];
		action.tag = kActionAttack;
		[sprite runAction:action];
	}

December 21, 2011 at 1:01 am #2225

joeb

you might be able to get away with just commenting out the point toward attackee and ensuring your bullets travel in a vector that matches the current DIR index. Should be no problem – it is likely though that you’ll need to redefine this method for the player.m so the point toward won’t apply for the player but can still be inherited by enemies etc that do want to point toward you when attacking.

December 21, 2011 at 1:44 am #2226

GPP

thanks joeb, what I was planning was copy-pasting that -(void)attack method’s definition in the player class with the following:

-(void) attack:(Player*)attackee
{
		// create attack sequence (animation + gotHit callback)
		CCAction* action = [CCSequence actions:
			[CCAnimate actionWithAnimation:[profile getAnimation:@"attack" index:currentDir]],
			[CCCallFuncND actionWithTarget:attackee selector:@selector(gotHit:data:) data:self],
			nil];
		action.tag = kActionAttack;
		[sprite runAction:action];
               //set the position, velocity, and sprite frames before shooting
              CGPoint shootPos = CGPointMake(player.position.x + [player contentSize] width * 0.5f,
                                                                                                                                        ship.position.y;
             float spread = (CCRANDOM_0_1() - 0.5f) * 0.5f;
             CGPoint velocity = CGPointMake (1, spread);
            [bulletCache shootBulletAt: shootPos velocity:velocity
                                                                                           frameName:@"bullet.png"];
}
January 1, 2012 at 10:50 pm #2235

GPP

joeb while you’re at it, could you paste the projectile class? I can’t do much if I only have a child class.

January 22, 2012 at 9:46 pm #2244

GPP

hey joeb, idk if you saw my last post, but could you post the projectile code? It’s not part of my Quexlor

January 23, 2012 at 3:53 am #2245

joeb

The projectile is in the current gamekit release, which should be available as a free lifetime update/upgrade to anyone who bought the rights to use it. Here’s the class as you requested, but you really should be able to get anything you need from the download.

@implementation Projectile
	@synthesize vector;
	-(void) setProperties:(NSDictionary*)props
	{
		[super setProperties:props];
		vector = CGPointZero;
		speed = 0.0f;
		damage = 0.0f;
		life = maxLife = 0.0f;
	}
	-(void) update:(ccTime)delta
	{
		// move
		self.position = ccpAdd(self.position, ccpMult(vector, delta * speed));
		// destroy after a bit
		life -= delta;
		if( life < 0.0f )
			[self destroy];
	}
	-(Enemy*) findEnemy
	{
		AI* ai = [[self getLevel] getAINear:self.position maxDistanceInTiles:0.5f];
		if( [ai isKindOfClass:[Enemy class]] )
			return (Enemy*)ai;
		return nil;
	}
@end
Viewing 19 posts - 1 through 19 (of 19 total)

You must be logged in to reply to this topic.