Firebase Backend v1.5
Setup & Run Guide
Scope ‑ This document explains how to integrate the BulletHell Elemental Template v1.4 with Firebase Authentication and Cloud Firestore. It covers both a Quick Start for experienced users and a Step‑by‑Step walkthrough with screenshots for newcomers.
Read the offline backend page first
1. Quick Start (Experienced Users)
Unity
Unity Version: 2022.3 LTS (or newer)
Unity Packages (via Package Manager)
Input System
AI Navigation
In-App Purchasing
2D Sprite
TextMeshPro (TMP)
Firebase SDK
Import the following Firebase SDK packages (same major/minor, recommended):
FirebaseAuth 12.10.1
FirebaseFirestore 12.10.1
FirebaseDatabase (Realtime Database) 12.10.1
Project setup:
In Project Settings → Player → Scripting Define Symbols, add:
FIREBASE
Import the BulletHell Elemental Template.
Add scenes to Build Settings in this order:
LoginHomeOther gameplay/maps scenes
Tags / Layers:
Ensure the Monster tag exists and is assigned to all enemy prefabs.
google-services.json:Download from Firebase Console.
Place in:
Assets/StreamingAssets/(create the folder if missing).File name must remain google-services.json.
2. Firebase Console Setup
2.1 Authentication
In Authentication → Sign-in method:
Enable:
Email/Password
Anonymous
No special action needed here for user IDs; they are handled by the game code using Firebase Auth UID + Firestore metadata.
2.2 Firestore Database
Create a Cloud Firestore database in Production mode (or test mode during development, but with the rules below).
Required Collections & Documents
Players
Will be created automatically by the game on first login/registration.
No manual documents required.
BattlePass / SeasonInfo
Create:
Collection:
BattlePassDocument ID:
SeasonInfoFields:
Season— Number, e.g.1StartSeason— Timestamp, season start date(Optional)
DurationDays— Number, total days for the season
Admin / Admin
Create:
Collection:
AdminDocument ID:
AdminFields:
gm— Array of stringEach entry is a Firebase Auth UID of a GM user.
support— Array of stringEach entry is a Firebase Auth UID of a support user.
These arrays control who can use GM tools (Events, Mailbox, Global Message, Ban, etc).
Admin / Events
Create:
Collection:
AdminDocument ID:
EventsYou can leave it empty or add fields according to your tooling.
Used by the in-game GM tools for map events (must exist for writes under provided rules).
Admin / GlobalMessage
Create:
Collection:
AdminDocument ID:
GlobalMessageUsed for global announcements.
Admin / Mailbox
Create:
Collection:
AdminDocument ID:
MailboxCan start empty.
Used by GM tools to send reward mails.
Friendships
Create a root-level collection:
Friendships.Add one placeholder document (for example
placeholder), so the collection exists.Any shape is fine when created via Console (rules don’t block console writes).
Actual friendship documents are created and managed by the game code using the rules below.
Meta / UserCounters
Used to assign incremental numeric
userIdvalues.Create collection:
MetaCreate document:
UserCountersAdd field:
lastUserId— NumberExample:
1000(or0, or any starting point you prefer)
The game will atomically increment this value when new users are provisioned.
2.3 Firestore Security Rules
Paste the following into Firestore → Rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
/* Helpers */
function isSignedIn() {
return request.auth != null;
}
function isOwner(uid) {
return isSignedIn() && request.auth.uid == uid;
}
function hasRole(list, uid) {
return list is list && uid in list;
}
/**
* Returns true if the current user UID is listed as GM or Support
* inside Admin/Admin document ("gm" or "support" string arrays).
*/
function isGmOrSupport() {
return isSignedIn()
&& (
hasRole(
get(/databases/$(database)/documents/Admin/Admin).data.gm,
request.auth.uid
)
|| hasRole(
get(/databases/$(database)/documents/Admin/Admin).data.support,
request.auth.uid
)
);
}
/* PLAYERS */
match /Players/{uid} {
allow get, list: if isSignedIn();
allow create: if isOwner(uid);
allow delete: if isOwner(uid);
// Owner can update anything.
// GM/Support can only change "isBanned".
allow update: if
isOwner(uid)
|| (
isGmOrSupport()
&& resource.data.diff(request.resource.data).changedKeys().hasOnly(['isBanned'])
);
match /PurchasedItems/Items/List/{itemId} {
allow get, list, create, update, delete: if isOwner(uid);
}
match /{document=**} {
allow get, list, create, update, delete: if isOwner(uid);
}
}
/* PLAYER NAMES */
match /PlayerNames/{nameLower} {
allow get: if true;
allow list: if false;
allow create: if isSignedIn()
&& request.resource.data.keys().hasOnly(['ownerUid', 'createdAt'])
&& request.resource.data.ownerUid == request.auth.uid;
allow update, delete: if isSignedIn()
&& resource.data.ownerUid == request.auth.uid
&& request.resource.data == resource.data;
}
/* CURRENCIES */
match /Players/{uid}/Currencies/{coinId} {
allow get: if isOwner(uid);
allow list: if false;
allow delete: if false;
allow create: if isOwner(uid)
&& request.resource.data.keys().hasOnly(['initialAmount', 'amount'])
&& request.resource.data.initialAmount is int
&& request.resource.data.amount is int
&& request.resource.data.initialAmount >= 0
&& request.resource.data.amount >= 0;
allow update: if isOwner(uid)
&& request.resource.data.keys().hasOnly(['amount'])
&& request.resource.data.amount is int
&& request.resource.data.amount >= 0;
}
/* BATTLE PASS META */
match /BattlePass/{docId} {
allow get: if isSignedIn() && docId == "SeasonInfo";
allow list: if false;
allow create, update, delete: if false;
}
/* ADMIN / ROLES */
match /Admin/Admin {
allow get: if isSignedIn();
allow list: if false;
allow create, update, delete: if false;
}
/* ADMIN MAP EVENTS */
match /Admin/Events {
allow get: if isSignedIn();
allow list: if false;
allow create, update, delete: if isGmOrSupport();
}
/* ADMIN MAILBOX */
match /Admin/Mailbox {
allow get, list, create, update, delete: if isGmOrSupport();
}
/* ADMIN GLOBAL MESSAGE */
match /Admin/GlobalMessage {
allow get: if isSignedIn();
allow list: if false;
allow create, update, delete: if isGmOrSupport();
}
/* OTHER ADMIN DOCS DEFAULT */
match /Admin/{other} {
allow get: if isSignedIn();
allow list, create, update, delete: if false;
}
/* FRIENDSHIPS */
match /Friendships/{fid} {
function validUsersArray(data) {
return data.users is list
&& data.users.size() == 2
&& data.users[0] is string
&& data.users[1] is string
&& data.users[0] != data.users[1];
}
function sortedIdFor(data) {
let a = data.users[0];
let b = data.users[1];
return (a < b) ? (a + "_" + b) : (b + "_" + a);
}
function isParticipant(uid) {
return uid in resource.data.users;
}
function userIndex(uid) {
return resource.data.users[0] == uid ? 0 :
(resource.data.users[1] == uid ? 1 : -1);
}
function isRequester(uid) {
return resource.data.requestedBy == uid;
}
function friendshipTransitionAllowed() {
let before = resource.data.state;
let after = request.resource.data.state;
let uid = request.auth.uid;
let idx = userIndex(uid);
return (
(before == "PENDING" && after == "ACCEPTED" && !isRequester(uid)) ||
(before == "PENDING" && after == "PENDING" && isRequester(uid)) ||
(after == "BLOCKED_0" && idx == 0) ||
(after == "BLOCKED_1" && idx == 1)
);
}
// Read: only participants.
allow get, list: if isSignedIn() && isParticipant(request.auth.uid);
// Create: send friend request (PENDING).
allow create: if isSignedIn()
&& validUsersArray(request.resource.data)
&& sortedIdFor(request.resource.data) == fid
&& request.auth.uid in request.resource.data.users
&& request.resource.data.requestedBy == request.auth.uid
&& request.resource.data.state == "PENDING"
&& request.resource.data.createdAt is timestamp
&& request.resource.data.updatedAt is timestamp;
// Update: accept, block, or refresh pending.
allow update: if isSignedIn()
&& isParticipant(request.auth.uid)
&& validUsersArray(request.resource.data)
&& request.resource.data.users == resource.data.users
&& request.resource.data.requestedBy == resource.data.requestedBy
&& request.resource.data.createdAt == resource.data.createdAt
&& request.resource.data.updatedAt is timestamp
&& friendshipTransitionAllowed();
// Delete:
// - requester can cancel pending
// - other user can reject pending
// - either can unfriend (ACCEPTED)
// - blocker can unblock (BLOCKED_x)
allow delete: if isSignedIn()
&& isParticipant(request.auth.uid)
&& (
(resource.data.state == "PENDING") ||
(resource.data.state == "ACCEPTED") ||
(resource.data.state == "BLOCKED_0"
&& request.auth.uid == resource.data.users[0]) ||
(resource.data.state == "BLOCKED_1"
&& request.auth.uid == resource.data.users[1])
);
}
match /Meta/UserCounters {
allow get: if isSignedIn();
allow create, update: if isSignedIn()
&& request.resource.data.keys().hasOnly(['lastUserId'])
&& request.resource.data.lastUserId is int
&& (
!resource.exists()
||
(request.resource.data.lastUserId > resource.data.lastUserId)
);
allow delete: if false;
}
}
}
3. Realtime Database Setup
3.1 Create Realtime Database
In Firebase Console → Realtime Database:
Create a new database in the same project.
Choose a location (any supported region).
You can start in locked mode and then paste the rules below.
3.2 Base Structure
The game will create nodes automatically when presence & invites are used, but you may create them upfront (empty) for clarity:
Root-level:
status(empty object)invites(empty object)chatis created automatically when global chat is used.
Example initial JSON:
{
"status": {},
"invites": {}
}3.3 Realtime Database Rules
Paste the following into Realtime Database → Rules:
{
"rules": {
"status": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
},
"invites": {
"$targetUid": {
".read": "auth != null && auth.uid == $targetUid",
"$inviteId": {
".write": "auth != null && newData.child('fromUid').val() === auth.uid"
}
}
},
"chat": {
".read": "auth != null",
".write": "false",
".indexOn": ["timestamp"],
"$messageId": {
".write": "auth != null && !data.exists()",
".validate": "newData.hasChildren(['playerId', 'playerName', 'iconId', 'frameId', 'message', 'timestamp', 'isHighlighted'])",
"playerId": {
".validate": "newData.isString() && newData.val() === auth.uid"
},
"playerName": {
".validate": "newData.isString() && newData.val().length > 0 && newData.val().length <= 50"
},
"iconId": {
".validate": "newData.isString() && newData.val().length > 0"
},
"frameId": {
".validate": "newData.isString() && newData.val().length > 0"
},
"message": {
".validate": "newData.isString() && newData.val().length > 0 && newData.val().length <= 500"
},
"timestamp": {
".validate": "newData.isNumber() && newData.val() <= now"
},
"isHighlighted": {
".validate": "newData.isBoolean()"
}
}
}
}
}For developers who have not yet set up a project with Firebase or who have any questions, below is a detailed step-by-step guide with images:
2. Step‑by‑Step Guide (With Images)
2.1 Create a URP Project
Open Unity Hub → New Project.
Select Universal 3D (URP) template.
Name your project and click Create.
Optional – HDRP/Built‑in pipelines work, but demo textures may need conversion.

