@agent-os-lab/agent-game-sdk 0.1.2 → 0.1.4
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 +40 -10
- package/USAGE.md +333 -0
- package/package.json +3 -2
- package/src/office/core/types.ts +7 -0
- package/src/office/index.ts +1 -0
- package/src/office/layout/config.ts +76 -0
- package/src/office/layout/index.ts +2 -0
- package/src/office/layout/resolver.ts +978 -0
- package/src/office/mount.ts +6 -0
- package/src/office/react/AgentGameOfficeView.ts +25 -1
- package/src/office/renderers/three/agent-body-instancing.ts +1 -0
- package/src/office/renderers/three/agent-effect-instancing.ts +1 -0
- package/src/office/renderers/three/agent-layout.ts +12 -56
- package/src/office/renderers/three/mount.ts +8 -6
- package/src/office/renderers/three/scene.ts +256 -84
- package/src/runtime-client.ts +10 -0
package/src/office/mount.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
resolveOfficeSource,
|
|
4
4
|
} from "./core/source";
|
|
5
5
|
import { mergeAgentPresence, type GameRuntimeSubscription } from "../runtime-client";
|
|
6
|
+
import { resolveOfficeLayout } from "./layout";
|
|
6
7
|
import type {
|
|
7
8
|
AgentGameOfficeMountOptions,
|
|
8
9
|
AgentGameOfficeRenderer,
|
|
@@ -18,6 +19,7 @@ export async function mountAgentGameOffice(
|
|
|
18
19
|
options: AgentGameOfficeMountOptions,
|
|
19
20
|
): Promise<AgentGameOfficeController> {
|
|
20
21
|
const renderer = resolveRenderer(options.renderer ?? "three");
|
|
22
|
+
const officeLayout = resolveOfficeLayout(options.office);
|
|
21
23
|
const mutableSource = options.source.type === "snapshot"
|
|
22
24
|
? createSnapshotOfficeSource({ agents: options.source.agents })
|
|
23
25
|
: null;
|
|
@@ -38,6 +40,7 @@ export async function mountAgentGameOffice(
|
|
|
38
40
|
controller = await renderer.mount(container, latestSnapshot ?? emptySnapshot(), {
|
|
39
41
|
...options,
|
|
40
42
|
renderer: options.renderer ?? "three",
|
|
43
|
+
officeLayout,
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
if (options.source.type === "runtime" && runtimeSource) {
|
|
@@ -72,6 +75,9 @@ export async function mountAgentGameOffice(
|
|
|
72
75
|
focusAgent(agentId) {
|
|
73
76
|
controller?.focusAgent?.(agentId);
|
|
74
77
|
},
|
|
78
|
+
refreshAgents() {
|
|
79
|
+
runtimeSubscription?.refresh();
|
|
80
|
+
},
|
|
75
81
|
destroy() {
|
|
76
82
|
unsubscribe();
|
|
77
83
|
runtimeSubscription?.close();
|
|
@@ -9,15 +9,27 @@ import type {
|
|
|
9
9
|
export type AgentGameOfficeViewProps = AgentGameOfficeMountOptions & {
|
|
10
10
|
className?: string;
|
|
11
11
|
style?: React.CSSProperties;
|
|
12
|
+
onReady?: (controller: AgentGameOfficeController) => void;
|
|
13
|
+
onDestroy?: () => void;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
export function AgentGameOfficeView({
|
|
15
17
|
className,
|
|
18
|
+
onDestroy,
|
|
19
|
+
onReady,
|
|
16
20
|
style,
|
|
17
21
|
...options
|
|
18
22
|
}: AgentGameOfficeViewProps) {
|
|
19
23
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
20
24
|
const viewRef = useRef<AgentGameOfficeController | null>(null);
|
|
25
|
+
const onDestroyRef = useRef(onDestroy);
|
|
26
|
+
const onReadyRef = useRef(onReady);
|
|
27
|
+
const officeConfigKey = createOfficeConfigKey(options.office);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
onReadyRef.current = onReady;
|
|
31
|
+
onDestroyRef.current = onDestroy;
|
|
32
|
+
}, [onDestroy, onReady]);
|
|
21
33
|
|
|
22
34
|
useEffect(() => {
|
|
23
35
|
const container = containerRef.current;
|
|
@@ -32,14 +44,16 @@ export function AgentGameOfficeView({
|
|
|
32
44
|
return;
|
|
33
45
|
}
|
|
34
46
|
viewRef.current = view;
|
|
47
|
+
onReadyRef.current?.(view);
|
|
35
48
|
});
|
|
36
49
|
|
|
37
50
|
return () => {
|
|
38
51
|
disposed = true;
|
|
39
52
|
viewRef.current?.destroy();
|
|
40
53
|
viewRef.current = null;
|
|
54
|
+
onDestroyRef.current?.();
|
|
41
55
|
};
|
|
42
|
-
}, [options.renderer, options.source]);
|
|
56
|
+
}, [options.renderer, options.source, officeConfigKey]);
|
|
43
57
|
|
|
44
58
|
useEffect(() => {
|
|
45
59
|
viewRef.current?.focusAgent(options.focusedAgentId ?? null);
|
|
@@ -56,3 +70,13 @@ export function AgentGameOfficeView({
|
|
|
56
70
|
},
|
|
57
71
|
});
|
|
58
72
|
}
|
|
73
|
+
|
|
74
|
+
function createOfficeConfigKey(office: AgentGameOfficeMountOptions["office"]): string {
|
|
75
|
+
if (!office) {
|
|
76
|
+
return "default";
|
|
77
|
+
}
|
|
78
|
+
return JSON.stringify(office.rooms.map((room) => ({
|
|
79
|
+
type: room.type,
|
|
80
|
+
capacity: room.capacity ?? null,
|
|
81
|
+
})));
|
|
82
|
+
}
|
|
@@ -2,71 +2,27 @@ import * as THREE from "three";
|
|
|
2
2
|
|
|
3
3
|
import type { AgentGameOfficeAgent } from "../../core/types";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
LOUNGE_CENTER,
|
|
9
|
-
MEETING_SEATS,
|
|
10
|
-
OFFICE_LAYOUT_SCALE,
|
|
11
|
-
scaleOfficeX,
|
|
12
|
-
scaleOfficeZ,
|
|
13
|
-
} from "./scene";
|
|
5
|
+
resolveOfficeAgentAnchor,
|
|
6
|
+
type ResolvedOfficeLayout,
|
|
7
|
+
} from "../../layout";
|
|
14
8
|
|
|
15
9
|
export function resolveAgentPosition(
|
|
16
10
|
agent: AgentGameOfficeAgent,
|
|
17
11
|
index: number,
|
|
18
12
|
totalAgents: number,
|
|
13
|
+
layout: ResolvedOfficeLayout,
|
|
19
14
|
): THREE.Vector3 {
|
|
20
15
|
if (agent.targetPosition) {
|
|
21
16
|
return new THREE.Vector3(agent.targetPosition.x, 0, agent.targetPosition.z);
|
|
22
17
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
0,
|
|
28
|
-
DESK_SEATS[index % DESK_SEATS.length]!.z,
|
|
29
|
-
);
|
|
30
|
-
case "meeting-room":
|
|
31
|
-
return new THREE.Vector3(
|
|
32
|
-
MEETING_SEATS[index % MEETING_SEATS.length]!.x,
|
|
33
|
-
0,
|
|
34
|
-
MEETING_SEATS[index % MEETING_SEATS.length]!.z,
|
|
35
|
-
);
|
|
36
|
-
case "lounge":
|
|
37
|
-
return new THREE.Vector3(
|
|
38
|
-
LOUNGE_CENTER.x - 1.7 * OFFICE_LAYOUT_SCALE + (index % 4) * 1.15 * OFFICE_LAYOUT_SCALE,
|
|
39
|
-
0,
|
|
40
|
-
LOUNGE_CENTER.z - 0.8 * OFFICE_LAYOUT_SCALE + Math.floor(index / 4) * 0.95 * OFFICE_LAYOUT_SCALE,
|
|
41
|
-
);
|
|
42
|
-
case "pantry":
|
|
43
|
-
return new THREE.Vector3(scaleOfficeX(-9.2), 0, scaleOfficeZ(5.2 + index * 0.4));
|
|
44
|
-
case "review-area": {
|
|
45
|
-
const angle = (index / Math.max(totalAgents, 1)) * Math.PI * 2;
|
|
46
|
-
return new THREE.Vector3(
|
|
47
|
-
scaleOfficeX(0.5) + Math.cos(angle) * 2.2 * OFFICE_LAYOUT_SCALE,
|
|
48
|
-
0,
|
|
49
|
-
scaleOfficeZ(4) + Math.sin(angle) * 1.4 * OFFICE_LAYOUT_SCALE,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
case "offstage":
|
|
53
|
-
return new THREE.Vector3(scaleOfficeX(-13), 0, scaleOfficeZ(8));
|
|
54
|
-
}
|
|
18
|
+
void index;
|
|
19
|
+
void totalAgents;
|
|
20
|
+
const anchor = resolveOfficeAgentAnchor(layout, agent);
|
|
21
|
+
return new THREE.Vector3(anchor.position.x, 0, anchor.position.z);
|
|
55
22
|
}
|
|
56
23
|
|
|
57
|
-
export function resolveAgentFacingTarget(agent: AgentGameOfficeAgent): THREE.Vector3 {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
case "meeting-room":
|
|
62
|
-
return new THREE.Vector3(LARGE_MEETING_X, 0, LARGE_MEETING_Z);
|
|
63
|
-
case "lounge":
|
|
64
|
-
return new THREE.Vector3(LOUNGE_CENTER.x, 0, LOUNGE_CENTER.z);
|
|
65
|
-
case "review-area":
|
|
66
|
-
return new THREE.Vector3(scaleOfficeX(0.5), 0, scaleOfficeZ(3.05));
|
|
67
|
-
case "pantry":
|
|
68
|
-
return new THREE.Vector3(scaleOfficeX(-9.2), 0, scaleOfficeZ(5.2));
|
|
69
|
-
case "offstage":
|
|
70
|
-
return new THREE.Vector3(scaleOfficeX(-13), 0, scaleOfficeZ(8));
|
|
71
|
-
}
|
|
24
|
+
export function resolveAgentFacingTarget(agent: AgentGameOfficeAgent, layout: ResolvedOfficeLayout): THREE.Vector3 {
|
|
25
|
+
const anchor = resolveOfficeAgentAnchor(layout, agent);
|
|
26
|
+
const facing = anchor.facing ?? anchor.position;
|
|
27
|
+
return new THREE.Vector3(facing.x, 0, facing.z);
|
|
72
28
|
}
|
|
@@ -32,6 +32,8 @@ type AgentMeshRecord = {
|
|
|
32
32
|
|
|
33
33
|
export const THREE_RENDERER_PIXEL_RATIO_LIMIT = 1.5;
|
|
34
34
|
export const THREE_RENDERER_TARGET_FPS = 30;
|
|
35
|
+
export const THREE_RENDERER_MIN_CAMERA_DISTANCE = 8;
|
|
36
|
+
export const THREE_RENDERER_MAX_CAMERA_DISTANCE = 150;
|
|
35
37
|
|
|
36
38
|
export function createOfficeMouseButtons(): OrbitControls["mouseButtons"] {
|
|
37
39
|
return {
|
|
@@ -89,8 +91,8 @@ export function mountThreeAgentGameOffice(
|
|
|
89
91
|
controls.enableRotate = true;
|
|
90
92
|
controls.enableZoom = true;
|
|
91
93
|
controls.mouseButtons = createOfficeMouseButtons();
|
|
92
|
-
controls.minDistance =
|
|
93
|
-
controls.maxDistance =
|
|
94
|
+
controls.minDistance = THREE_RENDERER_MIN_CAMERA_DISTANCE;
|
|
95
|
+
controls.maxDistance = THREE_RENDERER_MAX_CAMERA_DISTANCE;
|
|
94
96
|
controls.maxPolarAngle = Math.PI * 0.48;
|
|
95
97
|
controls.addEventListener("change", () => {
|
|
96
98
|
labelsDirty = true;
|
|
@@ -112,7 +114,7 @@ export function mountThreeAgentGameOffice(
|
|
|
112
114
|
container.appendChild(root);
|
|
113
115
|
|
|
114
116
|
addLights(scene);
|
|
115
|
-
buildOfficeScene(scene);
|
|
117
|
+
buildOfficeScene(scene, _options.officeLayout);
|
|
116
118
|
const agentBodyLayer = createAgentBodyInstancedLayer(scene);
|
|
117
119
|
const agentEffectLayer = createAgentEffectInstancedLayer(scene);
|
|
118
120
|
|
|
@@ -135,7 +137,7 @@ export function mountThreeAgentGameOffice(
|
|
|
135
137
|
snapshot.agents.forEach((agent, index) => {
|
|
136
138
|
const record = agents.get(agent.id) ?? createAgentRecord(agent, index);
|
|
137
139
|
agents.set(agent.id, record);
|
|
138
|
-
const target = resolveAgentPosition(agent, index, snapshot.agents.length);
|
|
140
|
+
const target = resolveAgentPosition(agent, index, snapshot.agents.length, _options.officeLayout);
|
|
139
141
|
record.agent = agent;
|
|
140
142
|
record.renderIndex = index;
|
|
141
143
|
record.target.copy(target);
|
|
@@ -148,7 +150,7 @@ export function mountThreeAgentGameOffice(
|
|
|
148
150
|
|
|
149
151
|
function createAgentRecord(agent: AgentGameOfficeAgent, index: number): AgentMeshRecord {
|
|
150
152
|
const mesh = createAgentMesh(agent, index);
|
|
151
|
-
const position = resolveAgentPosition(agent, index, initialSnapshot.agents.length);
|
|
153
|
+
const position = resolveAgentPosition(agent, index, initialSnapshot.agents.length, _options.officeLayout);
|
|
152
154
|
mesh.group.position.copy(position);
|
|
153
155
|
scene.add(mesh.group);
|
|
154
156
|
|
|
@@ -180,7 +182,7 @@ export function mountThreeAgentGameOffice(
|
|
|
180
182
|
mesh: record.mesh,
|
|
181
183
|
agent: record.agent,
|
|
182
184
|
target: record.target,
|
|
183
|
-
facingTarget: resolveAgentFacingTarget(record.agent),
|
|
185
|
+
facingTarget: resolveAgentFacingTarget(record.agent, _options.officeLayout),
|
|
184
186
|
elapsedSeconds,
|
|
185
187
|
deltaSeconds,
|
|
186
188
|
});
|