Heliodor 2020/06/07 00:12

オーバーライドっ!

なんか必殺技っぽいタイトルですが、プログラム話です。


ちょっと懐かしい話なんですが、ヴィータ大脱出を作ったとき、各キャラクターはオブジェクトという単位で表されていました。要するにただの class です。
CChara というクラスが最上位にあって、そこから敵、プレイヤー、エフェクト、背景などが派生していました。
敵クラスからはさらに個別の敵が派生し、プレイヤークラスからはさらにノーマルヴィータ、ユニゾンヴィータなどが派生していたわけです。

……ところが、これは最後の方で破綻してきます。


いろんなキャラクターで共通して使う機能をキャラクターではなくその大元のクラス CChara にいろいろくっつけたせいで、CChara がほとんどヴィータ大脱出専用クラスみたいになってしまいました。
専用クラスということは、他のゲームで使い回しできないということです。


一見、使い回せる部分はいくらでもありそうなこのふたつのゲーム。実はほぼ丸ごと作り直し(泣)。

さらに悪いことに、オブジェクト指向(死語か?)の副作用ばかりが出てしまい、プログラムした本人ですらプログラム構成が把握できなくなってきたのです。エフェクトひとつ足すのもそれはもう大変でした。

というのも、継承関係がどんどん深くなると同時にオーバーライド(特に描画関係)しまくったせいで、もはやメソッド呼び出しだけを見ただけでは、そこで本当に実行される処理が何なのかが分からないんですよね。

オーバーライドは機能を上書きするってことですから「本当はAしかしないはずだった」メソッドも、継承先のクラスで「Aに加えてBもやる」みたいに書き換えられて、さらにそれを継承した先で「AはやらないでBとCをやる」みたいにその場その場でどんどん変化していてしまうんですよ。まあ全部自分がやったことなんですが。

どこで何のメソッドがオーバーライドされているとも限らないので、ある1行のソースコードを読むだけでも、その定義元クラスとそこから派生したあらゆるクラスについて調べないといけなくなりました。

不幸中の幸いで演算子のオーバーロードには手を出さなかったのですが、もしこれをやっていたら + みたいな超基本的な記号ですらその意味を疑ってかからないといけないところでした。


関係ないですけど、とあるライブラリのファイル名処理では / の記号がオーバーロードされていて

pathA = "AAA"
pathB = "BBB"
pathC = pathA / pathB

って書くと
"AAA/BBB"
って結合されるようになっていたんですよ。これって

pathC = pathA / pathB

の部分だけ見たら普通に割り算やってるようにしか見えないですよね。
ここで本当は何が起こっているのか調べたかったら、最上位クラスまで辿りながら演算子 / のオーバーロードを調べないといけないんですよ。
= の記号ですら、ただの代入とは限らないですからね。


これでとにかく疲労しまくった私は、藁にも縋る思いで
「ゲームエンジン・アーキテクチャー」
という本を購入ました。
これはアンチャーテッド等を開発したノーティードッグの中の人が書いたもので、ゲームエンジンについてのあらゆる話とテクニックが載っています。
私がハマったオブジェクト指向の副作用問題についてもばっちり書かれていたんですよ、これが!

ああ、やっぱりみんなハマるとこなんだなあ……と思いましたね。


そして、そこで得た知識を基にしてして「滅びの国の王女」を作り始めたのです。
(長くなったので次回に続く)




……と、その前にプログラムになじみのない方のために、今回の記事のキーワード「オーバーライド」についてちょっと説明します。


例えば「歩け」という命令があったとして、これを「2本足で立って左右交互に足を出して移動する」と実装したとします。
これは人間キャラクターに対する命令としては適切です。何の問題もありません。

ところが犬のキャラクターを増やしたとき、このままだとちょっと困ることになります。
命令通りなら犬は2本足で歩こうとしますが、それでは不自然です。犬にはちゃんと4本足で歩いてほしいですね。


これを解決するための方法が少なくとも二つあります。

一つは条件分岐です。
「歩け」という命令の内容を書き換えて
「2本足で立って左右交互に足を出して移動せよ。
 ただし、犬は4本足で移動せよ」

という風にします。命令を書きかえたので「上書き」のように見えますが、プログラムで言うオーバーライド(上書き)はこの意味ではありません。

二つ目の方法がオーバーライド(上書き)です。
オーバーライドでは人間にも犬にも同じように
「歩け(2本足で移動せよ)」
と命令します。
条件分岐はなく、相手が誰であろうと関係ありません。
人間は本来の命令通りに2本足で歩きますが、犬はそれを「4本足で移動する」と解釈します。

どこが上書きなの?というと、もともとは「2本足で歩く」という意味だったものを、犬だけは「4本足で歩く」と解釈をする(命令の上書き)ということです。


なんだか面倒くさそうですが、これのメリットはキャラクターの種類が増えたとき、命令を出す側には何の変更も要らないということです。
条件分岐方式だと、命令する側はすべてのキャラクターについて、それがどんな風に移動するのか知ってないといけません。
犬が4つ足の動物だと知らなければ、犬に対して「4つ足で歩け」とは命令できないわけです。

それに対してオーバーライド方式は相手が誰であろうと関係無く、ただ「歩け(2本足で移動せよ)」と命令するだけです。
実際に「どんな風に」歩くのかは各自が独自に解釈します。


例えば人間、犬、鳥、車、カエル というキャラクターがいたとして、

条件分岐方式だと
「歩け(2本足で前進せよ。
 ただし犬だったら4本足で前進、
 鳥だったら羽をばたつかせて前進、
 車だったらタイヤを回転させて前進、
 カエルだったらぴょんぴょん跳ねて前進せよ)」

となりますが、オーバーライド方式だとキャラクターによって解釈が変わるので

「歩け(2本足で前進せよ)」
→人間 「はい(命令通りにしよう)」
→犬  「はい(いや、4本足で歩こう)」
→鳥  「はい(いや、羽ばたこう)」
→車  「はい(いや、タイヤを回そう)」
→カエル「はい(いや、跳ねよう)」

となります。


……ね、簡単でしょ?

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

記事のタグから探す

月別アーカイブ

限定特典から探す

記事を検索