ゲームオーバー画面で表示されるアニメが重複描画される問題

重なって表示されるウパンダアニメ

プレイしたゲームに於いてハイスコアを獲得したか否かに応じて ゲームオーバー画面に表示するアニメーションを変更しようとゲーム起動時 AppDelegate.mに生成した可変辞書 toyBox を以てハイスコアフラグをゲームオーバー画面に渡して分岐条件とすべく実装したのが前回の記事 ハイスコアに準じてUIViewぱらぱらアニメを変更する だったのですが分岐表示には成功したものの問題も発生してしまいました。 ゲームを繰り返す内にアニメーションが重複して描画されてしまうのです。 本稿では此の問題を解決した記事としてものします。

手元の開発ゲームでは リプレイ 機能をもたせていますが此れがトラブルの引き金となりました。 ほんの僅かでも機能追加すればバグの元となるとのセオリーを地で行き 端なくも其の証左となりました。 しかしリプレイ機能は出来得るならば保たせたい機能ではありますので何とか解決を図った次第。

スポンサーリンク
日付:2014年4月9日
開発機:MacBook Air(11-inch, Mid 2013)
MacOSバージョン:OS X 10.9.2
Xcodeバージョン:5.1
言語:Objective-C
主関連アプリ:uPanda Breaks Out Fruits

手元の開発ゲームではゲームオーバー画面にリプレイボタンを表示しており 此れをタップした際に再度SpriteKitを用いたプレイシーンを読み込ませるように仕組んであります。 ゲーム画面に於いては土台のゲーム画面用ViewControllerが ゲームの情報を表示するUIViewと SpriteKitを用いたゲーム専用画面のSKSceneを読み込んでいます。 ゲームオーバーとなったタイミングでプレイシーンは破棄され 元々表示されていたUIViewが表に現れると言った塩梅です。 此のUIViewにプレイシーンから受け取ったハイスコア判定値を受け取って 表示されるアニメーションが分岐するのです。 此の仕組みに於いては一度目は問題ないのですが 二度目のゲームオーバー時にアニメーションが重複して表示されてしまいます。 一度目に描画されたアニメーションがリプレイ画面の水面下で動き続けており 二度目のゲームオーバー時にプレイシーンが破棄されると 二度目で指定された表示されたアニメーションの下に見えてしまって重複表示の状態を招いてしまったいたのでした。

問題はプレイシーンを搭載するサブビューのクラスを何処で何度呼び出しているかにあります。 -(void)didMoveToSuperview メソッドは ViewController 上で PlayUIView.m が呼ばれた時にシーンを1回だけ呼ぶ具合のメソッドであり 従って PlayUIView.m の subview の PlayScene.m をリプレイで何度呼んでも此処の処理は最初の処理したものから金輪際変わらない筈です。

一方 -(void)willRemoveSubview:(UIView *)subview メソッドは PlayUIView.m が subview を自ら加える度に呼び出されるため BOPlayUIView.m の subview の PlayScene.m をリプレイの度に呼ぶ処か 別の subview を add した時さえ呼ばれてしまうので一度のリプレイで何度も呼び出されるメソッドです。

さて以下に今回惹起された問題を列挙しましょう。 此れ等を鑑みて如何に処方すべきか考えます。

  • リプレイの度に呼ばれるので最初此処でアニメを生成するとリプレイの度にアニメが重複し 挙げ句の果てにはハイスコアアニメと目眩アニメが同時に描画されてしまう始末と言う根本的な重複描画問題。
  • 上の根本的な重複描画問題を受けてハイスコアフラグを設定し アニメ描画と共にハイスコアフラグをリセット(0)にしたら ハイスコアの時にも目眩アニメが表示されてしまう現象が発生する。 なんとなればリプレイ1度で何度も呼ばれる為に一旦はハイスコアアニメを描画しても 別のaddsubviewでハイスコアフラグがリセットされ、 もう一つ別のaddsubviewで目眩アニメに切り替わってしまう構造的問題。
  • 最初の根本的な重複描画問題を受け PlayUIView.m 側でハイスコアセーブのタイミング、及び親view PlayViewController.m のリプレイボタンが押されて -(void)seguePlayViewController:(UIButton *)button メソッドが呼ばれるタイミングでsprite.tagを利用してアニメをremoveFromSuperviewしようとしたが 此の目論見は外れるものの以下のコードは機能して画面が真っ暗になるいやらしい問題。 此れは今回の問題とは別にiPhoneアプリ作者としての課題ともしたい。
    for (UIView *view in [self.view subviews]) {
      [view removeFromSuperview];
    }

