JASRACが怖くて日和りました。

 Swiftの話だと思った?

 残念、ジュリーでした。

 

 前回(半年前だけどな)予告してた空気抵抗を考慮した自由落下を数値的に解くをやってみます。

 まずは、数字見ても面白くもなんともないので、cube-600.htmlを加工して、平面の上に球を落とすことにします。

 cube-600.htmlは「WebGLでいきますぜい」で紹介したページね。

 

 ↓これね

http://www.tetera.jp/xcc/ameba/cube-600.html

 

 何やってるかや、使い方は以下を順に読みましょう。

 

WebGLでいきますぜい

JavaScriptちゃんでプログラムしますぜい

 
 で、今回はこのcube-600.htmlを加工して、立方体(cube)に向けて、球を(sphere)を自由落下させてみます。
 だいたいドバイタワーくらいの高さ(828m)から落下させようと思うので、カメラポジションとかも調整が必要。高度を変えるたびに調整するのは面倒なので
 

 altitude(高度)

 

という変数を用意して、このaltitudeに設定された高さから、高度0mに配置したcubeまでを1画面にとらえるようにします。

 

 

 具体的には、カメラポジションとカメラの向きをaltitudeをもとに計算することで上のような画面になる。

    var altitude = 828;                    //    初期高度 m
   ・・・
    function threeStart() {
   ・・・
        camera.position.set(0, altitude * 1.1, altitude * 1.1);
        camera.up.set(0, 1, 0);
        camera.lookAt( {x:0, y:altitude * 0.55, z:0 } );

 ここで指定してる3次元座標の1はそのまま1mと考えてください。座標の単位をなんにするかは作る人の勝手です。m単位でもkm、mmなんでもいいけど、自分がどういった単位で座標を扱ってるかは、忘れないように。

 物理ではMKS単位系で計算式を表現することが多いのでmにしました。

 前回の抵抗を考慮した自由落下の式も単位はMKS単位系です。

 

 ↓こいつね

 

 

 cubeの大きさやsphereの大きさも微妙に調整しますた。

 ここら辺は、興味がある人は各自で

    function arrengeObjects()

を見てください。特に重要な話じゃないです。

 重要な点は、ページの表示から経過した時間によって、sphereを動かさなければいけないこと。

 なので、時刻を記憶するための変数を用意。

    var lastDate;                            //    最後の移動処理の時刻

 こいつに、ページ表示時に呼ばれるthreeStart関数内で

        lastDate = new Date();

とすることで、この処理が実行された時点の時刻を記憶させています。

 そして繰り返し呼び出されるloop関数で

        var now = new Date();
        var deltasec = (now.getTime() - lastDate.getTime()) /1000;

として、記憶した時刻(lastDate)からの経過時間を秒単位で計算します。getTime()はミリ秒単位なので1000で割ってる。

 これで経過時間が測定できたので、あとはこの経過時間を使ってオイラー法(前回説明した、移動距離を数値的に解くやり方の呼び名です)で位置を更新するわけですよ。そして最後に、次回のloop関数呼び出し時に、今の時刻からの差分を計算させるためにlastDateを更新する。

        lastDate = now;

 loop関数は、window.requestAnimationFrame(loop)によって、画面の更新間隔(大抵、毎秒60回)で呼び出されるんで、こんな面倒なことせずにdeltasecを1/60=0.17に固定してもいいんですけどね。実際、私のマシンではdeltasecは0.017秒くらいになってたし…

 ただ、毎秒60回の呼び出しはあくまで努力目標なのと、必ずしも画面の更新間隔(リフレッシュレートと呼ぶのだよ)が60Hz(ヘルツと呼ぶのだよ、いや、わかっとるわ)とも限らないんで、今回は真面目に上記のような経過時間測定をしてます。

 

 で、いよいよオイラー法。

 オイラー法で位置を更新する部分はupdateTime関数というのを作ってまとめています。

 当然、変数velocityとdisplacementは値を記憶するためにupdateTime関数外で定義ね。

    var velocity = 0;                 //    初速度 m/s
    var displacement = 0;             //    変位 m
   ・・・
    function updateTime(deltasec) {
   ・・・
        displacement += velocity * deltasec;
        sphere.position.set(0, altitude + displacement, 0);
    }

 velocityとdisplacementがオイラー法で計算される速度と変位です。

 altitudeの初期高度に現時刻での変位を加算して、現時刻で高度としてます。

 それをsphereのy座標に設定して位置を移動させている。

 

 で、ここで残念なお知らせ。

 粘性抵抗側を求める式

 
 
のαの適切な値が見つかりませんでした。
 どこ探しても球体での式

 

 6πrηv   π=3.14  r=球体の半径(m)

 

しか見つからない。Lの人間の体積はだいたい

 

 L = 0.06

 

みたいで、η(エータと読むのだよ)は1.8 x 10-5てのがわかったんだけどαが見つからない。

 

 ↓ここね

http://www.mterm-pro.com/machine-yougo/fluid-dynamics/water-air-bussei.html

 

 しょうがないので、球体の体積が0.06になるrを計算してα=76と決めました。

 

 

 もういっそのこと、粘性抵抗側は無かったことにしたいな。

 まあ、気を取り直して慣性抵抗側。

 

 

 こっちはスムーズに見つかって、βを形状を円柱とした1.2、ρ(ピーじゃないよローだよ)を1.2kg/m3と決定。

 

 ↓ここから

https://ja.wikipedia.org/wiki/抗力

 

 進行方向断面積は適当に0.33m2、質量mは60kgとして、いざオイラー法!

    function updateTime(deltasec) {        
        var a = 76;                          //        粘性側係数
        var b = 1.2;                         //        抵抗側係数(円柱)
        var L = 0.06;                        //        体積(人間、適当)    m3
        var n = 1.8 / 100000;                //        粘度(空気)  Pas
        var Lnv = L * n * velocity;
        var s = 0.33;                        //        進行方向断面積(人間、適当)  m2
        var p = 1.2;                         //        密度(空気)    kg/m3
        var spv2 = s / 2 * p * velocity * velocity;
        var m = 60;                          //        質量    kg
        velocity += - ((9.8 + (-a * Lnv - b * spv2) / m)) * deltasec;
        displacement += velocity * deltasec;    
        sphere.position.set(0, altitude + displacement, 0);
    }
 ドバイタワー(828m)から飛び降りたらどうなるかシミュレーションしてみた。
 
 ↓とうっ!
 ↓jsdo.itの方にも置いておく
 180km/hくらいで落下速度が安定しますな。
 てなわけで、ドバイタワーから飛び降りると19秒くらい落下を楽しめますよっと。
 空気抵抗ないなら、どんどん加速して460km/hくらいになって13秒で地上に激突。
 確かめたい人はupdateTime関数の変数a,bを0にすればいいと思うよ。
 実際、成層圏から飛び降りた映像でも、空気の薄いところでは音速超えて、そこから落ちていって220km/hくらいで落ち着いてるのがわかる。
 以外に遅い。
 

 

 

 「途中でパラシュート開いたらどうなるの?」てのを試すなら、高度が100m切ったくらいのところでsの値を20くらいにするといいでしょう。
 一気に20km/hまで減速して、無事地上に降りられます。
 ま、パラシュートが一瞬で開くわけでもないから、0.5秒くらいで全開になるようにしても面白いかと。そこらへんは各自で。
 

 画面下にある現在の速度や経過時間、高度を更新する

    function updateInfo(now)

に関しても説明はパスします。計算結果を画面に表示してるだけです。

 「走れJavaScriptちゃん!」を読むとか、JavaScript getElementById innerHTMLあたりで検索すればすぐわかります。

 

 あとthreeStart関数でやってる
        light = new THREE.DirectionalLight(0xFFFFFF, 100);
        light.position.set( 0, 2000, 0 );
        scene.add(light);
   ・・・
        renderer.setClearColor(0xEEEEEE, 1.0);
        renderer.shadowMap.enabled = true;
        light.castShadow = true;
        light.shadow.camera.left = -altitude;
        light.shadow.camera.bottom = -altitude;
        light.shadow.camera.right = altitude;
        light.shadow.camera.top = altitude;
        light.shadow.camera.far = 3000;
        cube.receiveShadow = true;
        sphere.castShadow = true;
という部分も、画面上で影をつけるための処理で単なる見栄え。
 気になる人は直接Threeのサイトで調べるか、Three.js 影 あたりでググりましょう。
 
 ↓Threeのサイト

リブートキャンプ by Swift 目次

 

 前回

 

 実例:

http://swift.sandbox.bluemix.net/#/repl/5955b35d2a36cb5cf81581a4

 

からの続き。

 

ラノベ本の原型から抜粋(前回の続き)


「例えば、さっきの勇者の質問を、それぞれの村人に行うならこう書くわけですが、これだと魔法使いには質問できないんですよ」

 安堂が指差す画面では、勇者のaskの引数に、ma、ma2を渡したメッセージにはエラーマークがついている。

 


 

 興味がある人は実際に上の、実例の一番下に追加して試すといいでしょう。

  ・・・
let m2 = Murabito()
let y = Yusha()
let ma = Mahotsukai()
let ma2 = Mahotsukai()

ma2.maho = "爆裂系"            //	ma2のmahoプロパティを変更
ma.answer()
ma2.answer()
y.ask(m:ma)  追加
y.ask(m:ma2) 追加

 Runするとエラーになります。

 で、オブジェクト指向では、これをどう解決するかというと

 

ラノベ本の原型から抜粋(上の続き)


「askの引数の型が村人クラスなんで、魔法使いクラスのオブジェクトは受け付けてくれないのかな?」

「そうなんです」

「魔法使いクラスにもanswerメソッドがあるけどダメなんだ」

「ダメですね。受け取る引数の型が違うと受け付けてくれません。これは関数でも同じですね。引数の型はそれだけ重要ってことなんですが…」

「魔法使いにも質問できるようにしたければ、勇者側に魔法使い用のaskメソッドを追加する必要がある?」

「そうなんですよ」

 

 

「でも、これって、オブジェクトに送るメッセージや、中でやることは全く同じなのに、メソッドは別々って、どうなんだよ?て感じでしょう」

「だね〜」

「こういう時、オブジェクト指向ではクラスの派生という機能が使えるんです」

 そう言うと、安堂は魔法使いの定義を書き換えた。

 
 

「こう書くことでSwiftは、魔法使いクラスを村人クラスが特化したものと解釈するようになるんです。これをクラスの派生と言います」

魔法使いクラスは、村人クラスから派生した

 書き換えと同時に、さっき出ていた場所のエラーマークが消える。

「こうすると魔法使いは村人でもあるので、村人用のaskメソッドに、魔法使いも渡せるようになります」

 しかし、新たに魔法使いクラス定義でエラーが表示されていた。その場所を指差しながら安堂が続ける。

 

 

「クラスを派生させると、派生元クラスがもつ特徴はそのまま継承されます。派生元が持っているメソッドも、そのまま使えることになるんです」

「ですから魔法使い側のanswerメソッドの記述は不要になるんです。というか、二重定義だと注意されます。これがこのエラーの理由です」


 

 というように、派生という機能を使って「魔法使いも村人の一種ですよ〜」とします。

 ただし、これだけだとやっぱりエラーになるんですな。

 それが、村人から派生させたことよって、魔法使い側のanswerメソッドが二重定義となる問題。

 一番簡単なのは、次のように魔法使い側のanswerメソッドを消すことなんですが

class Mahotsukai {  //  魔法使い
    var maho = "回復系"
    func answer() {
        print(maho + "魔法が使えます")
    }
}

 こうしちゃうと、魔法使いも勇者の問いかけに「村の者です」って答えてしまうんですよ。そのための解決策が〜

 

ラノベ本の原型から抜粋(上の続き)


 

「でも、今回は村人のanswerメソッドを、そのまま魔法使い側で使いたくないんですよ。なので、二重定義じゃないよということをXcodeに知らせるために、村人のanswerメソッドにキーワードを追加します」


 

 それがoverrideというキーワードっす。

 

    override func answer() {

 

 

 これで、勇者のaskメソッド内での「あなたは?」の問いかけに対して、村人は「村の者です」と答えるが、魔法使いは使える魔法を答えるようになる。

 

ラノベ本の原型から抜粋(上の続き)


「これって、answerメッセージを送った相手が魔法使いだったから、魔法使いクラスでオーバーライドしたanswerメソッドが使われたってこと?」

「そうなんです。askメソッド内ではあくまで村人として扱われるんですが、実際は村人でもあるが魔法使いでもあるので、魔法使いの振る舞いをしたんですね」

 

メッセージを送る相手によって振る舞いが異なる

 

「これがメソッドと関数の大きな違いであり、オブジェクト指向プログラミングの最大の特徴でもあるんです。iPhoneアプリのプログラムは、このオブジェクト指向プログラミングの特徴を使ってるんですよ。例えば、このtestプログラムなんですが…」

 そう言うと、今度は一昨日のSingle View Applicationテンプレートを選んで用意したtestプロジェクトを開く安堂。そして左のリストからViewController.swiftを選ぶ。

 

 

「このアプリは、起動するとViewControllerオブジェクトが1つ用意されるようになってるんですよ」

「で、ここでさっきの継承の話です。ViewControllerのクラス定義を見てください」

 

 

「ViewControllerは、UIViewControllerを派生させたオブジェクトなのか。てことは、何もオーバーライドしなければUIViewControllerとして振る舞うことになる?」

「そうなんです。じゃあ、その派生元のUIViewControllerは、一体何をするオブジェクトかというと、iPhoneの画面を表示してユーザーと対話するオブジェクトなんですよ」

 

 

「具体的には、UIViewControllerは自分のプロパティとして、画面を受け持つUIViewを1つ用意するようになっていて…」

 

「さっき説明したイベント駆動ループでのイベントが、このUIView、その持ち主のUIViewControllerによって処理されるんです」


 

 ここで出てくる、イベント駆動ループの話は別のところでやってるんですが、ゲームでもなんでも、ユーザーと対話するアプリは大概、このイベント駆動ループという形式で作られてます。

 簡単にいうとユーザーからのキー入力といったイベントを受けて、それに対応するという処理を永遠に繰り返す形式です。でもってイベントの一つに、アプリ終了というものがある。

 iPhoneのアプリもこのイベント駆動ループの形式で作られてます。

 で、XcodeでSingle View Applicationテンプレートを選んで用意したtestプロジェクトでは、アプリが起動されるとイベント駆動ループに入って、その中でViewControllerオブジェクトを用意するまでを自動でやってくます。

 で、ViewControllerは前回見たように、testプロジェクト内に存在する、自分たちが定義するオブジェクトってことになっているんですが。Appleが用意してるUIViewControllerの派生としてるんで、何も書かなければUIViewControllerとして振舞うことになるわけです。

 

ラノベ本の原型から抜粋(上の続き)


 ただし、と言って安堂は続ける。

「ここで実際にメッセージを受け取っているのは、UIViewControllerから派生したViewControllerなので、僕らの方で動作を書き換えることができるんですよ。例えば、もし表示する画面をカスタマイズしたければ、ViewController側で、UIViewController側が持っている、画面を準備するメソッドを…」

 

 

「オーバーライドすればいい?」

 
 

「そういうことです。これがSingle View Applicationテンプレートで用意されるアプリの構造てことになります」

「じゃあiPhoneアプリで、このViewControllerクラスの定義で、派生元のメソッドをオーバーライドすれば、独自のアプリを作れるってことか?」

「そうなります」

 ボタンやウィンドウ、メニューといったグラフィカルなユーザーインターフェースをもつアプリ(GUIアプリ)は、メニューバーをクリックすればメニューが現れ、ウィンドウのタイトルバーを持てば、自由にウィンドウの位置を移動させたりできるが、そういった決まりきった動作を毎回プログラムするのは効率が悪い。

 1からアプリを組み立てるのではなく、最初にベースとなるキットを用意しておき、プログラマがこれを部分部分でカスタマイズできる仕組みがあれば…

 その仕組みをオブジェクト指向の派生とオーバーライドで実現するのが、現在のGUIアプリの作り方の主流だった。

 iPhoneアプリの開発でもそれは変わらない。

「そういった派生やオーバーライドが主体になってるから、今朝俺が書いたプログラムとまるっきり違うことになるのか」

「そうなんです」


 

 例えば、ViewController.swiftでオーバーライドしてるviewDidLoadメソッドの記述に次にように数行の変更を加えると〜

 

 

 真っ白い画面に文字列が表示されるようになります。

 
 
 

 

 ↓Single View Applicationテンプレートを選んでプロジェクトを用意する手順はこれね

 

 

 ↓上の手順で作られるプロジェクトがこれ(一応置いておく)

http://tetera.jp/xcc/book-sample/test.zip

 

 ↓viewDidLoadメソッドに追加記述したプロジェクトがこれ

 これでが自分のアプリを作る第一歩なんですが、果たしてviewDidLoadメソッドでやってることはなんなのか〜?
 以下次回。

リブートキャンプ by Swift 目次

 

 前回までの話を読んで、Mac持ってる人の中には、そろそろ俺もiPhoneアプリのプログラム読めんじゃね?と思って、「iPhoneアプリ開発、号外 ラノベっぽくない」のビデオで作り方紹介したiPhoneアプリ制作プロジェクトの中身を見た人もいるんじゃないすかね?

 

 ↓これね

 

 

 プロジェクトのアイコンをダブルクリックして表示されるウィンドウは、ワークスペースウィンドウって呼ばれます。

 こいつは、アプリを作るために必要なファイルや設定群を管理するためのもので、左側のナビゲーションエリアに、アプリ作成時に使うプログラムの書かれたファイル(以後Swiftソースファイルって呼ぶ)や画像ファイル等の一覧が表示されるようになってます。

 

 

 このうち、ファイル名の末尾のドットで区切られた部分(拡張子というのだよ)が.swiftってなってるのが、Swiftソースファイルです。

 

 

 でもって、そのSwiftソースファイルの項目をクリックすると、右側のエディットエリアにプログラムが表示されるわけで、こいつを書き直したり、新規にSwiftソースファイルを追加したりしてアプリを自分独自のものに変更するわけなんですが…

 試しにAppDelegate.swiftを選ぶと、ぱっと見、関数しかないじゃんてことになるんですよ。


 これじゃ、プログラム、どこから始まるかわからねーよと。

 しかもメモ書きばっかじゃんというね。

 なぜなのか?

 

 その理由は、iPhoneアプリがオブジェクト指向で作られることにあります。

 

オブジェクト指向ってなんだ?

 という問いかけに対する答えがこれ。

 

ラノベ本の原型から抜粋(168ページ〜部分に相当)


「宝探しからちょっと脱線しますが、例えば、ロールプレイングゲームをプログラムすることを考えてみてください」

 今度は、安堂が黒板に説明を書き始めた。

「登場人物には、村人や勇者、剣士、魔術師、それぞれにいろいろな役割があります。オブジェクト指向プログラミングでは、こういった役割ごとに、新しい型を定義するんです」

 

 

「ほら、数値の変数にDouble型とInt型があったでしょう。あの型のことです」

 黒板の次は、新しいPlaygroundを作成してSwiftのプログラムとして書き込んでいく。

「こうやってclassキーワードを使って書くと、Murabito型とかYusya型、Mahotsukai型っていう、オブジェクト用の新しい型が定義できるんです」

 

 

「あと、こういったオブジェクト用の型はクラスって呼びます。『新しい型としてMurabitoクラスを定義した』って感じですね」

 

クラス

「で、このクラスを元に作られるのが、オブジェクトってことになります。オブジェクトは、こんな感じで作ります」

let m1 = Murabito()
let m2 = Murabito()
let y = Yusya()
let ma = Mahotsukai()

実例:

http://swift.sandbox.bluemix.net/#/repl/5955b1132a36cb5cf81581a1

 

「関数の呼び出しみたいだね」

「そうですね。オブジェクトを作って戻す関数と考えてもいいのかもしれませんね」

「じゃ、作ったオブジェクトをm1やm2っていう定数に、設定してるってことか」

「そうです。この場合、Murabito(村人)クラスのオブジェクトを2つと、Yusya(勇者)クラス、Mahotsukai(魔法使い)クラスのオブジェクトをそれぞれ1つ、作ったことになります」

「プログラミングの定数や変数ってのは数値だけじゃないってことか。かなり抽象的なんだな」

「はい。文字列なんかも指定できますよ」

文字列変数・定数

 多くのプログラム言語は、数値に限定されず、色々なものが、定数・変数として扱えるようになっている。Swiftでは文字列の両端を ” (ダブルクォーテーション)で囲むことで、文字列定数・変数の値を意味することになる。また + による文字列の連結も可能だ。

 

 

 

実例:

http://swift.sandbox.bluemix.net/#/repl/5955b16b2a36cb5cf81581a2

 

「で、こうやって作成されるオブジェクトには、プロパティとメソッドと呼ばれる、オブジェクトを特徴づけるための仕組みがありまして…」

プロパティとメソッド

「例えば魔法使いクラスのオブジェクトの特徴として、その魔法使いが使える魔法の種類名を、プロパティとして用意するなら、こう書きます」

class Mahotsukai {		
	var maho = “回復系”	←プロパティ
}

「プロパティは変数ってこと?」

 左京が安堂に尋ねる。

「ええ、実質、変数なんですが、こんな風にクラス定義内で用意すると、この変数は、作成されるオブジェクトごとに別々に用意されることになるんです。このことから、ただの変数と区別してプロパティと呼びます」

let ma = Mahotsukai()		← maはmahoを持つ
let ma2 = Mahotsukai()		← ma2もmahoを持つ
ma2.maho = “爆裂系”		← ma2側だけmahoを変更

「こんな感じで、プロパティを設定する時や参照する時は、対象となるオブジェクトを指定する必要があるんですよ」

 

 

実例:

http://swift.sandbox.bluemix.net/#/repl/5955b21e2a36cb5cf81581a3

 

 Playgroundの出力を見ると、後から作ったma2側のmahoだけ“爆裂系”に変わっていることがわかる。

「ああ、確かに、こうやってオブジェクトごとに変数を持てると便利だね。いかにも、その個体ごとの属性って感じがするよ」

「はい。プロパティはクラスごとに固有のものなので、mahoプロパティは魔法使いクラスのオブジェクトしか持ってないことになりますから、その点でも、いかにも属性ですね」

y.maho = “爆裂系” ← 勇者オブジェクトはmahoを持たないので設定できない

「勇者クラスなんかには、クリアしたクエストの数をプロパティとして持たせてもいいですね。


 

 てことで、今見ているAppDelegate.swiftの内容はクラスの定義ってわけです。

 その中に関数定義がある。

 これが何を意味するかというと

 

 

ラノベ本の原型から抜粋(177ページ〜部分に相当)


それはそれとして、もう1つのメソッドってのは、オブジェクトが持つ機能を定義できるんです。例えば『勇者には村人と喋る機能を持たせる』ならこうです」

「勇者には村人と喋るメソッド、村人には勇者からの問いに答えるメソッドをそれぞれ用意しました」

 

 

「今度は関数そっくりだね」

「そうです。書き方は関数と同じで、クラス定義内で用意するとメソッドと呼ぶことになります」

 村人側のanswerメソッド定義、勇者側のaskメソッド定義をそれぞれ指差しながら安堂が説明する。

「呼び出し方も関数と同じなんですが、プロパティと同じで、どのオブジェクトにするのかを指定する必要があります」

 

 

「それと、関数みたいに呼び出しとは言わずに、オブジェクトにメッセージを送ると言います」

 

「メソッドもプロパティと同じく、クラスごとに固有のものなので、勇者にanswerメッセージを送ったりもできません。そんなメソッド持ってませんから」

y.answer()    ←勇者にanswerメッセージは送れない

「性質自体も関数と違うところがあって、まず、メソッド内でプロパティを使う場合、自分自身のプロパティを使うことになるってのが1つ」

 そう言いながら安堂が、魔法使いのクラス定義にもanswerメソッドを書き加えていく。

 

 

 最後に、各魔法使いオブジェクトにanswer()メッセージを送ると、出力画面には、それぞれの使える魔法名が表示された。

 

 

 実例:

http://swift.sandbox.bluemix.net/#/repl/5955b35d2a36cb5cf81581a4

 

「ちゃんと自分に設定されたmahoの値を表示するんだね」

 

「はい、それと、もう1つ。最も重要なのが…」


 

 

ということで、以下次回!