2.2 Install Required Unity Packages
Window → Package Manager → Unity Registry. Add Input System, AI Navigation, In‑App Purchasing, 2D Sprite, TextMeshPro.

2.3 Import Firebase SDK
Download lastest
FirebaseAuth.unitypackage,FirebaseFirestore.unitypackageandFirebaseDatabase.unitypackagefrom the Firebase Unity SDK Archive.Double‑click each package to import.
When prompted, allow ExternalDependancyManager to resolve libraries.
add FIREBASE in Scripting Define Symbols in Build Settings > Player

2.4 Import the Template
Drag the BulletHell Elemental Template .unitypackage into the Editor or install via Package Manager → My Assets.
2.5 Configure Project Settings
Player Settings → Identification → Package Name must match the Android/iOS package names you register in Firebase.
Switch Platform to PC, Android or iOS before building (Editor → File → Build Settings).
2.6 Firebase Console Configuration
Create Project → add Android and/or iOS app with the correct package name.
Download google‑services.json (Android) or GoogleService‑Info.plist (iOS).
Place json file in Assets/StreamingAssets/. If Unity cannot locate the file, verify the folder name and that the filename has no suffix (e.g., (1)).




Firestore Database → Start in production mode.
See the step-by-step instructions and rules at the beginning regarding which fields to create in Firestore.
Apply the security rules (see Quick Start above).
2.8 Prepare Cloud Firestore
Authentication → Sign‑in method: enable Email/Password and Anonymous.
2.7 Enable Authentication