上記列挙の3問題を解決する為に最初に -(void)didMoveToSuperview メソッドでアニメを生成し其の際にノードにtag付けをしてユニーク判断を可能にしておいて -(void)willRemoveSubview:(UIView *)subview メソッドが呼ばれる度にハイスコアフラグを呼び出し tag付けされたアニメを差し替える方法で実装し 漸く目論見に沿う動きが得られたのでした。 ラフなコーディングでリファクタリングが必要なものの其の際のコードが以下となります。

-(void)willRemoveSubview:(UIView *)subview {
  
  /* ハイスコア・フラグ */
  BOAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
  highScoreFlag = [appDelegate.toyBox objectForKey:@"hs_flag"];
  
  if ([highScoreFlag isEqualToString:@"1"]) {
    /* ハイスコアボードを掲げてハイスコアダンスを踊るウパンダアニメ */
    // アニメーション用イメージビューの用意
    CGRect highScoreUpandaRect = CGRectMake(110, 330, 105, 120);
    upandaAnime = [[UIImageView alloc]initWithFrame:highScoreUpandaRect];
    upandaAnime.image = [UIImage imageNamed:@"hiscore_dance-1.png"];
    upandaAnime.tag = 5;
    // [self addSubview:upandaAnime];
    upandaAnime = (UIImageView *)[self viewWithTag:5];
    // アニメーション用画像を配列にセット
    NSMutableArray *highScoreUpandaImageList = [NSMutableArray array];
    for (NSInteger i = 1; i <= 6; i++) {
      NSString *highScoreUpandaImagePath = [NSString stringWithFormat:@"hiscore_dance-%d.png", i];
      UIImage *highScoreUpandaImg = [UIImage imageNamed:highScoreUpandaImagePath];
      [highScoreUpandaImageList addObject:highScoreUpandaImg];
    }
    // アニメーション用画像をセット
    upandaAnime.animationImages = highScoreUpandaImageList;
    // アニメーションの速度
    upandaAnime.animationDuration = 1.5;
    // アニメーションのリピート回数
    upandaAnime.animationRepeatCount = 0;
    // アニメーションのスタート
    [upandaAnime startAnimating];
  } else {
    NSLog(@"HS FLAG FALSE:%@",highScoreFlag);
    /* 目の回ったウパンダアニメ */
    // アニメーション用イメージビューの用意
    CGRect dizzyUpandaRect = CGRectMake(110, 330, 105, 120);
    upandaAnime = [[UIImageView alloc]initWithFrame:dizzyUpandaRect];
    upandaAnime.image = [UIImage imageNamed:@"dizzy-1.png"];
    upandaAnime.tag = 5;
    // [self addSubview:upandaAnime];
    upandaAnime = (UIImageView *)[self viewWithTag:5];
    // アニメーション用画像を配列にセット
    NSMutableArray *dizzyUpandaImageList = [NSMutableArray array];
    for (NSInteger i = 1; i <= 8; i++) {
      NSString *dizzyUpandaImagePath = [NSString stringWithFormat:@"dizzy-%d.png", i];
      UIImage *dizzyUpandaImg = [UIImage imageNamed:dizzyUpandaImagePath];
      [dizzyUpandaImageList addObject:dizzyUpandaImg];
    }
    // アニメーション用画像をセット
    upandaAnime.animationImages = dizzyUpandaImageList;
    // アニメーションの速度
    upandaAnime.animationDuration = 2.0;
    // アニメーションのリピート回数
    upandaAnime.animationRepeatCount = 0;
    // アニメーションのスタート
    [upandaAnime startAnimating];
  }

上記コードに於いてのポイントは赤字で記した描画時の命令 [self addSubview:upandaAnime]; を以下で差し替えの緑色の命令に書き換えている部分となるでしょう。 upandaAnime = (UIImageView *)[self viewWithTag:5];

追記 (2016年7月22日)
以上のコードの冗長部分を除くべく ゲームオーバー画面重複アニメ問題解決コードのリファクタリング として記事を配信しました。

uPanda Breaks Out Fruits
無料:カテゴリ: ゲーム: 4+ 評価
バージョン: 4.12
リリース: 2014年9月15日
更新: 2022年4月20日
サイズ : 10.7 MB
互換性: iOS 14.4 以降のiPhone、iPod touch に対応。および、macOS 11.0以降とApple M1 チップを搭載したMac に対応。

1件のコメント

コメントは受け付けていません。