FiveMでキャラクター枠を作る【slotと論理削除の基準を決めます】

HaruuuCoreのhc_characterで、slot 1〜3、active character、deleted character、論理削除をどのように扱うかを整理します。

この記事の見出し 開く

キャラクター枠を先に決める理由

FiveM の Vanilla 環境で自作基盤を作る場合、プレイヤーが接続したあとに、どのキャラクターを操作するのかを自分で決める必要があります。 HaruuuCore では、接続中の player を表す source、アカウント側の識別子である identifier、DB 上のゲーム内キャラクターを表す character_id を分けて扱う方針にしています。

その次に必要になったのが、1つの identifier に対して複数のキャラクター枠を持たせるための基準です。 どの slot にキャラクターを作るのか、どの character を選択中として扱うのか、削除した character をどう残すのかを決めないと、money、rank、job などの後続機能を安全に積み上げにくくなります。

この作業では、hc_character に character slot と論理削除の土台を追加し、hc_corehc_money と接続できる状態まで確認しました。

identifierとcharacter slotの関係

identifier は、プレイヤーのアカウント側を識別する値として扱います。 HaruuuCore では、現時点では license identifier を優先して取得します。 ただし、identifier は character そのものではありません。

character slot は、1つの identifier が持てるキャラクター枠です。 現在は slot 1〜3 を扱い、rank や XP による slot unlock は入れていません。 すべての slot を最初から利用可能な枠として扱います。

identifier
├── slot 1
├── slot 2
└── slot 3

この時点で重要なのは、slot を永続データの主キーにしないことです。 slot は UI 上の枠や選択位置として使い、money、rank、job などの gameplay data は character_id を基準にします。

slotはUI上の枠として扱う

HaruuuCore では、slot を「キャラクター選択画面に表示する枠」として扱います。 slot 1 に作成した character は slot 1 のカードに表示され、slot 2 が空なら新規作成できる枠として返します。

ただし、slot 自体を character の永続 ID にはしません。 character の実体は hc_characters.id であり、slot はその character をどの枠に表示するかを表す値です。

slot:
  UI上の枠

character_id:
  DB上のcharacter実体

identifier:
  どのaccountが所有するcharacterかを判定する値

この分け方にしておくと、削除後に同じ slot を再利用しても、過去の character と新しい character を character_id で区別できます。

active characterとdeleted characterの分離

character 一覧や slot payload では、通常利用できる character だけを返します。 HaruuuCore では、deleted_at が入っていない character を active character として扱います。

active character:
  deleted_at IS NULL
  slot IS NOT NULL

deleted character:
  deleted_at IS NOT NULL
  slot IS NULL

deleted character は通常の character 一覧や character selection payload には出しません。 これにより、削除済み character を誤って選択したり、job や money の対象にしたりする状態を避けます。

削除済み character の復元や管理者向け一覧は、現時点では扱いません。 まずは通常のプレイ導線から削除済み character を外すことを優先しています。

deleted_atで論理削除する

character を削除するとき、DB row は物理削除しません。 HaruuuCore では deleted_at を入れ、slotNULL にする論理削除にしています。

delete character:
  original_slot = 削除前のslot
  slot = NULL
  deleted_at = CURRENT_TIMESTAMP

物理削除にしない理由は、削除という操作を後から追跡しやすくするためです。 現時点では削除済み character の復元機能は作っていませんが、DB 上に削除状態を残しておくことで、後から復元や監査の設計を検討できます。

一方で、slot は空けます。 これにより、削除後に同じ slot へ新しい character を作成できます。

original_slotを残す理由

削除時には、削除前の slot を original_slot に保存します。 slotNULL にするため、削除後の row だけを見ると、元々どの枠にいた character なのか分からなくなります。

hc_characters
├── id
├── identifier
├── slot
├── original_slot
├── deleted_at
└── ...

original_slot は、削除済み character の履歴を見たい場合の補助情報です。 通常の character selection では使いません。 active character の表示や選択は、あくまで slot が入っている character だけを対象にします。

この扱いにより、slot の再利用と削除履歴の保持を分けられます。

現在選択中のcharacterは削除しない

現在の実装では、選択中の character は削除できないようにしています。 削除しようとすると character_currently_selected として拒否します。

/deletechar 1

character_currently_selected

選択中 character を削除するには、単に DB 上で deleted にするだけでは足りません。 hc_character の cache、hc_corePlayers[source].characterIdhc_money の同期対象、進行中 job、spawn や待機状態の扱いまで整理する必要があります。

そのため、現時点では別 character に切り替えてから削除する方針にしています。 選択中 character の削除は、別の設計課題として残します。

slot payloadをUI向けに組み立てる

character selection UI では、単に active character の配列を返すだけでは不十分です。 slot 1〜3 のそれぞれが occupied なのか empty なのか、作成可能なのか、削除可能なのか、現在選択中なのかを UI が判断できる形にする必要があります。

slot payload:
  slot
  state
  character
  isSelected
  canCreate
  canDelete

