Documentation index for AI agents

GS2-Friend

フレンド・フォロー機能

ゲーム専用のソーシャルグラフを形成するための機能を提供します。 ソーシャルグラフはプラットフォーム側でも提供されることがありますが、ゲーム内で独自に形成することにも価値があります。

プラットフォーム全体で使用されるフレンド関係は登録を行うのに慎重になりがちです。 なぜなら「私はこの人と今遊んでるゲームは一緒に遊びたいけれど、他のゲームを一緒に遊びたいとは思っていない」というケースが存在するためです。 プラットフォーマーが提供するソーシャルグラフはリアルに近い関係性を反映したもので、ゲーム固有のソーシャルグラフはそのゲーム内の関係性を反映したものとして扱うと、プレイヤーはより気軽にフレンド機能を利用してくれます。

フレンド

フレンドになるためには、双方向の同意関係が必要です。

フレンド関係を構築したいいずれかのプレイヤーが、相手にフレンドリクエストを送信し、フレンドリクエストを受け取ったプレイヤーがそれに同意することでフレンド関係が成立します。

フォロー

フォローは相手の同意なく、関係を構築することができます。

フォローされている人は自分が誰にフォローされているかを一覧として知る術はありません。 この仕様はソーシャルネットワークのフォローの仕様を想像していると違和感があるかもしれません。 しかし、ゲーム内でフォロー機能を実装している多くのゲームの仕様をみていて、その必要性がないと判断しました。

自分をフォローしてくれているプレイヤーが自分のゴーストキャラクターと一緒に冒険に出た場合、 冒険で得た報酬を分け与えてくれるだけで十分で、誰が一緒に冒険してくれたのかにはあまり興味がなかったためです。

一覧で取得することはできませんが、冒険で得た報酬を分け与える処理として GS2-Inbox に報酬付きメッセージを届ける際に メッセージのペイロードに、一緒に冒険に出たプレイヤーのユーザーIDを載せることで、プロフィールを伝えること自体は可能です。

プロフィール

GS2-Friend はプレイヤーのプロフィールを保持する領域を提供します。 プロフィールには任意の値を保持でき、スペースが3箇所存在します。

  • 他プレイヤーが自由に参照できる「パブリックプロフィール」
  • そのプレイヤーをフォローしているプレイヤーが参照できる「フォロワープロフィール」
  • フレンドが参照できる「フレンドプロフィール」

それぞれ、用途に合わせて使い分けることが可能です。

ブラックリスト

ゲームを長期間プレイしていると、不快に感じるプレイヤーがいるかもしれません。 そのようなプレイヤーをリスト化して永続化する機能です。

あくまで永続化する機能が存在するだけで、ここに追加するだけでは何も機能は果たしません。 GS2-Matchmaking のマッチメイキング条件にリストを渡すなど別途ここに記録したリストを必要に応じて使用する必要があります。

スクリプトトリガー

ネームスペースに followScriptunfollowScriptsendRequestScriptcancelRequestScriptacceptRequestScriptrejectRequestScriptdeleteFriendScriptupdateProfileScript を設定すると、フォローやフレンドリクエスト、プロフィール更新など各操作の前後でカスタムスクリプトを実行できます。スクリプトは同期・非同期の実行方式を選択でき、非同期では GS2-Script や Amazon EventBridge を介した外部連携にも対応します。これらの設定は GS2-Deploy や各言語向け CDK でテンプレート化して管理できます。

設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。

  • followScript(完了通知: followDone): フォローの前後
  • unfollowScript(完了通知: unfollowDone): フォロー解除の前後
  • sendRequestScript(完了通知: sendRequestDone): フレンドリクエスト送信の前後
  • cancelRequestScript(完了通知: cancelRequestDone): フレンドリクエスト取消の前後
  • acceptRequestScript(完了通知: acceptRequestDone): フレンドリクエスト承諾の前後
  • rejectRequestScript(完了通知: rejectRequestDone): フレンドリクエスト拒否の前後
  • deleteFriendScript(完了通知: deleteFriendDone): フレンド削除の前後
  • updateProfileScript(完了通知: updateProfileDone): プロフィール更新の前後

プッシュ通知

設定できる主なプッシュ通知と設定名は以下の通りです。

  • followNotification: フォローされたときに通知
  • receiveRequestNotification: フレンドリクエスト受信時に通知
  • cancelRequestNotification: フレンドリクエストがキャンセルされたときに通知
  • acceptRequestNotification: フレンドリクエスト承諾時に通知
  • rejectRequestNotification: フレンドリクエスト拒否時に通知
  • deleteFriendNotification: フレンド削除時に通知

いずれも GS2-Gateway 経由でリアルタイムに通知でき、オフライン端末へのモバイルプッシュ通知転送も有効化できます。

トランザクションアクション

GS2-Friend では以下のトランザクションアクションを提供しています。

  • 入手アクション: プロフィールの更新

「プロフィールの更新」を入手アクションとして利用することで、ゲーム内通貨を消費してプレイヤー名を変更するといった、一連のトランザクションの中にプロフィール更新を組み込むことが可能になります。

実装例

