SpriteKit - Run a method with two arguments after a random period of time - objective-c

While trying to create a game for iOS I'm facing a problem: I cannot find a way to call a method that creates a SpriteKit node from another class that "calls itself" or repeats after a random period of time in an easy way.
The idea is this: I have a class where the scene is created. But then I have another class (subclass of SKSPriteNode) that creates the different SKSpriteNode that I need. I have one method called createObjectWithName: name position: position that takes two arguments (name and position). I need to call this method from my scene (fine until here), but I also need to repeat this method constantly in random periods of time. So, once it is called one time, it calls itself after a period of time, creating more SKSPriteNodes.
I've tried using performSelector and dispatch_after, but I hadn't had any luck so far.
Thank you in advance.

Unless I am missing something, I think you want to use SKAction for this solution.
You could have a method for starting the spawner like this :
-(void)startSpawner:(float)duration range:(float)range
{
SKAction *delay = [SKAction waitForduration:duration withRange:range];
SKAction *spawnBlock = [SKAction runBlock:^(void)
{
NSString *spawnName = #"name";
CGPoint *spawnPosition = CGPointMake(someX, someY);
SpriteNodeSubclass *node = [SpriteNodeSubclass createObjectWithName:spawnName andPosition:spawnPosition];
// do something with that node if you need to.
}];
SKAction *sequence = [SKAction sequence:#[delay, spawnBlock]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];
}
I think it's ideal to use SKAction as opposed to dispatch_after, because if you pause SpriteKit, the SKAction will also pause.

You can store a timestamp property in your scene class (let's call it timeElapsedFromLastSpawn and initialize it with 0). Then you can use this property in your update method :
timeElapsedFromLast += timeElapsedFromLastUpdate;
if (timeElapsedFromLast > 5.0) {
[self spawnSpriteNode];
timeElapsedFromLast = 0;
}
This will spawn a new sprite every 5 seconds. (And you can randomize it easily)
I would also recommend for the spawning method to be not in a SKSpriteNode instance but outside (e.g. in the parenting scene/node class) as SKSpriteNode role is to represent a sprite and not being a factory of sprites (unless it creates child sprites for it to control directly)
EDIT:
To calculate timeElapsedFromLastUpdate you can use the following code (taken from Ray Wenderlich's site which has great tutorials about this stuff)
- (void)update:(NSTimeInterval)currentTime {
// Handle time delta.
// If we drop below 60fps, we still want everything to move the same distance.
CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
self.lastUpdateTimeInterval = currentTime;
if (timeSinceLast > 1) { // more than a second since last update
timeSinceLast = 1.0 / 60.0;
self.lastUpdateTimeInterval = currentTime;
}
[self updateWithTimeSinceLastUpdate:timeSinceLast];
}
you should implement updateWithTimeSinceLastUpdate method in your scene class or use the calculation directly in the update method above

If this is how you call the method from your scene:
[otherClass createObjectWithName:name position:position];
Then this is how you can use GCD to schedule that call for a random amount of time later:
dispatch_after(dispatch_time(
DISPATCH_TIME_NOW,
(int64_t)(arc4random_uniform((u_int32_t)(MAX_SECONDS * NSEC_PER_SEC)))),
dispatch_get_main_queue(),
^{
[otherClass createObjectWithName:name position:position];
});
(assuming MAX_SECONDS * NSEC_PER_SEC is small enough to be represented by a 32-bit quantity; otherwise look at piling two arc4random_uniform calls with suitable modulo arithmetic)
So a way to that in a repeating fashion, with a __weak safeguard to prevent arbitrary extension of the lifetime of your object could be to use a tail call:
- (void)scheduleNextObject
{
__weak YourClass *weakSelf = self;
dispatch_after(dispatch_time(
DISPATCH_TIME_NOW,
(int64_t)(arc4random_uniform((u_int32_t)(MAX_SECONDS * NSEC_PER_SEC)))),
dispatch_get_main_queue(),
^{
// weakSelf prevents self itself from being captured, so that self
// can be deallocated even with this loop ongoing. We'll explicitly
// check whether what was self still exists to stick with the idiom,
// though it's strictly unnecessary
YourClass *strongSelf = weakSelf;
if(!strongSelf) return;
[otherClass createObjectWithName:name position:position];
[strongSelf scheduleNextObject];
});
}

Related

ReactiveCocoa MVVM with UITableView

I'm using ReactiveCocoa and am trying to apply MVVM. I have a fairly typical UITableView scenario with a refresh control for reloading data.
I've omitted the the UITableDataSource/Delegate methods as these are straight forward. The code below illustrates how I've designed the ViewModel and the ViewController to fit together.
ViewModel.h
#property (strong, nonatomic, readonly) RACCommand *getItemsCommand;
#property (strong, nonatomic, readonly) NSArray *items;
ViewModel.m
- (instancetype)init {
self = [super init];
if (!self) return nil;
#weakify(self);
self.getItemsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [[ItemsDataSource getItems]
doNext:^(NSArray *items) {
#strongify(self);
// I actually do a little extra work here such as sorting
// the items appropriately for the view.
self.items = items;
}];
}];
return self;
}
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView addSubview:self.refreshControl];
RACSignal *refreshSignals = [RACSignal merge:#[
[self.refreshControl rac_signalForControlEvents:UIControlEventValueChanged],
[RACSignal return:nil]
]];
[refreshSignals
subscribeNext:^(id x) {
[self.viewModel.getItemsCommand execute:nil];
}];
[RACObserve(self.viewModel, items)
subscribeNext:^(NSArray *items) {
[self.tableView reloadData];
} completed:^{
[self.refreshControl endRefreshing];
}];
}
Questions/Problems
The completed block where I call endRefreshing never gets executed and for the life of me I can't figure out why.
Would it be better to use a public method - (RACSignal *)getItems instead of the getItems RACCommand?
Is my usage of doNext: in the ViewModel correct in order to apply side effects (i.e. the sorting of the items array) without causing an additional subscription?
I suggest making getItemsCommand use -map: to sort and process the items array. Leave any other side effect work to be done in a separate -doNext:. Once you have your command following this pattern (which is more compositional in RAC), then you can use the RAC() macro to assign the command's finished product, the sorted array, to the items property.
RAC(self, items) = [self.getItemsCommand.executionSignals concat];
RAC has a built-in command support for UIRefreshControl that will start/stop the refresh control along with the start/stop of the command. You should find that you can reduce your UIRefreshControl code to:
self.refreshControl.rac_command = self.getItemsCommand;
For table reloading, you can do:
[RACObserve(self, items) subscribeNext:^(id _) {
#strongify(self);
[self.tableView reloadData];
}];
Hope that helps.
1) Well, let's look at the signal:
RACObserve(self.viewModel, items)
When will that complete? Only when self.viewModel or self is deallocated, just like any other RACObserve. As long as those objects are around, it'll keep on nexting any time you set self.items.
It appears that you want it to endRefreshing once the getItemsCommand finishes executing, and you have this sort of implicit expectation that, since you know that command sets self.viewModel.items, that completion will somehow propagate -- but this isn't the case. To see when the command completes, you have to subscribe to the command's returned signal.
2) The advantage of RACCommand is the auto enabling/disabling behavior, which you aren't really taking advantage of here. The more canonical thing to do would be self.refreshControl.rac_command = self.viewModel.getItemsCommand;. That'll handle the endRefreshing stuff for you, saving you the headache from part 1.
3) ...sort of. The do* family of methods injects side effects for every subscription to the signal. So if you subscribe to a signal twice, and it sends a next, any doNext block it has will be invoked twice. An explicit subscription is more clear, since you want to execute this exactly once per next, regardless of how many times it's subscribed to.
#weakify(self);
self.getItemsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
RACSignal *itemsSignal = [ItemsDataSource getItems];
[itemsSignal subscribeNext:^(NSArray *items) {
#strongify(self);
// Do stuff...
self.items = items;
}];
return itemsSignal;
}];

