scheduledTimerWithTimeInterval only triggers once when while Repeats is set to YES - objective-c

I'm working through exercises in one of the Apress books. In the this chunk of code, an NSTimer object is created that is supposed to repeatedly trigger and call the gameloop function. I've set breakpoints in gameloop and it is only firing once. I'm trying to understand why. This seems fairly straightforward in terms of what it is supposed to do. Does anyone have guidance about what might be happening here?
#define RANDOM_SEED() srandom(time(NULL))
#define RANDOM_INT(__MIN__, __MAX__) ((__MIN__) + random() % ((__MAX__+1) - (__MIN__)))
#define kSteps 8
#define kSpeed 200
#define kFPS 20.0
#define kBounce 30
#define kDirForward 0
#define kDirBackward 1
#define kDirUp 2
#define kDirDown 3
static int kForward[] = {0,1,2,3,4,5,6,7};
static int kUpward[] = {8,9,10,11,12,13,14,15};
static int kDownward[] = {16,17,18,19,20,21,22,23};
static int kBackward[] = {24,24,25,26,27,28,29,30};
/** for mario.png
#define kSteps 6
static int kForward[] = {10,11,11,10,9,9};
static int kBackward[] = {22,23,23,22,21,21};
static int kUpward[] = {4,5,5,4,3,3};
static int kDownward[] = {16,17,17,16,15,15};
**/
- (id) initWithCoder: (NSCoder *) coder {
if (self = [super initWithCoder: coder]) {
test = [AtlasSprite fromFile: #"walk.png" withRows: 4 withColumns: 8];
test.angle = 0;
test.speed = kSpeed;
direction = kDirForward;
timer = [NSTimer scheduledTimerWithTimeInterval: 1.0/kFPS
target:self
selector:#selector(gameLoop)
userInfo:nil
repeats:YES];
self.backgroundColor = [UIColor whiteColor];
}
return self;
}
- (void) gameLoop
{
frame = (frame+1)%kSteps;
[test tic: 1.0/kFPS];
if (test.offScreen) {
RANDOM_SEED();
int toCenter = round(atan2(-test.y,-test.x)*180.0/3.141592);
if (toCenter < 0) toCenter += 360;
int bounce = (toCenter+RANDOM_INT(-kBounce,kBounce))%360;
if (bounce <= 60 || bounce >= 300) direction = kDirForward;
else if (bounce > 60 && bounce < 120) direction = kDirUp;
else if (bounce >= 120 && bounce <= 240) direction = kDirBackward;
else direction = kDirDown;
test.angle = bounce;
test.scale = 0.4+1.6*RANDOM_INT(0,10)/10.0;
while (test.offScreen) [test tic: 1.0/kFPS];
}
switch (direction) {
case kDirForward: test.frame = kForward[frame]; break;
case kDirBackward: test.frame = kBackward[frame]; break;
case kDirUp: test.frame = kUpward[frame]; break;
case kDirDown: test.frame = kDownward[frame]; break;
}
[self setNeedsDisplay];
}

Rich:
NSTimer is an autoreleased object. Because you are wanting it to repeat, you will need to retain the object. Try this:
timer = [[NSTimer scheduledTimerWithTimeInterval: 1.0/kFPS
target:self
selector:#selector(gameLoop)
userInfo:nil
repeats:YES] retain];
By retaining the timer you will need to make sure you release it when you no longer need it. Also make sure that you invalidate the timer when you are done with it.
I hope this resolves your issue.

Related

Sprite Kit Objective C

I am creating a game using sprite kit but I seem to have trouble with the bodyWithTexture when using it with collisions. bodyWithRectangle and circleOfRadius work fine, but when i use bodyWithTexture it looks like the didBeginContact method is being called more than once.
here is an example of the code i'm using
-(SKNode *) createPlayer
{
level3Player = [SKNode node];
player3Sprite = [SKSpriteNode spriteNodeWithImageNamed:#"character.png"];
level3Player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player3Sprite.size.width/2];
level3Player.physicsBody.categoryBitMask = playerCategory3;
level3Player.physicsBody.contactTestBitMask = platformCategory3 | rockCategory;
[level3Player setPosition:CGPointMake(self.size.height/2, screenHeightL3 *11)];
level3Player.physicsBody.affectedByGravity = NO;
player3Sprite.physicsBody.dynamic = YES;
level3Player.zPosition = 2;
[level3Player setScale:0.6];
[level3Player addChild:player3Sprite];
return level3Player;
}
-(void) addRocksL3
{
int randomNumber = arc4random_uniform(300);
rock1 = [SKSpriteNode spriteNodeWithImageNamed:#"AsteroidFire.png"];
rock1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:rock1.size.width/2];
rock1.position = CGPointMake(self.size.width * 3, randomNumber);
rock1.physicsBody.categoryBitMask = rockCategory;
rock1.physicsBody.contactTestBitMask = playerCategory3;
rock1.physicsBody.dynamic = NO;
rock1.physicsBody.affectedByGravity = NO;
rock1.zPosition = 2;
[rock1 setScale:0.3];
[foregroundLayerL3 addChild:rock1];
[self addChild:rock1];
}
-(void) didBeginContact:(SKPhysicsContact*) contact
{
SKPhysicsBody *firstBody, *secondBody;
if(contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if((secondBody.categoryBitMask == platformCategory3) | redPlatformCategory)
{
level3Player.physicsBody.velocity = CGVectorMake(0, 100);
level3Player.physicsBody.affectedByGravity = YES;
player3Sprite.texture = [SKTexture textureWithImageNamed:#"goo5.png"];
SKAction *sound1 = [SKAction playSoundFileNamed:#"squish.wav" waitForCompletion:NO];
[self runAction:sound1];
gestureRec3.enabled = YES;
}
if(secondBody.categoryBitMask == rockCategory)
{
gestureRec3.enabled = YES;
playerL3.physicsBody.velocity = CGVectorMake(0, 200);
SKAction *playSound = [SKAction playSoundFileNamed:#"Hurt.wav" waitForCompletion:NO];
[self runAction:playSound];
hitCountL3++;
}
switch (hitCountL3)
{
case 1:
[health1Level3 removeFromParent];
[self healthNodelevel31];
break;
case 2:
[hit1L3 removeFromParent];
[self healthNodeLevel32];
break;
case 3:
[hit2L3 removeFromParent];
player3Sprite.texture = [SKTexture textureWithImageNamed:#"splat.png"];
[self gameOverSplatLevel3];
didDie3 = true;
SKAction *playSplat = [SKAction playSoundFileNamed:#"splat.wav" waitForCompletion:NO];
[self runAction:playSplat];
break;
}
when i use this code my character will sometimes take 1 hit and sometimes take all 3 hits when i collide with the rock. I could use circleOfRadius which works fine, but it's not what I am really looking for. Is there anyway i could use bodyWithTexture so my character only takes 1 hit each time?
If you are experiencing multiple collisions eg. didBeginContact is called multiple times, you have few options...
Without looking at your code, lets say you have a player and a rock. Each time when player collides with rock you want to remove the rock. So, you remove the rock from its parent. But before that, you make this change in your code (pseudo code):
if([rockNode parent]){
[rockNode removeFromParent];
}
The other way would be to subclass SKSpriteNode and make a Rock class and to make a custom boolean property which will change its value once when first collision happens. But this is just unnecessary complication:
if(rockNode.canCollide){
[rockNode removeFromParent];
rockNode.canCollide = NO;
}
I had many problems with correct amount of collisions. Sometimes it would get one, sometimes none. So I tried this and it works. The only thing to change is in didBeginContact method.
I will presume that you declared categories like this:
//define collision categories
static const uint32_t category1 = 0x1 << 0;
static const uint32_t category2 = 0x1 << 1;
static const uint32_t category3 = 0x1 << 2;
Try to replace your code in didBeginContact with this one. I remember that correct collisions finally got to work after I did this.
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKNode *newFirstBody = contact.bodyA.node;
SKNode *newSecondBody = contact.bodyB.node;
uint32_t collision = newFirstBody.physicsBody.categoryBitMask | newSecondBody.physicsBody.categoryBitMask;
if (collision == (category1 | category2))
{
NSLog(#"hit");
}
}
Hope it helps

Timer Outputs for Objective-C

This question is a branch off of my previous question from earlier today, but is still a new question. I'm having issues with a timer loop that I declared in the .m file:
// battery level connection timer
- (void)batteryLevelTimerRun
{
batteryLevelSecondsCount = batteryLevelSecondsCount - 1;
int batteryLevelProgress = batteryLevelSecondsCount;
NSString *timerOutput = [NSString stringWithFormat:#"Battery Level: %d%%", batteryLevelProgress];
batteryLevelLabel.text = timerOutput;
float batteryLevelProgressFloat = batteryLevelSecondsCount / 100;
[batteryLevel setProgress:batteryLevelProgressFloat animated:YES];
if (batteryLevelSecondsCount == 20)
{
batteryLevelLabel.textColor = [UIColor orangeColor];
}
else if (batteryLevelSecondsCount == 10)
{
batteryLevelLabel.textColor = [UIColor redColor];
}
else if (batteryLevelSecondsCount == 0)
{
[batteryLevelTimer invalidate];
batteryLevelTimer = nil;
}
}
// battery level connection timer
- (void)batteryLevelSetTimer
{
batteryLevelSecondsCount = 100;
batteryLevelTimer = [NSTimer scheduledTimerWithTimeInterval:9.0 target:self selector:#selector(batteryLevelTimerRun) userInfo:nil repeats:YES];
}
In the .h file, I declared:
#interface MonitoringViewController : UIViewController
{
IBOutlet UILabel *batteryLevelLabel;
IBOutlet UIProgressView *batteryLevel;
NSTimer *batteryLevelTimer;
int batteryLevelSecondsCount;
}
I assigned [self batteryLevelTimer] to a button press. When I ran the code, I got an odd response in the batteryLevelLabel UILabel field. It said Battery Level: -1%. Any idea why this would be? I also setup the UIProgressView to decrement along with label, and that is also dysfunctional (was set to 0), probably because it thinks that the value it was given was -1 so that it defaulted to 0 (UIProgressView value goes from 0.0 to 1.0). Is there some mathematical/logic error I'm missing out on here?
Your code seems ok and works just fine in my test project (I connected button to batteryLevelSetTimer and label counted down to zero as expected). Except for progress bar part -- you should change the line:
float batteryLevelProgressFloat = batteryLevelSecondsCount / 100;
to
float batteryLevelProgressFloat = batteryLevelSecondsCount / 100.0;
because both batteryLevelSecondsCount and 100 are integers, and integer division always yields integer result, which varies between 0 and 1 in your case.

How do I run a method when an action is finished

I was wondering how to call a method after a CCAction is finished (I would also like to call it every 2.5 seconds after it is first called). I have a sprite that goes to a random position, and I want it to run this method (a shooting bullet one) after it is done moving to its random position. So far the method is called when it is still moving. Can anyone help?
Here is the enemy create method:
(void)enemy1{
gjk= arc4random()%6;
enemy1 = [CCSprite spriteWithFile:#"enemy1.png"];
int d = arc4random()%480+480;
int o = arc4random()%320+320;
x = arc4random()%480;
if( x <= 480 && x>= 460){
x=x-100;
}
if(x <= 100){
x = x+50;
}
y = arc4random()%320;
if(y <=320 && y >= 290){
y = y-100;
}
if(y < 100){
y = y + 100;
}
enemy1.position = ccp(o,d);
[enemy1 runAction:[CCMoveTo actionWithDuration:3 position: ccp(x,y)]];
CCRotateBy *rotation = [CCRotateBy actionWithDuration:20 angle:1080];
CCRepeatForever * repeatforever = [CCRepeatForever actionWithAction:rotation];
[enemy1 runAction:repeatforever];
[self addChild:enemy1];
[enemy addObject :enemy1];
}
And the method for shooting a projectile:
(void)projectileShooting:(ccTime)dt {
projcount++;
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
}
}
The shooting method is called in the init method by the code, so it is called every 2.5 seconds.
[self schedule:#selector(projectileShooting:) interval:2.5];
I know I tried to make the shooting happen by making it so that is shoots when the y position is < 320, but it is still moving when it passes the position of 320.
You can make a sequence of actions and at the end of sequence you can give a callback function which will be executed at the end. Something like this
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(methodToRunAfterAction)],
nil]];
Adding on to Tayyab's answer, you can simply start the actual scheduling of your projectile firing within the startShooting method (or whatever you want to call it) which is fired at the end of your move action:
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(startShooting)],
nil]];
where startShooting is defined as follows:
- (void) startShooting
{
// start scheduling your projectile to fire every 2.5 seconds
[self schedule:#selector(projectileShooting:) interval:2.5];
}

CCSprite runAction and Remove

I have a ccLayer where I'm trying to make it rain.
On init i schedule the following:
[self schedule:#selector(throwRain) interval:0.1f];
And here is the rest of the code:
-(void) throwRain {
CCSprite *gota;
for (int i = 1; i <= 6; i++){
gota = [CCSprite spriteWithFile:#"4_gota.png"];
gota.position = ccp(arc4random() % 768, 1060);
gota.scale = (arc4random () % 25 + 50.0f) / 100.0f;
gota.rotation = 35 ;
[self addChild:gota z:arc4random() % 5 + 7];
[gota runAction:[CCSequence actions:[CCEaseRateAction actionWithAction:[CCMoveTo actionWithDuration:3.0f + (arc4random() % 200) / 100.0f position:ccp(gota.position.x, 0)] rate:3] , [CCCallFunc actionWithTarget:self selector:#selector(spriteDone:)], nil]];
}
}
-(void) spriteDone:(id)sender {
[self removeChild:sender cleanup:YES];
}
However, the drops gets to the bottom and just stays there, and never gets removed. Any idea?
Thanks.
Try changing from CCCallFunc to CCCallFuncN. The 'N' stands for Node, and will pass the Node that is performing the action to the selector.

About the fate of malloc()ed arrays of arrays

my first question on Stackoverflow.
Let me start with a bit of code. It's a bit repetitive so I'm going to cut out the parts I repeat for different arrays (feel free to ask for the others). However, please ignore the code in preference to answering the Qs at the bottom. Firstly: thank you to answerers in advance. Secondly: the freeing of data.
#implementation ES1Renderer
GLfloat **helixVertices;
GLushort **helixIndices;
GLubyte **helixColors;
- (void)freeEverything
{
if (helixVertices != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixVertices[i]);
free(helixVertices);
}
if (helixIndices != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixIndices[i]);
free(helixIndices);
}
if (helixColors != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixColors[i]);
free(helixColors);
}
}
(I will get to the calling of this in a moment). Now for where I malloc() the arrays.
- (void)askForVertexInformation
{
int nrows = self.helper.numberOfAtoms / 300;
int mrows = [self.helper.bonds count] / 300;
int alphaCarbonRows = [self.helper.alphaCarbons count] / 30;
helixVertices = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
helixIndices = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
helixColors = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
for (int i=0; i < alphaCarbonRows + 1; i++)
{
helixVertices[i] = malloc(sizeof(helixVertices) * HELIX_VERTEX_COUNT * 3 * 33);
helixIndices[i] = malloc(sizeof(helixIndices) * HELIX_INDEX_COUNT * 2 * 3 * 33);
helixColors[i] = malloc(sizeof(helixColors) * HELIX_VERTEX_COUNT * 4 * 33);
}
[self.helper recolourVerticesInAtomRange:NSMakeRange(0, [self.helper.alphaCarbons count]) withColouringType:CMolColouringTypeCartoonBlue forMasterColorArray:helixColors forNumberOfVertices:HELIX_VERTEX_COUNT difference:30];
self.atomsToFree = self.helper.numberOfAtoms;
self.bondsToFree = [self.helper.bonds count];
self.alphasToFree = [self.helper.alphaCarbons count];
}
Finally, the bit which calls everything (this is a separate class.)
- (void)loadPDB:(NSString *)pdbToLoad
{
if (!self.loading)
{
[self performSelectorOnMainThread:#selector(stopAnimation) withObject:nil waitUntilDone:YES];
[self.renderer freeEverything];
[renderer release];
ES1Renderer *newRenderer = [[ES1Renderer alloc] init];
renderer = [newRenderer retain];
[self performSelectorOnMainThread:#selector(stopAnimation) withObject:nil waitUntilDone:YES]; // need to stop the new renderer animating too!
[self.renderer setDelegate:self];
[self.renderer setupCamera];
self.renderer.pdb = nil;
[renderer resizeFromLayer:(CAEAGLLayer*)self.layer];
[newRenderer release];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(setup:) object:pdbToLoad];
[self.queue addOperation:invocationOperation];
[invocationOperation release];
}
}
- (void)setup:(NSString *)pdbToLoad
{
self.loading = YES;
[helper release];
[renderer.helper release];
PDBHelper *aHelper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
helper = [aHelper retain];
renderer.helper = [aHelper retain];
[aHelper release];
if (!resized)
{
[self.helper resizeVertices:11];
resized = YES;
}
self.renderer.helper = self.helper;
[self.helper setUpAtoms];
[self.helper setUpBonds];
if (self.helper.numberOfAtoms > 0)
[self.renderer askForVertexInformation];
else
{
// LOG ME PLEASE.
}
[self performSelectorOnMainThread:#selector(removeProgressBar) withObject:nil waitUntilDone:YES];
[self performSelectorOnMainThread:#selector(startAnimation) withObject:nil waitUntilDone:YES];
self.renderer.pdb = pdbToLoad;
self.loading = NO;
}
What I'm doing here is loading a molecule from a PDB file into memory and displaying it on an OpenGL view window. The second time I load a molecule (which will run loadPDB: above) I get the Giant Triangle Syndrome and Related Effects... I will see large triangles over my molecule.
However, I am releasing and reallocating my PDBHelper and ES1Renderer every time I load a new molecule. Hence I was wondering:
1. whether the helixVertices, helixIndices and helixColors which I have declared as class-wide variables are actually re-used in this instance. Do they point to the same objects?
2. Should I be setting all my variables to NULL after freeing? I plan to do this anyway, to pick up any bugs by getting a segfault, but haven't got round to incorporating it.
3. Am I even right to malloc() a class variable? Is there a better way of achieving this? I have no other known way of giving this information to the renderer otherwise.
I can't answer your general questions. There's too much stuff in there. However, this caught my eye:
[helper release];
[renderer.helper release];
PDBHelper *aHelper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
helper = [aHelper retain];
renderer.helper = [aHelper retain];
[aHelper release];
I think this stuff possibly leaks. It doesn't make sense anyway.
If renderer.helper is a retain or copy property, do not release it. It already has code that releases old values when it is assigned new values. Also do not retain objects you assign to it.
You have alloc'd aHelper, so there's no need to retain it again. The above code should be rewritten something like:
[helper release];
helper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
renderer.helper = helper;
Also, I think your helix malloced arrays should probably be instance variables. As things stand, if you have more than one ES1Renderer, they are sharing those variables.