自己紹介
はじめまして。
株式会社シーエー・アドバンスに勤めている當銘塁と申します。
最近 League of Legends
にハマりまくっています!
どのくらいハマりまくっているかというと、
一日のスケジュールの三分の一が League of Legends
になるほどハマりまくっています!
なぜLeague of Legendsのサモナー統計ダッシュボードアプリを作成したのか?
入社後半年が経過し、普段Next.jsで開発していたので、自分の趣味であるLeague of Legends(LoL)
を題材に何か作れないかと考えました。
アプリの概要
このアプリでは、Riot Games APIを使って以下の情報を取得して表示します
- サモナーの基本情報
- サモナー名、レベルなど
- ランク情報
- ソロランクやフレックスランクのティア、LP、勝敗など
- チャンピオン統計
- 使用頻度、勝率、KDA
- レーン統計
- レーン別のプレイ状況(勝率やKDA)
Riot Games APIを取得する
1. 上記のURLにアクセスすると、ログインが求めらるので、ログインする
2. ログインすると下記の画面が表示されるので、APIキーの生成ボタンを押す
APIキー取得完了🎉🎉🎉
上記の方法で取得したAPIキーは、開発環境でのみ使用して良いとのことなので、注意してください!
サモナー名とタグラインをサーバーに送信する
formタグの中にサモナー名とタグ名のinputを用意し、統計情報を取得ボタンを押すと、handleSubmit関数が走り、サーバーサイドに送信されて、サーバーサイドで処理されたデータが返ってくるという仕組みです!
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const data = await fetchSummonerStats(gameName, tagLine);
setStats(data);
} catch (err) {
console.error(err);
setError(
err instanceof Error
? err.message
: "サモナーの統計情報の取得に失敗しました"
);
} finally {
setLoading(false);
}
};
return (
<div className="container mx-auto p-4 bg-gray-100 min-h-screen">
<h1 className="text-4xl font-bold text-center text-blue-600 mb-8">
サモナー統計
</h1>
<form
onSubmit={handleSubmit}
className="mb-8 bg-white shadow-md rounded-lg p-6"
>
<div className="flex flex-col sm:flex-row gap-4">
<input
type="text"
value={gameName}
onChange={(e) => setGameName(e.target.value)}
placeholder="サモナー名を入力"
required
className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<div className="mt-3 font-bold">#</div>
<input
type="text"
value={tagLine}
onChange={(e) => setTagLine(e.target.value)}
placeholder="タグラインを入力"
required
className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<button
type="submit"
disabled={loading}
className="bg-blue-500 text-white p-3 rounded-lg hover:bg-blue-600 disabled:bg-gray-400 transition duration-300 ease-in-out"
>
{loading ? "読み込み中..." : "統計情報を取得"}
</button>
</div>
</form>
Riot Games API を使った統計情報の取得
サーバーサイドの関数 fetchSummonerStats は以下のように実装しています。
概要
1. サモナー名とタグラインからPUUID(プレイヤー固有ID)を取得。
2. PUUIDを使ってサモナーの基本情報(レベル、ランク)を取得。
3. 最新のマッチ履歴を取得して試合結果を解析。
4. チャンピオンごとの統計(使用頻度、勝率、KDA)を計算。
5. レーンごとの統計(勝率、KDA)を計算。
実装の流れ
1. Riot IDからPUUIDを取得
Riot Games APIでは、Riot ID(サモナー名 + タグライン)を元にPUUIDを取得できます。
const accountResponse = await fetch(
`${RIOT_ACCOUNT_URL}/riot/account/v1/accounts/by-riot-id/${encodeURIComponent(
gameName
)}/${encodeURIComponent(tagLine)}`,
{ headers: { "X-Riot-Token": RIOT_API_KEY! } }
);
const accountData = await accountResponse.json();
const puuid = accountData.puuid;
2. サモナー情報の取得
PUUIDを使用して、サモナーのレベルなどの基本情報を取得します。
const summonerResponse = await fetch(
`${RIOT_API_BASE_URL}/lol/summoner/v4/summoners/by-puuid/${puuid}`,
{ headers: { "X-Riot-Token": RIOT_API_KEY! } }
);
const summonerData = await summonerResponse.json();
3. ランク情報の取得
サモナーのランクデータを取得します。
const rankResponse = await fetch(
`${RIOT_API_BASE_URL}/lol/league/v4/entries/by-summoner/${summonerData.id}`,
{ headers: { "X-Riot-Token": RIOT_API_KEY! } }
);
const rankData = await rankResponse.json();
4. マッチ履歴の取得と解析
最新10試合のマッチデータを取得し、それを元にチャンピオン統計やレーン統計を計算します。
const matchListResponse = await fetch(
`${RIOT_MATCH_URL}/lol/match/v5/matches/by-puuid/${puuid}/ids?start=0&count=10`,
{ headers: { "X-Riot-Token": RIOT_API_KEY! } }
);
const matchIds = await matchListResponse.json();
const matchDataPromises = matchIds.map((matchId: string) =>
fetch(`${RIOT_MATCH_URL}/lol/match/v5/matches/${matchId}`, {
headers: { "X-Riot-Token": RIOT_API_KEY! },
}).then((res) => res.json())
);
const matchesData = await Promise.all(matchDataPromises);
5. チャンピオン統計とレーン統計の計算
各試合のデータから、チャンピオンごとの使用頻度、勝率、KDA、またレーンごとの統計を計算します。
matchesData.forEach((match: any) => {
const participant = match.info.participants.find(
(p: any) => p.puuid === puuid
);
if (participant) {
totalGames++;
if (participant.win) totalWins++;
// チャンピオン統計
const championName = participant.championName;
if (!championStats[championName]) {
championStats[championName] = {
games: 0,
wins: 0,
kills: 0,
deaths: 0,
assists: 0,
};
}
championStats[championName].games++;
championStats[championName].wins += participant.win ? 1 : 0;
championStats[championName].kills += participant.kills;
championStats[championName].deaths += participant.deaths;
championStats[championName].assists += participant.assists;
// レーン統計
const lane = participant.teamPosition || "UNKNOWN";
if (!laneStats[lane]) {
laneStats[lane] = {
games: 0,
wins: 0,
kills: 0,
deaths: 0,
assists: 0,
};
}
laneStats[lane].games++;
laneStats[lane].wins += participant.win ? 1 : 0;
laneStats[lane].kills += participant.kills;
laneStats[lane].deaths += participant.deaths;
laneStats[lane].assists += participant.assists;
}
});
クライアントサイドでの表示
取得した統計情報をReactのコンポーネントで表示します。
基本情報の表示
<StatsCard
title="基本情報"
icon={<FaUser className="text-blue-500" />}
content={
<>
<p className="text-lg">
<span className="font-medium">名前:</span>
{stats.accountData.gameName}#{stats.accountData.tagLine}
</p>
<p className="text-lg">
<span className="font-medium">レベル:</span>
{stats.summonerData.summonerLevel}
</p>
</>
}
/>
チャンピオン統計の表示
<StatsCard
title="チャンピオン統計"
icon={<FaGamepad className="text-purple-500" />}
content={
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{Object.entries(stats.championStats).map(([champion, data]) => (
<div
key={champion}
className="bg-gray-100 p-3 rounded-lg hover:shadow-md transition duration-300"
>
<h4 className="font-medium text-lg">{champion}</h4>
<p>ゲーム数: {data.games}</p>
<p>勝率: {((data.wins / data.games) * 100).toFixed(2)}%</p>
<p>
KDA: {((data.kills + data.assists) / data.deaths).toFixed(2)}
</p>
</div>
))}
</div>
}
/>
そして、ついに完成したものがこちら🎉🎉🎉
自分のアカウントを検索してみると....
正常に表示されました🎉🎉🎉
自分のプレイヤー名が厨二病ってことと、TOPレーナーってことやイラオイOTPってことがバレました!!
まとめ
自分の大好きなLeague of Legends(LoL)
を題材に開発ができて、よかったです。
下記のイベントに参加することから、急いでChatGPTを活用して開発をしました。
1日で完成することができたので、上出来なのではないかと思っています。
今後も追加したい機能があるので、趣味で開発していきたいと思います!
参考