mcpField
Y
y8-js-sdk

y8-js-sdk

github.com/hungify開発016 回インストール1 週間前に更新
リポジトリ

y8-js-sdk について

y8-js-sdk は、JavaScript ゲームに Y8 のプレイヤー、クラウドストレージ、広告、実績、ランキング API を組み込むためのガイドです。Y8 ゲーム開発者向けに、確認手順を伴う導入フローを示します。

主な機能

  1. 01Y8 SDK の読み込みと初期化
  2. 02ログイン状態と認証の確認
  3. 03プレイヤーデータのクラウド保存
  4. 04インタースティシャル広告とリワード広告
  5. 05実績の解除と検索
  6. 06スコア送信とランキング取得

活用シーン

  1. 01ゲーム内でプレイヤーのログイン情報を取得
  2. 02認証済みプレイヤーのデータをクラウド保存
  3. 03インタースティシャル広告やリワード広告を表示
  4. 04スコアを送信して実績を表示

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/hungify/playables-skills --skill y8-js-sdk

インストール後、Claude Code で/y8-js-sdkを入力して読み込まれたか確認します。

詳細

Y8 JavaScript SDK

統合ワークフロー

以下の手順を順番に実行してください。各手順には、次に進む前の検証チェックポイントが含まれています。

  1. SDK を読み込む — スクリプトを挿入し、window.ID をポーリングします。
    • ✓ 確認:typeof window.ID !== 'undefined'
  2. ID.init({ appId }) を呼び出す前に、id.init をサブスクライブします。
    • ✓ 確認:id.init のコールバックが実行されること。実行されない場合は、appId を確認してください。
  3. プレイヤーのプロフィールを設定するため、id.init 内でログイン状態を取得します。
    • ✓ 確認:data.status === 'ok' は、プレイヤーがサインイン済みであることを意味します。
  4. id.init 内で、ログイン状態の取得と並行して AdSense を読み込みます。
    • ✓ 確認:adsbygoogle スクリプトが読み込まれ、onReady が実行されること。
  5. profile.isAuthorized === true の場合に限り、ストレージ、実績、リーダーボードを利用します。
    • ✓ 各 API 呼び出しの前に確認:プレイヤーが認証済みであること。未認証の場合は、先にログインを促してください。
  6. ID.login の呼び出しが成功した後、cachedUserData を無効化(null に設定)し、新しいセッションで自身のクラウド状態を読み込むようにします。

インストール

const SDK_URL = 'https://cdn.y8.com/api/sdk.js';

function loadScript(src) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;
        script.async = true;
        script.onload = () => resolve();
        script.onerror = () => reject(new Error('Failed to load ' + src));
        document.head.appendChild(script);
    });
}

function waitFor(globalName, timeoutMs = 10000) {
    return new Promise((resolve, reject) => {
        const started = Date.now();
        const tick = () => {
            if (typeof window[globalName] !== 'undefined') {
                resolve(window[globalName]);
                return;
            }
            if (Date.now() - started > timeoutMs) {
                reject(new Error(globalName + ' is not available'));
                return;
            }
            setTimeout(tick, 50);
        };
        tick();
    });
}

初期化

まず id.init をサブスクライブし、その後 ID.init({ appId }) を呼び出します。appId(別名 Game API Key)は、ゲームの登録時に Y8 から発行されます。id.init が実行されたら、ログイン状態の取得と AdSense の読み込みを並行して行います。

async function initY8({ gameId, adSenseId, channelId }) {
    if (!gameId) {
        throw new Error('gameId (Y8 appId) is required');
    }

    await loadScript(SDK_URL);
    const sdk = await waitFor('ID'); // ✓ checkpoint: window.ID exists

    return new Promise((resolve, reject) => {
        sdk.Event.subscribe('id.init', () => {
            // ✓ checkpoint: id.init fired — SDK is ready
            loadAdsByGoogle({ adSenseId, channelId })
                .then((showAd) => {
                    sdk.getLoginStatus((data) => {
                        const profile = readPlayerInfo(data);
                        resolve({ sdk, showAd, profile });
                    });
                })
                .catch(reject);
        });

        sdk.init({
            appId: gameId
        });
    });
}

プレイヤー/認証

ID.getLoginStatus(callback) は、プロンプトを表示せずに現在のセッションを返します。ID.login(callback) は、Y8 のログインダイアログを開きます。どちらも同じ形式のペイロードを返します。

