Menu
Coddy logo textTech

ゲームキャラクター開発

CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 86/87。

このチャレンジでは、複雑でインタラクティブなエンティティをモデリングする際のOOP(オブジェクト指向プログラミング)の力を示す、ゲームキャラクター開発システムを構築します。

基底となる Character クラスが health、name、level といった共通の属性を定義する、キャラクターの階層構造を設計します。WarriorMageArcher などの特化したクラスは、独自の能力や戦闘スタイルでこの基底クラスを拡張します。

インターフェースはここでは自然に機能します。Attackable は戦闘メソッドを定義でき、一方で Healable は回復能力を扱います。Mage は両方を実装するかもしれませんが、Warrior は攻撃のみに集中します。

異なる攻撃行動にはストラテジーパターン(Strategy pattern)の使用を検討してください。これにより、キャラクターが戦闘スタイルを動的に切り替えられるようになります。ステートパターン(State pattern)は、行動に影響を与える通常、毒、スタン状態といったキャラクターの状態を管理するのに適しています。

キャラクターが武器や防具を継承するのではなく、それらを「持っている」という装備システムのコンポジションについて考えてみましょう。キャラクタークラスやダメージタイプには enum を使用し、カプセル化を適用して不正な変更からステータスを保護します。

challenge icon

チャレンジ

簡単

複雑でインタラクティブなゲームエンティティをモデリングすることで、OOPの力を示すゲームキャラクター開発システムを構築しましょう!異なる戦闘スタイル、特殊能力、そしてキャラクターの振る舞いに影響を与えるステータスシステムを持つキャラクター階層を作成します。

コードは以下の7つのファイルに分けて構成します:

  • DamageType.java: ゲーム内の異なるダメージタイプを表す列挙型(enum)を作成します。PHYSICALMAGICALRANGED の値を含めてください。プライベートフィールド 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:strength
    • CREATE_MAGE:name:maxHealth:level:intelligence:mana
    • ATTACK:attackerName:targetName
    • HEAL:mageName:amount
    • STATUS: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 ---" を出力する
        // その後、各キャラクターのステータスを出力する
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン