14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

STYLY StudioのUndoの仕組みを解説!

Posted at

STYLY StudioのUndoの仕組み

STYLY Studioの開発を担当しているせぎゅと申します!

今回はUndo・Redoの設計について、STYLY Studioで実践している内容を解説したいと思います!

Mementoパターン

Undoの設計にはさまざまなアプローチがありますが、もっとも直感的なのが Mementoパターン です。STYLY Studioは、この設計パターンを土台としています!

まず、シーンの状態がA→B→Cと変化した時に、各状態のコピーを作成します。

以下のように、インスタンスを差し替えることで、Undo・Redoを実現します。

  • 状態CでUndoを実行したら、状態Cのインスタンスを破棄して、状態Bのインスタンスを復元する。
  • 状態BでUndoを実行したら、状態Bのインスタンスを破棄して、状態Aのインスタンスを復元する。
  • 状態AでRedoを実行したら、状態Aのインスタンスを破棄して、状態Bのインスタンスを復元する。
  • 状態BでRedoを実行したら、状態Bのインスタンスを破棄して、状態Cのインスタンスを復元する。

この状態を記録する場所を、UndoStackと呼びます。

Mementoパターンについては、こちらの記事がわかりやすかったです!

UndoStackに何を記録するのか?

UndoStackに何を記録するのか?というのを定義する必要があります。
Stepを踏んで説明します!

Step1: GameObjectのインスタンスを記録する案

たとえば、GameObjectのインスタンスをまるごと記録したら、どうなるでしょう?
いわゆるスナップショットです!

Undoした際には、スナップショットされたインスタンスを有効化するだけで、状態の復元ができ、設計・実装は非常にシンプルになりそうです。

20230213_Studio_Undo_1.png

しかしUndoStackは、ユーザーがなにかの操作をするたびに、データが記録・蓄積されるものです。

容量の大きいスナップショットが、どんどんメモリに溜まっていき、あっというまにあふれることは想像に難くありません…

Step2: STYLYシーンのXMLを記録する案

STYLY Studioは、STYLYシーンを編集するためのツールです。
STYLYシーンの情報は、XMLファイルとしてサーバーに保管しています。

STYLYでシーンを読み込む際には、このXMLを解析し、GameObjectを描画しています。

このXMLをUndo用に記録する案はどうでしょうか?

UndoやRedoをするたびに、XMLをもとに再描画するのです!

20230213_Studio_Undo_2.png

これはStep1よりメモリ負荷が圧倒的に軽減されています!

しかし、XMLからシーン全体のインスタンスを生成する処理は、非常に重たい処理であり、読込に時間がかかります。

Undo・Redoをするたびに、プログレスバーを表示することになってしまいそうです…

Step3: 変更箇所のXMLを記録する案

STYLYシーンは、以下のような階層構造を持っています。

例: シーンの階層構造

  • Scene
    • Asset A
      • Modifier AA
      • Modifier AB
    • Asset B
      • Modifier BA
      • Modifier BB
    • Asset C
      • Modifier CA
      • Modifier CB

例:

  • Modifier AAを変更したときに、Modifier ABは変化していません。
  • Asset Aを変更したときに、Asset BやAsset Cは変化していません。

この変更箇所の位置と値のみを記録すれば、メモリも節約できますし、復元処理も高速化できます!

20230213_Studio_Undo_3.png

Step4: 変更箇所のStateを記録する案

Step3だとXML形式で記録していますが、XMLの読み書きは時間のかかる処理です。

さらなる最適化を図るために、Stateという概念を導入します!

ここでのStateとは、HTMLにおけるDOMのような役割を果たします。

XMLを解析した結果を、Stateというオブジェクトに保管します。
このStateをもとに、GameObjectを描画します。

ユーザー操作による状態変更は、このStateオブジェクトに記録します。

このStateという単位を、UndoStackに記録します!

20230213_Studio_Undo_4.png

このStateをUndoStackに記録することで、メモリの節約と、復元処理の高速化を実現できます!
STYLY Studioでは、この案を採用しています!

複数箇所を同時に変更する操作を記録したい場合

ここまでは、ユーザーの操作1回につき、変更箇所1つ分の記録をするという前提で説明をしてきました。
しかし、実態はもっと複雑で、操作と変更箇所が1対1で対応するとは限りません…

たとえば、3つのAssetを選択した状態で、削除ボタンを押したケースを考えます。

ユーザーは以下の挙動を期待します。

  • 3つのAssetを選択した状態で、削除ボタンを押した時
    • 3つのAssetが削除される。
  • 上記の直後にUndoをした時
    • 3つのAssetが同時に復活する。

しかし、実際には3つのAssetのStateが変更されるため、変更箇所は3箇所あります。

ここまで説明した仕組みだと、3つのAssetが同時に復活するという挙動を実現できません…!

RecordGroupの導入

この問題を解決するために、RecordGroupという概念を導入します!

以下のシーケンス図のようなイメージです。

Undo操作をしたときは、以下のような挙動になります。

これで、Undoの仕組みの解説は以上です!

おつかれさまでした!

さいごに

本記事作成にあたり、以下のサイトを参考にさせていただきました。ありがとうございました。

14
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?