Segue, Landscape, Touch events, Alpha andUser Defined Runtime Attributes
Yutaka Yasuda, Kyoto Sangyo University

目標• ぱしぱし叩けるゲームを作りたい
タッチイベントに反応火花が出て消える（アニメーション）• ステージを IB の操作だけで簡単に増やせるSegue での場面進行

Segue• 場面が変わるだけのアプリを作る• ボタンをクリックすると指定した View に進む

ゴール！
場面#1

場面#2

新規プロジェクト作成
step 1.

step 2.

New Project... でプロジェクト名指定時にSingle View App を選択

Use Storyboards をチェック
今回は iPhone 限定で作った

Storyboard 初期状態
StoryBoard ができている
とりあえず標準のViewController で
1 scene 存在

最初の飛び込み
先を示す

方眼紙 double
click で縮小表示

場面の追加（View Controller の Drag & Drop）

元からある View

適当なところに Drag & Drop

場面移動のためのボタンを設置出発する View に Drag & Drop

そのボタンを Modal でつなぐ

Control + Click でメニューを出すとこんなのがあるこれだけで移動はOK• 往路だけならこれでOK• 戻りは要コード

戻るためのコード
ViewController.m// このビューを閉じて一つ前に戻る- (IBAction)dismissButton:(id)sender {[self dismissModalViewControllerAnimated:YES];}

遷移を覚えているのだけれど、、、
さてループするような場合はどうするのが？

Navigation View Controller• 戻りのボタンは自動合成（コードも不要）• ちょっと操作がトリッキー

start

scene
A1

scene
A2
goal

scene
B1

scene
B2

ViewController の変更処理する ViewController を変えた
い場合は、
1. 新しい VC クラスを作って2. この欄を作った VC に変更

はじめから横向けでの起動

横長モードでの起動
常に横長モードで画面を表示するアプリケーションは、このモードで起動するよう、システムに対して明示的に指示しなければなりません。通常、iOSアプリケーションは縦長モードで起動します。そして、必要に応じてデバイスの向きに合うようにインターフェイスを回転します。アプリケーションで縦長と横長の両方の向きをサポートする場合は、必ず縦長モードでビューを設定してから、View Controllerを利用して任意の向きに回転する処理を行わなければなりません。ただし、アプリケーションが縦長の向きをサポートせず、横長のみをサポートする場合は、最初から横長モードで起動したように見せるために、以下のタスクを実行しなければなりません。

• アプリケーションのInfo.plistファイルにUIInterfaceOrientationキーを追加し、このキーの値をUIInterfaceOrientationLandscapeLeftまたはUIInterfaceOrientationLandscapeRightに設定する。

• ビューを横長モードにレイアウトし、ビューの自動サイズ変更オプションを確実に正しく設定する。

• View ControllerのshouldAutorotateToInterfaceOrientation:メソッドをオーバーライドし、横長左向き、または横長右向きに対してはYES、縦長の向きに対してはNOを返す。
iOSアプリケーション プログラミングガイド
https://developer.apple.com/jp/devcenter/ios/library/documentation/iPhoneAppProgrammingGuide.pdf

Info.plist に UIInterfaceOrientation を追加

ここはProjectのtarget
設定の Supported
Device Orientations 操
作で設定される

値は UIInterfaceOrientation のはずだがこのような表示になる（この表示の通り入力してもちゃんと値はUIInterfaceOrientation になる）

ここも表示とは異なり、値はUIInterfaceOrientationLandscapeRight or ...Left になるView を横長モードにレイアウト• info-plist の Initial Interface Orientation が Landscape• StoryBoard で ViewController の Orientation (AttributesInspector) が inferred (推論)であれば、自動的に View は横長 (480x320) になる
ViewController の Attributes Inspector

View の Size Inspector

shouldAutorotateToInterfaceOrientation
ViewController.m- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return ((interfaceOrientation == UIInterfaceOrientationLandscapeLeft) ||(interfaceOrientation == UIInterfaceOrientationLandscapeRight));}

ここで設定しているのだから、自動処理してくれてもいいのに！（実験済）タッチイベントの取得

ぱしぱし叩きたい• 画面上のどの位置をタッチしたか知りたい• ボタンの TouchUpInsideなどでは座標位置の情報が足りない• タッチイベントをそのまま取得タッチイベントの取得• touchesBegan イベントがそのまま拾える• 当該ビューでの座標位置が得られる

ViewController.m- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {// タッチ開始地点を取得
CGPoint lastTouchPoint = [[touches anyObject] locationInView:self.view];[self punch:lastTouchPoint];}

自前のアクションを呼び出すなり★が出て消えるアニメーション
ViewController.m- (void)punch:(CGPoint)point {// イメージビューを置く
UIImageView *anImageView = [[UIImageView alloc] initWithImage:flashImage];[anImageView setCenter:point];
最初は Alpha 値 1.0 で用意[anImageView setAlpha:1.0];[anImageView setUserInteractionEnabled:NO];
addSubview で「置く」[self.view addSubview:anImageView];// 徐々に薄くして最後は消す

0.5秒かけて、[UIView animateWithDuration:0.5f
animations:^(void){
anImageView.alpha = 0.3;
Alpha 値 0.3 まで変化させる}
completion:^(BOOL finished){[anImageView removeFromSuperview];
終了したら削除}];}

どこかで画像を初期処理などで flashImage プロパティに読み込み、保持しておく
flashImage = [UIImage imageNamed:@"flash01.png"];全体アニメーション制御
ViewController.m