3. Realtime Database Setup
3.1 Create Realtime Database
In Firebase Console → Realtime Database:
Create a new database in the same project.
Choose a location (any supported region).
You can start in locked mode and then paste the rules below.
3.2 Base Structure
The game will create nodes automatically when presence & invites are used, but you may create them upfront (empty) for clarity:
Root-level:
status(empty object)invites(empty object)chatis created automatically when global chat is used.
Example initial JSON (optional):
{
"status": {},
"invites": {}
}
2.9 Verify Tags, Layers & Scenes
If you imported the template into an existing project:
Add Monster tag in Edit → Project Settings → Tags & Layers.
Assign it to enemy prefabs in
BulletHellTemplate/Resources/Monsters/.Ensure scene order in Build Settings:
Login,Home, others.
2.10 Test in Editor
Open
Loginscene.Press Play – the template auto‑creates a local account, syncs with Firebase, and loads initial data.
Check Console for Firebase initialization logs.
3. Common Issues & Fixes
Issue
Cause
Fix
google‑services.json not found
File in wrong location
Place inside Assets/StreamingAssets/. Refresh project.
"Unknown error" on account creation
Weak password
Use at least 8 chars, 1 number, 1 special char.
Android/iOS build fails on "Validate References"
Unused platform DLLs
Edit → Project Settings → Player → Other Settings, disable Validate References for unsupported platform.
4. Next Steps
Customize Battle Pass seasons by updating
SeasonandStartSeasonfields.Harden security with App Check or move save logic to Cloud Functions (requires paid Firebase plan).
Join our community for support and feature requests.
Discord – https://discord.com/invite/EGGj77g3eQ Email – rafbizachi5@gmail.com
Last updated