Simple Singleton and Node Porting Problem

Discussion in 'iOS Programming' started by TwinofSian, Dec 31, 2011.

  1. TwinofSian macrumors member

    Joined:
    Oct 23, 2011
    #1
    I have been trying to port over a bullet engine which features a Bullet and BulletCache class into another project. My main difficulty is that the Bullet/BulletCache methods are defined with the assumption of a GameScene singleton. This is a bad practice and the code I am trying to port it to does not have a singleton GameScene class.

    In fact, the only singelton this code has is for the Sound engine!


    Code:
    -(void) checkForBulletCollisions
    {
    	EnemyEntity* enemy;
    	CCARRAY_FOREACH([batch children], enemy)
    	{
    		if (enemy.visible)
    		{
    			BulletCache* bulletCache = [[GameScene sharedGameScene] bulletCache];
    			CGRect bbox = [enemy boundingBox];
    			if ([bulletCache isPlayerBulletCollidingWithRect:bbox])
    			{
    				// This enemy got hit ...
    				[enemy gotHit];
    			}
    		}
    	}
    }
    Game classes:

    Code:
    @interface GameScene : CCScene
    	+(void) newGame:(NSString*)levelFile;
    	-(void) reloadGame:(NSString*)levelFile;
    @end
    
    @interface GameLayer : CCLayer
    	{
    		CGPoint cameraOffset,cameraMin,cameraMax;
    	}
    	-(void) moveCamera:(CGPoint)pos;
    	-(CGPoint) getCameraPosition;
    @end

    the code calls the GameScene because in the original code, that is where the BulletCache is, but in my code, BulletCache is a subclass of LevelObject (which is a subclass of CCNode) and so my accessors are in the Level Object.

    However, LevelObject is not a singelton; I need some tips on how to cleanly port this code over. Thanks!
     
  2. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #2
    There are various ways to do this. One would be to rewrite this checkForBulletCollisions method so it takes as parameters the various state values (list of enemies, bullet cache, whatever else) does its calculation and returns an array of enemies that gotHit. Then this method could live anywhere or be a simple function rather than a method.

    Another would be to store as a property the owner of the bulletCache or a reference to the bulletCache itself. Then in this method just reference the bulletCache. For something like this you would typically set this property either in an init method or shortly after the object is created.

    BTW, I would move the "bulletCache =" line outside of the loop in this method.
     
  3. TwinofSian, Dec 31, 2011
    Last edited: Dec 31, 2011

    TwinofSian thread starter macrumors member

    Joined:
    Oct 23, 2011
    #3
    Hey PDev, thanks for the advice, definitely appreciate it. Being new to coding, I was caught up by your phrasing:

    To that end, I created added the BulletCache class as a property in the Character class (subclass of LevelObject)

    Code:
    Character.h
     @property BulletCache,CGFloat life,maxLife,strength,dexterity;
    
    Code:
    Character.m
    @synthesize BulletCache,life,maxLife,strength,dexterity;

    Code:
    -(void) checkForBulletCollisions
    	{
    		BulletCache* bulletCache;
    		Character* character;
    		CCARRAY_FOREACH([batch children], 
                                                            character)
    		{
    			if (character.visible)
    			{
    			CGRect bbox = [enemy boundingBox];
    			if ([bulletCache 
                                 isPlayerBulletCollidingWithRect:bbox])
    				{
    				// This character got hit ...
    					[character gotHit:(id)sender data:
                                                            (void*)data];
    				}
    			}
    		}
    	
    	}
    
    I am still at loss about the init method you were describing. In which class's init method do I create a BulletCache object?
     
  4. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #4
    Well, I don't know where you're creating the BulletCache object. But there is some controller object that is doing the hit-testing and which needs access to the BulletCache object. So when you create this controller object either set its BulletCache property or pass the BulletCache object into its init method. Something like

    Code:
    MyController* controller = [[MyController alloc] init];
    controller.bulletCache = theBulletCache;
    
    or

    Code:
    MyController* controller = [[MyController alloc] initWithBulletCache:theBulletCache];
    These are effectively the same. Do whichever makes sense to you.

    Then in the checkForBulletCollisions method use self.bulletCache.
     
  5. TwinofSian, Dec 31, 2011
    Last edited: Dec 31, 2011

    TwinofSian thread starter macrumors member

    Joined:
    Oct 23, 2011
    #5
    You've really made me think about how to tackle this, which is awesome, that really helps me.

    However, I am truly stumped about this MyController class being instanced in the BulletCache.

    There are two bullet-related classes: The Bullet Class, and the BulletCache class.

    Code:
    Bullet.h
    Code:
    @interface Bullet : LevelObject
    {
    	CGPoint velocity;
    	bool isPlayerBullet;
    }
    
    @property (readwrite, nonatomic) CGPoint velocity;
    @property (readwrite, nonatomic) bool isPlayerBullet;
    
    +(id) bullet;
    
    -(id)initWithBulletImage
    
    -(void) shootBulletAt:(CGPoint)startPosition velocity:
           (CGPoint)vel frameName:(NSString*)frameName 
                                      isPlayerBullet:(bool)playerBullet;
    
    -(void) update:(ccTime)delta;
    
    
    @end

    Code:
    BulletCache.h
    Code:
    @interface BulletCache : LevelObject
    
    {
    	CCSpriteSheet* batch;
    	int nextInactiveBullet;
    }
    
    -(id) init;
    
    -(void) shootBulletFrom:(CGPoint)startPosition 
                                   velocity:(CGPoint)velocity 
    	            frameName:(NSString*)frameName 
                              isPlayerBullet:(bool)isPlayerBullet;
    
    -(bool) isPlayerBulletCollidingWithRect:(CGRect)rect;
    
    
    @end

    I am genuinely curious about this MyController however, it does not appear to be mentioned anywhere in the project I was porting the code from.
     
  6. TwinofSian thread starter macrumors member

    Joined:
    Oct 23, 2011
    #6
    I have ported over a Bullet and BulletCache class. I was able to get the game to compile on the simulator with 1 warning even with the Bullet and BulletCache classes implemented.


    The Bullet is a subclass of CCZSprite, while BulletCache is a subclass of CCNode.

    HOWEVER:

    In the Player (a subclass of Character) class I am overriding a Character method:


    Code:
    -(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];
    	}
    And I am trying to get this method to fire off bullets.


    Code:
    -(void) attack:(Character*)attackee
    {
    	BulletCache* bulletCache;
    
    	// 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];
    
    	if ([self runAction:action] == YES)
    	{
    		CGPoint shotPos = CGPointMake (self.position.x, self.position.y);
    		float spread = (CCRANDOM_0_1() - 0.5f) * 0.5f;
    		CGPoint velocity = CGPointMake(4, spread);
    		[bulletCache shootBulletFrom:shotPos velocity:velocity 
                                frameName:@"bullet1big.png"
    					  isPlayerBullet:YES];
    	}
    That's what I tried, but I got a Warning: Comparison between pointer and Integer.

    Needless to say, when the attack method was called (after hitting the attack button with an enemy in an adjacent tile) the game crashed.

    Any tips would be loved. Happy new year!
     

Share This Page