ポストモダン解析学: 補題5.13

これは何?

補題5.13の行間(?)が気になって、結局2日ぐらい考えたら分かったので書く。引用は全て丸善出版ポストモダン解析学 原書第3版 J.ヨスト著」から。引用中の赤字強調は全てashiato45による。

問題

状況設定

  • Iは実数の閉区間で、I=[a, b]
  • DはIの可算部分集合*1
  • fはIから実数への関数。以下の性質を満たす。
    • fはI上連続
    • fはI-D上で微分可能(I-D=I \cap D^c)
    • 任意のI-Dの元xについて、f'(x)\le M
    • \rho_nはDの番号付け(Dの定義により、番号付けは可能である。)
    • ηは任意の正実数。

証明しようとしているもの

f(b)-f(a)\le M(b-a)+\eta(b-a+1)

本題

証明から問題の箇所を引用します。

A:=\{\xi\in I: a\le \zeta <\xiとなるすべての\zetaf(\zeta)-f(a)\le M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}とおく。a\in Aなので、Aは空集合ではない。c:=\sup Aとおく。fの連続性よりc\in Aであり…(略)

というのが本文なのだが、「fの連続性」からどうしてc\in Aが出るのか分からなかった。cがここに書いてある条件を満たしているのかとも思ったのだが、この直後にc\in Aそのものから
f(c)-f(a)\le M(c-a)+\eta (c-a)+\eta \sum_{\rho_n <  c} 2^{-n}
を導いているのでそうではないっぽい。

「fの連続性より」で何がおこったのか。

かいとう

登場人物が多くて大変なので、問題と直接の関わりがあるかは分からないがとりあえずいろいろ調べてみる。状況をなんとか目視しやすくしてみる。

まず、Aの定義からして\xi'<\xi\xi \in Aならxi'\in Aなことがわかる。*2よって、Aはぶちぶち途中で千切れたりせず、A=[a,c ] A=[a,c)のどちらかであることが分かる。
f:id:sle:20130427230710p:plain

図の下の青字にあるように、AがA=[a,c ] だろうがA=[a,c)だろうがAの任意の点\zeta\in [a,c)
f(\zeta)-f(a)\le M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}がなりたつ。なぜなら、cはAの上限なので、cがAに属するとすればAの定義そのままで言え、cがAに属さなかったとしても、cよりちょっとだけ小さい点c-\epsilonを十分小さい正数εで表わせばこれはAに属するので、任意の正数εと任意の\zeta\in [a,c-\epsilon] について
f(\zeta)-f(a)\le M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}がなりたち、結局同じことになる。証明おわり。なんなら、一方の点を一個とって、それが他方に入ることを言ってもいい。

ではAから出てしまったらどうなるのかというと、それはf(\zeta)-f(a)M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}の大小はどちらだか分からなくなってしまう。先程言ったように、「千切れて」とびとびにf(\zeta)-f(a)\le M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}がなりたっているかもしれないし、ひょっとしたらそんな区間はないのかもしれない。

してみると、cは「aからここまでは確実にf(\zeta)-f(a)\le M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}と言える」という右端の限界だということになる。そしてこれが大事なのだが、緑字で書いたとおり「右端の限界」ということは、その(cの)ちょっと右ではf(\zeta)-f(a) > M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}という風に否定が確実に成りたつということが分かる。

以上をまとめてみると、青字の情報を特殊化してとりだして、「cのちょっと左まではf(\zeta)-f(a)\le M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}(不等号が小なりイコール)」が言え、緑字より「cのちょっと右まではf(\zeta)-f(a) > M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}\}(不等号が大なり)」が言える。連続性めいてきた。

不等号の両端に式を書くのが面倒なので、以下
F(\zeta):=[f(\zeta)-f(a)]-[M(\zeta-a)+\eta(\zeta-a)+\eta\sum_{\rho_n<\zeta} 2^{-n}]
と定義する。すると、先程のは「cのちょっと左まではFは0以下」で「cのちょっと右まではFは正」ということが言える。

今は簡単のために、このcの左右ちょっとの範囲で\sum_{\rho_n<\zeta} 2^{-n}は定数であるとする。*3すると、fは連続関数であること、Fはfの足し引き定数倍であること、連続関数の足し引き定数倍は連続関数となることより、Fが連続関数であることが言える。連続関数Fが、cのちょっと左まで0以下、ちょっと右まで正なので、F(c)=0が言えた。

