Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I have a game where when it ends, a gameover image (gameoverscreen.png) is visible (formerly hidden), and a button appears to play again. What I tried doing was creating a IBAction for it, then in that action, reloading the viewDidLoad.
example:
-(IBAction)restartGame:(id)sender{
[self viewDidLoad]; //to reload the screen
gameoverscreen.hidden = YES;
replayButton.hidden = YES;
}
I thought this would reload the screen, but it simply appears for half a second, then the gameoverscreen is visible again. I thought it had something to do with my NSTimer, but the timer is invalidated when the gameoverscreen.hidden = NO; so its not that.. Any reason on why it won't reload the screen properly, and allow the user to play again?
Well I can't answer the question per se but I can give advice... this is what I imagine your starting code looks like...
-(void)viewDidLoad
{
NSTimer *t = [some timer code to start the loop and process events]
//init some other state stuff, arrays of rects etc.
}
-(IBAction)restartGame:(id)sender{
[self viewDidLoad]; //to reload the screen
gameoverscreen.hidden = YES;
replayButton.hidden = YES;
}
this is what your code should look like:
-(void)viewDidLoad
{
[self restartGame];
}
- (void)setUpInitalGameState
{
//init some other state stuff, arrays of rects etc.
}
- (void)start
{
NSTimer *t = [some timer code to start the loop and process events]
}
-(IBAction)restartGame:(id)sender{
[self setUpInitialGameState];
[self start];
}
You should not be calling viewDidLoad to reset your game to a starting state. Let viewDidLoad only be called once automatically when the application launches, and then manually reset the variables and positions in your game. In viewDidLoad, you should load any images, audio, and initialize variables used in your games. Handle your game reset in a separate function with a format similar to the one below:
(IBAction)resetGame:(id)sender{
// Set positions of all images to a starting state.
// Change image visibilities as desired.
// Reset all game variables to the initial state.
}
My iOS game SlothDrop only calls viewDidLoad once. When the game ends, all objects are animated off screen and are repositioned, where they are later animated back on screen. At no time is the view loaded again, all objects are just moved around. You can see how I reset the game in the free version here: https://itunes.apple.com/us/app/slothdrop-free/id789603341?mt=8&uo=4
In this way you need wholly re-init your controller and then push it to the screen.
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 days ago.
Improve this question
I have two classes, one is a player , the other one is a viewcontroller.
the player has two public methods as below
- (void)playRoomId:(NSSting *)roomId
- (void)stop
the viewcontroller call it's methods, first call
playroomId:
then user pressed a button, call stop
stop
all code as below
//***********player.m*********
// use afnetworking to request
- (void)playRoomId:(NSSting *)roomId {
__weak typeof(self) ws = self;
self.task = [self reqeust:roomId successBlock:^{
[ws rellayPlay]; /// succes callBack
} failure:^{reBlock:^{
}
}
- (void)stop {
[self.task cancel]; /// cancelRequest
[self reallyStop]; /// reallystop
}
//***********player.m*********
/// ****viewController.m****
- (void)viewDidLoad {
[self.player playRoomId:#"2234"];
}
// button did touch
- (void)buttonAction {
[self.player stop];
}
/// ****viewController.m****
For above code,is it possible that it execute in this order?
[self.task cancel]
[self reallyStop]
[ws rellayPlay]
I worry some error might happen in this situation.
For example, when the network data is just returned and submitted to the main queue, the button is clicked. Since the submission to the main queue is an asynchronous submission, the button click method will be executed firstly. Since the network data has been received, the task might not be cancelled succssfully.
I tried many times in this situation, and no error is found, but I still worry it might happen.
I have created a UIButton and on click event, I am showing an image in the web view. Also, I am refreshing the image in every 30 sec. But when I click on button multiple times, refresh method get called multiple time as well.
I want it to work like, It saves last click time and refreshes as per that time instead of multiple times.
What can I do for it?
I tried to kill all previous thread instead of the current thread but that's not working.
Please help if anyone already know the answer.
Below is my image refresh code:
- (void)refreshBanner:(id)obj {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (![SNRunTimeConfiguration sharedInstance].isInternetConnected) {
[self removeBannerAdWithAdState:kADViewStateNotConnectedToInternet];
return;
}
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
self.bannerPaused = YES;
return;
}
self.adView.hidden = YES;
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
topController = [SNADBannerView topViewControllerWithRootViewController:topController];
if ([self checkInViewHierarchy:self parentView:topController.view]) {
// NSLog(#"Visible View Is: %#", self.adId);
SNADMeta *meta = [[SNADDataBaseManager singletonInstance] adToShowWithBanner:YES excludeTyrooAd:YES audio:NO zoneId:self.adSoptZoneId fixedView:NO condition:nil contextualKeyword:nil onlyFromAJ:NO];
SNADAdLocationType type = SNADAdLocationTypeHeader;
if (self.bannerType == SmallViewTypeFooter) {
type = SNADAdLocationTypeFooter;
}
if (self.isFromCustomEvent) {
type = SNADAdLocationTypeAdMobBanner;
}
NSString *message = meta ? nil : kSNADOppMissReason_NoAdToShow;
[SNRunTimeConfiguration fireOpportunityForAdLocation:type zoneId:self.adSoptZoneId reason:message];
NSLog(#"******************* Opportuninty fired for refresh banner ***************************");
if (meta) {
self.meta = meta;
[self updateContentForWebAd:nil];
[self updateStatsForAd];
//fireImpression
[SNADBannerView fireImpression:self.meta];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
self.sdkMediation = [[SdkMediation alloc] init];
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:YES];
}
// Ad Height Delegate.
if ([self.meta.displayType isEqualToString:kSNADDisplayType_web]) {
self.adHeightDelegateCalled = YES;
NSInteger height = self.meta.height.integerValue;
self.bannerCH.constant = height;
if ([self.callBackDelegate respondsToSelector:#selector(adWillPresentWithHeight:adId:adType:)]) {
[self.callBackDelegate adWillPresentWithHeight:height adId:self.adId adType:SeventynineAdTypeMainStream];
}
}
} else {
[self removeBannerAdWithAdState:kADViewStateNoAdToShow];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:NO];
}
return;
}
} else {
// NSLog(#"View Which Is Not Visible Now: %#", self.adId);
}
SNAdConfiguration *configuration = [SNAdConfiguration sharedInstance];
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:configuration.autoRefRate target:self selector:#selector(refreshBanner:) userInfo:nil repeats:NO];
}];
}
Use GCD, and not NSOperationQueue.
Then you step away from your immediate task. You do lots and lots of complicated things inside refreshBanner. And you will do more complicated things to make it work when the user taps multiple times.
Think about what exactly you need. Abstract the "refresh automatically, and when the button is clicked, but not too often" into a class. Then you create a class that takes a dispatch_block_t as an action, where a caller can trigger a refresh anytime they want, and the class takes care of doing it not too often. Then you create an instance of the class, set all the needed refresh actions as its action block, refreshBanner just triggers a refresh, and that class takes care of the details.
You do that once. When you've done it, you actually learned stuff and are a better programmer than before, and you can reuse it everywhere in your application, and in new applications that are coming.
NSOperationQueue have cancelAllOperations method. But for the main queue it's not a good decision to use this method, cause main queue is shared between different application components. You can accidentally cancel some iOS/other library operation together with your own.
So you can create NSOperation instances and store them in an array. Then you can call cancel for all scheduled operations by iterating trough this array, and it will only affect your operations.
Note that block operations doesn't support cancellation. You will need to create your own NSOperation subclass, extract code from your execution block into that subclass main method. Also, you'll need to add [self isCancelled] checks that will abort your logic execution at some points.
I forgot to mention that currently your execution block is fully performed on the main queue. So, you'll need to move any heavy-lifting to background thread if you want to cancel your operation in the middle of processing from main thread.
I need to add that I agree with #gnasher729 - this doesn't look like an optimal solution for the problem.
I have resolved the issue.
Multiple threads created because a new view is created every time I call the API to display image. So now I am removing views if any available before displaying image, then only last object remains and refresh is called as per last called time.
Every View has it's own object that's why multiple threads has created.
By removing views my issue has been resolved.
Thanks everyone for replying.
We are using cocos2d-js to develop an iOS App which can launch different games. So I add an button in the native app viewcontroller and start the game by clicking the button, just like this:
-(void)didClickGame2Btn:(id)sender
{
//加载游戏
cocos2d::Application *app = cocos2d::Application::getInstance();
// Initialize the GLView attributes
app->initGLContextAttrs();
cocos2d::GLViewImpl::convertAttrs();
// Use RootViewController to manage CCEAGLView
RootViewController *rootViewController = [[RootViewController alloc] init];
rootViewController.wantsFullScreenLayout = YES;
[self.navigationController presentViewController:rootViewController animated:YES completion:^{
// IMPORTANT: Setting the GLView should be done after creating the RootViewController
cocos2d::GLView *glview = cocos2d::GLViewImpl::createWithEAGLView((__bridge void *)rootViewController.view);
cocos2d::Director::getInstance()->setOpenGLView(glview);
NSString *documentDir = [SEGetDirectories dirDoc];
NSString *wPath = [NSString stringWithFormat:#"%#/GameData/Game2",documentDir];
NSLog(#"document------:%#",documentDir);
std::vector<std::string> searchPathList;
searchPathList.push_back([wPath UTF8String]);
cocos2d::FileUtils::getInstance()->setSearchPaths(searchPathList);
//run the cocos2d-x game scene
app->run();
}];
[[UIApplication sharedApplication] setStatusBarHidden:true];
}
the rootViewController contains the game view. And then we add an button in the game, which is used to exit the game. The click event code of the exit game button likes:
//exit the game and close the view controller
gameEndCallBack:function(sender){
cc.log("director end............");
cc.director.end();
var ojb = jsb.reflection.callStaticMethod("ViewControllerUtils", "dismissCurrentVC");
}
We use the reflection to dismiss the rootViewController:
+(void)dismissCurrentVC
{
UIViewController *currentVC = [ViewControllerUtils getCurrentVC]; //这里获取最顶层的viewcontroller
[currentVC dismissViewControllerAnimated:YES completion:^{
NSLog(#"xxx");
}];
}
Everything is ok when the first time to enter the game, but after dismissing the rootViewController, we try to enter the game again, it crash.
The crash line is in the ScriptingCore::runScript metod and executing the code:
evaluatedOK = JS_ExecuteScript(cx, global, *script, &rval);
And the crash info is "exc_bad_access".
It is much the same problem as this topic, but the approaches in it did not solve the problem.
http://discuss.cocos2d-x.org/t/how-to-destroy-a-cocos-game-on-ios-completely/23805
This problem has been confusing me serveral days, I have no solution for this. Can anyone give me some help?
You can make the app to support multiple games with in the app.
All you have done is required but in addition to that please follow the below instructions.
First of all, cocos provide a singleton instance of cocos2d::Application that can not be restarted again especially in iOS. So the approach of ending the Director cc.director.end(); won't help you.
You should start the application only once by using the function call cocos2d::Application::getInstance()->run(); and next time if you want to start the game layer, you should not call this method again.
Instead, just pause cocos2d::Director::getInstance()->pause(); and resume cocos2d::Director::getInstance()->resume(); the director when you want to stop the game.
In this approach, if you dismiss/dealloc the view-controller instance then you should create the glview cocos2d::GLView instance again without calling the run method.
One more problem is, take care of the delay in loading the new scene. GLView will display the previous game scene for a while. Do a work around that will show blank screen while the new scene is ready.
Hope this will help you.
Just to clarify: this is not a question about sequences or groups of SKActions.
I have 7-20 sprites representing monsters on a 2D game board. I would like them to act one after another. The action can be a move, or move and attack.
The issue that I'm running in is that the first few monsters seem to act normally, - one monster moves, attacks, then the next one, etc. But after a few of them (4-5) the sequence breaks and multiple monsters start to act at the same time.
Is this an issue with the recursive move/AI methods I have below or if something within Sprite Kit is interfering with the sequence?
I see that the call stack in xCode seems to grow, and I see multiple calls to processMonsterAI, but it's not a 1 call per monster situation.
In particular I'm interested if I should use NSTimers or Delayed dispatch queue blocks instead of relying on Sprite Kit's completion blocks?
Every turn, my AI has to move monsters on the board:
-(void)processMonsterAI
{
CharacterModelNode* monster = [allMonstersCopy lastObject];
[allMonstersCopy removeLastObject];
AIBase* ai = monster.character.AI;
ai.delegate = self;
ai doAIAction];
}
AI frequently makes decisions to move around the board, which are animated as move from one cell to the next, step by step until the monster is out of moves or destination is reached:
//do recursive movement animation
-(void)recursiveMoveWithCharacter:(CharacterModelNode*)characterModel path:(NSMutableArray*)path
{
//check if we ran out of moves
if(path.count == 0)
{
//notify delegate
[self moveCompleteForCharacter:characterModel];
}else
{
NSNumber* tileIndex = path[0];
CGPoint destination = [MapOfTiles positionForTileAtIndex:tileIndex.intValue];
[path removeObjectAtIndex:0];
[self runAction:[SKAction moveTo:destination duration:1]
completion:^{
characterModel.character.currentMoves = characterModel.character.currentMoves-1;
[self recursiveMoveWithCharacter:characterModel path:path];
}];
}
}
Once the animation completes, the AI is notified that the monster is in a correct position, and attack sequence can be performed:
//once animation is complete, call
-(void)moveComplete
{
if(self.bestTarget)
{
[self.actor attack:self.bestTarget];
}
//notify whoever is the delegate that we are done here and the next AI can do it's magic.
[self finishAction];
}
//AI is done, notify delegate to process next monster
-(void)finishAction
{
NSLog(#"Finished :%#",self.actor.character.name);
[self.actor debugFlashRed];
if([self.delegate respondsToSelector:#selector(didFinishAIAction:)])
{
[self.delegate didFinishAIAction:self];
}
}
Proceed to move/attack with the next monster
//either repeat the process, or terminate if all monsters acted
-(void)didFinishAIAction:(AIBase*)AI
{
if(allMonstersCopy.count==0)
{
//finished with monster turn
[self preventUserInteraction:NO];
[[GameDataManager sharedInstance] processEndTurn];
}else
{
[self processMonsterAI];
}
}
UPDATE:
Here's the call stack, It hits the assertion that finishAction gets called by some AI more than once:
Does doAIAction possibly go through multiple paths that may call finishAction more then once? That does seem to be a possibility. There isn't really any branching in the code you have shown that would lead to multiple processMonsterAI calls.
I create a GLKViewController like this:
// Create a GLK View Controller to handle animation timings
_glkVC = [[GLKViewController alloc] initWithNibName:nil bundle:nil];
_glkVC.preferredFramesPerSecond = 60;
_glkVC.view = self.glkView;
_glkVC.delegate = self;
_glkVC.paused = YES;
NSLog(#"initial state: %#", _glkVC.paused ? #"paused" : #"running");
but it immediately starts calling the delegate update method and the output from the NSLog above is: initial state: running
I am managing my view updates with setNeedsDisplay but I want the GLKViewController to handle animations from time to time so I want to unpause it only when needed. Is there a way to start the controller in a paused state?
have you tried pausing in the viewDidAppear method instead of the viewDidLoad method? It should look something like this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// self.paused automatically set to NO in super's implementation
self.paused = YES;
}
Boom, done! If this works then you save an "if" check thousands of times a minute just to pause on launch!
The viewDidAppear method works for me, but not optimally. A few frames of visible animation occur before the pause takes effect. Using viewWillAppear worked much better:
- (void) viewWillAppear: (BOOL) animated
{
[ super viewDidAppear: animated ];
self.paused = YES;
}
In lieu of any answers I'm using this work-around:
I set .preferredFramesPerSecond = 1 initially and then in the update method I check if(preferredFramesPerSecond == 1) and set .paused = YES (and also set my real desired value for preferredFramesPerSecond). I can then allow the rest of the update method to run once after initialisation, or return immediately if I don't want it to run yet.
I then trigger redraws manually as needed with setNeedsDisplay and unpause it when I need it to animate.
If anyone has a better solution please answer as usual.
Have you tried overriding resumeOnDidBecomeActive to return NO? This should keep the animation paused on any activation, including the first.