ループフラグを立てておく// アニメーションの開始- (void)startAnimation{isAnimationRunning = YES;

直線的動作（イーズなどなし）
アニメート中のユー
ザ操作を認める[UIView animateWithDuration:0.1f // コマの周期

delay:0.0f
options:UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAllowUserInteraction
animations:^(void){ // アニメーション 1 サイクルの処理[self moveChars]; // 一歩移動}
completion:^(BOOL finished){if( isAnimationRunning ) [self startAnimation];}];}

ループフラグが立っている限り再びアニメーション処理を行う

・ViewDidAppear イベントなどによって startAnimation 呼び出し・ゲームオーバー判定になればループフラグを落として停止

ようやくゲーム的なこと

逃げるキャラクターを先回りして追う敵• 移動するキャラクターと、どこで遭遇できるかを予想して走る追跡キャラクター

てくる敵

衝突予測：ベクター & 三角関数
以下の状況で A, B が衝突する時間 t と、そのために必要な角θを調べる

追いかける敵Spee

A (Ax, Ay)dA(x, y)

衝突予定地点逃亡する味方B (Bx, By)

ed
SpeB

単純化しました（B 原点出発・横移動のみ）
以下の状況で A, B が衝突する時間 t と、そのために必要な角θを調べる

A(Ax, Ay)
θSpeed A

B(x, y)
Speed B

関係式
A
(Ax, Ay)	 x = SpeedB * t		 x = cos θ * t * SpeedA + Ax	 y = sin θ * t * SpeedA + Ay	 y = 0

θSpeed A

B

x を消す
y を消す

(x, y)
Speed B

SpeedB * t = cos θ * t * SpeedA + Ax	 0 = sin θ * t * SpeedA + Ay
ここで t が最小になる θ を求める

勉強して出直してきます・・・・いや、我々には Alpha があるMathematica (Alpha) で θ について解かせる
SpeedB * t = cos θ * t * SpeedA + Ax	 0 = sin θ * t * SpeedA + Ay

S = Speed B
R = Speed A
X = AxY = Ayr=θと表記Alpha への入力式：
S*t=cos(r)* t* R + X, 0=sin(r) * t * R + Y for r and t

Yがゼロでない解

この 前負号はまずい
R > S なのでこれでは t が負（マイナス時刻）になってしまう

こちらは時刻が常に正になってよろ
しいたぶん無限大が出る(divide by zero)

速度が逆符号の同値でない

速度が同一でない（同一だったら追いつかない）

検算
試しに下側の式に S, R, X, Y の値を入れて t について解かせてみた。
入力式は以下のとおり：(-S X+sqrt(R^2 X^2+R^2 Y^2-S^2 Y^2))/((R-S) (R+S)), R=2, S=1, X=5, Y=5

ルート 7 は 2.6 あたりなので、値は正。よしよし。

code : 最短合流地点への θ を得る
float thetaToMeetFor(float bSpeed, float ax, float ay, float aSpeed){
float theta;// 先に計算不能状態を処理する// もし A の y がゼロ（最初から B の真横にいた）なら theta は π or 0if( fabs(ay) < 10.0 ) {if(ax>0.0)
theta = M_PI;
elsetheta = 0.0;
return theta;}// 解
theta = 2.0 * (atan((-1.0 * sqrt((aSpeed * aSpeed * ax * ax) +(aSpeed * aSpeed * ay * ay) (bSpeed * bSpeed * ay * ay)) - ( bSpeed * ax ) )/ ( ay * ( aSpeed + bSpeed ) )));return theta;}一般化
一般解が欲しくてcos(R) * t * S2 + X2 = cos(r) * t * S1 + X1, sin(R) * t * S2 + Y2 = sin(r) * t * S1 + Y1 for r and tつまりをやらせてみたが、一般解は爆発して無理。
特殊条件のものについては並ぶが、それらではだめ。

レイアウトするだけで新しいステージを作るレイアウトするだけでステージを作る• scene を作り Segue で接続（勝つまで遷移ボタンは非表示）• キャラクターはすべて共通のViewで作成• キャラの性質はそのViewのproperty として埋め込み

User Defined Runtime Attributes
そのViewの任意の property に初期値を設定できるキャラの種類、速度、的の大きさなどを設定

奇妙な制限事項• ほとんど資料なし• 使える形式は右の通り• 数は Number のみ、かつ整数のみ• Point, Size, Rect, Range は実数を受け入れるが使うとエラーになる

Xcode 4.3 以前までは使えた？

コード
ViewController.h@property NSMutableArray *charViews;

ViewController.m@synthesize charViews;// 現在の view の上にある subviews を取り出し、そこから CharView だけを抜き出す- (void)getCurrentCharViews{
NSArray *subViews = [self.view subviews];
CharView *charView;[charViews removeAllObjects]; // キャラの消滅を前提に毎回一旦空にする}

int i;
for(i=0; i<subViews.count; i++) {if([[subViews objectAtIndex:i] isKindOfClass:[CharView class]]) {charView = [subViews objectAtIndex:i];[charViews addObject:charView];}}

isKindOfClass で種別を判定し、該当するものだけ加える

おしまい• Segue での場面進行• Landscape mode での起動
• touchesBegan の利用• Mathematica Alpha• 謎の User Defined Runtime Attributes• Interface Builder だけで新ステージを作れる！