「本ページはプロモーションが含まれています」
こんにちは。ここまでの連載では
- 第1回:モンスター育成ゲームAPIの概要
- 第2回:シーケンス図(ガチャやフロー)をMermaidで可視化
- 第3回:DB構造をMermaidのER図で考案(マイグレーションなし)
- 第4回:FastAPIの環境構築とフォルダ構成
- 第5回:ユーザー管理APIを実装し、CRUD操作の基本を学習
…という流れで準備してきました。
今回はモンスター管理にチャレンジします。ゲームっぽい要素が入ってくるので、ワクワクする一方で「何をどう管理すればいいんだっけ?」と戸惑いがちですが、、、
まずは“モンスターの種類”を登録・取得・削除できるAPIを作り、そのあとに“ユーザーが所有するモンスター”も追加してみる流れを。。。
- 1. モンスターに関するテーブルを振り返る
- 2. モンスター種モデルとDBテーブル
- 3. MonstersのCRUDエンドポイントを実装
- 4. 動作確認
- 5. 今後の展望:ユーザーが所有するモンスター
- 6. まとめ
1. モンスターに関するテーブルを振り返る
第3回のDB設計で書いたMermaid ER図を思い出しながら、以下のテーブルを用意しておく想定です。
- monsters: ゲーム内で登場するモンスター種(名前、レア度、ステータスなど)
- user_monsters: ユーザーが所有しているモンスター個体(user_id、monster_id、レベル、経験値…など)
まずは“monsters”テーブルのCRUDを実装し、それが問題なく動くようになってから“user_monsters”をやります。 最終的には「ユーザーがガチャでモンスターをゲットする」APIを作りたいのですが、それにはmonstersテーブルがきちんと動いている前提が必要なので、順番に進めます。
2. モンスター種モデルとDBテーブル
2.1 モデル定義(app/models.py)
以下のように、monsters テーブルを追加します。
# app/models.py (抜粋) from sqlalchemy import Column, Integer, String, DateTime, func, ForeignKey from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = "users" # ...(ユーザー定義は第5回で紹介した通り) class Monster(Base): __tablename__ = "monsters" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) rarity = Column(String, default="common") base_hp = Column(Integer, default=10) base_attack = Column(Integer, default=5) base_defense = Column(Integer, default=5) created_at = Column(DateTime, server_default=func.now()) class UserMonster(Base): __tablename__ = "user_monsters" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) monster_id = Column(Integer, ForeignKey("monsters.id"), nullable=False) level = Column(Integer, default=1) exp = Column(Integer, default=0) obtained_at = Column(DateTime, server_default=func.now())
Monster: モンスター種を管理。rarity は "common" / "rare" / "legendary" など想定。 ★とかでもそのまま返却できるかな?それとも、intで管理とか?まぁ、一覧見たとき、一発でわかる文字列にしておきます。
UserMonster: ユーザーが所有している個体情報(今回は作りませんが、、、)
2.2 DBテーブルの作成
Base.metadata.create_all(bind=engine) を呼び出せば、monsters と user_monsters テーブルが作られるはずです。 (すでに第5回でユーザーテーブルを作成済み、その流れと同様に呼び出す)
3. MonstersのCRUDエンドポイントを実装
3.1 app/routers/monsters.py
以下の例では、モンスターの登録・取得・削除を実装しています。上級的にはステータス更新などもあり得ますが、まずは最低限の形で。。。
from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from pydantic import BaseModel from typing import Optional from app.database import get_db from app.models import Monster router = APIRouter() # Pydanticスキーマ class MonsterCreate(BaseModel): name: str rarity: Optional[str] = "common" base_hp: Optional[int] = 10 base_attack: Optional[int] = 5 base_defense: Optional[int] = 5 class MonsterRead(BaseModel): id: int name: str rarity: str base_hp: int base_attack: int base_defense: int class Config: orm_mode = True # CREATE (モンスター種の登録) @router.post("/", response_model=MonsterRead) def create_monster(monster_data: MonsterCreate, db: Session = Depends(get_db)): new_monster = Monster( name=monster_data.name, rarity=monster_data.rarity, base_hp=monster_data.base_hp, base_attack=monster_data.base_attack, base_defense=monster_data.base_defense, ) db.add(new_monster) db.commit() db.refresh(new_monster) return new_monster # READ (全モンスター種の一覧取得) @router.get("/", response_model=list[MonsterRead]) def get_all_monsters(db: Session = Depends(get_db)): monsters = db.query(Monster).all() return monsters # READ by ID @router.get("/{monster_id}", response_model=MonsterRead) def get_monster_by_id(monster_id: int, db: Session = Depends(get_db)): monster = db.query(Monster).filter(Monster.id == monster_id).first() if not monster: raise HTTPException(status_code=404, detail="Monster not found") return monster # DELETE @router.delete("/{monster_id}") def delete_monster(monster_id: int, db: Session = Depends(get_db)): monster = db.query(Monster).filter(Monster.id == monster_id).first() if not monster: raise HTTPException(status_code=404, detail="Monster not found") db.delete(monster) db.commit() return {"detail": "Monster deleted"}
- POST /monsters:モンスター種の新規登録
- GET /monsters:全モンスター種を一覧取得
- GET /monsters/{id}:個別取得
- DELETE /monsters/{id}:削除
3.2 main.py へのルーター追加
app.include_router(monsters.router, prefix="/monsters", tags=["monsters"])
4. 動作確認
- POST /monsters に下記のようなJSONを送信
{ "name": "Slime", "rarity": "common", "base_hp": 10, "base_attack": 5, "base_defense": 5 }
- GET /monsters で一覧が見られるか確認
- GET /monsters/1 で特定IDを取得
- DELETE /monsters/1 で削除テスト
5. 今後の展望:ユーザーが所有するモンスター
「monster種」の管理ができたところで、次の一歩としてuser_monstersを操作するAPIを検討します。
- POST /users/{user_id}/owned-monsters で所有モンスターを追加
- GET /users/{user_id}/owned-monsters でユーザーが持っている個体一覧を取得
- PUT /users/{user_id}/owned-monsters/{owned_monster_id} でレベルアップ・経験値加算などの更新
…など
ここを実装すると、やっとゲームらしい「育成」要素が少しずつ形にできそうです。 また、ガチャでランダムにモンスターを引く仕組みを作るなら、monstersテーブルの中から確率に応じて1体を選出してuser_monstersに追加する、というロジックが必要になってきます。
6. まとめ
- モンスター種の管理を実装し、ゲームらしいデータ構造が見えてきた
- 新しいテーブル (monsters) を追加して、FastAPIのAPIRouterでCRUDを定義する手順はユーザーAPIと同様
- 今後はuser_monstersやガチャ機能などを作って、育成ゲームに近い形へ発展させる予定
これで第6回「モンスター管理APIの実装」が完了です。 次回は「ユーザーが所有する個体」をどう扱うか、UserMonsterテーブルを軸にしてAPIを充実させたいですね。あるいはガチャ機能を先にやってみても面白そうです。少しずつ手を動かしながら進めます。
本来は、UnitTestを行う予定でしたが、ガチャ実装を次回にします。