function readPlayerInfo(data) {
    const profile = {
        isAuthorized: false,
        id: null,
        name: null,
        locale: null,
        photos: []
    };

    if (!data || data.status !== 'ok') {
        return profile;
    }

    const details = data.authResponse.details;
    const { pid, locale, nickname, first_name: firstName, last_name: lastName, avatars } = details;

    profile.isAuthorized = true;
    profile.id = pid || null;
    profile.locale = locale || null;
    profile.name = [firstName, lastName].filter((x) => !!x).join(' ') || nickname || null;

    const { thumb_url: photoSmall, medium_url: photoMedium, large_url: photoLarge } = avatars || {};

    if (photoSmall) profile.photos.push(photoSmall);
    if (photoMedium) profile.photos.push(photoMedium);
    if (photoLarge) profile.photos.push(photoLarge);

    return profile;
}

function authorizePlayer(sdk) {
    return new Promise((resolve, reject) => {
        sdk.login((response) => {
            const profile = readPlayerInfo(response);
            if (response && response.status === 'ok') {
                resolve(profile);
            } else {
                reject(new Error('Y8 login was cancelled or failed'));
            }
        });
    });
}

ストレージ

クラウド上のユーザーデータには ID.api() を通じてアクセスします。すべてのゲームデータを、1 つのキーの下に単一の JSON オブジェクトとして保存します。一度読み込み、ローカルで変更してから書き戻してください。

ログイン後: cachedUserData = null を設定し、新たに認証されたプレイヤーが自身のクラウド状態を読み込むようにします。

const USERDATA_KEY = 'userData';
const NOT_FOUND_ERROR = 'Key not found';

let cachedUserData = null;

function loadUserData(sdk) {
    return new Promise((resolve, reject) => {
        if (cachedUserData) {
            resolve(cachedUserData);
            return;
        }

        sdk.api('user_data/retrieve', 'POST', { key: USERDATA_KEY }, (response) => {
            if (response.error && response.error !== NOT_FOUND_ERROR) {
                reject(response);
                return;
            }

            let userData = {};
            try {
                if (response.jsondata) {
                    userData = JSON.parse(response.jsondata);
                }
            } catch (e) {
                // Leave userData as an empty object on parse failure
            }

            cachedUserData = userData;
            resolve(userData);
        });
    });
}

function saveUserData(sdk, userData) {
    return new Promise((resolve, reject) => {
        const payload = { key: USERDATA_KEY, value: JSON.stringify(userData) };
        sdk.api('user_data/submit', 'POST', payload, (response) => {
            if (response.status === 'ok') {
                cachedUserData = userData;
                resolve();
            } else {
                reject(response);
            }
        });
    });
}

function getStorageValue(sdk, key) {
    return loadUserData(sdk).then((userData) => userData[key] ?? null);
}

function setStorageValue(sdk, key, value) {
    return loadUserData(sdk).then((userData) => {
        const next = { ...userData, [key]: value };
        return saveUserData(sdk, next);
    });
}

function deleteStorageValue(sdk, key) {
    return loadUserData(sdk).then((userData) => {
        const next = { ...userData };
        delete next[key];
        return saveUserData(sdk, next);
    });
}

広告

adsbygoogle.push は、インタースティシャル広告用の type: 'start' と、リワード広告用の type: 'reward' を備えた adBreak API を提供します。Y8 からチャネル ID が提供されている場合は data-ad-channel を使用し、提供されていない場合は AdSense のクライアント ID を使用します。

const ADS_ID = '6129580795478709';

function loadAdsByGoogle({ adSenseId, channelId }) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
        script.setAttribute('crossorigin', 'anonymous');
        script.setAttribute('data-ad-frequency-hint', '180s');

        script.setAttribute('data-ad-client', channelId ? `ca-pub-${ADS_ID}` : adSenseId);
        if (channelId) {
            script.setAttribute('data-ad-channel', channelId);
        } else {
            script.setAttribute('data-ad-host', `ca-host-pub-${ADS_ID}`);
        }

        script.addEventListener('load', () => {
            window.adsbygoogle = window.adsbygoogle || [];
            window.adsbygoogle.push({
                preloadAdBreaks: 'on',
                sound: 'on',
                onReady: () => {}
            });
            resolve((adOptions) => window.adsbygoogle.push(adOptions));
        });

        script.addEventListener('error', () => reject(new Error('adsbygoogle failed to load')));
        document.head.appendChild(script);
    });
}
インタースティシャル
function showInterstitial(showAd, { onOpened, onClosed, onFailed } = {}) {
    if (!showAd) {
        onFailed?.('ads_not_ready');
        return;
    }

    showAd({
        type: 'start',
        name: 'start-game',
        beforeAd: () => {
            onOpened?.();
        },
        afterAd: () => {
            onClosed?.();
        },
        adBreakDone: (placementInfo) => {
            if (placementInfo.breakStatus !== 'viewed') {
                onFailed?.(placementInfo.breakStatus);
            }
        }
    });
}
リワード広告

