構造体のDictionaryでは直接値を変更できない
コンテナの返値が値型か参照型かに気をつけましょうねという話。
シチュエーション
例えば、以下のようなコードを書いてみたとする。
public class Program { struct MyStruct { public int val1; public int val2; } public static void Main() { var dic = new Dictionary<int, MyStruct>(); dic[0].val1 = 1; dic[0].val2 = 2; } }
自前の構造体をDictionaryに格納し、その値を変更するコードである。
しかし、これはコンパイルエラーになる。
error CS1612: 変数ではないため、'Dictionary.this[int]' の戻り値を変更できません
Dictionary内の要素に値を代入することができていないようだ。
dic[0]の返値は参照型ではなく値型
実は、これはMyStructが値型であることが原因である。
つまり、dic[0]というのは、dic内の0番目の要素のコピーであって、それに対していかなる操作を行ってもdic内の要素には一切影響しない。
上記のように[]で参照したものの値を直接書き換えたいならば、MyStructをclassにするしかない。
public class Program { class MyStruct { public int val1; public int val2; } public static void Main() { var dic = new Dictionary<int, MyStruct>(); dic.Add(0, new MyStruct()); dic[0].val1 = 1; dic[0].val2 = 2; } }
これならdic[0]はdic内の0番目の要素への参照となり、正しく動く。
C++ならコンテナは必ず参照型を返してくれるので、原因が分かるのに時間がかかった。
neosnippetに自分のスニペットを追加する
前回の記事ではneosnippetを導入したが、自作のスニペットを使いたくなったので方法をメモ。
neosnippetの設定
まずは、スニペットファイルの保存先を指定する。
プラグイン起動時の設定に一行追加する。
let g:neosnippet#snippets_directory = ~/.vim/dein/repos/github.com/Shougo/neosnippet-snippets/snippets/'
したら再起動する。
スニペットの編集
まずは、スニペットを追加したいタイプのファイルを開く。
そして、「:NeoSnippetEdit」とコマンドを入力する。
すると、スニペットの編集画面が開く。
スニペットの構文は以下の通り。
snippet [識別子] alias [略記法] <インデント>スニペット内容 <インデント>スニペット内容 …
スニペット内容は基本は展開したい内容をそのまま書く。
また、Ctrl+Kで順にカーソルが移動できるような場所は「${数字:デフォルト値}」として書けばよい。
終わったら「:w」で保存する。
以上でスニペットの追加は完了である。
OOP初心者がブロック崩しを作ってみる その2
※この記事は「OOP初心者がブロック崩しを作ってみる その1」の続きです
衝突判定を考える
さて、衝突判定のやり方を考えよう。
衝突はボールとReflectorの間で起こる。とりあえず以下のようにクラス分けしてみる。
ReflectorCollectionにReflectorを全て格納しておいて、Ball側から参照、衝突判定を行う。
move関数はBallに速度に従っての移動を指示する関数で、移動するたびに衝突判定を行う。
BallとReflectorのgetRect関数はそれぞれの専有する範囲を返す。(衝突判定に使用)
衝突判定を外に出す
これでも悪くはないが、Ballが「移動する物体」と「衝突する物体」の2つの役割を持っているので、分割したい。
ということで、衝突判定は外に出し、Ballは速度に従って移動することに専念することにする。
CollisionManagerが衝突判定を担当する。
流れとしては、
- Ballのmove関数が呼ばれる
- Ballが速度に従って移動
- BallがexecuteCollisionを呼ぶ
- CollisionManagerが全Reflectorと衝突判定
- CollisionManagerがgiveImpulseを呼び、ボールに力積を与える
という感じ。
Observerパターンを適用する
結合度の面から言えば、BallとCollisionManagerがお互いの関数を直接呼び出しているのであまり良くない。
そもそも、CollisionManagerはボールの移動をトリガーとして衝突判定を行うのである。
したがって、CollisionManagerがボールの移動を監視すれば良い。
つまり、Observerパターンを適用する。
move関数で移動した後、observerに移動を通知する。
これで多少はすっきりした。
Reflectorが衝突を知るには
ブロックのように衝突の有無を情報として必要とするReflectorが存在するので、それを通知する必要がある。
それもCollisionManagerが担当するのが妥当であろう。
以上のことを加味してまとめたクラス図はこうなった。
衝突判定に関しては概ねこのような感じでいいだろう。
次回予告
UMLをテキストで書ける「PlantUML」を試す
UML図を書くフリーのツールにはいろいろあるが、テキストコマンドで書けるものの一つに「PlantUML」がある。
テキストベースで書けると何がいいかというと、
- 保存・編集・移動・再利用が容易である
- Gitなどのバージョン管理システムとの親和性
などの点が挙げられる。
使い方等
PlantUML自体はJavaで書かれたテキストプロセッサであるが、Web上でこれを試せるサービスがある。
リファレンスはここにある。
クラス図・フローチャートのほかにも色々とかけるらしい。
使用例
テキスト
@startuml start :ボールを移動; repeat :Reflectorを一つ取り出す; if (ボールと衝突したか?) then (yes) :ボールの移動方向を変える; else (no) endif repeat while (Reflectorはあまだあるか?) stop @enduml
出力
これは積極的に使っていきたい。
OOP初心者がブロック崩しを作ってみる その1
オブジェクト指向の設計やコーディングについて自分なりに情報を集め始めてからかなり経って、自分なりに手法というものがある程度見えてきた。
ということで、それの実践としてひとつ簡単なゲームを制作してみたいと思う。
概要
今回作るのはよくある美少女を剥ぐ感じのブロック崩しである。
理由は友人にもらったネタがこれだったからだけであって、それ以上の理由はない。
設計の指針
まずオブジェクト指向の原則として
- クラスはなるべく疎結合にする
- 小さなクラスを多数組み合わせる
- データを求めるのではなくメッセージをやり取りする
などの原則があるのでそれを意識する。
さらに、GoFのデザインパターンの中でも有用なものは積極的に取り入れていく。
※主に以下のサイトの影響を受けている
オブジェクト指向の設計と実装の学び方のコツ - SlideShare
その1:要求分析
今回はブロック崩しがどんなソフトであるか具体的に分析しながら設計を考えてみる。
実際のゲームではメニューやら設定やらもあるのだが今回はコア部分のみに注目する。
非常に簡略だが図を書いてみた。
ブロックがメッシュ状になっているのは画像を分割した図を想定しているからだ。
ここでボールの動きに注目すれば、
- 普段は一定速度で動き続ける
- 壁やパドルにぶつかったら跳ね返る
- ブロックにぶつかったらブロックを壊して跳ね返る
ということになる。
つまり、ボールは「移動するか」「跳ね返るか」のどちらかの動きをする。
そうすれば、とりあえず「壁」「パドル」「ブロック」はすべて「ボールを跳ね返らせるもの」として抽象化できそうだ。
とりあえず跳ね返らせるものなのでReflectorという名前でくくっておくことにする。
ボール関係の処理
ボールに関する処理を簡単にまとめてみる。
とりあえず1フレーム分の処理。
ボールを移動した後、各オブジェクトと衝突判定を行う。
とりあえず正しく動くようにするため、衝突判定は全オブジェクトに対して行うことにした。
「壁」「パドル」「ブロック」といった衝突対象の違いを考慮しなくて良くなったので、割と簡単な処理になった。
パドル関係の処理
パドルもReflectorの一つで、衝突判定はよそで勝手にやってくれる。
したがって、パドルが関心を持つ必要があるのは自身の移動のみである。
とりあえず、左キー、右キーで左右に移動できるようにしてみよう。
衝突を考えなくて良いのでかなりシンプル。
ブロック関係の処理
ブロックもReflectorの一つで、衝突判定自体を自身でする必要はない。
しかし、衝突された時には自身を消さなければならない。
ということで、こんな感じになるだろうか。
衝突したかどうかを情報として使うので、ブロックの処理はボールの処理より後になるだろう。
処理のまとめ
各々のオブジェクトが担当する処理は概ね明確になったので、これらの組み合わせ方を考える。
フロー図ではこんな感じだろうか。
これでコア部分で必要な処理が明確になった。
無論、この構造が最後まで完璧に通るとは思わないが。
次回予告
次回 「OOP初心者がブロック崩しを作ってみる その2」ではコア部分の処理のクラス化について考えていく。