0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

戦闘・交渉・調査で同じ能力値設計を使う――AbilitySetの考え方【第18回】

0
Last updated at Posted at 2026-08-28

4Hえんぴつ|React+TypeScriptでWeb RPGを作る

第4章「戦闘・交渉・調査と状態管理」
第18回:戦闘・交渉・調査に能力値を対応させる――AbilitySetの考え方

連載トップ・第0回はこちら

本連載では、設計・実装・テスト・改善にAIを活用しながら、React+TypeScriptによるWeb RPG開発を進めています。

1. 今回のテーマ

前回は、

export type ResolutionType =
  | "combat"
  | "negotiation"
  | "investigation";

として、

戦闘
交渉
調査

を別々のシステムではなく、

「イベントを解決する方法」

として共通化しました。

では、プレイヤーがその方法を選んだとき、

何を基準に得意・不得意を判定するのか

が次の問題になります。

今回のRPGでは、そのために、

export interface AbilitySet {
  combat: number;
  negotiation: number;
  investigation: number;
}

というAbilitySetを使っています。

今回は、

「プレイヤーが何を得意としているか」を、TypeScriptのデータとしてどう表現するか

を見ていきます。


2. 解決方法だけでは判定できない

例えば、イベントで、

戦闘
交渉
調査

の3つを選べるとします。

プレイヤーが「戦闘」を選んだとき、

戦闘を選んだ
↓
成功

と固定してしまうと、能力値の意味がありません。

一方、

戦闘を選んだ
↓
プレイヤーの戦闘能力を見る
↓
イベント側の難易度と比較する
↓
結果を決める

とすれば、

同じ選択でも、キャラクターによって結果が変わる

ようになります。


3. AbilitySetを作る

現在の設計では、プレイヤーの主能力を次のように持っています。

export interface AbilitySet {
  combat: number;
  negotiation: number;
  investigation: number;
}

例えば、

const abilities: AbilitySet = {
  combat: 4,
  negotiation: 2,
  investigation: 3,
};

なら、

戦闘:4
交渉:2
調査:3

というキャラクターです。

この例なら、

戦闘が得意で、交渉はやや苦手

という特徴をデータとして表現できます。


4. ResolutionTypeと同じキーにする

ここでポイントになるのが、

combat
negotiation
investigation

というキーです。

前回作ったResolutionTypeも、

export type ResolutionType =
  | "combat"
  | "negotiation"
  | "investigation";

でした。

つまり、

ResolutionType
combat
negotiation
investigation

AbilitySet
combat
negotiation
investigation

で対応しています。

現在のMVPでは、この対応を1対1にしています。


5. AbilityKeyもResolutionTypeに合わせる

現在の設計では、

export type AbilityKey = ResolutionType;

としています。

これによって、

戦闘を選ぶ
↓
combat能力を使う

交渉を選ぶ
↓
negotiation能力を使う

調査を選ぶ
↓
investigation能力を使う

という対応を型で表現できます。

別の対応表を用意しなくても、

解決方法の種類そのものを能力値のキーとして使える

わけです。


6. 選んだ方法から能力値を取得する

以下は考え方を説明するため、実装を簡略化しています。

function getAbilityValue(
  abilities: AbilitySet,
  type: ResolutionType
): number {
  return abilities[type];
}

例えば、

const abilities: AbilitySet = {
  combat: 4,
  negotiation: 2,
  investigation: 3,
};

getAbilityValue(abilities, "combat");
// 4

getAbilityValue(abilities, "negotiation");
// 2

となります。

この形なら、

getCombatAbility();
getNegotiationAbility();
getInvestigationAbility();

のように、3種類の関数を別々に作る必要がありません。


7. なぜ能力値も3種類にしたのか

RPGの能力値には、もっと多くの項目を持たせることもできます。

例えば、

腕力
敏捷
知力
魅力
精神力
器用さ
運

などです。

それらから、

戦闘判定
交渉判定
調査判定

を計算する設計も可能です。

ただし今回のWeb RPGは、最初から複雑なキャラクタービルドを作ることが目的ではありませんでした。

まず必要だったのは、

戦闘が得意
交渉が得意
調査が得意

という違いを表現できることです。

そのためMVPでは、

解決方法と同じ3能力に絞っています。


8. 能力値を増やしすぎない

例えば最初から、

interface AbilitySet {
  strength: number;
  agility: number;
  intelligence: number;
  charisma: number;
  perception: number;
  luck: number;
}

として、

combat =
strength × 0.6
+ agility × 0.4

negotiation =
charisma × 0.7
+ intelligence × 0.3