HaruuuCore では、slot state は現時点で occupiedempty のみです。 locked slot や progression unlock は扱いません。

occupied slot:
  canCreate = false
  canDelete = true

empty slot:
  canCreate = true
  canDelete = false

selected character:
  isSelected = true
  canDelete = false

選択中 character は削除できないため、occupied であっても canDelete=false にします。 UI 側はこの payload を見て、作成ボタンや削除ボタンを出し分けます。

自動作成しない選択用payload

HaruuuCore には、既存互換用の getCharacterSlots と、character selection 用の getCharacterSlotsForSelection を分けています。

getCharacterSlots:
  既存互換
  ensureCharacter(source) を呼ぶ
  未ロード時に slot=1 を自動ロードまたは作成する可能性がある

getCharacterSlotsForSelection:
  character selection 用
  ensureCharacter(source) を呼ばない
  slot=1 を自動作成しない
  slot=1 を自動選択しない

character selection UI の初期表示では、まだ character を選んでいない状態を正常に扱う必要があります。 そのため、UI 用の payload では ensureCharacter(source) を呼ばず、selectedCharacterId=nilselectedSlot=nil を正常な状態として返します。

これにより、接続直後に自動で slot 1 が作成されたり、勝手に selected character が決まったりする状態を避けられます。

createとselectを分ける

character の作成と選択は別の操作として扱います。 createCharacter は指定 slot に character を作成するだけで、自動選択しません。

createCharacter:
  characterを作成する
  selected characterは変更しない
  hc_core stateは変更しない
  money targetは変更しない

selectCharacter:
  active characterを選択する
  selected characterを変更する
  hc_coreへcharacterSelectedを通知する
  hc_moneyの同期対象が変わる

作成した瞬間に自動選択すると、UI 上で「作成」と「その character で開始する」が同じ操作になってしまいます。 HaruuuCore では、作成後に明示的に select したときだけ selected character が変わるようにしています。

この線引きにより、slot browsing 中に money target が変わったり、core state が ready へ進んだりする状態を避けられます。

hc_characterとhc_coreの境界

hc_character は character DB、character API、slot payload の source of truth として扱います。 ただし、接続中 player の state は hc_core が持ちます。

hc_character:
  character DB
  active / deleted 判定
  slot payload
  create / select / delete

hc_core:
  Players[source]
  identifier
  characterId
  state
  ready

selectCharacter が成功したとき、hc_characterhc_core:server:characterSelected を発火します。 hc_core はこの event を受けて、Players[source].characterId を更新し、ready state へ進めます。

selectCharacter(source, slot)

hc_core:server:characterSelected

Players[source].characterId = character.id
Players[source].state = ready
Players[source].ready = true

DB 操作を hc_core に寄せず、player state を hc_character に寄せないことで、責務の境界を追いやすくしています。

moneyとの接続

hc_money は character_id ベースで cash / bank を扱います。 slot 一覧を見たり、新しい character を作成したりするだけでは、money の対象は変わりません。

money の同期対象が変わるのは、character を明示的に select したときです。 hc_money は character selection の結果を受けて、選択中 character の money を同期します。

slot browsing:
  money targetは変わらない

create character:
  money targetは変わらない

select character:
  money targetがselected character_idへ変わる

character 未選択時の money sync skip は異常ではありません。 まだ money の対象になる character が決まっていない状態として扱います。

確認した操作

character slot と論理削除の挙動は、server command で確認しました。 ここでは、UI ではなく hc_character の API と state の確認を目的にしています。

/charslots_select

/charslots_select では、slot 1〜3 の状態を確認します。 character 未選択状態では、selectedCharacterId=nilselectedSlot=nil を正常に返します。

/createchar 2 Jiro Sato
/charslots_select

character 作成後、slot 2 は occupied になります。 ただし、作成した character は自動選択されません。

/selectchar 2
/hcstate
/money
/rank

character を選択すると、hc_corecharacterId が selected character に変わり、state は ready になります。 money と rank も selected character_id を基準に確認します。

/selectchar 1
/deletechar 2
/charslots_select

選択中ではない slot 2 を削除すると、slot 2 は empty になります。 削除済み character は通常 payload から消え、同じ slot を再利用できる状態になります。

キャラクター枠の実装で到達した状態

この作業で、HaruuuCore の hc_character は、単一 character の自動作成だけではなく、slot 1〜3 を前提にした character 管理へ進みました。

確認済み:
  slot 1〜3 の payload を返せる
  active character と deleted character を分けられる
  character を作成できる
  character を明示的に選択できる
  character を論理削除できる
  削除後に同じ slot を再利用できる
  現在選択中 character の削除を拒否できる
  select 後に hc_core と hc_money へ接続できる

まだ扱っていないものもあります。 選択中 character の削除、削除済み character の復元、last selected restore、appearance 保存、DB 座標 spawn、locked slot、progression unlock などは未実装です。

ただし、character slot、active / deleted の区別、create / select / delete の分離が入ったことで、character selection UI や money、rank、job へ接続するための土台は作れました。

見出しへ戻る 開く