最後に、「cの左右ちょっとの範囲で\sum_{\rho_n<\zeta} 2^{-n}は定数である」の仮定を外す。心配なのは、「cのちょっと左まではFは0以下だったのに、不連続性をつかってぴょんとcで0でない正になってしまうのでは」ということだが、\sum_{\rho_n<\zeta} 2^{-n}が単調増加であることから、この項はFを減少させる方向にしか寄与しない。したがって不連続性を使って「増える」ことは絶対にできない。おわり。

ところで

Dで区切られた区間の各々について、千切れない区間での有限増分の定理をつかって、それを足しあわせるんじゃだめなんですか。

*1:1,2,...とIDを振れるようなIの点の集まり。Dとして[a,(a+b)/2]とかはとれない。

*2:「すべての」があるので、特殊ケースとして得られる。

*3:あとで外せることが分かる。

微分積分学(初等の解析学)やるときに大事っぽい式

ポストモダン解析学を読んでいたら、イプシロンデルタ論法の性質で単独でまとめられてもいんじゃねって思ったのがいくつかできたので書きます。多分、微分積分の教科書の行間埋めに役立つんじゃないかと。「かつ」は楔(くさび)の記号\wedgeで表わしてます。あと、PとかQとかは、括弧のなかの文字の関係について述べる*1命題をさします。

2つのイプシロンデルタの式を結びつける

\left(\left(\exists N; \forall n \ge N; P(n)\right) \wedge \left(\exists M; \forall m \ge M; Q(m)\right)\right) \Rightarrow \exists L; \forall l \ge L ; \left(P(l) \wedge Q(l)\right)
lをnとmとのうち、大きいほうをとれば満たされる。2つ以上のイプシロンデルタの命題を結びつけてひとつにしたいときに使う。たとえば、実数列\{a_n\}\lim_{n\rightarrow \infty} a_n = aと収束するとき、
\lim_{n\rightarrow \infty} \frac{a_1 + a_2 + \cdots + a_n}{n}=aを示せ、みたいなときにつかえるはず。

「小さい」のイメージ

fは常に0以上として、P(\epsilon) \Leftrightarrow \exists N; \forall n\ge N; f(n) \le \epsilonとすると、0\le a\le b \wedge P(a) \Rightarrow P(b)がなりたつ。

よく教科書とかにしれっと書いてある、「εは十分小さいので、\epsilon < 1としてよい」みたいなやつの根拠になる。そこで示せたなら、このことによりそれより大きいところは自動的に証明されたことになる。

イプシロンを加工してよい

\left(\forall \epsilon; P\left(\epsilon\right)\right) \Rightarrow \left(\forall \epsilon; P\left(f(\epsilon)\right)\right)
例えばなりゆきで2\epsilonとかで最後の評価をしてしまったときとかに使える。さっきのと組合せて。

訂正

\left(\lim_{n\rightarrow\infty}b_n=0 \wedge \exists N; \forall n\ge N |a_n-a| \le b_n\right)\Rightarrow \forall \epsilon \exists N \forall n\ge N |a_n-a|\le \epsilon
特に、
\left(k\ge 0 \wedge \forall \epsilon \exists N; \forall n\ge N |a_n-a| \le k\epsilon\right)\Rightarrow \forall \epsilon \exists N \forall n\ge N |a_n-a|\le \epsilon

要するにいくらでも縮むもので差を抑えられれば、εで直接に抑えなくてもいいという話。なりゆきで2\epsilonで抑えちゃったときとかの合法化に使える。あと、εで抑えるところでは<\leかを気にしなくてよい、ということも保証する。

全称と存在の交換

\left(\exists a; \forall b; P(a,b)\right) \Rightarrow \forall b; \exists a; P(a,b)
一様連続性から普通の連続性まで情報を落して使うときに。

*1:使わない文字があってもよいが、他の変数には依存しない

変数スコープに関する初歩的なミスで詰んでた

なんか書いてるjsのコードでの問題が切り離せたので備忘録に。

    var array = [];
    
    for(var i=0; i < 3; i++){
      console.log("i=", i);
      array.push(function(){
        console.log("in function ", i);
      });
      array[i]()
    }

    console.log(array.length);
    for(var j=0; j < array.length; j++){
      console.log("run function no.", j);
      array[j]();
    }

こういうコードで、最後の配列に入った関数を実行するときに、

run function no. 0
in function 0
run function no. 1
in function 1
run function no. 2
in function 2