プロフィールの更新

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Profile(
    ).UpdateProfileAsync(
        publicProfile: "public",
        followerProfile: "follower",
        friendProfile: "friend"
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Profile(
    );
    const auto Future = Domain->UpdateProfile(
        "public", // publicProfile
        "follower", // followerProfile
        "friend" // friendProfile
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).profile(
    )

var async_result = await domain.update_profile(
    "public", # public_profile
    "follower", # follower_profile
    "friend" # friend_profile
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

自分のプロフィールを取得

    var item = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Profile(
    ).ModelAsync();
    const auto Domain = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Profile(
    );
    const auto item = Domain.Model();
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).profile(
    )

var async_result = await domain.model()
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

他人の公開プロフィールを取得

    var item = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).User(
        userId: "user-0001"
    ).PublicProfile(
    ).ModelAsync();
    const auto Domain = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->User(
        "user-0001" // userId
    )->PublicProfile(
    );
    const auto item = Domain.Model();
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).user(
        "user-0001"
    ).public_profile(
    )

var async_result = await domain.model()
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

フレンドリクエストを送信

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SendRequestAsync(
        targetUserId: "user-0002"
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SendRequest(
        "user-0002"
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session)

var async_result = await domain.send_request(
    "user-0002" # target_user_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

送信したフレンドリクエストの一覧を取得

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SendRequestsAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SendRequests(
    );
    TArray<Gs2::UE5::Friend::Model::FEzFriendRequestPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.friend.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).send_requests(
    )

var async_result = await iterator.load()
if async_result.error != null:
    # エラーを処理
    push_error(str(async_result.error))
    return

var items = async_result.result

受信したフレンドリクエストの一覧を取得

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).ReceiveRequestsAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->ReceiveRequests(
    );
    TArray<Gs2::UE5::Friend::Model::FEzFriendRequestPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.friend.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).receive_requests(
    )

var async_result = await iterator.load()
if async_result.error != null:
    # エラーを処理
    push_error(str(async_result.error))
    return

var items = async_result.result

フレンドリクエストを承諾

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).ReceiveFriendRequest(
        fromUserId: "user-0002"
    ).AcceptAsync(
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->ReceiveFriendRequest(
        nullptr, // targetUserId
        "user-0002" // fromUserId
    )->Accept(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).receive_friend_request(
        "user-0002"
    )

var async_result = await domain.accept(
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

フレンドリクエストを拒否

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).ReceiveFriendRequest(
        fromUserId: "user-0002"
    ).RejectAsync(
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->ReceiveFriendRequest(
        nullptr, // targetUserId
        "user-0002" // fromUserId
    )->Reject(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).receive_friend_request(
        "user-0002"
    )

var async_result = await domain.reject(
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

フレンドの一覧を取得

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).FriendsAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Friends( // withProfile
    );
    TArray<Gs2::UE5::Friend::Model::FEzFriendUserPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.friend.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).friends(
    )

var async_result = await iterator.load()
if async_result.error != null:
    # エラーを処理
    push_error(str(async_result.error))
    return

var items = async_result.result

フレンドの削除

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Friend(
        withProfile: null
    ).FriendUser(
        targetUserId: "user-0002"
    ).DeleteFriendAsync(
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Friend(
        nullptr // withProfile
    )->FriendUser(
        "user-0002" // targetUserId
    )->DeleteFriend(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).friend(
        null
    )

var async_result = await domain.delete_friend(
    "user-0002" # target_user_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

フォロー

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).FollowUser(
        targetUserId: "user-0002",
        withProfile: true
    ).FollowAsync(
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->FollowUser(
        "user-0002", // targetUserId
        true // withProfile
    )->Follow(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).follow(
        true
    ).follow_user(
        "user-0002"
    )

var async_result = await domain.model()
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

フォロー中のプレイヤーの一覧を取得

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).FollowsAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Follows(
    );
    TArray<Gs2::UE5::Friend::Model::FEzFollowUserPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.friend.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).follows(
    )

var async_result = await iterator.load()
if async_result.error != null:
    # エラーを処理
    push_error(str(async_result.error))
    return

var items = async_result.result

アンフォロー

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).FollowUser(
        targetUserId: "user-0002",
        withProfile: true
    ).UnfollowAsync(
    );
    const auto Domain = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->FollowUser(
        "user-0002", // targetUserId
        true // withProfile
    );
    const auto Future = Domain->Unfollow(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).follow(
        null
    ).follow_user(
        "user-0002"
    )

var async_result = await domain.unfollow(
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

ブラックリストに登録

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).BlackList(
    ).RegisterBlackListAsync(
        targetUserId: "user-0002"
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->BlackList(
    )->RegisterBlackList(
        "user-0002" // targetUserId
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).black_list(
    )

var async_result = await domain.register_black_list(
    "user-0002" # target_user_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

ブラックリストから解除

    var result = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).BlackList(
    ).UnregisterBlackListAsync(
        targetUserId: "user-0002"
    );
    const auto Future = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->BlackList(
    )->UnregisterBlackList(
        "user-0002" // targetUserId
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.friend.namespace_(
        "namespace-0001"
    ).me(game_session).black_list(
    )

var async_result = await domain.unregister_black_list(
    "user-0002" # target_user_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

ブラックリスト一覧を取得

    var items = await gs2.Friend.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).BlackListUsersAsync(
    ).ToListAsync();
    const auto It = Gs2->Friend->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->BlackListUsers(
    );
    TArray<FString> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.friend.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).black_list_users(
    )

var async_result = await iterator.load()
if async_result.error != null:
    # エラーを処理
    push_error(str(async_result.error))
    return

var items = async_result.result

上限人数

フレンド数およびフォロー数の上限はそれぞれ1000人です。ただし、連携するサービスによっては各サービスのポリシーに従って追加の制限が加わる可能性があります。

詳細なリファレンス