のような計算もできます。

ただし、そうすると、

能力値設計
計算式
バランス調整
UI表示
職業補正
スキル補正

など、決めることが一気に増えます。

MVP段階では、

プレイヤーの選択によって結果が変わる仕組みを確認すること

を優先しました。


9. 小さい型でもキャラクターの違いを作れる

能力値が3つだけでも、組み合わせによって個性は作れます。

例えば、

const warrior = {
  combat: 5,
  negotiation: 1,
  investigation: 2,
};

なら戦闘型です。

const negotiator = {
  combat: 1,
  negotiation: 5,
  investigation: 3,
};

なら交渉型。

const investigator = {
  combat: 2,
  negotiation: 2,
  investigation: 5,
};

なら調査型です。

つまり、能力値の数を増やさなくても、

どの方法を選びやすいキャラクターなのか

は表現できます。


10. PlayerはAbilitySetを持つ

現在の設計では、プレイヤーデータに能力値を持たせています。

export interface Player {
  id: string;
  name: string;
  jobId: string;
  abilities: AbilitySet;
  skillIds: string[];
}

ここで、

abilities: AbilitySet;

として持つことで、

Player
↓
abilities
↓
combat / negotiation / investigation

という形で参照できます。


11. 職業と能力値は同じものではない

Playerには、

jobId: string;

もあります。

ここで、

職業
=
能力値

にはしていません。

例えば、

戦士
↓
combat = 5

と完全に固定する方法もあります。

しかし、職業は、

初期能力
スキル
推奨される解決方法

などをまとめた、

キャラクター構成の元になるデータ

として扱えます。

プレイヤー自身が持つ現在の能力値とは分けています。


12. JobにはbaseAbilitiesを持たせる

現在の職業モデルは、概念的に次のような構造です。

export interface Job {
  id: string;
  name: string;
  description: string;
  baseAbilities: AbilitySet;
  skillIds: string[];
  recommendedResolution: ResolutionType;
}

例えば、

Job
↓
初期能力値
↓
Player作成時のabilities

という流れを作れます。

この分離によって、将来、

成長
装備
イベント
スキル

などでプレイヤー能力が変わっても、職業マスターそのものを書き換えずに済みます。


13. スキルも能力値とは分ける

Playerには、

skillIds: string[];

もあります。

また、スキル側には、

export interface Skill {
  id: string;
  name: string;
  description: string;
  resolutionType: ResolutionType;
  modifier: number;
}

という情報を持たせられます。

例えば、

基礎戦闘能力
4

+
戦闘系スキル補正
1

=
判定時の値
5

という使い方ができます。

つまり、

AbilitySet
↓
基本的な得意・不得意

Skill
↓
追加の補正

と役割を分けています。


14. 能力値そのものと判定値は同じとは限らない

ここは重要です。

例えば、

abilities.combat

が4だったとしても、

最終的な判定に必ず4だけを使うとは限りません。

将来的には、

基礎能力
+
スキル補正
+
イベント補正
+
フラグ補正

のように計算できます。

そのため、

AbilitySet
=
判定の最終結果

ではなく、

判定に使う基本値

として考える方が拡張しやすくなります。


15. 能力値の意味をUIにも利用できる

能力値を明確なデータとして持つと、画面にも利用できます。

例えば、

戦闘 ★★★★☆
交渉 ★★☆☆☆
調査 ★★★☆☆

のように表示できます。

さらにイベント画面でも、

戦闘 得意
交渉 やや苦手
調査 普通

といった補助表示へつなげることもできます。

ただし、

表示用の文字列そのものをAbilitySetへ保存する必要はありません。

数値から画面側で組み立てられます。


16. 「得意だから必ず成功」にはしない

能力値が高いからといって、

combat = 5
↓
戦闘なら必ず成功

にすると、選択が単純になります。

今回の設計では、

プレイヤー能力

だけではなく、

イベントごとの難易度

も組み合わせて結果を決めます。

つまり、

能力値が高い
↓
有利

能力値が低い
↓
不利

ではありますが、

能力値だけで結果を固定するわけではありません。


17. 同じキャラクターでもイベントによって結果は変わる

例えば、

戦闘能力:4

のプレイヤーがいたとします。

簡単な相手なら、

戦闘能力 4
vs
難易度 2

で有利です。

しかし強い相手なら、

戦闘能力 4
vs
難易度 5

となります。

つまり、

プレイヤー側
AbilitySet

+

イベント側
difficulty

によって判定が変わります。

次回は、このイベント側の難易度を扱います。


