horizon-mcp 0.0.0-development
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/debug-connection.d.ts +3 -0
- package/dist/prompts/debug-connection.d.ts.map +1 -0
- package/dist/prompts/debug-connection.js +17 -0
- package/dist/prompts/debug-connection.js.map +1 -0
- package/dist/prompts/explain-feature.d.ts +3 -0
- package/dist/prompts/explain-feature.d.ts.map +1 -0
- package/dist/prompts/explain-feature.js +32 -0
- package/dist/prompts/explain-feature.js.map +1 -0
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +14 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/integrate-feature.d.ts +3 -0
- package/dist/prompts/integrate-feature.d.ts.map +1 -0
- package/dist/prompts/integrate-feature.js +35 -0
- package/dist/prompts/integrate-feature.js.map +1 -0
- package/dist/prompts/setup-auth.d.ts +3 -0
- package/dist/prompts/setup-auth.d.ts.map +1 -0
- package/dist/prompts/setup-auth.js +26 -0
- package/dist/prompts/setup-auth.js.map +1 -0
- package/dist/resources/index.d.ts +6 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +208 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/__tests__/api-client.test.d.ts +2 -0
- package/dist/tools/__tests__/api-client.test.d.ts.map +1 -0
- package/dist/tools/__tests__/api-client.test.js +156 -0
- package/dist/tools/__tests__/api-client.test.js.map +1 -0
- package/dist/tools/api-client.d.ts +25 -0
- package/dist/tools/api-client.d.ts.map +1 -0
- package/dist/tools/api-client.js +78 -0
- package/dist/tools/api-client.js.map +1 -0
- package/dist/tools/auth.d.ts +3 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +123 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/cloud-save.d.ts +3 -0
- package/dist/tools/cloud-save.d.ts.map +1 -0
- package/dist/tools/cloud-save.js +50 -0
- package/dist/tools/cloud-save.js.map +1 -0
- package/dist/tools/connection.d.ts +3 -0
- package/dist/tools/connection.d.ts.map +1 -0
- package/dist/tools/connection.js +20 -0
- package/dist/tools/connection.js.map +1 -0
- package/dist/tools/feedback.d.ts +3 -0
- package/dist/tools/feedback.d.ts.map +1 -0
- package/dist/tools/feedback.js +36 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/gift-codes.d.ts +3 -0
- package/dist/tools/gift-codes.d.ts.map +1 -0
- package/dist/tools/gift-codes.js +52 -0
- package/dist/tools/gift-codes.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/leaderboard.d.ts +3 -0
- package/dist/tools/leaderboard.d.ts.map +1 -0
- package/dist/tools/leaderboard.js +96 -0
- package/dist/tools/leaderboard.js.map +1 -0
- package/dist/tools/news.d.ts +3 -0
- package/dist/tools/news.d.ts.map +1 -0
- package/dist/tools/news.js +29 -0
- package/dist/tools/news.js.map +1 -0
- package/dist/tools/remote-config.d.ts +3 -0
- package/dist/tools/remote-config.d.ts.map +1 -0
- package/dist/tools/remote-config.js +42 -0
- package/dist/tools/remote-config.js.map +1 -0
- package/dist/tools/tool-helpers.d.ts +24 -0
- package/dist/tools/tool-helpers.d.ts.map +1 -0
- package/dist/tools/tool-helpers.js +54 -0
- package/dist/tools/tool-helpers.js.map +1 -0
- package/dist/tools/user-logs.d.ts +3 -0
- package/dist/tools/user-logs.d.ts.map +1 -0
- package/dist/tools/user-logs.js +30 -0
- package/dist/tools/user-logs.js.map +1 -0
- package/package.json +52 -0
- package/src/resources/api/app-api.md +495 -0
- package/src/resources/docs/auth.md +280 -0
- package/src/resources/docs/cloud-save.md +180 -0
- package/src/resources/docs/feedback.md +126 -0
- package/src/resources/docs/gift-codes.md +153 -0
- package/src/resources/docs/leaderboard.md +201 -0
- package/src/resources/docs/news.md +114 -0
- package/src/resources/docs/overview.md +80 -0
- package/src/resources/docs/remote-config.md +149 -0
- package/src/resources/docs/user-logs.md +144 -0
- package/src/resources/quickstart/godot.md +224 -0
- package/src/resources/quickstart/unity.md +317 -0
- package/src/resources/quickstart/unreal.md +390 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# User Logs
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
User Logs allow you to track player events, errors, and actions on the server. This is useful for debugging, analytics, and monitoring player behavior. Log entries are viewable per-user in the horizOn Dashboard.
|
|
6
|
+
|
|
7
|
+
**Important:** User Logs require **BASIC tier or higher**. The feature is not available on the FREE tier and will return a `403` error.
|
|
8
|
+
|
|
9
|
+
## Endpoints
|
|
10
|
+
|
|
11
|
+
### Create Log
|
|
12
|
+
|
|
13
|
+
**`POST /api/v1/app/user-logs/create`**
|
|
14
|
+
|
|
15
|
+
Creates a log entry for a user.
|
|
16
|
+
|
|
17
|
+
**Request Body:**
|
|
18
|
+
|
|
19
|
+
| Field | Type | Required | Description |
|
|
20
|
+
|-------|------|----------|-------------|
|
|
21
|
+
| `message` | string | Yes | Log message (max 1000 characters) |
|
|
22
|
+
| `type` | string | Yes | Log type: `INFO`, `WARN`, or `ERROR` |
|
|
23
|
+
| `userId` | string | Yes | The user's ID |
|
|
24
|
+
| `errorCode` | string | No | Error code for categorization (max 50 characters) |
|
|
25
|
+
|
|
26
|
+
**Response (200):**
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"id": "log-abc123",
|
|
31
|
+
"createdAt": "2025-01-15T10:30:00Z"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Log Types
|
|
36
|
+
|
|
37
|
+
| Type | Use Case |
|
|
38
|
+
|------|----------|
|
|
39
|
+
| `INFO` | General events: tutorial complete, level up, purchase |
|
|
40
|
+
| `WARN` | Non-critical issues: low memory, retry attempt, degraded performance |
|
|
41
|
+
| `ERROR` | Critical issues: crash, save failure, network error |
|
|
42
|
+
|
|
43
|
+
## Code Examples
|
|
44
|
+
|
|
45
|
+
### Godot (GDScript)
|
|
46
|
+
|
|
47
|
+
```gdscript
|
|
48
|
+
# Create typed log entries
|
|
49
|
+
await Horizon.userLogs.info("Player completed tutorial")
|
|
50
|
+
await Horizon.userLogs.warn("Low memory detected")
|
|
51
|
+
await Horizon.userLogs.error("Failed to load asset", "ERR_001")
|
|
52
|
+
|
|
53
|
+
# Log game events
|
|
54
|
+
await Horizon.userLogs.logEvent("level_complete", "Level 5")
|
|
55
|
+
await Horizon.userLogs.logEvent("purchase", "item_sword_01")
|
|
56
|
+
|
|
57
|
+
# Log errors with stack trace
|
|
58
|
+
await Horizon.userLogs.logError("Null reference in combat system", "Line 42: combat.gd")
|
|
59
|
+
|
|
60
|
+
# Full createLog call
|
|
61
|
+
var result = await Horizon.userLogs.createLog(
|
|
62
|
+
HorizonUserLogs.LogType.ERROR,
|
|
63
|
+
"Detailed error message here",
|
|
64
|
+
"SAVE_FAILED"
|
|
65
|
+
)
|
|
66
|
+
if not result.is_empty():
|
|
67
|
+
print("Log ID: %s, Created: %s" % [result.id, result.createdAt])
|
|
68
|
+
|
|
69
|
+
# Listen for events
|
|
70
|
+
Horizon.userLogs.log_created.connect(func(id, created_at): print("Log %s created" % id))
|
|
71
|
+
Horizon.userLogs.log_create_failed.connect(func(error): print("Failed: %s" % error))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Unity (C#)
|
|
75
|
+
|
|
76
|
+
```csharp
|
|
77
|
+
using PM.horizOn.Cloud.Manager;
|
|
78
|
+
using PM.horizOn.Cloud.Enums;
|
|
79
|
+
|
|
80
|
+
// Convenience methods
|
|
81
|
+
await UserLogManager.Instance.Info("Tutorial completed");
|
|
82
|
+
await UserLogManager.Instance.Warn("Low memory detected");
|
|
83
|
+
await UserLogManager.Instance.Error("Save failed", errorCode: "SAVE_001");
|
|
84
|
+
|
|
85
|
+
// Full CreateLog call
|
|
86
|
+
var result = await UserLogManager.Instance.CreateLog(
|
|
87
|
+
LogType.ERROR,
|
|
88
|
+
"Detailed error message",
|
|
89
|
+
errorCode: "CRASH_002"
|
|
90
|
+
);
|
|
91
|
+
if (result != null)
|
|
92
|
+
{
|
|
93
|
+
Debug.Log($"Log ID: {result.id}, Created: {result.createdAt}");
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### REST (cURL)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
curl -X POST https://horizon.pm/api/v1/app/user-logs/create \
|
|
101
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
102
|
+
-H "Content-Type: application/json" \
|
|
103
|
+
-d '{
|
|
104
|
+
"message": "Player completed tutorial",
|
|
105
|
+
"type": "INFO",
|
|
106
|
+
"userId": "user123"
|
|
107
|
+
}'
|
|
108
|
+
|
|
109
|
+
# With error code
|
|
110
|
+
curl -X POST https://horizon.pm/api/v1/app/user-logs/create \
|
|
111
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
112
|
+
-H "Content-Type: application/json" \
|
|
113
|
+
-d '{
|
|
114
|
+
"message": "Failed to save game data",
|
|
115
|
+
"type": "ERROR",
|
|
116
|
+
"userId": "user123",
|
|
117
|
+
"errorCode": "SAVE_FAILED"
|
|
118
|
+
}'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Best Practices
|
|
122
|
+
|
|
123
|
+
- **Log meaningful events** — Track milestones (tutorial complete, level up), not routine actions.
|
|
124
|
+
- **Use error codes** — Consistent error codes make it easy to filter and analyze issues in the Dashboard.
|
|
125
|
+
- **Truncate long messages** — Messages are limited to 1000 characters. Both SDKs auto-truncate.
|
|
126
|
+
- **Be aware of rate limits** — Each log creation counts toward the 10 req/min limit. Batch or throttle logging.
|
|
127
|
+
|
|
128
|
+
## Tier Requirements
|
|
129
|
+
|
|
130
|
+
| Tier | User Logs Available |
|
|
131
|
+
|------|-------------------|
|
|
132
|
+
| FREE | No (returns 403) |
|
|
133
|
+
| BASIC | Yes |
|
|
134
|
+
| PRO | Yes |
|
|
135
|
+
| ENTERPRISE | Yes |
|
|
136
|
+
|
|
137
|
+
## Common Errors
|
|
138
|
+
|
|
139
|
+
| Status | Cause | Solution |
|
|
140
|
+
|--------|-------|----------|
|
|
141
|
+
| 400 | Missing required fields | Ensure `message`, `type`, and `userId` are provided |
|
|
142
|
+
| 401 | Invalid API key | Check `X-API-Key` header |
|
|
143
|
+
| 403 | FREE tier account | Upgrade to BASIC or higher |
|
|
144
|
+
| 429 | Rate limit exceeded | Throttle log submissions |
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Godot Quickstart Guide
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- **Godot 4.5** or later
|
|
6
|
+
- horizOn API key (get one at [horizon.pm](https://horizon.pm))
|
|
7
|
+
|
|
8
|
+
## Step 1: Install the SDK
|
|
9
|
+
|
|
10
|
+
### Option A: Asset Library (Recommended)
|
|
11
|
+
|
|
12
|
+
1. Open Godot and go to **AssetLib**
|
|
13
|
+
2. Search for "horizOn SDK"
|
|
14
|
+
3. Download and install
|
|
15
|
+
4. Enable the plugin: **Project > Project Settings > Plugins > horizOn SDK**
|
|
16
|
+
|
|
17
|
+
### Option B: Manual Installation
|
|
18
|
+
|
|
19
|
+
1. Download the latest release from [GitHub Releases](https://github.com/ProjectMakersDE/horizOn-SDK-Godot/releases)
|
|
20
|
+
2. Copy the `addons/horizon_sdk` folder to your project's `addons/` directory
|
|
21
|
+
3. Enable the plugin: **Project > Project Settings > Plugins > horizOn SDK**
|
|
22
|
+
|
|
23
|
+
## Step 2: Import Configuration
|
|
24
|
+
|
|
25
|
+
1. Go to the [horizOn Dashboard](https://horizon.pm) and navigate to SDK Settings
|
|
26
|
+
2. Download your `horizOn_config.json` file
|
|
27
|
+
3. In Godot, go to **Project > Tools > horizOn: Import Config...**
|
|
28
|
+
4. Select your downloaded JSON file
|
|
29
|
+
|
|
30
|
+
This creates a config resource at `res://addons/horizon_sdk/horizon_config.tres` with your API key and server hosts.
|
|
31
|
+
|
|
32
|
+
## Step 3: Connect to Server
|
|
33
|
+
|
|
34
|
+
The SDK is accessed through the `Horizon` singleton (auto-registered when the plugin is enabled).
|
|
35
|
+
|
|
36
|
+
```gdscript
|
|
37
|
+
extends Node
|
|
38
|
+
|
|
39
|
+
func _ready():
|
|
40
|
+
# Connect to the best available server (auto-selects lowest latency)
|
|
41
|
+
var connected = await Horizon.connect_to_server()
|
|
42
|
+
if not connected:
|
|
43
|
+
print("Failed to connect to horizOn!")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
print("Connected to %s" % Horizon.getActiveHost())
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Step 4: Authenticate
|
|
50
|
+
|
|
51
|
+
```gdscript
|
|
52
|
+
# Quick anonymous sign-in (creates new user or restores cached session)
|
|
53
|
+
var signed_in = await Horizon.quickSignInAnonymous("Player1")
|
|
54
|
+
if signed_in:
|
|
55
|
+
var user = Horizon.getCurrentUser()
|
|
56
|
+
print("Welcome, %s! (ID: %s)" % [user.displayName, user.userId])
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Other Authentication Methods
|
|
60
|
+
|
|
61
|
+
```gdscript
|
|
62
|
+
# Email sign-up
|
|
63
|
+
await Horizon.auth.signUpEmail("user@example.com", "password123", "MyUsername")
|
|
64
|
+
|
|
65
|
+
# Email sign-in
|
|
66
|
+
await Horizon.auth.signInEmail("user@example.com", "password123")
|
|
67
|
+
|
|
68
|
+
# Anonymous sign-up with explicit token
|
|
69
|
+
await Horizon.auth.signUpAnonymous("DisplayName")
|
|
70
|
+
|
|
71
|
+
# Restore anonymous session from cache
|
|
72
|
+
await Horizon.auth.restoreAnonymousSession()
|
|
73
|
+
|
|
74
|
+
# Sign out
|
|
75
|
+
Horizon.auth.signOut()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Step 5: Use Features
|
|
79
|
+
|
|
80
|
+
### Leaderboards
|
|
81
|
+
|
|
82
|
+
```gdscript
|
|
83
|
+
# Submit a score (only updates if higher than previous best)
|
|
84
|
+
await Horizon.leaderboard.submitScore(1000)
|
|
85
|
+
|
|
86
|
+
# Get top 10 players
|
|
87
|
+
var top: Array[HorizonLeaderboardEntry] = await Horizon.leaderboard.getTop(10)
|
|
88
|
+
for entry in top:
|
|
89
|
+
print("#%d %s: %d" % [entry.position, entry.username, entry.score])
|
|
90
|
+
|
|
91
|
+
# Get your rank
|
|
92
|
+
var rank: HorizonLeaderboardEntry = await Horizon.leaderboard.getRank()
|
|
93
|
+
|
|
94
|
+
# Get entries around your position
|
|
95
|
+
var around: Array[HorizonLeaderboardEntry] = await Horizon.leaderboard.getAround(5)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Cloud Saves
|
|
99
|
+
|
|
100
|
+
```gdscript
|
|
101
|
+
# Save a Dictionary (recommended)
|
|
102
|
+
await Horizon.cloudSave.saveObject({"level": 5, "coins": 1000})
|
|
103
|
+
|
|
104
|
+
# Load as Dictionary
|
|
105
|
+
var data: Dictionary = await Horizon.cloudSave.loadObject()
|
|
106
|
+
if not data.is_empty():
|
|
107
|
+
var level = data.get("level", 1)
|
|
108
|
+
|
|
109
|
+
# Save/load raw strings
|
|
110
|
+
await Horizon.cloudSave.saveData('{"custom": "json"}')
|
|
111
|
+
var json: String = await Horizon.cloudSave.loadData()
|
|
112
|
+
|
|
113
|
+
# Binary data
|
|
114
|
+
await Horizon.cloudSave.saveBytes(my_bytes)
|
|
115
|
+
var bytes: PackedByteArray = await Horizon.cloudSave.loadBytes()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Remote Config
|
|
119
|
+
|
|
120
|
+
```gdscript
|
|
121
|
+
# Typed getters with defaults
|
|
122
|
+
var max_level: int = await Horizon.remoteConfig.getInt("max_level", 100)
|
|
123
|
+
var difficulty: float = await Horizon.remoteConfig.getFloat("difficulty", 1.0)
|
|
124
|
+
var maintenance: bool = await Horizon.remoteConfig.getBool("maintenance_mode", false)
|
|
125
|
+
|
|
126
|
+
# Get all configs at once (recommended at startup)
|
|
127
|
+
var all_configs: Dictionary = await Horizon.remoteConfig.getAllConfigs()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### News
|
|
131
|
+
|
|
132
|
+
```gdscript
|
|
133
|
+
var news: Array[HorizonNewsEntry] = await Horizon.news.loadNews(20, "en")
|
|
134
|
+
for entry in news:
|
|
135
|
+
print("%s: %s" % [entry.title, entry.message])
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Gift Codes
|
|
139
|
+
|
|
140
|
+
```gdscript
|
|
141
|
+
var is_valid = await Horizon.giftCodes.validate("ABCD-1234")
|
|
142
|
+
if is_valid:
|
|
143
|
+
var result = await Horizon.giftCodes.redeem("ABCD-1234")
|
|
144
|
+
if result.get("success", false):
|
|
145
|
+
print("Rewards: %s" % result.get("giftData", ""))
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Feedback
|
|
149
|
+
|
|
150
|
+
```gdscript
|
|
151
|
+
await Horizon.feedback.submitBugReport("Crash on Level 5", "Game crashes when...")
|
|
152
|
+
await Horizon.feedback.submitFeatureRequest("Dark Mode", "Add dark mode option")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### User Logs
|
|
156
|
+
|
|
157
|
+
```gdscript
|
|
158
|
+
# Requires BASIC tier or higher
|
|
159
|
+
await Horizon.userLogs.info("Player completed tutorial")
|
|
160
|
+
await Horizon.userLogs.warn("Low memory detected")
|
|
161
|
+
await Horizon.userLogs.error("Failed to load asset", "ERR_001")
|
|
162
|
+
await Horizon.userLogs.logEvent("level_complete", "Level 5")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Signals (Event-Driven)
|
|
166
|
+
|
|
167
|
+
All operations emit signals for reactive programming:
|
|
168
|
+
|
|
169
|
+
```gdscript
|
|
170
|
+
# SDK lifecycle
|
|
171
|
+
Horizon.sdk_initialized.connect(func(): print("SDK ready"))
|
|
172
|
+
Horizon.sdk_connected.connect(func(host): print("Connected to %s" % host))
|
|
173
|
+
|
|
174
|
+
# Authentication
|
|
175
|
+
Horizon.auth.signin_completed.connect(func(user): print("Signed in: %s" % user.userId))
|
|
176
|
+
Horizon.auth.signin_failed.connect(func(error): print("Auth error: %s" % error))
|
|
177
|
+
|
|
178
|
+
# Features
|
|
179
|
+
Horizon.leaderboard.score_submitted.connect(func(score): print("Score: %d" % score))
|
|
180
|
+
Horizon.cloudSave.data_saved.connect(func(size): print("Saved %d bytes" % size))
|
|
181
|
+
Horizon.news.news_loaded.connect(func(entries): print("Loaded %d news" % entries.size()))
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Configuration Options
|
|
185
|
+
|
|
186
|
+
Edit your config resource at `addons/horizon_sdk/horizon_config.tres`:
|
|
187
|
+
|
|
188
|
+
| Option | Default | Description |
|
|
189
|
+
|--------|---------|-------------|
|
|
190
|
+
| `api_key` | - | Your horizOn API key |
|
|
191
|
+
| `hosts` | - | Array of backend server URLs |
|
|
192
|
+
| `connection_timeout_seconds` | 10 | HTTP request timeout |
|
|
193
|
+
| `max_retry_attempts` | 3 | Retry count for failed requests |
|
|
194
|
+
| `retry_delay_seconds` | 1.0 | Delay between retries |
|
|
195
|
+
| `log_level` | INFO | DEBUG, INFO, WARNING, ERROR, NONE |
|
|
196
|
+
|
|
197
|
+
## Rate Limit Best Practices
|
|
198
|
+
|
|
199
|
+
The rate limit is **10 requests per minute per client** (all tiers). Plan your API calls:
|
|
200
|
+
|
|
201
|
+
```gdscript
|
|
202
|
+
func _ready():
|
|
203
|
+
var connected = await Horizon.connect_to_server()
|
|
204
|
+
if not connected:
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
# Authenticate (1 request)
|
|
208
|
+
await Horizon.quickSignInAnonymous("Player")
|
|
209
|
+
|
|
210
|
+
# Startup batch (3 requests)
|
|
211
|
+
await Horizon.remoteConfig.getAllConfigs()
|
|
212
|
+
await Horizon.news.loadNews()
|
|
213
|
+
await Horizon.leaderboard.getTop(10)
|
|
214
|
+
|
|
215
|
+
# 6 requests remaining for gameplay actions
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Example Project
|
|
219
|
+
|
|
220
|
+
The SDK includes a test scene demonstrating all features:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
res://addons/horizon_sdk/examples/horizon_test_scene.tscn
|
|
224
|
+
```
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Unity Quickstart Guide
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- **Unity 2023.3+** (Unity 6)
|
|
6
|
+
- horizOn API key (get one at [horizon.pm](https://horizon.pm))
|
|
7
|
+
- Namespace: `PM.horizOn.Cloud`
|
|
8
|
+
|
|
9
|
+
## Step 1: Import the SDK
|
|
10
|
+
|
|
11
|
+
1. Download the horizOn Cloud SDK package
|
|
12
|
+
2. Import into your project: `Assets/Plugins/ProjectMakers/horizOn/`
|
|
13
|
+
3. The SDK is ready to use once imported (no plugin activation needed)
|
|
14
|
+
|
|
15
|
+
## Step 2: Import Configuration
|
|
16
|
+
|
|
17
|
+
1. Go to the [horizOn Dashboard](https://horizon.pm) and navigate to SDK Settings
|
|
18
|
+
2. Download your `horizOn_config.json` file
|
|
19
|
+
3. In Unity, go to **Window > horizOn > Config Importer**
|
|
20
|
+
4. Select your downloaded JSON file
|
|
21
|
+
5. The config is saved as a ScriptableObject in the SDK's Resources folder
|
|
22
|
+
|
|
23
|
+
## Step 3: Initialize and Connect
|
|
24
|
+
|
|
25
|
+
```csharp
|
|
26
|
+
using PM.horizOn.Cloud.Core;
|
|
27
|
+
using PM.horizOn.Cloud.Manager;
|
|
28
|
+
|
|
29
|
+
public class GameManager : MonoBehaviour
|
|
30
|
+
{
|
|
31
|
+
async void Start()
|
|
32
|
+
{
|
|
33
|
+
// Initialize the SDK (creates services, loads config)
|
|
34
|
+
HorizonApp.Initialize();
|
|
35
|
+
|
|
36
|
+
// Connect to the best available server
|
|
37
|
+
var server = new HorizonServer();
|
|
38
|
+
await server.Connect();
|
|
39
|
+
|
|
40
|
+
Debug.Log("Connected to horizOn!");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Step 4: Authenticate
|
|
46
|
+
|
|
47
|
+
```csharp
|
|
48
|
+
// Anonymous sign-up (auto-generates and caches token)
|
|
49
|
+
await UserManager.Instance.SignUpAnonymous("PlayerName");
|
|
50
|
+
|
|
51
|
+
// Check if signed in
|
|
52
|
+
if (UserManager.Instance.IsSignedIn)
|
|
53
|
+
{
|
|
54
|
+
var user = UserManager.Instance.CurrentUser;
|
|
55
|
+
Debug.Log($"Welcome, {user.DisplayName}! (ID: {user.UserId})");
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Other Authentication Methods
|
|
60
|
+
|
|
61
|
+
```csharp
|
|
62
|
+
// Email sign-up
|
|
63
|
+
await UserManager.Instance.SignUpEmail("user@example.com", "password123", "DisplayName");
|
|
64
|
+
|
|
65
|
+
// Email sign-in
|
|
66
|
+
await UserManager.Instance.SignInEmail("user@example.com", "password123");
|
|
67
|
+
|
|
68
|
+
// Anonymous sign-in (restore cached session)
|
|
69
|
+
await UserManager.Instance.RestoreAnonymousSession();
|
|
70
|
+
|
|
71
|
+
// Google sign-in
|
|
72
|
+
await UserManager.Instance.SignInGoogle("google-authorization-code");
|
|
73
|
+
|
|
74
|
+
// Change display name
|
|
75
|
+
await UserManager.Instance.ChangeName("NewName");
|
|
76
|
+
|
|
77
|
+
// Sign out (preserves anonymous token by default)
|
|
78
|
+
UserManager.Instance.SignOut();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Step 5: Use Features
|
|
82
|
+
|
|
83
|
+
### Leaderboards
|
|
84
|
+
|
|
85
|
+
```csharp
|
|
86
|
+
using PM.horizOn.Cloud.Manager;
|
|
87
|
+
|
|
88
|
+
// Submit score (only updates if higher than previous best)
|
|
89
|
+
await LeaderboardManager.Instance.SubmitScore(12500);
|
|
90
|
+
|
|
91
|
+
// Get top 10 players
|
|
92
|
+
var top = await LeaderboardManager.Instance.GetTop(10);
|
|
93
|
+
foreach (var entry in top)
|
|
94
|
+
{
|
|
95
|
+
Debug.Log($"#{entry.position} {entry.username}: {entry.score}");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Get your rank
|
|
99
|
+
var rank = await LeaderboardManager.Instance.GetRank();
|
|
100
|
+
Debug.Log($"My rank: #{rank.position} (Score: {rank.score})");
|
|
101
|
+
|
|
102
|
+
// Get entries around your position
|
|
103
|
+
var around = await LeaderboardManager.Instance.GetAround(5);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Cloud Saves
|
|
107
|
+
|
|
108
|
+
```csharp
|
|
109
|
+
using PM.horizOn.Cloud.Manager;
|
|
110
|
+
|
|
111
|
+
// Define your save structure
|
|
112
|
+
[System.Serializable]
|
|
113
|
+
public class GameData
|
|
114
|
+
{
|
|
115
|
+
public int Level;
|
|
116
|
+
public int Coins;
|
|
117
|
+
public string[] Inventory;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Save a typed object
|
|
121
|
+
var saveData = new GameData { Level = 5, Coins = 1000, Inventory = new[] { "sword" } };
|
|
122
|
+
await CloudSaveManager.Instance.SaveObject(saveData);
|
|
123
|
+
|
|
124
|
+
// Load a typed object
|
|
125
|
+
var loaded = await CloudSaveManager.Instance.LoadObject<GameData>();
|
|
126
|
+
if (loaded != null)
|
|
127
|
+
{
|
|
128
|
+
Debug.Log($"Level: {loaded.Level}, Coins: {loaded.Coins}");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Save/load raw strings
|
|
132
|
+
await CloudSaveManager.Instance.Save("{\"level\": 5}");
|
|
133
|
+
string json = await CloudSaveManager.Instance.Load();
|
|
134
|
+
|
|
135
|
+
// Binary data
|
|
136
|
+
byte[] data = GetBinaryData();
|
|
137
|
+
await CloudSaveManager.Instance.SaveBytes(data);
|
|
138
|
+
byte[] loadedBytes = await CloudSaveManager.Instance.LoadBytes();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Remote Config
|
|
142
|
+
|
|
143
|
+
```csharp
|
|
144
|
+
using PM.horizOn.Cloud.Manager;
|
|
145
|
+
|
|
146
|
+
// Type-safe getters with defaults
|
|
147
|
+
int maxLives = await RemoteConfigManager.Instance.GetInt("max_lives", 3);
|
|
148
|
+
float difficulty = await RemoteConfigManager.Instance.GetFloat("difficulty", 1.0f);
|
|
149
|
+
bool eventActive = await RemoteConfigManager.Instance.GetBool("holiday_event", false);
|
|
150
|
+
string version = await RemoteConfigManager.Instance.GetString("game_version", "1.0.0");
|
|
151
|
+
|
|
152
|
+
// Get all configs at once (recommended at startup)
|
|
153
|
+
var configs = await RemoteConfigManager.Instance.GetAllConfigs();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### News
|
|
157
|
+
|
|
158
|
+
```csharp
|
|
159
|
+
using PM.horizOn.Cloud.Manager;
|
|
160
|
+
|
|
161
|
+
// Load news entries
|
|
162
|
+
var news = await NewsManager.Instance.LoadNews(limit: 10);
|
|
163
|
+
foreach (var item in news)
|
|
164
|
+
{
|
|
165
|
+
Debug.Log($"{item.title}: {item.message}");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Filter by language
|
|
169
|
+
var germanNews = await NewsManager.Instance.LoadNews(limit: 10, languageCode: "de");
|
|
170
|
+
|
|
171
|
+
// Get cached entry by ID
|
|
172
|
+
var entry = NewsManager.Instance.GetNewsById("news-001");
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Gift Codes
|
|
176
|
+
|
|
177
|
+
```csharp
|
|
178
|
+
using PM.horizOn.Cloud.Manager;
|
|
179
|
+
|
|
180
|
+
// Validate first (optional)
|
|
181
|
+
bool? valid = await GiftCodeManager.Instance.Validate("PROMO2024");
|
|
182
|
+
|
|
183
|
+
// Redeem
|
|
184
|
+
var result = await GiftCodeManager.Instance.Redeem("PROMO2024");
|
|
185
|
+
if (result != null && result.success)
|
|
186
|
+
{
|
|
187
|
+
string giftData = result.giftData; // JSON string with rewards
|
|
188
|
+
Debug.Log($"Rewards: {giftData}");
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Feedback
|
|
193
|
+
|
|
194
|
+
```csharp
|
|
195
|
+
using PM.horizOn.Cloud.Manager;
|
|
196
|
+
|
|
197
|
+
// Bug report with auto device info
|
|
198
|
+
await FeedbackManager.Instance.ReportBug(
|
|
199
|
+
title: "Crash on level 5",
|
|
200
|
+
message: "Game crashes when opening inventory"
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Feature request
|
|
204
|
+
await FeedbackManager.Instance.RequestFeature(
|
|
205
|
+
title: "Dark mode",
|
|
206
|
+
message: "Please add dark mode option"
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// General feedback with email
|
|
210
|
+
await FeedbackManager.Instance.SendGeneral(
|
|
211
|
+
title: "Great game!",
|
|
212
|
+
message: "Really enjoying the new update",
|
|
213
|
+
email: "user@example.com"
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### User Logs
|
|
218
|
+
|
|
219
|
+
```csharp
|
|
220
|
+
using PM.horizOn.Cloud.Manager;
|
|
221
|
+
|
|
222
|
+
// Requires BASIC tier or higher
|
|
223
|
+
await UserLogManager.Instance.Info("Tutorial completed");
|
|
224
|
+
await UserLogManager.Instance.Warn("Low memory detected");
|
|
225
|
+
await UserLogManager.Instance.Error("Save failed", errorCode: "SAVE_001");
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Event System
|
|
229
|
+
|
|
230
|
+
Subscribe to SDK events for reactive patterns:
|
|
231
|
+
|
|
232
|
+
```csharp
|
|
233
|
+
using PM.horizOn.Cloud.Core;
|
|
234
|
+
using PM.horizOn.Cloud.Enums;
|
|
235
|
+
using PM.horizOn.Cloud.Objects.Data;
|
|
236
|
+
|
|
237
|
+
// Subscribe to auth events
|
|
238
|
+
HorizonApp.Events.Subscribe<UserData>(EventKeys.UserSignInSuccess, OnUserSignedIn);
|
|
239
|
+
HorizonApp.Events.Subscribe<string>(EventKeys.UserSignInFailed, OnSignInFailed);
|
|
240
|
+
|
|
241
|
+
void OnUserSignedIn(UserData user)
|
|
242
|
+
{
|
|
243
|
+
Debug.Log($"Welcome back, {user.DisplayName}!");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
void OnSignInFailed(string error)
|
|
247
|
+
{
|
|
248
|
+
Debug.Log($"Sign-in failed: {error}");
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Error Handling
|
|
253
|
+
|
|
254
|
+
```csharp
|
|
255
|
+
// Check return values
|
|
256
|
+
bool success = await UserManager.Instance.SignInEmail(email, password);
|
|
257
|
+
if (!success)
|
|
258
|
+
{
|
|
259
|
+
Debug.Log("Sign-in failed. Check credentials.");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Cloud save with fallback
|
|
263
|
+
var data = await CloudSaveManager.Instance.LoadObject<GameData>();
|
|
264
|
+
if (data == null)
|
|
265
|
+
{
|
|
266
|
+
data = new GameData(); // Use defaults on first load
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Tier Limits
|
|
271
|
+
|
|
272
|
+
| Feature | FREE | BASIC | PRO | ENTERPRISE |
|
|
273
|
+
|---------|------|-------|-----|------------|
|
|
274
|
+
| Cloud Save | 1 KB | 5 KB | 20 KB | 250 KB |
|
|
275
|
+
| User Logs | No | Yes | Yes | Yes |
|
|
276
|
+
| Rate Limit | 10/min | 10/min | 10/min | 10/min |
|
|
277
|
+
|
|
278
|
+
## Efficient Startup Pattern
|
|
279
|
+
|
|
280
|
+
Plan your API calls to stay within the rate limit:
|
|
281
|
+
|
|
282
|
+
```csharp
|
|
283
|
+
async void Start()
|
|
284
|
+
{
|
|
285
|
+
// Initialize and connect
|
|
286
|
+
HorizonApp.Initialize();
|
|
287
|
+
await new HorizonServer().Connect();
|
|
288
|
+
|
|
289
|
+
// Startup loads (3 requests)
|
|
290
|
+
await UserManager.Instance.CheckAuth();
|
|
291
|
+
await RemoteConfigManager.Instance.GetAllConfigs();
|
|
292
|
+
await NewsManager.Instance.LoadNews();
|
|
293
|
+
|
|
294
|
+
// 7 requests remaining for gameplay actions
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Project Structure
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
Assets/Plugins/ProjectMakers/horizOn/
|
|
302
|
+
├── CloudSDK/
|
|
303
|
+
│ ├── Core/ # HorizonApp, HorizonServer, HorizonConfig
|
|
304
|
+
│ ├── Manager/ # Feature managers (UserManager, LeaderboardManager, etc.)
|
|
305
|
+
│ ├── Service/ # EventService, NetworkService, LogService
|
|
306
|
+
│ ├── Objects/ # Data models, requests, responses
|
|
307
|
+
│ └── Resources/ # HorizonConfig.asset
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Common Status Codes
|
|
311
|
+
|
|
312
|
+
| Code | Meaning | Action |
|
|
313
|
+
|------|---------|--------|
|
|
314
|
+
| 400 | Bad Request | Check parameters |
|
|
315
|
+
| 401 | Unauthorized | Verify API key |
|
|
316
|
+
| 403 | Forbidden | Check tier/permissions |
|
|
317
|
+
| 429 | Rate Limited | Implement caching, wait and retry |
|