sui-game-sdk 0.0.1
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 +163 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +79 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/session.d.ts +23 -0
- package/dist/session.js +47 -0
- package/dist/spawner.d.ts +19 -0
- package/dist/spawner.js +37 -0
- package/dist/sync.d.ts +20 -0
- package/dist/sync.js +55 -0
- package/package.json +46 -0
- package/starter/README.md +57 -0
- package/starter/client/index.ts +79 -0
- package/starter/move/Move.toml +9 -0
- package/starter/move/sources/hero.move +109 -0
- package/starter/scripts/deploy.ts +37 -0
- package/starters/godot/README.md +71 -0
- package/starters/godot/deploy.js +39 -0
- package/starters/godot/move/Move.toml +9 -0
- package/starters/godot/move/sources/hero.move +109 -0
- package/starters/godot/project.godot +29 -0
- package/starters/godot/scenes/main.tscn +22 -0
- package/starters/godot/scripts/game_manager.gd +14 -0
- package/starters/godot/scripts/player.gd +10 -0
- package/starters/godot/scripts/sui/hero_controller.gd +56 -0
- package/starters/godot/scripts/sui/sui_service.gd +62 -0
- package/starters/nextjs/.env.local.example +2 -0
- package/starters/nextjs/README.md +110 -0
- package/starters/nextjs/app/globals.css +3 -0
- package/starters/nextjs/app/layout.tsx +22 -0
- package/starters/nextjs/app/page.tsx +5 -0
- package/starters/nextjs/components/HeroGame.tsx +161 -0
- package/starters/nextjs/lib/sui-client.ts +22 -0
- package/starters/nextjs/move/Move.toml +9 -0
- package/starters/nextjs/move/sources/hero.move +109 -0
- package/starters/nextjs/next.config.js +4 -0
- package/starters/nextjs/package.json +30 -0
- package/starters/nextjs/postcss.config.js +6 -0
- package/starters/nextjs/scripts/deploy.ts +42 -0
- package/starters/nextjs/tailwind.config.js +12 -0
- package/starters/nextjs/tsconfig.json +39 -0
- package/starters/unity/Assets/Scenes/Main.unity +197 -0
- package/starters/unity/Assets/Scripts/Core/GameManager.cs +38 -0
- package/starters/unity/Assets/Scripts/Core/PlayerController.cs +37 -0
- package/starters/unity/Assets/Scripts/Sui/HeroManager.cs +99 -0
- package/starters/unity/Assets/Scripts/Sui/SuiManager.cs +94 -0
- package/starters/unity/README.md +72 -0
- package/starters/unity/deploy.js +39 -0
- package/starters/unity/move/Move.toml +9 -0
- package/starters/unity/move/sources/hero.move +109 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module sui_quest::hero {
|
|
2
|
+
use sui::object::{Self, UID};
|
|
3
|
+
use sui::tx_context::{Self, TxContext};
|
|
4
|
+
use sui::transfer;
|
|
5
|
+
use sui::event;
|
|
6
|
+
|
|
7
|
+
// --- Structs ---
|
|
8
|
+
|
|
9
|
+
struct Hero has key, store {
|
|
10
|
+
id: UID,
|
|
11
|
+
name: vector<u8>,
|
|
12
|
+
level: u64,
|
|
13
|
+
experience: u64,
|
|
14
|
+
hp: u64,
|
|
15
|
+
strength: u64,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
struct GameAdmin has key {
|
|
19
|
+
id: UID,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --- Events ---
|
|
23
|
+
|
|
24
|
+
struct HeroMinted has copy, drop {
|
|
25
|
+
id: address,
|
|
26
|
+
name: vector<u8>,
|
|
27
|
+
owner: address,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
struct AttackEvent has copy, drop {
|
|
31
|
+
hero_id: address,
|
|
32
|
+
damage_dealt: u64,
|
|
33
|
+
enemy_hp_remaining: u64, // simplified
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
struct LevelUpEvent has copy, drop {
|
|
37
|
+
hero_id: address,
|
|
38
|
+
new_level: u64,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// --- Initializer ---
|
|
42
|
+
|
|
43
|
+
fun init(ctx: &mut TxContext) {
|
|
44
|
+
let admin = GameAdmin { id: object::new(ctx) };
|
|
45
|
+
transfer::transfer(admin, tx_context::sender(ctx));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- Entry Functions ---
|
|
49
|
+
|
|
50
|
+
public entry fun mint_hero(
|
|
51
|
+
_admin: &GameAdmin, // Require admin to mint (simulate controlled spawning)
|
|
52
|
+
name: vector<u8>,
|
|
53
|
+
recipient: address,
|
|
54
|
+
ctx: &mut TxContext
|
|
55
|
+
) {
|
|
56
|
+
let id = object::new(ctx);
|
|
57
|
+
let hero_id = object::uid_to_address(&id);
|
|
58
|
+
|
|
59
|
+
let hero = Hero {
|
|
60
|
+
id,
|
|
61
|
+
name,
|
|
62
|
+
level: 1,
|
|
63
|
+
experience: 0,
|
|
64
|
+
hp: 100,
|
|
65
|
+
strength: 10,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
event::emit(HeroMinted {
|
|
69
|
+
id: hero_id,
|
|
70
|
+
name,
|
|
71
|
+
owner: recipient
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
transfer::public_transfer(hero, recipient);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public entry fun slay_boar(hero: &mut Hero, ctx: &mut TxContext) {
|
|
78
|
+
// Simulate combat logic
|
|
79
|
+
// In reality, you'd use on-chain randomness or a specific Monster object
|
|
80
|
+
|
|
81
|
+
// 1. Calculate Damage (Strength +/- random noise simulated)
|
|
82
|
+
let damage = hero.strength + 2;
|
|
83
|
+
|
|
84
|
+
// 2. Grant XP
|
|
85
|
+
hero.experience = hero.experience + 10;
|
|
86
|
+
|
|
87
|
+
// 3. Check Level Up
|
|
88
|
+
if (hero.experience >= 100) {
|
|
89
|
+
hero.level = hero.level + 1;
|
|
90
|
+
hero.experience = 0;
|
|
91
|
+
hero.strength = hero.strength + 5;
|
|
92
|
+
event::emit(LevelUpEvent {
|
|
93
|
+
hero_id: object::uid_to_address(&hero.id),
|
|
94
|
+
new_level: hero.level
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
event::emit(AttackEvent {
|
|
99
|
+
hero_id: object::uid_to_address(&hero.id),
|
|
100
|
+
damage_dealt: damage,
|
|
101
|
+
enemy_hp_remaining: 0
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --- Accessors (View Functions) ---
|
|
106
|
+
public fun get_level(hero: &Hero): u64 {
|
|
107
|
+
hero.level
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const moveDir = path.resolve(__dirname, '../move');
|
|
6
|
+
console.log(`Deploying from ${moveDir}...`);
|
|
7
|
+
|
|
8
|
+
console.log('Building Move Package...');
|
|
9
|
+
// This assumes the user has 'sui' CLI installed and in path.
|
|
10
|
+
// We capture the output JSON to parse the package ID.
|
|
11
|
+
try {
|
|
12
|
+
const output = execSync('sui client publish --gas-budget 100000000 --json', {
|
|
13
|
+
cwd: moveDir,
|
|
14
|
+
encoding: 'utf-8'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const result = JSON.parse(output);
|
|
18
|
+
const packageId = result.objectChanges.find((c: any) => c.type === 'published')?.packageId;
|
|
19
|
+
|
|
20
|
+
if (!packageId) {
|
|
21
|
+
console.error('Could not find package ID in publish output.');
|
|
22
|
+
console.log(output);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log('\nSUCCESS! Package Published.');
|
|
27
|
+
console.log('Package ID:', packageId);
|
|
28
|
+
|
|
29
|
+
// You would typically save this to a config file for the client to use.
|
|
30
|
+
console.log('\nCopy this Package ID to your client config!');
|
|
31
|
+
} catch (e: any) {
|
|
32
|
+
console.error('Deployment validation failed:', e.message);
|
|
33
|
+
console.log('Ensure you have sufficient gas and the SUI CLI configured for the active network.');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Godot 4 RPG Starter + Sui Game SDK
|
|
2
|
+
|
|
3
|
+
A Godot 4 starter kit designed to seamlessly integrate with the `sui-game-sdk`.
|
|
4
|
+
|
|
5
|
+
## 🎮 Features
|
|
6
|
+
- **Godot 4 Architecture**: Uses `Scenes` and `Scripts` in a standard project layout.
|
|
7
|
+
- **Sui Integration**:
|
|
8
|
+
- `SuiService`: Validates connection and handles RPC calls. Mimics `Session` and `Sync` logic.
|
|
9
|
+
- `HeroController`: Demonstrates `Spawner` logic for minting and state updates.
|
|
10
|
+
- **Sui Quest**: A sample implementation of the cross-platform hero game.
|
|
11
|
+
- **Independent**: Includes its own Move contract and deployment script.
|
|
12
|
+
|
|
13
|
+
## 🔥 Quick Start
|
|
14
|
+
|
|
15
|
+
### Prerequisites
|
|
16
|
+
- Godot 4.2+
|
|
17
|
+
- Node.js (for deploying the contract)
|
|
18
|
+
- Sui CLI installed and configured
|
|
19
|
+
|
|
20
|
+
### 1. Deploy the Move Contract
|
|
21
|
+
```bash
|
|
22
|
+
# From the Godot starter directory
|
|
23
|
+
node deploy.js
|
|
24
|
+
```
|
|
25
|
+
Copy the **Package ID** output from the deployment.
|
|
26
|
+
|
|
27
|
+
### 2. Configuration
|
|
28
|
+
1. Open `scripts/sui/hero_controller.gd`.
|
|
29
|
+
2. Update the `GAME_PACKAGE_ID` constant with your deployed contract ID.
|
|
30
|
+
```gdscript
|
|
31
|
+
const GAME_PACKAGE_ID = "0x..."
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. Open in Godot
|
|
35
|
+
1. Open Godot 4.
|
|
36
|
+
2. Click **Import** and navigate to this folder.
|
|
37
|
+
3. Select `project.godot`.
|
|
38
|
+
|
|
39
|
+
### 4. Play
|
|
40
|
+
1. Press **F5** (Play Main Scene).
|
|
41
|
+
2. Watch the Output / Debug Console.
|
|
42
|
+
3. Hook up the `mint_hero` and `slay_boar` functions to UI Buttons (not included in starter to keep it unopinionated).
|
|
43
|
+
|
|
44
|
+
## 🛠 SDK Mapping
|
|
45
|
+
|
|
46
|
+
| Concept | Godot Script | Description |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| **Session** | `SuiService` | Manages connection parameters |
|
|
49
|
+
| **Spawner** | `sui_service.spawn_asset` | Creates Move Call payloads to spawn assets |
|
|
50
|
+
| **Sync** | `sui_service.sync_state` | Synchronizes on-chain state to game objects |
|
|
51
|
+
|
|
52
|
+
## 📁 Project Structure
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
godot/
|
|
56
|
+
├── project.godot
|
|
57
|
+
├── scenes/
|
|
58
|
+
│ └── main.tscn
|
|
59
|
+
├── scripts/
|
|
60
|
+
│ ├── game_manager.gd
|
|
61
|
+
│ ├── player.gd
|
|
62
|
+
│ └── sui/
|
|
63
|
+
│ ├── sui_service.gd
|
|
64
|
+
│ └── hero_controller.gd
|
|
65
|
+
├── move/ # Move smart contract
|
|
66
|
+
│ └── sources/
|
|
67
|
+
│ └── hero.move
|
|
68
|
+
└── deploy.js # Deployment script
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> **Pro Tip**: Use this starter as a module. Copy the `scripts/sui` folder into your existing Godot game!
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const moveDir = path.resolve(__dirname, '../move');
|
|
8
|
+
console.log(`Deploying from ${moveDir}...`);
|
|
9
|
+
|
|
10
|
+
console.log('Building Move Package...');
|
|
11
|
+
try {
|
|
12
|
+
const output = execSync('sui client publish --gas-budget 100000000 --json', {
|
|
13
|
+
cwd: moveDir,
|
|
14
|
+
encoding: 'utf-8'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const result = JSON.parse(output);
|
|
18
|
+
const packageId = result.objectChanges.find((c) => c.type === 'published')?.packageId;
|
|
19
|
+
const adminCap = result.objectChanges.find((c) => c.objectType?.includes('GameAdmin'))?.objectId;
|
|
20
|
+
|
|
21
|
+
if (!packageId) {
|
|
22
|
+
console.error('Could not find package ID in publish output.');
|
|
23
|
+
console.log(output);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log('\nSUCCESS! Package Published.');
|
|
28
|
+
console.log('Package ID:', packageId);
|
|
29
|
+
if (adminCap) console.log('Admin Cap:', adminCap);
|
|
30
|
+
|
|
31
|
+
console.log('\nUpdate hero_controller.gd:');
|
|
32
|
+
console.log(`const GAME_PACKAGE_ID = "${packageId}"`);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error('Deployment failed:', e.message);
|
|
35
|
+
console.log('Ensure you have sufficient gas and the SUI CLI configured.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
main();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module sui_quest::hero {
|
|
2
|
+
use sui::object::{Self, UID};
|
|
3
|
+
use sui::tx_context::{Self, TxContext};
|
|
4
|
+
use sui::transfer;
|
|
5
|
+
use sui::event;
|
|
6
|
+
|
|
7
|
+
// --- Structs ---
|
|
8
|
+
|
|
9
|
+
struct Hero has key, store {
|
|
10
|
+
id: UID,
|
|
11
|
+
name: vector<u8>,
|
|
12
|
+
level: u64,
|
|
13
|
+
experience: u64,
|
|
14
|
+
hp: u64,
|
|
15
|
+
strength: u64,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
struct GameAdmin has key {
|
|
19
|
+
id: UID,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --- Events ---
|
|
23
|
+
|
|
24
|
+
struct HeroMinted has copy, drop {
|
|
25
|
+
id: address,
|
|
26
|
+
name: vector<u8>,
|
|
27
|
+
owner: address,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
struct AttackEvent has copy, drop {
|
|
31
|
+
hero_id: address,
|
|
32
|
+
damage_dealt: u64,
|
|
33
|
+
enemy_hp_remaining: u64, // simplified
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
struct LevelUpEvent has copy, drop {
|
|
37
|
+
hero_id: address,
|
|
38
|
+
new_level: u64,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// --- Initializer ---
|
|
42
|
+
|
|
43
|
+
fun init(ctx: &mut TxContext) {
|
|
44
|
+
let admin = GameAdmin { id: object::new(ctx) };
|
|
45
|
+
transfer::transfer(admin, tx_context::sender(ctx));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- Entry Functions ---
|
|
49
|
+
|
|
50
|
+
public entry fun mint_hero(
|
|
51
|
+
_admin: &GameAdmin, // Require admin to mint (simulate controlled spawning)
|
|
52
|
+
name: vector<u8>,
|
|
53
|
+
recipient: address,
|
|
54
|
+
ctx: &mut TxContext
|
|
55
|
+
) {
|
|
56
|
+
let id = object::new(ctx);
|
|
57
|
+
let hero_id = object::uid_to_address(&id);
|
|
58
|
+
|
|
59
|
+
let hero = Hero {
|
|
60
|
+
id,
|
|
61
|
+
name,
|
|
62
|
+
level: 1,
|
|
63
|
+
experience: 0,
|
|
64
|
+
hp: 100,
|
|
65
|
+
strength: 10,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
event::emit(HeroMinted {
|
|
69
|
+
id: hero_id,
|
|
70
|
+
name,
|
|
71
|
+
owner: recipient
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
transfer::public_transfer(hero, recipient);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public entry fun slay_boar(hero: &mut Hero, ctx: &mut TxContext) {
|
|
78
|
+
// Simulate combat logic
|
|
79
|
+
// In reality, you'd use on-chain randomness or a specific Monster object
|
|
80
|
+
|
|
81
|
+
// 1. Calculate Damage (Strength +/- random noise simulated)
|
|
82
|
+
let damage = hero.strength + 2;
|
|
83
|
+
|
|
84
|
+
// 2. Grant XP
|
|
85
|
+
hero.experience = hero.experience + 10;
|
|
86
|
+
|
|
87
|
+
// 3. Check Level Up
|
|
88
|
+
if (hero.experience >= 100) {
|
|
89
|
+
hero.level = hero.level + 1;
|
|
90
|
+
hero.experience = 0;
|
|
91
|
+
hero.strength = hero.strength + 5;
|
|
92
|
+
event::emit(LevelUpEvent {
|
|
93
|
+
hero_id: object::uid_to_address(&hero.id),
|
|
94
|
+
new_level: hero.level
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
event::emit(AttackEvent {
|
|
99
|
+
hero_id: object::uid_to_address(&hero.id),
|
|
100
|
+
damage_dealt: damage,
|
|
101
|
+
enemy_hp_remaining: 0
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --- Accessors (View Functions) ---
|
|
106
|
+
public fun get_level(hero: &Hero): u64 {
|
|
107
|
+
hero.level
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
; Engine configuration file.
|
|
2
|
+
; It's best edited using the editor UI and not directly,
|
|
3
|
+
; since the parameters that go here are not all obvious.
|
|
4
|
+
;
|
|
5
|
+
; Format:
|
|
6
|
+
; [section] ; section goes between []
|
|
7
|
+
; param=value ; assign values to parameters
|
|
8
|
+
|
|
9
|
+
config_version=5
|
|
10
|
+
|
|
11
|
+
[application]
|
|
12
|
+
|
|
13
|
+
config/name="Sui Godot RPG"
|
|
14
|
+
run/main_scene="res://scenes/main.tscn"
|
|
15
|
+
config/features=PackedStringArray("4.2", "Forward Plus")
|
|
16
|
+
config/icon="res://icon.svg"
|
|
17
|
+
|
|
18
|
+
[autoload]
|
|
19
|
+
|
|
20
|
+
GameManager="*res://scripts/game_manager.gd"
|
|
21
|
+
|
|
22
|
+
[display]
|
|
23
|
+
|
|
24
|
+
window/size/viewport_width=1152
|
|
25
|
+
window/size/viewport_height=648
|
|
26
|
+
|
|
27
|
+
[dotnet]
|
|
28
|
+
|
|
29
|
+
project/assembly_name="Sui Godot RPG"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[gd_scene load_steps=2 format=3 uid="uid://c8q4q8q4q8q4"]
|
|
2
|
+
|
|
3
|
+
[ext_resource type="Script" path="res://scripts/sui/hero_controller.gd" id="1_hero"]
|
|
4
|
+
|
|
5
|
+
[node name="Main" type="Node2D"]
|
|
6
|
+
|
|
7
|
+
[node name="UI" type="Control" parent="."]
|
|
8
|
+
layout_mode = 3
|
|
9
|
+
anchors_preset = 0
|
|
10
|
+
offset_right = 1152.0
|
|
11
|
+
offset_bottom = 648.0
|
|
12
|
+
|
|
13
|
+
[node name="Label" type="Label" parent="UI"]
|
|
14
|
+
layout_mode = 0
|
|
15
|
+
offset_left = 469.0
|
|
16
|
+
offset_top = 281.0
|
|
17
|
+
offset_right = 684.0
|
|
18
|
+
offset_bottom = 304.0
|
|
19
|
+
text = "Sui Godot Starter"
|
|
20
|
+
|
|
21
|
+
[node name="HeroController" type="Node" parent="."]
|
|
22
|
+
script = ExtResource("1_hero")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
extends Node
|
|
2
|
+
|
|
3
|
+
# HeroController.gd
|
|
4
|
+
# Logic for the Hero game
|
|
5
|
+
|
|
6
|
+
const GAME_PACKAGE_ID = "0x_REPLACE_WITH_PACKAGE_ID"
|
|
7
|
+
const HERO_MODULE = "hero"
|
|
8
|
+
# In a real game, this comes from the Session/Wallet
|
|
9
|
+
const PLAYER_ADDRESS = "0x_PLAYER_ADDRESS_HERE"
|
|
10
|
+
|
|
11
|
+
var sui_service: SuiService
|
|
12
|
+
var hero_id = null
|
|
13
|
+
|
|
14
|
+
func _ready():
|
|
15
|
+
# Instance the service manually if not autoloaded, or find it
|
|
16
|
+
sui_service = SuiService.new()
|
|
17
|
+
add_child(sui_service) # Add to tree to enable processing
|
|
18
|
+
|
|
19
|
+
sui_service.rpc_response.connect(_on_sui_response)
|
|
20
|
+
|
|
21
|
+
# Wait a bit or trigger via UI
|
|
22
|
+
print("HeroController ready. Call check_hero() to start.")
|
|
23
|
+
check_hero()
|
|
24
|
+
|
|
25
|
+
func check_hero():
|
|
26
|
+
print("Checking for Hero...")
|
|
27
|
+
sui_service.sync_state(PLAYER_ADDRESS)
|
|
28
|
+
|
|
29
|
+
func mint_hero():
|
|
30
|
+
print("Minting Hero...")
|
|
31
|
+
sui_service.spawn_asset(GAME_PACKAGE_ID, HERO_MODULE, "mint_hero", ["GodotWarrior"], PLAYER_ADDRESS)
|
|
32
|
+
|
|
33
|
+
func slay_boar():
|
|
34
|
+
if hero_id:
|
|
35
|
+
print("Slaying Boar...")
|
|
36
|
+
sui_service.spawn_asset(GAME_PACKAGE_ID, HERO_MODULE, "slay_boar", [hero_id], PLAYER_ADDRESS)
|
|
37
|
+
else:
|
|
38
|
+
print("No Hero to slay with!")
|
|
39
|
+
|
|
40
|
+
func _on_sui_response(_method, result, error):
|
|
41
|
+
if error:
|
|
42
|
+
print("RPC Error: ", error)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
# Parse for Hero
|
|
46
|
+
if result and typeof(result) == TYPE_DICTIONARY and "data" in result:
|
|
47
|
+
var found = false
|
|
48
|
+
for obj in result["data"]:
|
|
49
|
+
var type_str = obj.get("data", {}).get("type", "")
|
|
50
|
+
if "%s::%s::Hero" % [GAME_PACKAGE_ID, HERO_MODULE] in type_str:
|
|
51
|
+
hero_id = obj["data"]["objectId"]
|
|
52
|
+
print("Hero FOUND: ", hero_id)
|
|
53
|
+
found = true
|
|
54
|
+
break
|
|
55
|
+
if not found:
|
|
56
|
+
print("No hero found.")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
extends Node
|
|
2
|
+
|
|
3
|
+
# SuiService.gd - Autoload or instanced helper
|
|
4
|
+
class_name SuiService
|
|
5
|
+
|
|
6
|
+
const RPC_URL = "https://fullnode.testnet.sui.io"
|
|
7
|
+
var http_request: HTTPRequest
|
|
8
|
+
signal rpc_response(method, result, error)
|
|
9
|
+
|
|
10
|
+
func _ready():
|
|
11
|
+
http_request = HTTPRequest.new()
|
|
12
|
+
add_child(http_request)
|
|
13
|
+
http_request.request_completed.connect(_on_request_completed)
|
|
14
|
+
print("[SuiService] Ready.")
|
|
15
|
+
|
|
16
|
+
func get_owned_objects(address: String):
|
|
17
|
+
# Request objects with Type info
|
|
18
|
+
var method = "suix_getOwnedObjects"
|
|
19
|
+
var params = [address, { "options": { "showType": true } }]
|
|
20
|
+
send_rpc(method, params)
|
|
21
|
+
|
|
22
|
+
func construct_move_call(package: String, module: String, function: String, args: Array, sender: String):
|
|
23
|
+
return spawn_asset(package, module, function, args, sender)
|
|
24
|
+
|
|
25
|
+
# Spawns asset / executes move call (AssetSpawner concept from SDK)
|
|
26
|
+
func spawn_asset(package: String, module: String, function: String, args: Array, sender: String):
|
|
27
|
+
var payload = {
|
|
28
|
+
"target": "%s::%s::%s" % [package, module, function],
|
|
29
|
+
"arguments": args,
|
|
30
|
+
"sender": sender,
|
|
31
|
+
"type": "move_call"
|
|
32
|
+
}
|
|
33
|
+
print("[SuiService] Asset Spawn Payload: ", JSON.stringify(payload))
|
|
34
|
+
return payload
|
|
35
|
+
|
|
36
|
+
# Syncs game state (GameStateSync concept from SDK)
|
|
37
|
+
func sync_state(address: String):
|
|
38
|
+
var method = "suix_getOwnedObjects"
|
|
39
|
+
var params = [address, { "options": { "showType": true } }]
|
|
40
|
+
send_rpc(method, params)
|
|
41
|
+
|
|
42
|
+
func send_rpc(method: String, params: Array):
|
|
43
|
+
var body = JSON.stringify({
|
|
44
|
+
"jsonrpc": "2.0",
|
|
45
|
+
"id": 1,
|
|
46
|
+
"method": method,
|
|
47
|
+
"params": params
|
|
48
|
+
})
|
|
49
|
+
var headers = ["Content-Type: application/json"]
|
|
50
|
+
var err = http_request.request(RPC_URL, headers, HTTPClient.METHOD_POST, body)
|
|
51
|
+
if err != OK:
|
|
52
|
+
emit_signal("rpc_response", method, null, "HTTP Request Failed")
|
|
53
|
+
|
|
54
|
+
func _on_request_completed(_result, response_code, _headers, body):
|
|
55
|
+
if response_code == 200:
|
|
56
|
+
var json = JSON.parse_string(body.get_string_from_utf8())
|
|
57
|
+
if "error" in json:
|
|
58
|
+
emit_signal("rpc_response", "unknown", null, json["error"])
|
|
59
|
+
else:
|
|
60
|
+
emit_signal("rpc_response", "unknown", json["result"], null)
|
|
61
|
+
else:
|
|
62
|
+
emit_signal("rpc_response", "unknown", null, str(response_code))
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Next.js Game Starter + Sui Game SDK
|
|
2
|
+
|
|
3
|
+
A modern Next.js 14 web game that **directly uses** the `sui-game-sdk` package.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
- **Direct SDK Integration**: Uses actual `SessionManager`, `AssetSpawner`, and `GameStateSync` from sui-game-sdk
|
|
7
|
+
- **Modern Stack**: Next.js 14 App Router + TypeScript + Tailwind CSS
|
|
8
|
+
- **Real-time Events**: Live event streaming from the blockchain
|
|
9
|
+
- **Beautiful UI**: Glassmorphic design with gradients and animations
|
|
10
|
+
|
|
11
|
+
## 🚀 Quick Start
|
|
12
|
+
|
|
13
|
+
### 1. Deploy the Move Contract
|
|
14
|
+
```bash
|
|
15
|
+
npm install
|
|
16
|
+
npm run deploy
|
|
17
|
+
```
|
|
18
|
+
Copy the **Package ID** and **Admin Cap** from the output.
|
|
19
|
+
|
|
20
|
+
### 2. Configure Environment
|
|
21
|
+
Copy the example env file:
|
|
22
|
+
```bash
|
|
23
|
+
cp .env.local.example .env.local
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Edit `.env.local` and add your deployed contract details:
|
|
27
|
+
```env
|
|
28
|
+
NEXT_PUBLIC_PACKAGE_ID=0x... # Your deployed package ID
|
|
29
|
+
NEXT_PUBLIC_ADMIN_CAP=0x... # Your admin cap ID
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 3. Run Development Server
|
|
33
|
+
```bash
|
|
34
|
+
npm run dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
38
|
+
|
|
39
|
+
## 📦 How It Uses sui-game-sdk
|
|
40
|
+
|
|
41
|
+
This starter demonstrates **direct usage** of the SDK:
|
|
42
|
+
|
|
43
|
+
### Session Management
|
|
44
|
+
```typescript
|
|
45
|
+
import { SessionManager } from 'sui-game-sdk';
|
|
46
|
+
|
|
47
|
+
const sessionManager = new SessionManager(suiClient);
|
|
48
|
+
console.log(sessionManager.sessionAddress); // Auto-generated session wallet
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Asset Spawning
|
|
52
|
+
```typescript
|
|
53
|
+
import { AssetSpawner } from 'sui-game-sdk';
|
|
54
|
+
|
|
55
|
+
const spawner = new AssetSpawner(suiClient, {
|
|
56
|
+
packageId: PACKAGE_ID,
|
|
57
|
+
module: 'hero',
|
|
58
|
+
function: 'mint_hero',
|
|
59
|
+
adminCapId: ADMIN_CAP
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Game State Sync
|
|
64
|
+
```typescript
|
|
65
|
+
import { GameStateSync } from 'sui-game-sdk';
|
|
66
|
+
|
|
67
|
+
const sync = new GameStateSync(suiClient);
|
|
68
|
+
|
|
69
|
+
sync.subscribe('AttackEvent', (event) => {
|
|
70
|
+
console.log('Damage:', event.parsedJson.damage_dealt);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
sync.startPolling(PACKAGE_ID);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 🎮 Game Flow
|
|
77
|
+
|
|
78
|
+
1. **Session Created**: Automatic on page load
|
|
79
|
+
2. **Hero Check**: Queries blockchain for existing hero
|
|
80
|
+
3. **Mint Hero**: Creates a new hero NFT (requires admin cap)
|
|
81
|
+
4. **Slay Boar**: Executes on-chain action, earns XP
|
|
82
|
+
5. **Level Up**: Automatic when XP threshold reached
|
|
83
|
+
|
|
84
|
+
## 🏗️ Project Structure
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
nextjs/
|
|
88
|
+
├── app/
|
|
89
|
+
│ ├── layout.tsx # Root layout
|
|
90
|
+
│ ├── page.tsx # Main page
|
|
91
|
+
│ └── globals.css # Global styles
|
|
92
|
+
├── components/
|
|
93
|
+
│ └── HeroGame.tsx # Main game component
|
|
94
|
+
├── lib/
|
|
95
|
+
│ └── sui-client.ts # SDK initialization
|
|
96
|
+
└── package.json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 🔧 Build for Production
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm run build
|
|
103
|
+
npm start
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 📝 Notes
|
|
107
|
+
|
|
108
|
+
- This is a **client-side** app. Session keys are generated in the browser.
|
|
109
|
+
- For production, integrate a proper wallet adapter (e.g., Sui Wallet, Ethos).
|
|
110
|
+
- The SDK handles all blockchain interactions seamlessly.
|