ゲームキャラクター開発
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 86/87。
このチャレンジでは、複雑でインタラクティブなエンティティをモデリングする際のOOP(オブジェクト指向プログラミング)の力を示す、ゲームキャラクター開発システムを構築します。
基底となる Character クラスが health、name、level といった共通の属性を定義する、キャラクターの階層構造を設計します。Warrior、Mage、Archer などの特化したクラスは、独自の能力や戦闘スタイルでこの基底クラスを拡張します。
インターフェースはここでは自然に機能します。Attackable は戦闘メソッドを定義でき、一方で Healable は回復能力を扱います。Mage は両方を実装するかもしれませんが、Warrior は攻撃のみに集中します。
異なる攻撃行動にはストラテジーパターン(Strategy pattern)の使用を検討してください。これにより、キャラクターが戦闘スタイルを動的に切り替えられるようになります。ステートパターン(State pattern)は、行動に影響を与える通常、毒、スタン状態といったキャラクターの状態を管理するのに適しています。
キャラクターが武器や防具を継承するのではなく、それらを「持っている」という装備システムのコンポジションについて考えてみましょう。キャラクタークラスやダメージタイプには enum を使用し、カプセル化を適用して不正な変更からステータスを保護します。
チャレンジ
簡単複雑でインタラクティブなゲームエンティティをモデリングすることで、OOPの力を示すゲームキャラクター開発システムを構築しましょう!異なる戦闘スタイル、特殊能力、そしてキャラクターの振る舞いに影響を与えるステータスシステムを持つキャラクター階層を作成します。
コードは以下の7つのファイルに分けて構成します:
DamageType.java: ゲーム内の異なるダメージタイプを表す列挙型(enum)を作成します。PHYSICAL、MAGICAL、RANGEDの値を含めてください。プライベートフィールドdescription(String)、それを設定するコンストラクタ、およびゲッターを追加します。物理ダメージ(Physical)の説明は "brute force"、魔法ダメージ(Magical)は "arcane energy"、遠距離ダメージ(Ranged)は "precision strike" としてください。Attackable.java: 戦闘能力を定義するインターフェースを作成します。攻撃を説明する String を返すattack(Character target)メソッドと、DamageType を返すgetDamageType()メソッドを宣言します。Healable.java: 回復能力のためのインターフェースを作成します。回復アクションを説明する String を返すheal(int amount)メソッドを宣言します。Character.java: すべてのゲームキャラクターの抽象基底クラスを作成します。name(String)、health(int)、maxHealth(int)、level(int) のプライベートフィールドを含めます。コンストラクタは name、maxHealth、level を受け取り、初期状態で health を maxHealth と等しく設定します。すべてのフィールドのゲッターを含めます。health が 0 から maxHealth の間に収まるようにする protected メソッドsetHealth(int health)を追加します。health を減少させ、"[name] takes [damage] damage! Health: [health]/[maxHealth]"を返すtakeDamage(int damage)メソッドを作成します。health が 0 より大きい場合に true を返すisAlive()メソッドを追加します。String を返す抽象メソッドgetCharacterClass()を宣言します。Warrior.java: Character を継承し、Attackable を実装するクラスを作成します。プライベートフィールドstrength(int) を追加します。コンストラクタは name、maxHealth、level、strength を受け取ります。getCharacterClass()を実装して"Warrior"を返すようにします。getDamageType()を実装してPHYSICALを返すようにします。attack(Character target)を実装して、strength と level の合計に等しいダメージを与え、ターゲットの takeDamage メソッドを呼び出し、"[name] strikes with [damageType description] for [damage] damage!"に続けて改行と takeDamage の結果を返すようにします。Mage.java: Character を継承し、Attackable と Healable の両方を実装するクラスを作成します。プライベートフィールドintelligence(int) とmana(int) を追加します。コンストラクタは name、maxHealth、level、intelligence、mana を受け取ります。getCharacterClass()を実装して"Mage"を返すようにします。getDamageType()を実装してMAGICALを返すようにします。attack(Character target)を実装して、intelligence の 2 倍に等しいダメージを与え、Warrior と同じ形式に従います。heal(int amount)を実装して、health を回復(setHealth を使用)し、"[name] channels healing magic! Health: [health]/[maxHealth]"を返すようにします。Main.java: ゲームシステムを動かしましょう!カンマ区切りのコマンド文字列を受け取ります:CREATE_WARRIOR:name:maxHealth:level:strengthCREATE_MAGE:name:maxHealth:level:intelligence:manaATTACK:attackerName:targetNameHEAL:mageName:amountSTATUS:characterName
キャラクターのコレクションを保持します。各コマンドを処理し、以下を出力してください:
- CREATE_WARRIOR/CREATE_MAGE:
[name] the [characterClass] joins the battle! - ATTACK: 攻撃結果(attack メソッドによって返される String)を出力します。
- HEAL: 回復結果を出力します。キャラクターが Mage でない場合は
[name] cannot healと出力します。 - STATUS:
[name] ([characterClass]) - Level [level] - Health: [health]/[maxHealth] - [Alive/Defeated]
すべてのコマンドの後、
--- Battle Summary ---を出力し、続けて各キャラクターのステータスを出力します。
入力として、カンマ区切りのコマンド文字列を1つ受け取ります。
例えば、入力が CREATE_WARRIOR:Thorin:100:5:15,CREATE_MAGE:Gandalf:80:7:12:50,ATTACK:Thorin:Gandalf,HEAL:Gandalf:25,STATUS:Gandalf の場合、出力は以下のようになります:
Thorin the Warrior joins the battle!
Gandalf the Mage joins the battle!
Thorin strikes with brute force for 20 damage!
Gandalf takes 20 damage! Health: 60/80
Gandalf channels healing magic! Health: 80/80
Gandalf (Mage) - Level 7 - Health: 80/80 - Alive
--- Battle Summary ---
Thorin (Warrior) - Level 5 - Health: 100/100 - Alive
Gandalf (Mage) - Level 7 - Health: 80/80 - Aliveこのチャレンジは、継承(Character 階層)、複数インターフェースの実装(Mage は Attackable と Healable の両方を実装)、メソッドを持つ列挙型(DamageType)、ポリモーフィズム(異なるキャラクタータイプを一律に扱う)、およびカプセル化(protected による health 管理)を、魅力的なゲームシステムに統合したものです!
チートシート
オブジェクト指向プログラミング(OOP)は、クラス、継承、インターフェース、およびデザインパターンを通じて、複雑なシステムをモデリングすることを可能にします。
クラス階層
共通の属性を持つベースクラスを作成し、それを特化したクラスで拡張します:
public abstract class Character {
private String name;
private int health;
private int maxHealth;
private int level;
public Character(String name, int maxHealth, int level) {
this.name = name;
this.maxHealth = maxHealth;
this.health = maxHealth;
this.level = level;
}
protected void setHealth(int health) {
this.health = Math.max(0, Math.min(health, maxHealth));
}
public abstract String getCharacterClass();
}インターフェース
クラスが実装できる特定の振る舞いのための規約(コントラクト)を定義します:
public interface Attackable {
String attack(Character target);
DamageType getDamageType();
}
public interface Healable {
String heal(int amount);
}フィールドを持つ列挙型(Enums)
列挙型はフィールド、コンストラクタ、およびメソッドを持つことができます:
public enum DamageType {
PHYSICAL("brute force"),
MAGICAL("arcane energy"),
RANGED("precision strike");
private String description;
private DamageType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}複数のインターフェースの実装
クラスは複数のインターフェースを実装できます:
public class Mage extends Character implements Attackable, Healable {
private int intelligence;
private int mana;
@Override
public String attack(Character target) {
// 実装
}
@Override
public String heal(int amount) {
// 実装
}
}カプセル化
アクセスを制御しデータを検証するために、privateフィールドとprotectedセッターを使用します:
protected void setHealth(int health) {
this.health = Math.max(0, Math.min(health, maxHealth));
}ポリモーフィズム(多態性)
共通のベースクラスやインターフェースを通じて異なるキャラクタータイプを一律に扱い、柔軟なコレクションやメソッド呼び出しを可能にします。
自分で試してみよう
import java.util.Scanner;
import java.util.HashMap;
import java.util.Map;
import java.util.LinkedHashMap;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: キャラクターを保持するためにコレクションを使用する (LinkedHashMap は挿入順序を維持します)
// LinkedHashMap<String, Character> characters = new LinkedHashMap<>();
// TODO: 入力をカンマで分割して個別のコマンドを取得する
// String[] commands = input.split(",");
// TODO: 各コマンドを処理する:
// - CREATE_WARRIOR:name:maxHealth:level:strength
// 出力: "[name] the Warrior joins the battle!"
//
// - CREATE_MAGE:name:maxHealth:level:intelligence:mana
// 出力: "[name] the Mage joins the battle!"
//
// - ATTACK:attackerName:targetName
// attack メソッドからの攻撃結果を出力する
//
// - HEAL:mageName:amount
// 回復結果を出力する。Mage でない場合は "[name] cannot heal" と出力する
//
// - STATUS:characterName
// 出力: "[name] ([characterClass]) - Level [level] - Health: [health]/[maxHealth] - [Alive/Defeated]"
// TODO: すべてのコマンドの後、"--- Battle Summary ---" を出力する
// その後、各キャラクターのステータスを出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造