beforeReward(showAdFn) で広告を制御します。広告を表示するには showAdFn(0) を呼び出してください。プレイヤーが広告を最後まで視聴すると adViewed が実行され、途中で終了すると adDismissed が実行されます。

function showRewarded(showAd, { onOpened, onRewarded, onClosed, onFailed } = {}) {
    if (!showAd) {
        onFailed?.('ads_not_ready');
        return;
    }

    showAd({
        type: 'reward',
        name: 'rewarded Ad',
        beforeAd: () => {
            onOpened?.();
        },
        afterAd: () => {
            onClosed?.();
        },
        beforeReward: (showAdFn) => {
            showAdFn(0);
        },
        adDismissed: () => {
            onFailed?.('dismissed');
        },
        adViewed: () => {
            onRewarded?.();
        },
        adBreakDone: (placementInfo) => {
            if (
                placementInfo.breakStatus === 'frequencyCapped' ||
                placementInfo.breakStatus === 'other'
            ) {
                onFailed?.(placementInfo.breakStatus);
            }
        }
    });
}

実績

Y8 のネイティブ実績は ID.GameAPI.Achievements の下にあります。achievement(表示名)と achievementkey(変更されない ID)の両方が必須です。

ロック解除
function unlockAchievement(sdk, options) {
    if (!options || !options.achievement || !options.achievementkey) {
        return Promise.reject(new Error('achievement and achievementkey are required'));
    }

    return new Promise((resolve) => {
        sdk.GameAPI.Achievements.save(options, (data) => {
            resolve(data);
        });
    });
}
実績の一覧取得(カスタムクエリ)

listCustom は、ネストされた player オブジェクトを含む実績を返します。扱いやすいよう、フラット化してください。

function getAchievementsList(sdk, options = {}) {
    return new Promise((resolve, reject) => {
        sdk.GameAPI.Achievements.listCustom(options, (data) => {
            if (!data.success) {
                reject(new Error(data.errorcode));
                return;
            }

            const list = data.achievements.map(({ player, ...achievement }) => ({
                ...achievement,
                playerid: player.playerid,
                playername: player.playername,
                lastupdated: player.lastupdated,
                date: player.date,
                rdate: player.rdate
            }));
            resolve(list);
        });
    });
}
ネイティブポップアップ

ID.GameAPI.Achievements.list(options) は、Y8 の組み込み実績 UI を開き、値を返しません。

function showAchievementsPopup(sdk, options = {}) {
    sdk.GameAPI.Achievements.list(options);
}

リーダーボード

各ボードは、Y8 ダッシュボードで設定された文字列の table ID で識別されます。

スコアの送信
function submitScore(sdk, tableId, score) {
    return new Promise((resolve, reject) => {
        const options = {
            table: tableId,
            points: score
        };

        sdk.GameAPI.Leaderboards.save(options, ({ success, errormessage }) => {
            if (success) {
                resolve();
            } else {
                reject(new Error(errormessage || 'leaderboard_save_failed'));
            }
        });
    });
}
エントリーの取得

有効な mode の値:'alltime'(デフォルト)、'daily'、'weekly'、'monthly'。

function getLeaderboardEntries(sdk, tableId) {
    return new Promise((resolve, reject) => {
        const options = {
            table: tableId,
            mode: 'alltime'
        };

        sdk.GameAPI.Leaderboards.listCustom(options, ({ scores, success, errormessage }) => {
            if (!success) {
                reject(new Error(errormessage || 'leaderboard_list_failed'));
                return;
            }

            const entries = scores.map((entry) => ({
                id: entry.playerid,
                name: entry.playername,
                score: entry.points,
                rank: entry.rank,
                photo: null
            }));
            resolve(entries);
        });
    });
}

コメント