Let a game hand values to the parameters of a sound FX #311
A sound FX played the same every time: its numbers were settings of the nodes. Now the Input declares parameters with a default, every number of Gain, Pitch and EQ is an input they can feed, and a Play sound hands in values for them. - Input declares number parameters in the inspector, each with a Default (DeclaredPort.default, SetPortDefaultCommand). They are outputs of the Input. Input and Output count as terminals now, one of each per graph. - Gain, Pitch and EQ take their numbers at inputs under the keys the settings had, so saved graphs sound as before without a migration. Each value is held to its range however it arrives. - The compiler gives an FxPlan with the parameters still open. fxChainOf fills them in when a voice starts. The player still only sees an FxChain. - New behavior node Sound FX parameters: names an FX and picks its parameters like a Variables node picks variables. Each picked one is a number input that starts at the FX's default. Its output has the new port type fxparams, which only fits Play sound's fx params. - Play sound warns when the parameters name another FX than the one it plays. Names the FX does not know are left out. - Renaming a parameter at the Input renames it in every Sound FX parameters node naming that FX, edges and values included. - The Sound FX tab has a number field per parameter in its header. Play uses those values, they are never written into the asset. - A value counts from the start of a voice. Changing a playing voice is #315, a Mix node is #316. Tests: - soundfx.test.ts 22 (+11): parameters in the plan, held values, a 0-by-default stage, EQ band, save with defaults, wrong port types, default command with undo, Sound FX parameters into Play sound, defaults of picked ones, node problems, mismatch, rename with undo - node-catalog 368 (+3): Play sound fx params, Sound FX parameters - e2e 34: ALL PASSED - 28 checks (+2: header field plays depth 5, a Sound FX parameters node gives the game's voice depth 7) - Gate on melektaus-atx: typecheck, lint, prettier clean, 240 test files, 3636 tests green - Counter-check: with fxChainOf ignoring the values and the rename not followed, 5 of the new tests fail. - Browser probe: header fields, inspector Default and the picked parameter looked at in two screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
de14f0f9e2
commit
183f356c99
25 files changed
+785
-99
No files matched your search
@@ -28,10 +28,29 @@ const ON_START = 'OnStart';
|
||||
export const SHADER_FUNCTION_INPUT = 'ShaderFunctionInput';
|
||||
export const SHADER_FUNCTION_OUTPUT = 'ShaderFunctionOutput';
|
||||
export const SHADER_CALL = 'ShaderCall';
|
||||
/** The two ends of a sound FX graph and the behavior node naming its parameters (#311). */
|
||||
export const SOUND_FX_INPUT = 'SoundFxInput';
|
||||
export const SOUND_FX_OUTPUT = 'SoundFxOutput';
|
||||
export const SOUND_FX_PARAMETERS = 'SoundFxParameters';
|
||||
const PLAY_SOUND = 'PlaySound';
|
||||
|
||||
export const BEHAVIOR_PARAM = 'behavior';
|
||||
export const FUNCTION_PARAM = 'function';
|
||||
export const VARIABLES_PARAM = 'variablesAsset';
|
||||
export const SOUND_FX_PARAM = 'soundFxAsset';
|
||||
|
||||
/** Whose parameters a `Sound FX parameters` node sets. */
|
||||
export const SOUND_FX_SETTING: PropertyDef = {
|
||||
key: SOUND_FX_PARAM,
|
||||
label: 'Sound FX',
|
||||
kind: 'asset',
|
||||
default: '',
|
||||
assetTypes: ['soundfx'],
|
||||
};
|
||||
|
||||
export function soundFxSourceOf(params: PropertyBag): string {
|
||||
return textOf(params[SOUND_FX_PARAM] ?? '');
|
||||
}
|
||||
|
||||
/** Whose declarations a `Variables` node reads; empty is the behavior the node sits in. */
|
||||
export const VARIABLES_SETTING: PropertyDef = {
|
||||
@@ -71,6 +90,11 @@ export function declaresPropertyPorts(type: string): boolean {
|
||||
return type === PROPERTIES;
|
||||
}
|
||||
|
||||
/** Nodes whose declared ports are parameters of the sound FX they name. */
|
||||
export function declaresSoundFxPorts(type: string): boolean {
|
||||
return type === SOUND_FX_PARAMETERS;
|
||||
}
|
||||
|
||||
/** Identifier-like, and not `exec`: that key belongs to the fixed flow port of every terminal node. */
|
||||
export function isValidPortName(name: string): boolean {
|
||||
return isValidFieldName(name) && name !== 'exec';
|
||||
@@ -127,13 +151,15 @@ export function isTerminalType(type: string): boolean {
|
||||
type === BEHAVIOR_START ||
|
||||
type === BEHAVIOR_END ||
|
||||
type === SHADER_FUNCTION_INPUT ||
|
||||
type === SHADER_FUNCTION_OUTPUT
|
||||
type === SHADER_FUNCTION_OUTPUT ||
|
||||
type === SOUND_FX_INPUT ||
|
||||
type === SOUND_FX_OUTPUT
|
||||
);
|
||||
}
|
||||
|
||||
/** The terminal whose ports a caller passes in, rather than takes out. */
|
||||
export function isEntryTerminal(type: string): boolean {
|
||||
return type === BEHAVIOR_START || type === SHADER_FUNCTION_INPUT;
|
||||
return type === BEHAVIOR_START || type === SHADER_FUNCTION_INPUT || type === SOUND_FX_INPUT;
|
||||
}
|
||||
|
||||
export function isTerminalNode(node: GraphNode): boolean {
|
||||
@@ -354,6 +380,7 @@ export function ghostProblem(node: GraphNode, graph: Graph, def: NodeDef): strin
|
||||
export function callProblem(node: GraphNode, lookup: GraphLookup, ownerId = ''): string | null {
|
||||
if (node.type === ON_BEHAVIOR) return listenProblem(node, lookup, ownerId);
|
||||
if (node.type === SHADER_CALL) return functionCallProblem(node, lookup, ownerId);
|
||||
if (node.type === SOUND_FX_PARAMETERS) return soundFxParametersProblem(node, lookup);
|
||||
if (node.type !== RUN_BEHAVIOR) return lerpProblem(node, lookup);
|
||||
const id = textOf(node.params[BEHAVIOR_PARAM] ?? '');
|
||||
if (!id) return 'No behavior picked';
|
||||
@@ -398,6 +425,27 @@ function functionCallProblem(node: GraphNode, lookup: GraphLookup, ownerId: stri
|
||||
return null;
|
||||
}
|
||||
|
||||
function soundFxParametersProblem(node: GraphNode, lookup: GraphLookup): string | null {
|
||||
const id = soundFxSourceOf(node.params);
|
||||
if (!id) return 'No sound FX picked';
|
||||
return lookup.soundFx(id) ? null : 'That sound FX is gone';
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Play sound` whose parameters come from a node naming another sound FX than the one it plays:
|
||||
* the names that FX does not know are left out, so the values would go nowhere. Only said where both
|
||||
* are picked in place – one coming over an edge may be either.
|
||||
*/
|
||||
export function soundFxParamsMismatch(node: GraphNode, graph: Graph): string | null {
|
||||
if (node.type !== PLAY_SOUND || edgesInto(graph, node.id, 'fx').length > 0) return null;
|
||||
const played = textOf(node.params.fx ?? '');
|
||||
const edge = edgesInto(graph, node.id, 'fxParams')[0];
|
||||
const from = edge && findNode(graph, edge.from.node);
|
||||
if (!played || from?.type !== SOUND_FX_PARAMETERS) return null;
|
||||
const named = soundFxSourceOf(from.params);
|
||||
return named && named !== played ? 'The parameters are for another sound FX' : null;
|
||||
}
|
||||
|
||||
/** The ids the nodes of one type in that graph name in `param`, picked or not found alike. */
|
||||
function namedBy(graph: Graph, type: string, param: string): string[] {
|
||||
return graph.nodes
|
||||
|
||||
Reference in new issue
Block a user