C言語入門|STEP15:敵キャラクターとAIの実装-敵テンプレートと行動選択アルゴリズム

はじめに:敵が動くからRPGは面白い

STEP14では、味方側の魔法とスキルの実装を解説しました。
STEP15では、いよいよ 敵キャラクターとAI(行動ロジック) に注目します。

敵がただ殴ってくるだけでは、
RPGの戦闘はすぐに単調になってしまいます。

このRPGでは、

  • 敵ごとの個性
  • 魔法を使う判断
  • HP状況による行動変化

シンプルなAIロジック で表現しています。

敵キャラクターを表す Enemy 構造体

まずは、敵を表す構造体を確認しましょう。

typedef struct {
    char name[24];

    int hp, max_hp;
    int mp, max_mp;

    int atk;
    int magic;

    int exp;
    int is_boss;

    int defending;
    EnemySpell spells[SPELLS_PER_ENEMY];
} Enemy;

Enemy 構造体の役割

項目内容
name敵の名前
hp / mpHP・MP管理
atk / magic攻撃力・魔力
exp倒した時の獲得経験値
is_bossボス判定
defending防御状態
spells使用可能な敵魔法

味方キャラと似た構成にしているため、
戦闘処理を共通化しやすくなっています。

敵専用魔法 EnemySpell の設計

敵の魔法は、味方とは別の構造体で管理されています。

typedef enum {
    EN_SPELL_DMG = 0,
    EN_SPELL_HEAL = 1,
    EN_SPELL_ATKUP = 2,
    EN_SPELL_DEFUP = 3
} EnemySpellKind;

typedef struct {
    char name[24];
    EnemySpellKind kind;
    int mp_cost;
    int power;
} EnemySpell;

なぜ味方と分けているのか

  • 敵は回復や強化を自動判断で使う。
  • 効果の意味が少し違う。
  • 実装をシンプルに保てる。

味方用と敵用を分ける設計 は、
RPGではよく使われる考え方です。

敵テンプレートという考え方

敵のデータは、
EnemyTemplate 配列として定義されています。

typedef struct {
    char name[24];
    int hp, mp, atk, magic, exp;
    int can_cast;
    EnemySpell spells[SPELLS_PER_ENEMY];
} EnemyTemplate;

テンプレートを使う理由

理由内容
敵追加が簡単データを1つ増やすだけ。
バランス調整数値を一覧で確認できる。
ロジック共通化生成処理を統一できる。

RPGでは、
敵データと処理を分離すること がとても重要です。

ランダムな敵を生成する処理

通常戦闘では、
ランダムに敵が選ばれます。

static Enemy make_random_enemy(void) {
    int n = sizeof(g_enemy_templates) / sizeof(g_enemy_templates[0]);
    const EnemyTemplate* t = &g_enemy_templates[rand() % n];

この処理のポイント

  • 敵の種類数を自動計算
  • rand でランダム選択
  • テンプレートから Enemy を生成

これにより、
戦闘ごとに違う敵と遭遇 する仕組みになります。

敵AIの入口:enemy_try_cast_spell

敵の行動判断は、
enemy_try_cast_spell 関数で行われます。

static int enemy_try_cast_spell(Enemy* e, Character party[])

この関数は、

  • 魔法を使うかどうか判断
  • 使う場合はどの魔法か選択
  • 成功したら1を返す

という役割を持っています。

魔法を使うかどうかの確率判定

最初に、魔法を使うかどうかを
確率で判定しています。

if ((rand() % 100) >= 50) return 0;

この判定の意味

  • 約50%の確率で魔法を使う。
  • 毎回同じ行動にならない。

これだけで、
敵が考えて行動しているように見える ようになります。

使用可能な魔法の絞り込み

次に、MPが足りる魔法だけを抽出します。

if (e->mp >= e->spells[i].mp_cost)

なぜ事前に絞り込むのか

  • MP不足の魔法を選ばない。
  • 無駄な失敗行動を防ぐ。

AIにとっても、
基本的な安全チェックは必須 です。

HP状況による行動選択

敵のHPが少ない場合、
回復魔法を優先するロジックがあります。

if (hp_rate <= 40) {
    if (sp.kind == EN_SPELL_HEAL) chosen = i;
}

この処理が生む効果

  • 瀕死で回復する賢い敵
  • 戦闘が一方的にならない。
  • プレイヤーに緊張感を与える。

簡単な条件分岐だけで、
AIらしさが一気に増します。

敵の通常攻撃との切り替え

もし魔法を使わなかった場合は、
通常攻撃が行われます。

int dmg = enemy.atk + (rand() % 4);

ランダム要素を入れる理由

  • ダメージが毎回同じにならない。
  • 戦闘に揺らぎが生まれる。

完全な固定ダメージより、
少しの乱数がある方がRPGらしい です。

防御フラグの扱い方

敵も味方と同様に、
defending フラグを使っています。

e->defending = 1;

フラグ設計のメリット

  • 次の攻撃だけ半減
  • 状態管理がシンプル
  • 味方側と処理を共通化しやすい。

敵と味方で
同じ考え方を使っている点 が重要です。

STEP15で押さえておきたいポイントまとめ

STEP15で理解しておきたいポイントはこちらです。

  • 敵は Enemy 構造体で管理する。
  • 敵データはテンプレート化する。
  • ランダム要素でAIらしさを出す。
  • HP状況による行動分岐が重要
  • 防御や強化もフラグで管理する。