となることを期待していたというのが本質のバグ作ってた。このコードの最後は実際は、

run function no. 0
in function 3
run function no. 1
in function 3
run function no. 2
in function 3

と実行される。「なんで"i<3"なのにi=3になってるんだよ!」という類のことで悩んでいたのだが、カウンタが生きているならば、forが終ったあとはその制約の一個次になるんだったなあと高校のBASICの授業を思いだす。

DIM I

FOR I = 0 TO 3
  PRINT I
NEXT
PRINT I

みたいに書くと(正確な文法忘れた)

0
1
2
3
4

となるはず

追記

"JavaScript: The Good Parts"のクロージャの章に全く同じの載ってました。結局こう直します。

    var array = [];

    for(var i=0; i < 3; i++){
      array.push(function(j){
        return function(){
          console.log("in function (revised): ", j);
        };
      }(i));
    }

    for(var j=0; j < array.length; j++){
      console.log("run function no.", j);
      array[j]();
    }

すごいH本のチェスの演習問題書いた

「すごいHaskellたのしく学ぼう!」の308ページの、3手で到達できるときその途中経路を出力する演習問題を書いてみた

import Control.Monad

type Route = [(Int, Int)]

routeStart :: (Int, Int) -> Route
routeStart x = [x]

inBoard :: Route -> Bool 
inBoard (x:xs) = x `elem` [(a,b) | a <- [1..8], b <- [1..8]]

possibleRoute :: Route -> [Route]
possibleRoute all@((x,y):xs) = do
  (dx,dy) <- [(2,1), (-2,1), (2,-1), (-2,-1), (1,2), (1,-2), (-1,2), (-1,-2)]
  let p = (x+dx, y+dy)
      r = p:all
  guard $ inBoard r
  return r

canReachIn3 :: (Int, Int) -> (Int, Int) -> [Route]
canReachIn3 s g = filter (\x -> head x == g)
                  (return (routeStart s) >>= possibleRoute >>= possibleRoute
                  >>= possibleRoute)

数学は若いころしか出来ない系語録(適当に更新)

いろいろためていってSAN値を下げていこう。随時募集中。

「若いころしか出来ないから急いだほうがいい、例外はあるけど」

うろおぼえ。「怠け数学者の記 (岩波現代文庫)」だったと思う。あと、小平邦彦が言ったわけではなくて、小平さんがプリンストンにいたときに一級の数学者の誰かに言われた言葉だったと思う。

「若くて一番頭のいい時期に数学をやり、少し悪くなったところで哲学をやり、それも出来なくなって歴史をやった。」

ラッセルらしい。

Amazon,ある数学者の生涯と弁明


ここのレビューから。

「かつてはあったがもう二度と帰って来ない創造的能に対する熱烈な嘆き」

Wikipedia,ある数学者の生涯と弁明

カナダ式キーボードでバックスラッシュを打つ

フランス語でTeXやろうとしたらバックスラッシュをどう打つかわからなかったのでメモ。

アクサンの入力