How to name a block of code and call it in a different method?

I use Grand Central Dispatch methods to do some executions of my app in a queue. I decide the frames for buttons in a calculation on that queue. I want my app to re-draw its scren and calculate new frames after rotation. Here is some pseudo code explanation from what i do:
CGFloat a=123, b=24;
dispatch_async(drawingQue, ^{
//needed loops to get the total button count-how many ones will be drawn et..
for(int x=0;x<someCount<x++){
for(int y=0;y<anotherCount;y++){
//needed frame&name ect assingments
button.frame= CGRectMake(x+y, x-y, a, b);
[button setTitle:#"abc"];}}
};
Here what i want is, how can i give this block a name and re-use it in the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
}
delegate method? For instance, if the rotation is landscape, i want to use a=234 instead of 123.. Any help please. Thanks in advance..
Declare an instance variable of block type and use Block_copy to keep the block:
#interface My {
void (^myBlock)(void);
}
#end
myBlock = Block_copy(^{
...block code...
});
// later call it
myBlock();
// don't forget to release it in dealloc
It is important to copy the block before storing it outside of the scope of its literal (^{...}), because the original block is stored on stack and will die when the scope exits.
Just make a #property that's a block, store it, and use it again later:
typedef void (^MyBlock)(CGFloat, CGFloat);
...
#property(readwrite, copy) MyBlock buttonFramesBlock;
...
#synthesize buttonFramesBlock;
...
self.buttonFramesBlock = ^(CGFloat a, CGFloat b){
//needed loops to get the total button count-how many ones will be drawn et..
for(int x=0;x<someCount<x++){
for(int y=0;y<anotherCount;y++){
//needed frame&name ect assingments
button.frame= CGRectMake(x+y, x-y, a, b);
[button setTitle:#"abc"];}}
};
...
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
dispatch_async(drawingQue, ^{
self.buttonFramesBlock(234,someOtherInt);
});
}
First, never change UI outside of the main thread. So your should modify your code into something like:
dispatch_async(drawingQue, ^{
// ...
dispatch_async(dispatch_get_main_queue(), ^{
button.frame= CGRectMake(x+y, x-y, a, b);
[button setTitle:#"abc"];
});
});
Second, never change UI inside the method shouldAutorotateToInterfaceOrientation. All you have to do inside that method is return whether the view should rotate or not. For instance, in some cases where you have a view controller hierarchy, the view might not get rotated even if you return YES in shouldAutorotateToInterfaceOrientation.
So, you should call your code inside the method:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
This can be achieved in many ways. The simplest (and the one I recommend) is to use a standard Objective-C method:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) { // landscape
[self rotateButtonWithA:234 b:24];
} else { // portrait
[self rotateButtonWithA:123 b:24];
}
}
- (void)rotateButtonWithA:(CGFloat)a b:(CGFloat)b
{
dispatch_async(drawingQue, ^{
// ...
dispatch_async(dispatch_get_main_queue(), ^{
button.frame= CGRectMake(x+y, x-y, a, b);
[button setTitle:#"abc"];
});
});
}
You don't really need to call the block itself from multiple places. But if you still want to do that, there are many answers here that show you how to do that.

Objective-C: Redrawing objects on screen

I've got a GameScreen.m file like (this is a simplified piece of the code):
- (IBAction) onCellClick:(id) sender
{
points +=1;
self.myScore.text = [[NSNumber numberWithInt: points] stringValue];
//myScore is a label in GameScreenViewController xib
}
That is, upon clicking a cell in the view, it will increase a text label by 1. So far so good.
then, in the same code, I've got a timer:
- (void) startTimer
{
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
its updateCounter method is:
- (void) updateCounter:(NSTimer *)theTimer
{
int seconds;
static int count = 0;
count +=1;
timeElapsed = [[NSString alloc] initWithFormat:#"%d", seconds + count];
self.time.text = timeElapsed;
//time is a label in GameScreenViewController xib
}
the thing is that "time" label is not updated (1 sec each time) in this case. I've inserted an AlertView to check if the startTimer method is valid and correctly called, and it actually is (it shows an annoying alertview each second with the timeElapsed value). However, I can' get the time label value to be changed.
Why is my score label updated upon action, while time label isn't updated every second? Is there any way I can update it without including my code in the ViewController?
//note: my coding splits into three files: the appDelegate flips screens and sends values among them; my viewControllers just the windows and, finally, my GameScreen class manages all the processes. From the xib, File's Owner is connected to the ViewController, and the view is connected to GameScreen class.
Thanks a lot for any feedback, please feel free to ask for any piece of additional code needed.
You have to do that (UI related operations) in main thread.
Instead of the line,
self.time.text = timeElapsed;
do as follows:
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed waitUntilDone:NO];
Edit:
- (void) updateCounter:(NSTimer *)theTimer
{
//int seconds;
static int count = 0;
count +=1;
NSString *timeElapsed1 = [[NSString alloc] initWithFormat:#"%d", count];
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed1 waitUntilDone:NO];
[timeElapsed1 release];
//time is a label in GameScreenViewController xib
}
I have gone through an UGLY walkaround. It works to some extent, but I went through such a crappy fix that I'm too embarassed to share...
Basically, I moved my timer directly to ViewController since I want it to be fired upon view load and can't get it to work with a call from ViewController's -(void)viewDidLoad to GameScreen's -(void) startTimer. All other stuff involving both methods is, pretty much, duplicated (ok, not duplicated, let's say 'polymorphed' since I handle some variables to fire them).
It seems my GameScreen.m IBActions can only fire other methods within my GameScreen.m, not on GameScreenViewController.m. Thus, I'm handling my buttons' behavior on GameScreen.m and, on GameScreenViewController.m, I just handle 'automatic' stuff; that is, anything not depending on user interaction. It made me have some IBOutlets duplicated depending on input/output needed so I guess that, since it's now working, you can't tell the difference if you don't go under the hood...
Thanks everyone for their feedback though.

cocos2d sub-classing

I know that cocos2d has scheduling callbacks to do nice things but when you need to use one CCAction (like CCMoveTo one) in order to move a sprite from position a to b, you do not have the ability to make small position arrangements to the sprite position for as long as the action is in effect.
The only possible way I found is by making a sub-class of CCMoveTo in order to check for obstacles and therefore provide some kind of movement to the left or right to a sprite that was moving from top to the bottom of the iPhone screen. The problem is that the sub-class does not have access to the parent class' instance variables (like the startPosition_ one) because they have not been declared as properties.
So I used the following snippet to overcome this situation but I wonder if I am doing something wrong...
- (void)myUpdate:(ccTime)time {
if(delegate && method_) {
NSNumber *num = (NSNumber *)[delegate performSelector:method_ withObject:ownTarget];
if(num) {
double xpos = [num doubleValue];
[num release];
CCMoveTo *parent = [super retain];
parent->startPosition_.x += xpos;
[parent release];
}
[super update:time];
}
Is it correct to retain/release the super-class? The "[super update:time];" at the bottom of the code will make the final positioning.
CCMoveTo *parent = [super retain];
Ouch! This statement makes absolutely no sense. It is the same as writing:
[self retain];
As for accessing the super class' instance variables: unless they're declared #private you can access them. I just checked: they're not #private. You should be able to write in your subclass:
startPosition_.x += xpos;
If that doesn't work make sure your class is really a subclass of CCMoveTo, and not some other class.
Finally, I'd like to say that actions are very limited when it comes to implementing gameplay. You're probably much better off to simply animate your game objects by modifying their position property every frame, based on a velocity vector. You have much more freedom over the position and position updates, and none of the side effects of actions such as a one-frame delay every time you run a new action.
-(void) update:(ccTime)delta
{
// modify velocity based on whatever you need, ie gravity, or just heading in one direction
// then update the node's position by adding the current velocity to move it:
self.position = CGPointMake(self.position.x + velocity.x, self.position.y + velocity.y);
}

Why I can't draw in a loop? (Using UIView in iPhone)

I can draw many things using this :
NSString *imagePath = [[NSBundle mainBundle] pathForResource:#"dummy2.png" ofType:nil];
UIImage *img = [UIImage imageWithContentsOfFile:imagePath];
image = CGImageRetain(img.CGImage);
CGRect imageRect;
double x = 0;
double y = 0;
for (int k=0; k<someValue; k++) {
x += k;
y += k;
imageRect.origin = CGPointMake(x, y);
imageRect.size = CGSizeMake(25, 25);
CGContextDrawImage(UIGraphicsGetCurrentContext(), imageRect, image);
}
}
CGImageRelease(img.CGImage);
So, it works, so, I put it into a command object's execute method. Then, I want to do similar thing, but this time, my execute method only do this:
NSString *imagePath = [[NSBundle mainBundle] pathForResource:#"dummy2.png" ofType:nil];
UIImage *img = [UIImage imageWithContentsOfFile:imagePath];
image = CGImageRetain(img.CGImage);
CGRect imageRect;
double x = inComingX;
double y = inComingY;
imageRect.origin = CGPointMake(x, y);
imageRect.size = CGSizeMake(25, 25);
CGContextDrawImage(UIGraphicsGetCurrentContext(), imageRect, image);
CGImageRelease(img.CGImage);
This time, this is also a Command, and it is the execute method. But I take the for loop away. I will have another method that pass the inComingX , and inComingY into my Command object.
My Drawing method is simply execute the Cmd that passed in my drawingEngine:
-(void)drawInContext:(CGContextRef)context
{
[self.cmdToBeExecuted execute];
}
I also have the assign method to assign the command,:
-(void)assignCmd:(Command* )cmd{
self.cmdToBeExecuted = cmd;
}
And this is the way I called the drawingEngine
for(int k=0; k<5; k++){
[self.drawingEngine assignCmd:[DrawingCmd setDrawingInformation:(10*k):0:#"dummy.png"]];
[self.drawingEngine setNeedsDisplay];
}
It can draw, but the sad thing is it only draw the last one. Why? and how to fix it? I can draw all the things in my First code, but after I take the loop outside, and use the loop in last code, it just only draw the last one. Plz help
That's because setNeedsDisplay does not actually call drawRect:. It simply schedules the view to be redrawn at the next "convenient" time, which is likely the next time, the application re-enters the run-loop. Since you overwrite the remembered command object on each call to the assignment function, by the time, the drawRect: is actually called, only the last assigned command is available and will be drawn.
A better way to do it would be: remember all commands to be drawn instead just the last one, say in an array, like:
#interface MyCanvas {
...
NSMutableArray* commandList;
...
}
and add commands to that array instead of assigning a single command member:
-(void) addCommand:(Command*) cmd {
[self.commandList addObject: cmd];
}
The commands should then be processed in your draw method
for( Command* cmd in self.commandList ) {
[cmd execute ...];
}
Alternatively, you could define "complex" commands, which consist of more than a single drawing step.
(EDIT to answer the question in the comments): Your original code did work, because it does the work all in one place in a single invocation of the appropriate draw method. Your last code does not draw anything at all while it runs. It simply remembers (via command object) that something has to be done, and notifies the view, that it should redraw itself on the next convenient occasion. It is important to note, that setNeedsDisplay will not cause any repainting to be done directly. It simply marks the view as "dirty", which will be picked up by other code in the Cocoa run-time later.
There is another thing in your code which I find slightly suspicious: your method drawInContext: takes a context argument, which is simply ignored. Neither is it passed on to the execute method of you command object, nor is it installed as some kind of "current" drawing context in an instance variable or somesuch. If you expect the code in drawRect: (or the command's execute method) to actually use that context, you have to pass it on to whoever is supposed to use it.