Use Followers In-Game
In this How-to, we will implement a basic Followers system in-game using LootLocker. You will learn how to show who follows a player, who they are following, allow players to follow/unfollow others, and build simple UI patterns like profile headers, lists, and follow buttons.
Prerequisites
Multiple player profiles (e.g. create via separate sessions)
(Optional) Some player ULIDs collected from the Player Manager
(Optional) Familiarity with managing relationships in the Web Console
Followers vs Friends
Followers are a one‑way relationship. Player A can follow Player B without approval (unless B has blocked A). This enables:
Creator / influencer style player profiles
Social feeds or “What your followed players did”
Asynchronous competitions (e.g. “Chase the ghost of players you follow”)
Notification or highlight surfaces (e.g. “New level published by someone you follow”)
If you need mutual, opt‑in relationships, use Friends instead (see: Use Friends in Game).
Core Concepts
Followers list: Players who follow the target player.
Following list: Players the target player has chosen to follow.
Pagination: Large lists return a
next_cursor
; request subsequent pages until empty.Blocking: A blocked player cannot follow or become friends with the blocker.
UI Caching: Cache following IDs locally to render instant button states (“Following” vs “Follow”).
Typical UX Flow
Open Profile (self or another player)
Fetch counts (followers + following) for header display
Lazy-load the tab the player opens first (e.g. “Following”)
Infinite scroll / “Load more” using cursor
Show Follow / Unfollow button with optimistic state change
Update local cache + optionally refresh counts
Fetch Follower and Following Lists
Use these to populate tabs or modals.
// This example shows how to:
// 1. Fetch the first page of followers for the logged-in player
// 2. Fetch the first page of players the logged-in player is following
// 3. Request additional pages using the `pagination.next_cursor`
//
// Assumptions / Pseudo Dependencies:
// - A Game Session has already been started (player authenticated)
// - You have a simple UI layer (not included here) with methods like:
// UIFollowersList.SetLoading(bool)
// UIFollowingList.AppendRows(IEnumerable<LootLockerFollower>)
// UIFollowingList.ShowEndOfList()
// - You store cursors locally for each list.
using System.Collections.Generic;
using LootLocker.Requests; // Namespace where the SDK classes live
public class FollowersExample
{
// Local caches (only store lightweight identity info; expand as needed)
private readonly List<LootLockerFollower> _followers = new();
private readonly List<LootLockerFollower> _following = new();
// Pagination cursors (empty or null means no more pages / not yet loaded)
private string _followersNextCursor = null;
private string _followingNextCursor = null;
// Page size – tune for your UX & bandwidth constraints
private const int PageSize = 25;
// PUBLIC API you might call from UI buttons / tab switches
public void LoadFirstFollowersPage()
{
_followers.Clear();
_followersNextCursor = null; // Reset so we know this is a fresh load
UIFollowersList.SetLoading(true);
// First page: pass null/empty cursor, and a count
LootLockerSDKManager.ListFollowersPaginated(Cursor: null, Count: PageSize, onComplete: (resp) =>
{
UIFollowersList.SetLoading(false);
if (!resp.success)
{
LogFailure("Followers", resp);
return;
}
if (resp.followers != null)
{
_followers.AddRange(resp.followers);
UIFollowersList.ReplaceRows(resp.followers); // Replace entire list for first load
}
_followersNextCursor = resp.pagination?.next_cursor; // Will be null/empty if no more pages
if (string.IsNullOrEmpty(_followersNextCursor))
{
UIFollowersList.ShowEndOfList();
}
});
}
public void LoadMoreFollowers()
{
if (string.IsNullOrEmpty(_followersNextCursor)) return; // No more pages
UIFollowersList.SetLoading(true);
LootLockerSDKManager.ListFollowersPaginated(Cursor: _followersNextCursor, Count: PageSize, onComplete: (resp) =>
{
UIFollowersList.SetLoading(false);
if (!resp.success)
{
LogFailure("Followers (more)", resp);
return;
}
if (resp.followers != null && resp.followers.Length > 0)
{
_followers.AddRange(resp.followers);
UIFollowersList.AppendRows(resp.followers); // Append for subsequent pages
}
_followersNextCursor = resp.pagination?.next_cursor;
if (string.IsNullOrEmpty(_followersNextCursor))
{
UIFollowersList.ShowEndOfList();
}
});
}
public void LoadFirstFollowingPage()
{
_following.Clear();
_followingNextCursor = null;
UIFollowingList.SetLoading(true);
LootLockerSDKManager.ListFollowingPaginated(Cursor: null, Count: PageSize, onComplete: (resp) =>
{
UIFollowingList.SetLoading(false);
if (!resp.success)
{
LogFailure("Following", resp);
return;
}
if (resp.following != null)
{
_following.AddRange(resp.following);
UIFollowingList.ReplaceRows(resp.following);
}
_followingNextCursor = resp.pagination?.next_cursor;
if (string.IsNullOrEmpty(_followingNextCursor))
{
UIFollowingList.ShowEndOfList();
}
});
}
public void LoadMoreFollowing()
{
if (string.IsNullOrEmpty(_followingNextCursor)) return;
UIFollowingList.SetLoading(true);
LootLockerSDKManager.ListFollowingPaginated(Cursor: _followingNextCursor, Count: PageSize, onComplete: (resp) =>
{
UIFollowingList.SetLoading(false);
if (!resp.success)
{
LogFailure("Following (more)", resp);
return;
}
if (resp.following != null && resp.following.Length > 0)
{
_following.AddRange(resp.following);
UIFollowingList.AppendRows(resp.following);
}
_followingNextCursor = resp.pagination?.next_cursor;
if (string.IsNullOrEmpty(_followingNextCursor))
{
UIFollowingList.ShowEndOfList();
}
});
}
private void LogFailure(string context, LootLockerResponse resp)
{
// Centralize error logging; you might map error codes to localized user messages.
UnityEngine.Debug.LogWarning($"[FollowersExample] {context} failed: {resp?.errorData?.message}");
}
}
Pagination Strategy
Always request the first page with no cursor.
Store
next_cursor
if present.Disable “Load more” when cursor is empty or null.
Consider prefetching the next page when the user scrolls past 70% of current content.
Displaying Counts
Instead of loading entire lists to show numbers, use the first page response (which includes total/size fields) or lazily populate counts after the first fetch. If exact counts are not required immediately, show placeholders (e.g. “—” then fade in).
// Displaying Counts Example
// There is no dedicated "counts only" endpoint, so we infer counts from:
// - Length of the page we just received
// - Presence (or absence) of pagination.next_cursor to know if more pages exist
// Optionally, you can lazily request the first page of each list only when a UI panel is opened.
//
// Assumptions:
// - UI has two text labels: UIFollowersHeader.SetCount(int? count, int? total) & UIFollowingHeader.SetCount(int? count, int? total)
// - Passing null shows a placeholder (e.g. "—") until data arrives.
public class FollowerCounts
{
private int? _followersLoadedCount;
private int? _followingLoadedCount;
private int? _totalFollowersCount;
private int? _totalFollowingCount;
public void PrimePlaceholders()
{
UIFollowersHeader.SetCount(null, null);
UIFollowingHeader.SetCount(null, null);
}
public void FetchInitialFollowers()
{
LootLockerSDKManager.ListFollowersPaginated(null, 25, (resp) =>
{
if (!resp.success)
{
// Handle error
return;
}
_followersLoadedCount = resp.followers?.Length ?? 0;
_totalFollowersCount = resp.pagination?.total);
UIFollowersHeader.SetCount(_followersLoadedCount, _totalFollowersCount);
});
}
public void FetchInitialFollowing()
{
LootLockerSDKManager.ListFollowingPaginated(null, 25, (resp) =>
{
if (!resp.success)
{
UIFollowingHeader.SetCount(0);
return;
}
_followingLoadedCount = resp.following?.Length ?? 0;
_totalFollowingCount = resp.pagination?.total);
UIFollowingHeader.SetCount(_followingLoadedCount, _totalFollowingCount);
});
}
}
Follow a Player
Trigger from a profile card, leaderboard row, chat user badge, or “Suggested Players” carousel.
// Follow Player (Optimistic UI) Example
// Goal: Player taps a Follow button on another player's profile card.
// We immediately reflect the new state in UI, then roll back if the API fails.
//
// Assumptions:
// - UI elements: UIButton FollowButton; UILabel FollowLabel
// - Local cache: HashSet<string> FollowingIds storing public UIDs we follow
// - Method ShowToast(string msg) for transient user feedback
// - playerPublicUID is the target player's public UID
public class FollowActionController
{
private readonly HashSet<string> _followingIds;
public FollowActionController(HashSet<string> followingIds) => _followingIds = followingIds;
public void OnFollowButtonClicked(string playerPublicUID)
{
if (string.IsNullOrEmpty(playerPublicUID)) return;
if (_followingIds.Contains(playerPublicUID))
{
// Already following (button might have been double-clicked)
ShowToast("Already following");
return;
}
// Optimistic state change
SetButtonStateLoading();
SetVisualStateFollowing();
_followingIds.Add(playerPublicUID);
LootLockerSDKManager.FollowPlayer(playerPublicUID, (resp) =>
{
if (!resp.success)
{
// Rollback
_followingIds.Remove(playerPublicUID);
SetVisualStateFollow();
ShowToast("Follow failed: " + resp.errorData?.message);
return;
}
// Success: Optionally increment a displayed count
PlayerProfileHeader.IncrementFollowingCount(1);
ShowToast("Now following player: " + playerPublicUID);
});
}
private void SetVisualStateFollowing()
{
FollowButton.interactable = false; // Optionally re-enable later as an unfollow button
FollowLabel.text = "Following";
}
private void SetVisualStateFollow()
{
FollowButton.interactable = true;
FollowLabel.text = "Follow";
}
private void SetButtonStateLoading()
{
FollowLabel.text = "..."; // Brief loading indicator
}
}
Optimistic Updates
Disable the button and swap label to “Following…”
Send follow request
On success: set to “Following” (or icon toggle)
On failure: revert and show a subtle toast
Unfollow a Player
Offer this option from the “Following” list or a context / overflow menu.
// Unfollow Player Example (with optional confirm + undo)
// Assumptions:
// - _followingIds HashSet<string>
// - UI provides a confirmation popup Confirm("Unfollow X?", onYes, onNo)
// - ShowUndo(message, action) displays a snackbar with Undo button
// - PlayerProfileHeader.DecrementFollowingCount(int)
public class UnfollowActionController
{
private readonly HashSet<string> _followingIds;
public UnfollowActionController(HashSet<string> followingIds) => _followingIds = followingIds;
public void RequestUnfollow(string targetPublicUID, string targetDisplayName)
{
if (!_followingIds.Contains(targetPublicUID)) return; // Already not following
Confirm($"Unfollow {targetDisplayName}?", () => ExecuteUnfollow(targetPublicUID, targetDisplayName), () => { /* canceled */ });
}
private void ExecuteUnfollow(string targetPublicUID, string targetDisplayName)
{
// Optimistic removal
_followingIds.Remove(targetPublicUID);
PlayerProfileHeader.DecrementFollowingCount(1);
UpdateButtonToFollow();
LootLockerSDKManager.UnfollowPlayer(targetPublicUID, (resp) =>
{
if (!resp.success)
{
// Rollback
_followingIds.Add(targetPublicUID);
PlayerProfileHeader.DecrementFollowingCount(-1); // undo decrement
UpdateButtonToFollowing();
ShowToast("Unfollow failed: " + resp.errorData?.message);
return;
}
// Provide Undo for a short window
ShowUndo($"Unfollowed {targetDisplayName}", () => UndoUnfollow(targetPublicUID));
});
}
private void UndoUnfollow(string targetPublicUID)
{
if (_followingIds.Contains(targetPublicUID)) return; // Already re-added somehow
_followingIds.Add(targetPublicUID);
PlayerProfileHeader.DecrementFollowingCount(-1);
UpdateButtonToFollowing();
// Optionally silently call FollowPlayer again (depends if you want immediate server re-sync)
LootLockerSDKManager.FollowPlayer(targetPublicUID, _ => {});
}
private void UpdateButtonToFollow() => FollowLabel.text = "Follow"; // pseudo
private void UpdateButtonToFollowing() => FollowLabel.text = "Following";
}
Confirmation Pattern
Use lightweight confirmations only if unfollow has downstream impact (e.g. curated feed). Otherwise allow instant toggle with an Undo snackbar.
Determining If Current Player Follows Target
To check if the logged-in player follows a specific other player you do NOT need to build or hydrate a full cache. Use the single-item pagination shortcut: call the following-list endpoint with the target player's public UID as the cursor (or path parameter depending on SDK variant) and request Count: 1
. If one entry is returned, the relationship exists; if zero, it does not.
Why this works: the server returns the players the current player is following starting “from” the supplied cursor (player ULID). Asking for only one result lets the backend tell you immediately if that specific ID is in the set without scanning client‑side.
Pros:
O(1) network request per check (no growing local data structure)
Constant payload size (either 0 or 1 item)
Always fresh (no stale cache issues)
Consider caching only if you batch many checks in the same frame (e.g. rendering 100 profile tiles). For a single profile view, prefer this direct probe.
// Determine if we follow targetPlayerULID by requesting just one item "starting at" that player.
// If the first returned player matches the target, we are following them.
public static class FollowProbe
{
public static void CheckFollowing(string targetPlayerULID, System.Action<bool> onResult)
{
if (string.IsNullOrEmpty(targetPlayerULID)) { onResult?.Invoke(false); return; }
// Ask for a single entry starting at the target cursor
LootLockerSDKManager.ListFollowingPaginated(targetPlayerULID, 1, (resp) =>
{
if (!resp.success || resp.following == null || resp.following.Length == 0)
{
onResult?.Invoke(false);
return;
}
// Server returned at least one player; see if it is the one we asked about.
bool isFollowing = resp.following[0].player_id == targetPlayerULID;
onResult?.Invoke(isFollowing);
});
}
}
// Usage:
// FollowProbe.CheckFollowing(otherPlayerUlid, (isFollowing) => UIProfileButton.SetState(isFollowing));
Performance Tips
Debounce rapid follow toggles (e.g. leaderboards) to one in-flight request per target.
Paginate aggressively (e.g. 25–50 per page) for scrolling lists.
Preload the first page of “Following” at session start if many UI surfaces need that state.
Avoid full refresh after each follow action; surgically mutate local structures.
Example Feature Ideas
Activity Feed: Show recent achievements of players you follow.
“Players Following This Creator Also Follow…” recommendations (intersect follower sets).
Seasonal Follow Goals: Reward cosmetics when a player reaches follower milestones.
Conclusion
In this How-to we listed followers, listed who a player is following, implemented follow/unfollow actions, handled pagination, and optimized UI responsiveness with caching and optimistic updates. You can now extend this to social feeds, recommendations, or creator-style profile experiences. Next, explore adding Friends or managing relationships in the Web Console.
Last updated