カナダキーボードでは本来はAltGr(AltCar)キーとナンバーキー(?、#キー。)を打てばよいのだが、日本語キーボードではそれぞれ右Altと半角全角キーに対応しているので、右Altを押しながら半角全角を押せばよい。が、うちには右AltがなかったのでKeySwapを使ってひらがなキーを右Altにした。

ギュメ(引用符)の入力

スクリーンキーボードには出ているが対応するキーがよくわからなかったのでEmacs上で何とかしようと思います。キーボードマクロでなんとかしましたが、できあがったものはとにかくこれです。

.emacsのなかに

(load-file "~/french.macs")

homeのfrench.macsに

;; -*-coding:iso-8859-1-*-

(fset 'insert-guillemet
[?« ?» ?\C-b])

としました。ファイル分割しないと文字コードが面倒なのです…


Emacs上でのエンコーディング

%% -*-mode: yatex; coding:iso-8859-1-*-

をつけて保存しました。

TeXでLatin-1を使う

とりあえずこのへん(http://www.tuteurs.ens.fr/logiciels/latex/manuel.html)を参考にいろいろくっつけて、

\usepackage[latin1]{inputenc}
\usepackage[francais]{babel}
\usepackage[cyr]{aeguill}
\usepackage[T1]{fontenc}

とした。何が何かは後で調べよう。

TeXにかます

pLaTeXがsjisにしか対応しないらしくあれなのでLaTeXにやったらうまくいった。

画面遷移構造雑考(一応完結)

Bonjour!前回の更新からもう200日近くたっているようです。あれまあ。最近はC#+XNAでいそいそゲーム開発駆け出しをしていて、今はライブラリ整備をしているのですが、遷移構造のところで霧中になってしまったので、書いて整理をしたいと思います。

定義

「画面遷移」という言葉は割りとポピュラーなようですが一応自分の確認のためにも定義を確認してみます。ゲームにおいては、大きく画面の見た目と機能が変わることがあります。たとえば、タイトル画面で"Press Any Key"と出ているときに何らかのキーを押せばたぶんメインメニュー画面に飛ぶでしょう。タイトル画面においては画面上で何か動かす処理やら、キーに反応してメインメニュー画面に飛ぶ処理やらが要求されますし、メインメニュー画面においては矢印キーで項目を選択したり、項目を表示する機能が要求されるでしょう。このように、タイトル画面の処理とメインメニュー画面の機能には明らかに関連が薄いので、これらの処理を一箇所にまとめて書くのはコードの可読性からして問題だと思われます。そこで、画面の状態で明らかに違うように分けたとき、それらの画面を互いに(何らかの規則にしたがって)移り変わる構造のことをこの記事の中では「画面遷移」と呼ぶことにします。

なぜこれのことで悩む必要があるのか。いい加減に作ればいいのではないか。

確かにいい加減に作って問題が出てから直せばいい、悩んだって徒に時間を消費するだけだというのはそんな気もするし、今のモットーとして「できるだけ小柄に」を採用しているのではっきり言って真理なのですが、ここであえてじっくり悩んだほうが良いと考える理由は次のとおりです。

画面遷移構造はどんなゲームにも出てくる

ミニゲームにしてもタイトル画面、ゲーム画面、リザルト画面の3つは出てきますし、何を作るにしても画面遷移から目を背けている間ずっと苦しむことになります。

可読性にたぶん効いてくる

「困難は分割せよ」は理系の基本ですが、画面遷移を上手に作れば画面ごとに分けてプログラムを書くことをプログラマに推奨することが出来る、というわけで眠いときにプログラムを書いてもそこそこ可読性の高いものを書くことが出来る、はず。

pygame時代の反省

pygame時代に安直に組んだら(あれはあれでそこそこ使えたのですが)もっと複雑なものを作ろうとすると面倒なことになるということが当時わかったのですがその問題点については後述。

考えてみる

ざっくり考えてみる

まず前提として、ゲームは主に描画をつかさどるDrawメソッドとロジックをつかさどるUpdateメソッドとから成っています。というわけで、SceneクラスにDrawメソッドとUpdateメソッドを持たせて、メインのところにSceneの変数CurrentSceneを持たせ、そのSceneを次々取り替えていき、メインのDrawとUpdateからCurrentSceneのDrawとUpdateを呼び出せば作れそうです。

どう実現するか

いきなり閑話休題なのですが、これをXNAでどう実現するかが不安なので少し考えてみます。いまいちXNAのフィロソフィー*1がよくわからないのですが、基本的にゲームで扱うもの全般はGameComponentというものらしく、それをGame.Components(ComponentsはGameComponentのCollection)にAddすると有効になるみたいな雰囲気みたいです。DrawableGameComponentというGameComponentを継承したクラスはUpdateとDrawを持っていて、Addしておくと自動的に呼んでくれます。というわけで登場人物的なものはGameComponentを主軸に書いていくことを考えています。もっといい方法(というかXNA開発者の意図した方法)を知っている人がいたら教えて下しあ><。問題はGame.Componentsがデフォルトでは1個しかなく、これに階層構造を持たせる方法も見たらないので、これをどう扱っていくかというところになりそうです。(そしてこの基本方針が一番怪しい。

そういうわけで、各SceneにIsDeadみたいなメソッドを持たせて、メインのUpdateからそのIsDeadを呼び出し、そのSceneがすでに終了したことがわかったらそのSceneに所属するGameComponentをSceneから取得してそれらをGame.Componentsから削除し、またそのSceneから次のSceneを受け取ってそれをメインのCurrentSceneに代入して作ればよさげです。*2

遷移エフェクトについて考えてみる

ところでこのまま実装するとたぶん画面が一瞬で切り替わってしまいますが、普通画面が切り替わるときは画面がゆっくり暗転するなりの画面の移り変わりのエフェクトがかかるものです。パワーポイントをご存知の方は「ワイプアウト」などを想像していただければそれです。というわけでそれを組み込むことを考えてみます。画面遷移時に遷移前のSceneと遷移後のSceneの両方にアクセスすることが考えられるので、先ほどあったCurrentSceneのほかにPreviousSceneも必要になりそうです。またSceneは、今自分が遷移中でCurrentSceneであるのか遷移が終わってCurrentSceneであるのかPreviousSceneであるのかを区別する方法が必要になります。うち、PreviousSceneであるかどうかは、そのSceneを終わらせるかの裁量はそのSceneにあるわけで、それを示すのがIsDeadでしたから明らかですが、遷移中でCurrentSceneであるのか遷移が終わってCurrentSceneであるのかの情報は遷移をつかさどるものに裁量があるはずです。というわけで、いったんこの「遷移をつかさどるもの」をどのように作るべきかを考えるのがよさそうです。

と、一瞬思ったのですが

CurrentSceneだけでなんとかなる気がしてきました。つまり、Scene甲がその役目を終えたときに、そのScene甲を次のScene乙に渡してしまい、CurrentSceneをScene乙にして、遷移エフェクトはScene乙に担わせればそれでいいような気がしてきました。そして、Scene乙は遷移が終了したときにScene甲を破棄します。このほうがすっきりしていますし、「ざっくり考えてみる」で考えたところからたいした変更なしに実現できそうです。一応、Scene甲はScene乙を生み出して参照せずにメインに渡しますし、Scene乙はScene甲を参照するだけなので、相互参照にはなっていないはずですが、破棄タイミングは不安です。そういうわけで、SceneがPreviousSceneを持つことになりそうです。

どう実現するか

二つの画面を扱うということは、それぞれのSceneについて、その画面を直接にスクリーンにアウトプットするわけには行きません。具体的に考えればすぐにわかる話で、たとえば簡単そうなワイプアウト、つまりScene甲の画面が徐々に左に押し出されていき、Scene乙の画面が右からScene甲を押し出すように入ってくるような遷移エフェクトを考えれば、直接にScene甲やScene乙の画面がスクリーンに直接アウトプットされると、そのアウトプット位置が制御できないためにワイプアウトは実現できなくなってしまうことがわかると思います。というわけで、少なくとも画面遷移中はScene甲や乙の画面は画像としてアウトプットされ、その画像を配置するという風にしないと実現しえません。画像の出力先を2DテクスチャであるTexture2Dにするためには、
http://stackoverflow.com/questions/8425922/draw-a-string-to-texture-in-xna
この辺のことを使えばいいと思うのですが、この辺で煮詰まったのでまた後で。

一度Componentsについて考えてみる(そして昨日煮詰まった原因)

なんかComponentsのなれない仕様に惑わされている気がするので、一度Componentsについてまとめなおしてみます。Game.ComponentsはGameComponentCollectionであって、MSDNhttp://msdn.microsoft.com/ja-jp/library/microsoft.xna.framework.game.components%28v=xnagamestudio.40%29.aspxによれば、

ゲームに対してゲーム コンポーネントを登録するには、対象のコンポーネントを Game.Components.Add に渡します。登録されたコンポーネントは、Game.Initialize、Game.Update、および Game.Draw メソッドから呼び出される描画、更新、および初期化メソッドを持ちます。

とのことなので、描画、更新のするされないはComponentsに含まれているいないで決まるわけです。(本当はGameComponentのVisibleをfalseにしてしまうととまるのですが、使わないものがコレクションに居座っているのも気持ちが悪いので、これに関しては心の片隅に止めつつなかったものとして考えて見ます。)このことを意識しておくと、(抽象的な言葉遣いになってしまうのですが)Sceneは基本的にはGameComponentを包むものであって、SceneがComponentsへSceneの保有するGameComponentを出し入れするという使い方が基本になりそうです。

おそらく(自分のことなんですけどね)昨日煮詰まった原因はここにあります。CurrentSceneがPreviousSceneSceneを保有する形にしてしまうと、CurrentSceneがアクティブなわけですからCurrentSceneのGameComponentはComponentsに登録されているわけですが、その内部でPreviousSceneを生かしておくからにはCurrentScene.PreviousSceneのGameComponentもComponentsに登録されてしまっていて、2画面を描き分けることが出来なくなってしまいます。つまり、機能考えた「と、一瞬思ったのですが」の「どう実現するか」は結構気持ち悪いということがわかりました。

で、どうするか

Game.Componentsの仕組みから言って、どう考えても1フレームには1Sceneにつき1回しかDrawやUpdateしか呼べないことになりますから、割と振り出しに戻った観はありますがComponentsをSceneにも持たせるということになりそうです。なんかすごい回り道でしたが(ここまでで約4500文字)、まあこれしかないという証明が出来てよかった…

とりあえず遷移なしのSceneの再構成

SceneはGameComponentを継承したクラスで、Scene.Componentsを持つ。Scene.DrawではScene.Componentsのすべてに対してDrawが呼ばれ、Scene.UpdateではScene.Componentsのすべてに対してUpdateが呼ばれ、Scene.InitializeではScene.Componentsのすべてに対してInitializeがよばれる。Sceneがアクティブなとき、そのSceneはメインの(つまりGameの)CurrentSceneに入っていて、またGame.Componentsにも入っている。基本的にGame.Componentsの中は1つのSceneだけが入っていることになる。デバッグ用とかで2つ以上にはなるかも

画面遷移ありのSceneの構成

やっぱり遷移のコードとSceneのコードは切り分けておいたほうが良い気がするのでそういう方針で作る。遷移もある意味Scene的な要素を持っているので、むしろ遷移もSceneの一種として捉えたほうがいいような気がしてきた。つまり、Sceneはゲーム中の画面を担うGameSceneクラスと遷移を担うTransSceneクラスに派生し(インターフェースでもいい、というかSceneはインターフェースのほうがいいような気が…)、TransSceneクラスは2つのGameSceneである、遷移前のGameScene甲と遷移後のGameScene乙をとる。遷移中はメインのCurrentSceneはTransSceneになる。TransScene.DrawメソッドでGameScene甲や乙のDrawを呼び出してその画面をTextureに振り向け、適当に処理して描画するということになる。遷移が終わったらTransSceneにもIsDeadがあって、GameScene同様Scene切り替えの手続きでメインのCurrentSceneをScene乙に切り替える。

各GameSceneはやはり遷移中でアクティブであるのか遷移が終わってアクティブであるのかすでに役目を終えたのかの3状態を持つことになる。遷移中かどうかはメインから(あるいはTransSceneから)与えら得る情報なので、その窓口としてStartMainPhaseみたいな遷移が終わったときに呼ばれるvoidのメソッドが必要になる。

SceneはIsDeadとGetNextSceneを持つことになる。メインはUpdateでCurrentScene.IsDeadを呼んで、trueならGetNextSceneで次のSceneを取得しCurrentSceneとする。

だんだん文章がひどいことに…

Sceneは階層構造を持つかもしれない

いままでは2状態の遷移だけを考え、遷移したら遷移した前のことはさっぱり忘れてしまっていたのですが、それだけではすまないかもしれません。たとえば、シューティングゲームでのポーズ画面は一種の状態かもしれません。(最初の定義の「明らかに違う状態」かどうかは怪しいですが。)そのとき、ポーズ中のSceneはゲーム画面にオーバーラップして表示されることになるでしょう。つまり、ゲームのSceneにアクセスできないと困るわけです。

どう実現するか

すると、SceneがSceneを持つことになるかもしれませんが、これを実現するためにいままでの構造を変更する必要はなさそうです。
f:id:sle:20120919234005p:plain
たとえば上のような状態を考えてみたいと思います。TitleからGameってなんやねんという気がするのですが、間にMenuを挟んでもあまり本質的ではないので省きました。鍵は右二つのMainMenuとSystemMenuで、想定としてはゲーム中にEscか何かを押すとSystemMenuに入ってゲームの中断(Titleへ戻る)ができ、さらに左右キーでItemMenuとSystemMenuを行き来できるというところです。二つのMenuはGame画面にオーバーラップして描かれるわけですからGameへのアクセス権が必要です。

今までと主に違うのはGameからSystemMenuへの遷移ですが、これは遷移時にSystemMenuがGameを要求するようにしておけばいいのではないでしょうか。そして、CurrentSceneはSystemMenuのSceneになります。また、ItemMenuに移るときもやはりGameが要求され、これはSystemMenuから渡されます。


なんか満足したのでこの辺で。そのうち推敲するかも。

*1:不安になってぐぐってみたのですが、"Philosophy of Programming Languages"という用法があったので良かった。「その言語はどう使われることを意図しているか」ぐらいの意味のつもりです。

*2:相互参照でどうこうする方法もあるとは思うのですが、一応避けられる場面なら避けておこうかなーと。