アーキテクチャの設計
今回はパックマンのゲームを構築するにあたり、全体のアーキテクチャを設計する。
仕様書から画面モードは4つあり、それぞれのモード内にはキャラクタがいて移動処理などがある。これらを1つ1つのオブジェクトとして管理し扱っていく。
設計方針
各画面モードをオブジェクトとして扱い、簡単に切り替え操作したい。
attractMode.startSequence()
// ・・・アトラクトモード実行中・・・
// アトラクトモードからクレジットモードへ切り替える
attractMode.stopSequence()
creditMode.startSequence()
// ・・・クレジットモード実行中・・・
こんな感じ。
またイベント・メッセージを画面モード内のオブジェクトにも簡単に通知したい。
attractMode.sendEvent(message: .Update, parameter: [16])
attractMode.sendEvent(message: .Touch, parameter: [x,y])
イベント・メッセージは表示更新タイミング(Update)やタッチイベントなどで、画面モードが無効のものには通知しないようにする。
まとめると、以下のようなオブジェクトの階層構造が作れるようにする。
![Image4.png]
(https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678332/69e5f872-eea9-d0b3-9f27-f18ab6a4d332.png)
これらの基本コンポーネントのクラスを作成する。
CbObject class(基底クラス)
/// Based object class
class CbObject {
/// Kind of Message ID to handled events in object.
enum EnMessage: Int {
case None
case Update
case Timer
case Touch
case Swipe
case Accel
}
/// When it is true, object can handle events.
var enabled: Bool = true
/// For accessing parent objects.
private var parent: CbObject?
/// Initialize self without parent object
init() {
parent = nil
}
/// Initialize self with parent object
/// - Parameter object: Parent object
init(binding object: CbObject) {
parent = object
parent?.bind(self)
}
/// Bind self to a specified object
/// - Parameter object: Object to bind self.
func bind( _ object: CbObject) {
// TO DO: override
// (This is pure virtual method.)
}
// Send event messages
/// - Parameters:
/// - id: Message ID
/// - values: Parameters of message.
func sendEvent(message id: EnMessage, parameter values: [Int]) {
receiveEvent(sender: self, message: id, parameter: values)
}
/// Handler called by sendEvent method to receive events
/// - Parameters:
/// - sender: Message sender
/// - id: Message ID
/// - values: Parameters of message
func receiveEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
guard enabled else { return }
if message == .Update {
update(interval: values[0])
} else {
handleEvent(sender: sender, message: message, parameter: values)
}
}
/// Event handler
/// - Parameters:
/// - sender: Message sender
/// - id: Message ID
/// - values: Parameters of message
func handleEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
// TO DO: override
// (This is pure virtual method.)
}
/// Update handler
/// - Parameter interval: Interval time(ms) to update.
func update(interval: Int) {
// TO DO: override
// (This is pure virtual method.)
}
}
基底クラスの CbObject は、init で上位のオブジェクトとの関連付けを行える。sendEventメソッドでメッセージを送信でき、メッセージが、.Update なら、updateハンドラが呼ばれ、それ以外は、handleEvent が呼ばれる。ハンドラは派生クラスでオーバーライドして扱う。プロパティの enabled を false にすると、ハンドラは呼ばれなくなる。
CbContainer class(派生コンテナ・クラス)
/// Container class that bind objects.
class CbContainer : CbObject {
private var objects: [CbObject] = []
/// Bind self to a specified object
/// - Parameter object: Object to bind self.
override func bind( _ object: CbObject) {
objects.append(object)
}
/// Handler called by sendEvent method to receive events
/// It sends messages to all contained object.
/// - Parameters:
/// - sender: Message sender
/// - id: Message ID
/// - values: Parameters of message
override func receiveEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
guard enabled else { return }
super.receiveEvent(sender: sender, message: message, parameter: values)
for t in objects {
t.receiveEvent(sender: self, message: message, parameter: values)
}
}
}
派生コンテナ・クラスの CbContainer は、sendEventメソッドで受け取ったメッセージを、関連付けしている全てのオブジェクトに送信する。同様にプロパティの enabled を false にすると、イベント・ハンドラは呼ばれなくなる。
使用方法
class MyObject: CbObject {
override func update(interval: Int) {
print("Update!")
}
}
func test() {
root = CbContainer()
node1 = CbContainer(binding: root)
node2 = MyObject(binding: node1)
root.sendEvent(message: .Update, parameter: [16])
}
root に送ったメッセージが末端の node2 のオブジェクトに届くようになる。
まとめ
全体のオブジェクトの管理、イベント・メッセージの仕組みを設計した。
高々100行程度のコードだが、最初にこれらを決めておかないと、後々、苦労しそうな気がする。
次は、CbContainerクラスを継承して画面モード・クラスを作成していく。
次の記事
[【入門】iOS アプリ開発 #5【シーケンスの設計】]
(https://qiita.com/KIKU_CHU/items/0448daadb09ff581d483)