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.
Related
I am working with a C4 app, and have created a subclass of C4Shape. I am having trouble accessing the canvas from within the subclass but I'm not sure how to check it, or how to get access to it from another object.
This is the code I have so far:
#import "Platform.h"
#implementation Platform {
CGPoint o;
C4Timer *timer;
int speed;
}
-(void) setup {
speed = 10;
[self rect:CGRectMake(0, 0, 100, 100)];
timer = [C4Timer automaticTimerWithInterval:1.0f/30
target:self
method:#"push"
repeats:YES];
o = self.center;
}
+(id) platformWithRange:(CGRect)s {
Platform * bb = [Platform new];
bb.range = s;
return bb;
}
-(void) push {
// check boundaries
o.x-= speed;
if( 0 >= o.x - 50 ) {
o.x = range.size.width;
}
}
#end
Have a look at the second part of this answer: https://stackoverflow.com/a/15885302/1218605
You can create a property on your subclass into which you will set the canvas from the main workspace.
#implemenation C4WorkSpace
-(void)setup {
CustomSubclass *obj = [CustomSubclass new];
obj.canvas = self.canvas;
}
#end
I have a UILabel that I start at "0" and then as my game progresses I call the below code to add "1" to the score each time a button is pressed. This all works fine.
playerOneScore.text = [NSString stringWithFormat:#"%d",[playerOneScore.text intValue]+1];
My problem is that when the game ends and the user presses the "Replay" button. This replay button sets the label back to "0" by calling this.
playerOneScore.text = #"0";
But then when the game progresses my label jumps from "0" to where it had initially left off + 1. What am I doing wrong here? Not making sense.
Any help would be very grateful! Thank you!
More code requested (simplified):
- (void)viewDidLoad
{
playerOneScore.text = #"0";
}
-(IBAction)touchDown {
if (playerOneTurn == YES) {
if (button1.touchInside == YES) {
playerOneScore.text = [NSString stringWithFormat:#"%d",[playerOneScore.text intValue]+1];
}
}
}
-(void) replayGame {
playerOneScore.text = #"0";
}
Not sure what's going on, but I would suggest using a separate NSInteger property to track the score. Then override the property setter to update the label text.
Add the property to your interface or a private category:
#property (nonatomic, assign) NSInteger score;
Then override the setter:
- (void)setScore:(NSInteger)value {
_score = value;
playerOneScore.text = [NSString stringWithFormat:#"%d", value];
}
Then in your action, update the score and label using the new property:
-(IBAction)touchDown {
if (playerOneTurn == YES) {
if (button1.touchInside == YES) {
self.score += 1;
}
}
}
Note that I haven't compiled or tested this code, but it should be close.
EDIT: fixed typo in setter
I am new to the community, so let me know if my question is unclear. I am trying to make a choice reaction exercise on the iPAD. There are two images that should appear in random sequence on the left and right of the screen, and the user will respond by tapping a button that corresponds to the position of the appeared image. Here's the problem, I tried to get the two images to appear at random order using the following way:
- (void) viewDidAppear:(BOOL)animated
{
for(int n = 1; n <= 20; n = n + 1)
{
int r = arc4random() % 2;
NSLog(#"%i", r);
if(r==1)
{
[self greenCircleAppear:nil finished:nil context: nil];
}
else
{
[self redCircleAppear:nil finished:nil context: nil];
}
}
}
However, 20 random numbers get generated while only 1 set of animation is run. Is there a way to let the animation finish running in each loop before the next loop begins? Any help is appreciated, thanks in advance!
When you say "only one set of animation is run" I'm assuming that means greenCircleAppear and redCircleAppear begin the sequence of images appearing and the user pressing a button. If that's the case, I'd recommend not using a for loop in viewDidAppear but instead have viewDidAppear initialize the current state and call a method that presents the next animation. When the animation is finished, have it call the method that presents the next animation. Something along these lines:
Add this to the interface:
#interface ViewController ()
#property NSInteger currentIteration;
#end
This is in the implementation:
- (void)viewDidAppear:(BOOL)animated {
self.currentIteration = 0;
[self showNextAnimation];
}
- (void)greenCircleAppear:(id)arg1 finished:(id)arg2 context:(id)arg3 {
//perform animation
NSLog(#"green");
[self showNextAnimation];
}
- (void)redCircleAppear:(id)arg1 finished:(id)arg2 context:(id)arg3 {
//perform animation
NSLog(#"red");
[self showNextAnimation];
}
- (void)showNextAnimation {
self.currentIteration = self.currentIteration + 1;
if (self.currentIteration <= 20) { //you should replace '20' with a constant
int r = arc4random() % 2;
NSLog(#"%i", r);
if(r==1)
{
[self greenCircleAppear:nil finished:nil context: nil];
}
else
{
[self redCircleAppear:nil finished:nil context: nil];
}
}
else {
//do what needs to be done after the last animation
}
}
I've just started with the sparrow framework, and have been following "The Big Sparrow Tutorial" by Gamua themselves. I'm on the first part of the tutorial, using the AppScaffold 1.3 but when I try to compile my basic code it hangs at the loading screen and gives me a SIGABRT error.
I put an exception breakpoint, and it stopped here, in GameController.m (seen at bottom) of the AppScaffold:
mGame = [[Game alloc] initWithWidth:gameWidth height:gameHeight];
This was also my only output:
2012-07-30 07:19:54.787 AppScaffold[1682:10a03] -[Game initWithWidth:height:]: unrecognized selector sent to instance 0x7553980
(lldb)
I am using the stock AppScaffold, the only thing I changed was the Game.m.
This is my Game.m:
#interface Game : SPSprite
#end
#implementation Game
{
#private
SPImage *mBackground;
SPImage *mBasket;
NSMutableArray *mEggs;
}
- (id)init
{
if((self = [super init]))
{
//load the background image first, add it to the display tree
//and keep it for later use
mBackground = [[SPImage alloc] initWithContentsOfFile:#"background.png"];
[self addChild:mBackground];
//load the image of the basket, add it to the display tree
//and keep it for later use
mBasket = [[SPImage alloc] initWithContentsOfFile:#"basket.png"];
[self addChild:mBasket];
//create a list that will hold the eggs,
//which we will add and remove repeatedly during the game
mEggs = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc
{
[mBackground release];
[mBasket release];
[mEggs release];
[super dealloc];
}
#end
I've tried my best to use my basic troubleshooting tactics, but I'm very new to Obj-C and Sparrow and could use a hand :)
Thanks
EDIT: I've addded the GameController.m contents here for clarity:
//
// GameController.m
// AppScaffold
//
#import <OpenGLES/ES1/gl.h>
#import "GameController.h"
#interface GameController ()
- (UIInterfaceOrientation)initialInterfaceOrientation;
#end
#implementation GameController
- (id)initWithWidth:(float)width height:(float)height
{
if ((self = [super initWithWidth:width height:height]))
{
float gameWidth = width;
float gameHeight = height;
// if we start up in landscape mode, width and height are swapped.
UIInterfaceOrientation orientation = [self initialInterfaceOrientation];
if (UIInterfaceOrientationIsLandscape(orientation)) SP_SWAP(gameWidth, gameHeight, float);
mGame = [[Game alloc] initWithWidth:gameWidth height:gameHeight];
mGame.pivotX = gameWidth / 2;
mGame.pivotY = gameHeight / 2;
mGame.x = width / 2;
mGame.y = height / 2;
[self rotateToInterfaceOrientation:orientation animationTime:0];
[self addChild:mGame];
}
return self;
}
- (void)dealloc
{
[mGame release];
[super dealloc];
}
- (UIInterfaceOrientation)initialInterfaceOrientation
{
// In an iPhone app, the 'statusBarOrientation' has the correct value on Startup;
// unfortunately, that's not the case for an iPad app (for whatever reason). Thus, we read the
// value from the app's plist file.
NSDictionary *bundleInfo = [[NSBundle mainBundle] infoDictionary];
NSString *initialOrientation = [bundleInfo objectForKey:#"UIInterfaceOrientation"];
if (initialOrientation)
{
if ([initialOrientation isEqualToString:#"UIInterfaceOrientationPortrait"])
return UIInterfaceOrientationPortrait;
else if ([initialOrientation isEqualToString:#"UIInterfaceOrientationPortraitUpsideDown"])
return UIInterfaceOrientationPortraitUpsideDown;
else if ([initialOrientation isEqualToString:#"UIInterfaceOrientationLandscapeLeft"])
return UIInterfaceOrientationLandscapeLeft;
else
return UIInterfaceOrientationLandscapeRight;
}
else
{
return [[UIApplication sharedApplication] statusBarOrientation];
}
}
- (void)rotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
animationTime:(double)animationTime
{
float angles[] = {0.0f, 0.0f, -PI, PI_HALF, -PI_HALF};
float oldAngle = mGame.rotation;
float newAngle = angles[(int)interfaceOrientation];
// make sure that rotation is always carried out via the minimal angle
while (oldAngle - newAngle > PI) newAngle += TWO_PI;
while (oldAngle - newAngle < -PI) newAngle -= TWO_PI;
// rotate game
if (animationTime)
{
SPTween *tween = [SPTween tweenWithTarget:mGame time:animationTime
transition:SP_TRANSITION_EASE_IN_OUT];
[tween animateProperty:#"rotation" targetValue:newAngle];
[[SPStage mainStage].juggler removeObjectsWithTarget:mGame];
[[SPStage mainStage].juggler addObject:tween];
}
else
{
mGame.rotation = newAngle;
}
// inform all display objects about the new game size
BOOL isPortrait = UIInterfaceOrientationIsPortrait(interfaceOrientation);
float newWidth = isPortrait ? MIN(mGame.gameWidth, mGame.gameHeight) :
MAX(mGame.gameWidth, mGame.gameHeight);
float newHeight = isPortrait ? MAX(mGame.gameWidth, mGame.gameHeight) :
MIN(mGame.gameWidth, mGame.gameHeight);
if (newWidth != mGame.gameWidth)
{
mGame.gameWidth = newWidth;
mGame.gameHeight = newHeight;
SPEvent *resizeEvent = [[SPResizeEvent alloc] initWithType:SP_EVENT_TYPE_RESIZE
width:newWidth height:newHeight animationTime:animationTime];
[mGame broadcastEvent:resizeEvent];
[resizeEvent release];
}
}
// Enable this method for the simplest possible universal app support: it will display a black
// border around the iPhone (640x960) game when it is started on the iPad (768x1024); no need to
// modify any coordinates.
/*
- (void)render:(SPRenderSupport *)support
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
glEnable(GL_SCISSOR_TEST);
glScissor(64, 32, 640, 960);
[super render:support];
glDisable(GL_SCISSOR_TEST);
}
else
[super render:support];
}
*/
#end
Here is my Xcode project: http://cl.ly/2e3g02260N47
You are calling a
initWithWidth:height:
method, while none is defined in your class.
From your edit, it seems that the initWithWidth method is declared in the class GameController, not in Game.
So, it seems that the
In which context are you calling initWithWidth:height: method is declared in Game.h but you define it in GameController.m.
This explains both why you get the SIGABRT and the errors when compiling.
The fix is calling
mGame = [[GameController alloc] init];
from GameController initWithWidth...
- (id)initWithWidth:(float)width height:(float)height
{
if ((self = [super initWithWidth:width height:height]))
{
float gameWidth = width;
float gameHeight = height;
// if we start up in landscape mode, width and height are swapped.
UIInterfaceOrientation orientation = [self initialInterfaceOrientation];
if (UIInterfaceOrientationIsLandscape(orientation)) SP_SWAP(gameWidth, gameHeight, float);
mGame = [[Game alloc] init];
mGame.pivotX = gameWidth / 2;
mGame.pivotY = gameHeight / 2;
mGame.x = width / 2;
mGame.y = height / 2;
[self rotateToInterfaceOrientation:orientation animationTime:0];
[self addChild:mGame];
}
return self;
}
The tutorial was very old and was incompatible with the latest scaffold;
I did this:
- (id)init
{
if((self = [super init]))
{
when I should've done this:
- (id)initWithWidth:(float)width height:(float)height
{
if ((self = [super initWithWidth:width height:height]))
{
thanks, though sergio!
(There are much better sparrow tutorials and I'm even making my own video tutorials :P)
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.