18. ResolutionDefinitionへつながる

第17回で少し紹介したResolutionDefinitionには、

type: ResolutionType;
ability: AbilityKey;
difficulty: number;

があります。

これによって、

このイベントの
この解決方法では

どの能力を使うか
どれくらい難しいか

をデータとして定義できます。

つまり、

Player
↓
AbilitySet

ResolutionDefinition
↓
ability / difficulty

を組み合わせて判定する構造です。


19. AbilityKeyを分けている理由

現在は、

export type AbilityKey = ResolutionType;

なので、

combat → combat
negotiation → negotiation
investigation → investigation

の1対1です。

それなら、

abilityを持たず、typeだけ見ればよいのでは?

とも考えられます。

MVPだけを考えれば、それでも動かせます。

ただ、abilityという役割を明示しておけば、将来、

investigation型の行動だが
別の能力を使う

といった拡張を検討するときにも境界が分かりやすくなります。

現在は同じ値でも、

「解決方法」と「判定に使う能力」という意味は別

として設計しています。


20. 型を合わせると処理を共通化しやすい

ResolutionTypeAbilitySetのキーを合わせたことで、

const abilityValue =
  player.abilities[resolution.type];

のような処理ができます。

以下は考え方を説明するため、実装を簡略化しています。

function getResolutionAbility(
  player: Player,
  resolutionType: ResolutionType
): number {
  return player.abilities[resolutionType];
}

この形なら、

combatならcombatを読む
negotiationならnegotiationを読む
investigationならinvestigationを読む

というifswitchを何度も書かずに済みます。


21. 型の設計が条件分岐を減らす

例えば、キーがバラバラなら、

switch (resolutionType) {
  case "combat":
    return player.abilities.attack;

  case "negotiation":
    return player.abilities.talk;

  case "investigation":
    return player.abilities.search;
}

のような変換が必要になります。

一方、

ResolutionType
と
AbilitySetのキー

を揃えておけば、

player.abilities[resolutionType]

で済みます。

これは小さなコード差ですが、

データの意味を揃えることで処理を単純にする

例でもあります。


22. MVPでは1〜5程度の小さな値域にする

現在の能力値は、MVPでは大きな数値を必要としていません。

例えば、

1
2
3
4
5

程度でも、

苦手
やや苦手
標準
得意
かなり得意

という差を表現できます。

数値の桁を大きくすると、

42と45はどれくらい違うのか
83は強すぎるのか

など、バランス設計が難しくなります。

まずは小さな値域で、

選択による違いがゲームとして感じられるか

を確認します。


23. AbilitySetは小さいが中心的なデータ

AbilitySet自体は、

export interface AbilitySet {
  combat: number;
  negotiation: number;
  investigation: number;
}

という小さな型です。

しかし、この3値は、

プレイヤー
職業
スキル
解決方法
難易度
判定

へつながります。

つまり、

キャラクターとイベント判定をつなぐ中心的なデータ

になっています。


24. 今回のポイント

今回のポイントは3つです。

  • AbilitySetで、戦闘・交渉・調査の得意・不得意を数値として持つ
  • ResolutionTypeと能力値のキーを揃え、選んだ解決方法から能力値を直接参照できるようにする
  • 職業・スキル・判定結果とは分離し、AbilitySetは基本能力として扱う

第17回では、

どう解決するか
↓
ResolutionType

を作りました。

今回は、

その方法がどれくらい得意か
↓
AbilitySet

を加えました。

次に必要なのは、

そのイベントが
どれくらい難しいのか

です。


25. 次回

次回は、

イベントごとに難易度を持たせる――ResolutionDefinitionで判定条件を分ける【第19回】

です。

同じ戦闘でも、

弱い相手との戦闘

と、

危険な相手との戦闘

が同じ難しさでは不自然です。

また同じイベントでも、

戦闘なら難しい
交渉なら普通
調査なら簡単

という違いも作れます。

そこで、

ResolutionDefinition

に、

解決方法
使用能力
難易度
条件
結果への参照

を持たせます。

次回は、

GameEventから判定条件を切り離した理由

を中心に整理します。

 
 
この記事を最後まで読んでいただき、ありがとうございます。

♡ いいね」を押していただけるとうれしいです。これからの記事づくりの励みになります。

もし気に入っていただけましたら、フォローもよろしくお願いします。


前の記事

第17回 戦闘・交渉・調査を別々に作らない

次の記事

第19回 判定条件をイベントごとに変える――ResolutionDefinitionで難易度をデータ化する

連載トップ

第0回 連載の目的と開発ロードマップ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?