Skip to content

Commit 7093248

Browse files
authored
Inspector v2: Add scene explorer command for playing/stopping sprite animations (#17004)
This change adds a Play/Stop button to the property pane for sprites (for parity), and also adds a scene explorer command as a shortcut: <img width="504" height="351" alt="image" src="https://github.com/user-attachments/assets/91e27bc2-07f3-4c43-a970-d3b93b49d6b6" />
1 parent d9182ff commit 7093248

File tree

3 files changed

+60
-1
lines changed

3 files changed

+60
-1
lines changed

packages/dev/inspector-v2/src/components/properties/sprites/spriteAnimationProperties.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,26 @@ import type { FunctionComponent } from "react";
22

33
import type { Sprite } from "core/index";
44

5+
import { useCallback } from "react";
6+
7+
import { PlayFilled, StopFilled } from "@fluentui/react-icons";
8+
import { ButtonLine } from "shared-ui-components/fluent/hoc/buttonLine";
59
import { NumberInputPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/inputPropertyLine";
610
import { SwitchPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/switchPropertyLine";
11+
import { useInterceptObservable } from "../../../hooks/instrumentationHooks";
12+
import { useObservableState } from "../../../hooks/observableHooks";
713
import { BoundProperty } from "../boundProperty";
814

915
export const SpriteAnimationProperties: FunctionComponent<{ sprite: Sprite }> = (props) => {
1016
const { sprite } = props;
1117

18+
const animationStarted = useObservableState(
19+
useCallback(() => sprite.animationStarted, [sprite]),
20+
useInterceptObservable("function", sprite, "playAnimation"),
21+
useInterceptObservable("function", sprite, "stopAnimation"),
22+
useInterceptObservable("function", sprite, "_animate")
23+
);
24+
1225
return (
1326
<>
1427
<BoundProperty
@@ -31,6 +44,17 @@ export const SpriteAnimationProperties: FunctionComponent<{ sprite: Sprite }> =
3144
target={sprite}
3245
propertyKey="delay"
3346
/>
47+
<ButtonLine
48+
label={animationStarted ? "Stop Animation" : "Start Animation"}
49+
icon={animationStarted ? StopFilled : PlayFilled}
50+
onClick={() => {
51+
if (animationStarted) {
52+
sprite.stopAnimation();
53+
} else {
54+
sprite.playAnimation(sprite.fromIndex, sprite.toIndex, sprite.loopAnimation, sprite.delay);
55+
}
56+
}}
57+
/>
3458
</>
3559
);
3660
};

packages/dev/inspector-v2/src/services/panes/scene/spriteManagerExplorerService.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ServiceDefinition } from "../../../modularity/serviceDefinition";
33
import type { ISceneContext } from "../../sceneContext";
44
import type { ISceneExplorerService } from "./sceneExplorerService";
55

6-
import { LayerDiagonalPersonRegular, PersonSquareRegular } from "@fluentui/react-icons";
6+
import { LayerDiagonalPersonRegular, PersonSquareRegular, PlayFilled, StopFilled } from "@fluentui/react-icons";
77

88
import { Observable } from "core/Misc";
99
import { InterceptProperty } from "../../../instrumentation/propertyInstrumentation";
@@ -12,6 +12,7 @@ import { DefaultSectionsOrder } from "./defaultSectionsMetadata";
1212
import { SceneExplorerServiceIdentity } from "./sceneExplorerService";
1313

1414
import "core/Sprites/spriteSceneComponent";
15+
import { InterceptFunction } from "../../../instrumentation/functionInstrumentation";
1516

1617
function IsSpriteManager(entity: unknown): entity is ISpriteManager {
1718
return (entity as ISpriteManager).sprites !== undefined;
@@ -60,9 +61,42 @@ export const SpriteManagerExplorerServiceDefinition: ServiceDefinition<[], [ISce
6061
getEntityRemovedObservables: () => [scene.onSpriteManagerRemovedObservable],
6162
});
6263

64+
const spritePlayStopCommandRegistration = sceneExplorerService.addCommand({
65+
predicate: (entity: unknown) => IsSprite(entity),
66+
getCommand: (sprite) => {
67+
const onChangeObservable = new Observable<void>();
68+
const playHook = InterceptFunction(sprite, "playAnimation", {
69+
afterCall: () => onChangeObservable.notifyObservers(),
70+
});
71+
const stopHook = InterceptFunction(sprite, "stopAnimation", {
72+
afterCall: () => onChangeObservable.notifyObservers(),
73+
});
74+
const animateHook = InterceptFunction(sprite, "_animate", {
75+
afterCall: () => onChangeObservable.notifyObservers(),
76+
});
77+
78+
return {
79+
type: "action",
80+
get displayName() {
81+
return `${sprite.animationStarted ? "Stop" : "Play"} Animation`;
82+
},
83+
icon: () => (sprite.animationStarted ? <StopFilled /> : <PlayFilled />),
84+
execute: () => (sprite.animationStarted ? sprite.stopAnimation() : sprite.playAnimation(sprite.fromIndex, sprite.toIndex, sprite.loopAnimation, sprite.delay)),
85+
onChange: onChangeObservable,
86+
dispose: () => {
87+
playHook.dispose();
88+
stopHook.dispose();
89+
animateHook.dispose();
90+
onChangeObservable.clear();
91+
},
92+
};
93+
},
94+
});
95+
6396
return {
6497
dispose: () => {
6598
sectionRegistration.dispose();
99+
spritePlayStopCommandRegistration.dispose();
66100
},
67101
};
68102
},

packages/dev/inspector-v2/test/app/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// NOTE: This app is an easy place to test Inspector v2.
22
// Additionally, here are some PGs that are helpful for testing specific features:
33
// Frame graphs: http://localhost:1338/?inspectorv2#9YU4C5#23
4+
// Sprites: https://localhost:1338/?inspectorv2#YCY2IL#4
45

56
import HavokPhysics from "@babylonjs/havok";
67
import type { Nullable } from "core/types";

0 commit comments

Comments
 (0)