【Java実践】オブジェクト指向を総動員!RPGの「戦闘システム」を設計してみよう
はじめに
みなさんどうも、おげんです。
これまで、「カプセル化」「継承」「ポリモーフィズム」「インターフェース」「抽象クラス」と、オブジェクト指向の強力な武器を一つずつ手に入れてきました。
でも、正直こう思っていませんか?
「理屈はわかった。でも、結局これらをどう組み合わせて使えばいいの?」
武器は持っているだけでは意味がありません。
実際にそれらを使って、一つのシステムを組み上げてこそ真価を発揮します。
そこで今回は、これまでの知識を総動員して、RPGの「戦闘システム」をどう設計するかを考えてみます!
バラバラだった知識が一つに繋がる感覚を、ぜひ一緒に体験してみてください。
- オブジェクト指向の用語は覚えたけれど、使い道に迷っている人
- 「綺麗なコード」「修正しやすい設計」がどんなものか具体的に知りたい人
- プログラミングの「点と点が線で繋がる」瞬間を味わいたい人
- 自分でも簡単なゲームやシステムを作ってみたいと思っている人
登場人物の役割分担(設計図)
RPGの戦闘シーンを作る際、ただ闇雲にコードを書くのではなく、手に入れた武器を適材適所で配置していきます。
- 抽象クラス(Character): 全てのキャラの「骨格」です。「名前」や「HP」といった共通データと、「攻撃せよ(attack)」という絶対のルールを決めます。
- 継承(Warrior / Wizard): 具体的な「個性」です。親の骨格を引き継ぎつつ、「剣で斬る」「魔法を唱える」といった具体的な中身を書き込みます。
- インターフェース(Healable): 特定のキャラだけの「特殊スキル」です。職業に関係なく「回復ができる」という資格を、必要なキャラにだけセットします。
- カプセル化: キャラの「命綱」です。HPが勝手にマイナスになったりしないよう、セッター(setter)でしっかりガードします。
なぜわざわざ分けるのか?
もしこれらを分けずに、一つのクラスに全部詰め込んだらどうなるでしょう?
「もし戦士なら剣で、もし魔法使いなら魔法で、もし回復持ちなら回復して…」と、if文だらけの巨大なコードになってしまいます。
役割を分けることで、「自分の仕事だけに集中するクラス」が集まった、スッキリしたチームが出来上がるんです。
コードで見る「連携」の凄さ
実際にこの設計をコードに落とし込むと、メインの戦闘処理は驚くほどスッキリします。
「コードを書くときは、まず『何が必要か(抽象)』を定義してから、『具体的な中身』を作っていくのがコツです。」
まず連携するためのそれぞれのクラスを書きます。
// 1. 土台となる抽象クラス
abstract class Character {
protected String name; // カプセル化(継承先からは見える)
public Character(String name) {
this.name = name;
}
abstract void attack(); // 攻撃のルールだけ決める
}
// 2. 回復スキルの免許(インターフェース)
interface Healable {
void heal();
}
// 3. 具体的なジョブ(継承 + 実装)
class Warrior extends Character {
public Warrior(String name) {
super(name);
}
@Override
void attack() {
System.out.println(name + "の剣攻撃!");
}
}
class Wizard extends Character implements Healable {
public Wizard(String name) {
super(name);
}
@Override
void attack() {
System.out.println(name + "の魔法攻撃!");
}
@Override public void heal() {
System.out.println(name + "は呪文で回復した!");
}
}次に戦闘処理のコードを書きます。
ポイントは、「相手の正体が誰であれ、共通の型(Character)として扱う」ことです。
// 全員を「Character」のリストにまとめる(ポリモーフィズム)
List<Character> party = new ArrayList<>();
party.add(new Warrior("おげん")); // 戦士を追加
party.add(new Wizard("アリス")); // 魔法使いを追加
// たったこれだけのループで、全員に一斉指示!
for (Character c : party) {
// 1. 共通の攻撃指示
c.attack(); // 中身が戦士なら剣、魔法使いなら魔法が勝手に発動する!
// 2. 特殊スキルの確認(インターフェースの活用)
if (c instanceof Healable) {
((Healable) c).heal(); // 回復資格がある人だけ、ついでに回復
}
}指示を出す側(メイン処理)のコードを見てください。
「戦士のときは〜」「魔法使いのときは〜」というif文が一つもありません。
「君たちが誰であれ、とにかく Character なんだから attack() はできるよね。さあやって!」と丸投げするだけで、各キャラクターが自分の個性を発揮して動いてくれます。
これが、オブジェクト指向を学んだ人だけが書ける、スマートで美しい連携プレイです。
設計を分けるメリット
「わざわざクラスを分けるなんて、最初は面倒だな……」と思うかもしれません。
でも、この設計は後からじわじわと、「過去の自分、ナイス!」と思わせてくれるメリットがあります。
1. 「追加」にめちゃくちゃ強い
新しく「武闘家」や「忍者」をパーティに追加したくなったと想像してください。
- ダメな例: メインの戦闘処理に
if (忍者なら) { ... }を追加して、全コードを書き直す。 - 今回の設計:
Characterを継承した「忍者クラス」を新しく作るだけ。
なんと、メインの戦闘処理(for文)は1行も触らなくていいんです。
すでにある完成したコードを壊すリスクがゼロなのは、開発において最大の安心材料になります。
2. バグが「迷子」にならない
もし「魔法のダメージ計算がおかしい」というバグが見つかったら、どこを直せばいいでしょうか?
答えは、Wizard クラスの中だけです。
「戦士のコードをいじってたら、なぜか魔法使いまでバグった……」という、プログラミングあるあるの悲劇を防げます。
「ここを直せばここが治る」とハッキリわかるのが、良い設計の証拠です。
まとめ:道具を使いこなす楽しさ
今回は、これまで学んだ知識を総動員して、一つのシステム(戦闘シーン)を設計してみました。
最初は「カプセル化」や「ポリモーフィズム」といった難しい名前に戸惑ったかもしれません。
でも、それらが組み合わさったとき、バラバラだったパーツがカチッとはまって動き出す。
この瞬間こそが、プログラミングで最もワクワクする場面の一つです。
- カプセル化で守り、
- 継承・抽象クラスで土台を作り、
- インターフェースで可能性を広げ、
- ポリモーフィズムで自在に操る。
これらを使いこなすことで、あなたのコードは「ただ動くもの」から、「変化に強く、誰が見ても美しいシステム」へと進化します。
オブジェクト指向という強力な武器を手に入れたあなたなら、もうどんな複雑な世界も一歩ずつ形にしていけるはずです。
次は、あなただけの理想の「村」や「システム」を、自分の手で組み立ててみてくださいね!
今回も最後まで読んでいただき、ありがとうございました!
