🇮🇹 Italiano (Italian)
🇮🇹 Italiano (Italian)
Aspetto
🇮🇹 Italiano (Italian)
🇮🇹 Italiano (Italian)
Aspetto
Questa pagina si applica alla versione:
1.21.4
Questa pagina si applica alla versione:
1.21.4
INFO
Questa pagina è basata sulle pagine Riprodurre Suoni e Creare Suoni Personalizzati!
SoundEvents
Come abbiamo imparato nella pagina Usare i Suoni, è preferibile usare i SoundEvent
sul lato logico server, anche se è un po' controintuitivo. Dopo tutto, è il client che deve gestire il suono, trasmesso alle tue cuffie, no?
Questo modo di pensare è corretto. Tecnicamente è il lato client che dovrebbe gestire l'audio. Tuttavia, per la semplice riproduzione di un SoundEvent
, il lato server ha preparato un passo enorme che potrebbe non essere ovvio a prima vista. Quali client dovrebbero poter sentire quel suono?
Usare il suono sul lato logico server risolverà il problema della trasmissione dei SoundEvent
. Per farla semplice, a ogni client (ClientPlayerEntity
) nel raggio tracciato viene inviato un pacchetto di rete per riprodurre un certo suono specifico. L'evento audio viene praticamente trasmesso dal lato logico server, a ogni client partecipante, senza che tu te ne debba preoccupare. Il suono è riprodotto una volta sola, con i valori di volume e tono specificati.
Ma, e se questo non bastasse? E se il suono dovesse essere riprodotto in loop? E se dovesse cambiare il volume e il tono in maniera dinamica durante la riproduzione? Tutto questo magari in base a valori provenienti da cose come Entities
o BlockEntities
?
La semplice strategia di usare i SoundEvent
sul lato logico server non basta in questo caso.
Creeremo un nuovo audio che andrà in loop per un altro SoundEvent
. Se riesci a trovare un file audio che già va in loop perfettamente, ti basta seguire i passaggi da Creare Suoni Personalizzati. Se il suono non va ancora in loop perfettamente, dobbiamo prepararlo affinché faccia ciò.
Ripeto, la maggior parte delle DAW (Workstation Audio Digitali) moderne dovrebbero essere in grado di fare questo, ma preferisco usare Reaper se la modifica dell'audio è un po' più avanzata.
Il suono da cui partiamo proverrà da un motore.
Carichiamo il file nella DAW che abbiamo scelto.
Possiamo sentire e notare che il motore viene avviato all'inizio e interrotto alla fine, il che non è ottimo per i suoni in loop. Tagliamo via quelle parti e regoliamo le maniglie della selezione del tempo perché corrispondano con la nuova lunghezza. Attiva anche la modalità Toggle Repeat
così che l'audio vada in loop mentre lo regoliamo.
Se ascoltiamo con attenzione, c'è un segnale acustico sullo sfondo, che potrebbe provenire dalla macchina. Penso che questo non suonerebbe bene nel gioco, per cui proviamo a toglierlo.
È un suono costante, la cui frequenza rimane al di sopra della lunghezza dell'audio. Per questo un semplice filtro EQ dovrebbe essere sufficiente per filtrarlo.
Reaper include già un filtro EQ chiamato "ReaEQ". Questo potrebbe essere localizzato in altri posti o avere nomi diversi in altre DAW, ma l'utilizzo dell'EQ è uno standard nella maggior parte delle DAW odierne.
Se sei sicuro che la tua DAW non disponga di un filtro EQ, cerca alternative VST gratuite online che tu possa installare sulla DAW di tua scelta.
In Reaper usa la Finestra Effetti per aggiungere l'effetto audio "ReaEQ", o altri EQ.
Riproducendo adesso l'audio, tenendo la finestra del filtro EQ aperta, il filtro EQ mostrerà l'audio in entrata sul display. Lì si notano tante protuberanze.
Se non sei un ingegnere audio allenato, questa parte è abbastanza sperimentazione, andando a tentativi. C'è una protuberanza piuttosto notevole tra i nodi 2 e 3. Muoviamo i nodi in modo che si abbassi la frequenza solo per quella parte.
Inoltre altri effetti si possono ottenere con un semplice filtro EQ. Per esempio, tagliare le frequenze alte e/o basse può dare l'impressione di suoni trasmessi via audio.
Puoi anche sovrapporre più file audio, cambiare il tono, aggiungere del riverbero o usare effetti di suono più elaborati come "bit-crusher". La progettazione di suoni può essere divertente, soprattutto se trovi delle belle variazioni di suono dei tuoi audio per caso. La sperimentazione è la chiave, e magari alla fine il tuo suono sarà meglio di come è cominciato.
Andremo avanti con il filtro EQ soltanto, poiché l'abbiamo usato per togliere la frequenza problematica.
Confrontiamo il file originale con la versione ritoccata.
Nel suono originale puoi distinguere un segnale acustico proveniente forse da un elemento elettrico del motore.
Con il filtro EQ siamo riusciti a toglierlo quasi del tutto. Sicuramente è più piacevole da sentire.
Se lasciassimo che il suono si riproduca fino alla fine e ricominci dall'inizio, possiamo chiaramente sentire quando avviene la transizione. L'obiettivo è liberarci di questo applicando una transizione fluida.
Inizia tagliando via una parte dal fondo, grande quanto vuoi che sia lunga la transizione, e posizionala all'inizio di una nuova traccia audio. In Reaper puoi dividere l'audio semplicemente muovendo il cursore dalla posizione del taglio e premendo S.
Potresti dover copiare l'effetto audio EQ della prima traccia audio anche sulla seconda.
Ora lascia che la parte in fondo della nuova traccia si dissolva in chiusura, e che l'inizio della prima traccia audio si dissolva in apertura.
Esporta l'audio con le due tracce, ma con un solo canale audio (Mono) e crea un nuovo SoundEvent
per quel file .ogg
nella tua mod. Se non sei certo di come farlo, controlla la pagina Creare Suoni Personalizzati.
Ciò che abbiamo chiamato ENGINE_LOOP
è il suono in loop del motore completato per il SoundEvent
.
SoundInstance
Per riprodurre suoni sul lato client, è necessaria una SoundInstance
. Tuttavia usano comunque un SoundEvent
.
Se vuoi solo riprodurre qualcosa come un clic su un elemento dell'interfaccia grafica, esiste già la classe PositionedSoundInstance
.
Tieni a mente che questo sarà riprodotto solo sul client che ha eseguito questa parte del codice in particolare.
MinecraftClient client = MinecraftClient.getInstance();
client.getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
WARNING
Si prega di notare che la classe AbstractSoundInstance
, da cui le SoundInstance
ereditano, ha l'annotazione @Environment(EnvType.CLIENT)
.
Questo significa che questa classe (e tutte le sue sottoclassi) saranno disponibili solo lato client.
Se provassi ad usarla in un contesto logico lato server, potresti non notare inizialmente il problema in Giocatore Singolo, ma un server in ambiente Multiplayer crasherà, poiché non riuscirà a trovare quella parte del codice.
Se trovi difficoltà con queste questioni, si consiglia di creare la tua mod dal Generatore di Mod Modello online attivando l'opzione Split client and common sources
.
Una SoundInstance
può essere molto più potente rispetto a una semplice riproduzione di un suono una volta.
Dai un'occhiata alla classe AbstractSoundInstance
e a quali tipi di valori può tenere in considerazione. A parte le solite variabili di volume e tono, memorizza anche le coordinate XYZ, e un'opzione perché si ripeta dopo aver finito il SoundEvent
.
Poi, dando un'occhiata alla sua sottoclasse MovingSoundInstance, viene anche introdotta l'interfaccia
TickableSoundInstance, che aggiunge funzionalità legate ai tick alla
SoundInstance`.
Per cui per sfruttare queste utilità, basta creare una nuova classe per la tua SoundInstance
personalizzata ed estendere MovingSoundInstance
.
public class CustomSoundInstance extends MovingSoundInstance {
private final LivingEntity entity;
public CustomSoundInstance(LivingEntity entity, SoundEvent soundEvent, SoundCategory soundCategory) {
super(soundEvent, soundCategory, SoundInstance.createRandom());
// In this constructor we also add the sound source (LivingEntity) of
// the SoundInstance and store it in the current object
this.entity = entity;
// set up default values when the sound is about to start
this.volume = 1.0f;
this.pitch = 1.0f;
this.repeat = true;
this.setPositionToEntity();
}
@Override
public void tick() {
// stop sound instantly if sound source does not exist anymore
if (this.entity == null || this.entity.isRemoved() || this.entity.isDead()) {
this.setDone();
return;
}
// move sound position over to the new position for every tick
this.setPositionToEntity();
}
@Override
public boolean shouldAlwaysPlay() {
// override to true, so that the SoundInstance can start
// or add your own condition to the SoundInstance, if necessary
return true;
}
// small utility method to move the sound instance position
// to the sound source's position
private void setPositionToEntity() {
this.x = this.entity.getX();
this.y = this.entity.getY();
this.z = this.entity.getZ();
}
}
Usare la tua Entity
o il tuo BlockEntity
personalizzati invece dell'istanza basilare LivingEntity
può darti ancora più controllo, per esempio nel metodo tick()
basato su metodi accessori, ma non devi per forza far riferimento ad una fonte di suono del genere. Invece di ciò puoi anche accedere alla BlockPos
da altrove, o addirittura impostarla manualmente una volta sola nel costruttore soltanto.
Tieni solo a mente che tutti gli oggetti a cui fai riferimento nella SoundInstance
sono le versioni lato client. In certe circostanze specifiche, le proprietà di un'entità dal lato logico server possono differire dalla controparte lato client. Se noti che i tuoi valori non si allineano, assicurati che siano sincronizzati o con i TrackedData
dell'entità, o con pacchetti S2C (da server a client) di BlockEntity
o con pacchetti di rete S2C completamente personalizzati.
Dopo aver finito di creare la tua SoundInstance
personalizzata, è tutto pronto per usarla ovunque, a condizione che venga eseguita lato client con il gestore di suoni. Allo stesso modo, puoi anche fermare la SoundInstance
personalizzata manualmente, se necessario.
CustomSoundInstance instance = new CustomSoundInstance(client.player, CustomSounds.ENGINE_LOOP, SoundCategory.NEUTRAL);
// play the sound instance
client.getSoundManager().play(instance);
// stop the sound instance
client.getSoundManager().stop(instance);
Il loop di suono sarà adesso riprodotto solo sul client che ha eseguito quella SoundInstance
. In questo caso, il suono seguirà la ClientPlayerEntity
stessa.
Così abbiamo concluso la spiegazione di come creare e usare una semplice SoundInstance
personalizzata.
WARNING
Il contenuto seguente tratta di un argomento avanzato.
Da questo punto in poi è necessaria una conoscenza solida di Java, programmazione a oggetti, tipi generici e sistemi di callback.
Conoscere anche le Entities
, i BlockEntities
e il networking personalizzato aiuterà anche molto nella comprensione di questi casi e delle applicazioni pratiche dei suoni avanzati.
Per mostrare un esempio di come si possano creare sistemi di SoundInstance
più elaborati, aggiungeremo funzionalità ulteriori, astrazioni e utilità per rendere il lavoro con suoni del genere in contesti più larghi più semplice, dinamico e flessibile.
Ragioniamo un po' riguardo al nostro obiettivo con la SoundInstance
.
EngineBlockEntity
personalizzato a esso collegato è in esecuzioneSoundInstance
dovrebbe muoversi, seguendo la posizione del EngineBlockEntity
personalizzato (La BlockEntity
non si muoverà, per cui questo sarebbe più utile con le Entities
)Insomma, dobbiamo tener traccia di un'istanza di un BlockEntity
personalizzato, regolare i valori di volume e tono mentre la SoundInstance
è in esecuzione in base a valori provenienti da quel BlockEntity
personalizzato, e implementare "Stati di Transizione".
Se hai intenzione di creare più SoundInstance
diverse, che si comportino in maniere diverse, consiglierei di creare una nuova classe astratta AbstractDynamicSoundInstance
, che implementi il comportamento predefinito e lasciare che le classi SoundInstance
la estendano.
Se invece ne userai solo una, puoi saltare la creazione della superclasse astratta, e implementare invece quella funzionalità direttamente nella tua classe SoundInstance
personalizzata.
Sarebbe anche una buona idea avere una posizione centralizzata dove le SoundInstance
vengono tracciate, riprodotte e interrotte. In altre parole deve gestire i segnali in ingresso, come quelli provenienti da pacchetti di rete S2C personalizzati, elencare tutte le istanze in esecuzione e gestire i casi particolari, per esempio quali suoni è permesso riprodurre allo stesso tempo e quali suoni disattiverebbero potenzialmente altri suoni se attivati. Per fare ciò si può creare una nuova classe DynamicSoundManager
, per interagire con questo sistema sonoro facilmente.
Tutto sommato il nostro sistema sonoro potrebbe avere questo aspetto, una volta completato.
INFO
Tutti questi enum, interfacce e classi saranno create da zero. Adatta il sistema e le utilità alla tua situazione specifica. Questo è solo un esempio su come ci si può approcciare ad argomenti del genere.
DynamicSoundSource
Se scegli di creare una nuova classe AbstractDynamicSoundInstance
personalizzata e più modulare, da usare come superclasse, potresti voler fare riferimento non solo ad un tipo singolo di Entity
ma a più tipi diversi, o anche ad un BlockEntity
.
In quel caso la chiave è sfruttare l'astrazione. Invece di fare riferimento, per esempio, direttamente a un BlockEntity
personalizzato, tener solo conto di un'interfaccia che fornisce i dati risolve quel problema.
D'ora in poi useremo un'interfaccia personalizzata chiamata DynamicSoundSource
. È da implementare in tutte le classi che vogliono sfruttare quelle funzionalità di suoni dinamici, come il tuo BlockEntity
personalizzato, le entità o addirittura, usando Mixin, su classi preesistenti come ZombieEntity
. In pratica contiene solo i dati necessari della fonte di suono.
public interface DynamicSoundSource {
// gets access to how many ticks have passed for e.g. a BlockEntity instance
int getTick();
// gets access to where currently this instance is placed in the world
Vec3d getPosition();
// holds a normalized (range of 0-1) value, showing how much stress this instance is currently experiencing
// It is more or less just an arbitrary value, which will cause the sound to change its pitch while playing.
float getNormalizedStress();
}
Dopo aver creato questa interfaccia, assicurati di implementarla anche nelle classi ove necessario.
INFO
Questa è un'utilità che potrebbe essere usata sia lato client sia lato logico server.
Per cui questa interfaccia dovrebbe essere memorizzata nei package comuni, non in quelli esclusivi del client, se usi l'opzione "split sources".
TransitionState
Come detto prima, puoi interrompere la SoundInstance
con il SoundManager
del client, ma questo farà in modo che la SoundInstance
si zittisca immediatamente. Il nostro obiettivo non è, quando si riceve un segnale di stop, di interrompere il suono ma di eseguire una fase di chiusura del suo "Stato di Transizione". Solo dopo che la fase di chiusura è stata completata, si deve interrompere la SoundInstance
personalizzata.
Uno TransitionState
è un enum appena creato, che contiene tre valori. Essi verranno usati per tener traccia della fase in cui si trova il suono.
STARTING
: iniziando dal silenzio, il volume del suono aumenta lentamenteRUNNING
: il suono viene riprodotto normalmenteENDING
: iniziando dal volume originario esso diminuisce lentamente fino al silenzioTecnicamente basterebbe un semplice enum con le fasi.
public enum TransitionState {
STARTING, RUNNING, ENDING
}
Ma quando quei valori vengono inviati tramite rete, potresti voler definire un Identifier
per esse o anche aggiungere altri valori personalizzati.
public enum TransitionState {
STARTING("starting_phase"),
RUNNING("idle_phase"),
ENDING("ending_phase");
private final Identifier identifier;
TransitionState(String name) {
this.identifier = Identifier.of(FabricDocsReference.MOD_ID, name);
}
public Identifier getIdentifier() {
return identifier;
}
}
INFO
Ripeto, se stai usando le "split sources" devi ragionare su dove si posiziona questo enum. Tecnicamente, solo le SoundInstance
personalizzate, che sono solo disponibili lato client, useranno quei valori dell'enum.
Ma se questo enum venisse usato altrove, per esempio in pacchetti di rete personalizzati, potresti anche dover mettere l'enum nei package comuni invece dei package esclusivi del client.
SoundInstanceCallback
Questa interfaccia viene usata come callback. Per ora ci basta un metodo onFinished
, ma puoi anche aggiungere i tuoi metodi personalizzati se dovessi inviare anche altri segnali dall'oggetto SoundInstance
.
public interface SoundInstanceCallback {
// deliver the custom SoundInstance, from which this signal originates,
// using the method parameters
<T extends AbstractDynamicSoundInstance> void onFinished(T soundInstance);
}
Implementa quest'interfaccia in qualsiasi classe che deve poter gestire segnali in ingresso, per esempio la AbstractDynamicSoundInstance
che presto creeremo, e aggiungi le funzionalità nella SoundInstance
personalizzata stessa.
AbstractDynamicSoundInstance
Finalmente cominciamo a lavorare sul cuore del sistema di SoundInstance
dinamiche. AbstractDynamicSoundInstance
è la classe abstract
che dobbiamo creare. Essa implementa le funzioni e utilità predefinite caratteristiche della nostra SoundInstance
personalizzata, che da essa erediterà.
Possiamo considerare la CustomSoundInstance
di poco fa e migliorarla. Invece della LivingEntity
faremo ora riferimento alla DynamicSoundSource
. Inoltre definiremo altre proprietà.
TransitionState
per tener conto della fase attualeDynamicSoundManager
per la ripulizia, quando la SoundInstance
è terminatapublic abstract class AbstractDynamicSoundInstance extends MovingSoundInstance {
protected final DynamicSoundSource soundSource; // Entities, BlockEntities, ...
protected TransitionState transitionState; // current TransitionState of the SoundInstance
protected final int startTransitionTicks, endTransitionTicks; // duration of starting and ending phases
// possible volume range when adjusting sound values
protected final float maxVolume; // only max value since the minimum is always 0
// possible pitch range when adjusting sound values
protected final float minPitch, maxPitch;
protected int currentTick = 0, transitionTick = 0; // current tick values for the instance
protected final SoundInstanceCallback callback; // callback for soundInstance states
// ...
}
Poi configuriamo i valori iniziali predefiniti per la SoundInstance
personalizzata nel costruttore della classe astratta.
// ...
// set up default settings of the SoundInstance in this constructor
protected AbstractDynamicSoundInstance(DynamicSoundSource soundSource, SoundEvent soundEvent, SoundCategory soundCategory,
int startTransitionTicks, int endTransitionTicks, float maxVolume, float minPitch, float maxPitch,
SoundInstanceCallback callback) {
super(soundEvent, soundCategory, SoundInstance.createRandom());
// store important references to other objects
this.soundSource = soundSource;
this.callback = callback;
// store the limits for the SoundInstance
this.maxVolume = maxVolume;
this.minPitch = minPitch;
this.maxPitch = maxPitch;
this.startTransitionTicks = startTransitionTicks; // starting phase duration
this.endTransitionTicks = endTransitionTicks; // ending phase duration
// set start values
this.volume = 0.0f;
this.pitch = minPitch;
this.repeat = true;
this.transitionState = TransitionState.STARTING;
this.setPositionToEntity();
}
// ...
Dopo aver finito il costruttore, devi permettere alla SoundInstance
di essere riprodotta.
@Override
public boolean shouldAlwaysPlay() {
// override to true, so that the SoundInstance can start
// or add your own condition to the SoundInstance, if necessary
return true;
}
Ecco qui la parte fondamentale di questa SoundInstance
dinamica. In base al tick corrente dell'istanza, può applicare vari valori e comportamenti.
@Override
public void tick() {
// handle states where sound might be actually stopped instantly
if (this.soundSource == null) {
this.callback.onFinished(this);
}
// basic tick behaviour
this.currentTick++;
this.setPositionToEntity();
// SoundInstance phase switching
switch (this.transitionState) {
case STARTING -> {
this.transitionTick++;
// go into next phase if starting phase finished its duration
if (this.transitionTick > this.startTransitionTicks) {
this.transitionTick = 0; // reset tick for future ending phase
this.transitionState = TransitionState.RUNNING;
}
}
case ENDING -> {
this.transitionTick++;
// set SoundInstance as finished if ending phase finished its duration
if (this.transitionTick > this.endTransitionTicks) {
this.callback.onFinished(this);
}
}
}
// apply volume and pitch modulation here,
// if you use a normal SoundInstance class
}
Come puoi notare, non abbiamo ancora applicato alcuna modulazione a volume e tono qui. Applichiamo solo il comportamento condiviso. Per cui, in questa classe AbstractDynamicSoundInstance
, forniamo solo la struttura basilare e gli strumenti per le sottoclassi, e saranno queste a decidere il genere di modulazione che vogliono applicare.
Diamo un'occhiata ad alcuni esempi di strategie per quella modulazione del suono.
// increase or decrease volume and pitch based on the current phase of the sound
protected void modulateSoundForTransition() {
float normalizedTick = switch (transitionState) {
case STARTING -> (float) this.transitionTick / this.startTransitionTicks;
case ENDING -> 1.0f - ((float) this.transitionTick / this.endTransitionTicks);
default -> 1.0f;
};
this.volume = MathHelper.lerp(normalizedTick, 0.0f, this.maxVolume);
}
// increase or decrease pitch based on the sound source's stress value
protected void modulateSoundForStress() {
this.pitch = MathHelper.lerp(this.soundSource.getNormalizedStress(), this.minPitch, this.maxPitch);
}
Come noti, valori normalizzati combinati con interpolazione lineare (lerp) permettodo di adattare i valori ai limiti sonori preferiti. Tieni a mente che, aggiungendo metodi multipli che modificano lo stesso valore, devi fare attenzione e regolare il modo in cui lavorano insieme tra loro.
Ora dobbiamo solo aggiungere il resto dei metodi di utilità, e abbiamo finito la classe AbstractDynamicSoundInstance
.
// moves the sound instance position to the sound source's position
protected void setPositionToEntity() {
this.x = soundSource.getPosition().getX();
this.y = soundSource.getPosition().getY();
this.z = soundSource.getPosition().getZ();
}
// Sets the SoundInstance into its ending phase.
// This is especially useful for external access to this SoundInstance
public void end() {
this.transitionState = TransitionState.ENDING;
}
SoundInstance
Dando un'occhiata all'effettiva classe SoundInstance
personalizzata, che estende l'AbstractDynamicSoundInstance
appena creata, dobbiamo solo pensare a quali condizioni porterebbero all'interruzione del suono e a quale modulazione vogliamo applicare al suono.
public class EngineSoundInstance extends AbstractDynamicSoundInstance {
// Here we just use the default constructor parameters.
// If you want to specifically set values here already,
// you can clean up the constructor parameters a bit
public EngineSoundInstance(DynamicSoundSource soundSource, SoundEvent soundEvent, SoundCategory soundCategory,
int startTransitionTicks, int endTransitionTicks, float maxVolume, float minPitch, float maxPitch,
SoundInstanceCallback callback) {
super(soundSource, soundEvent, soundCategory, startTransitionTicks, endTransitionTicks, maxVolume, minPitch, maxPitch, callback);
}
@Override
public void tick() {
// check conditions which set this sound automatically into the ending phase
if (soundSource instanceof EngineBlockEntity blockEntity && blockEntity.isRemoved()) {
this.end();
}
// apply the default tick behaviour from the parent class
super.tick();
// modulate volume and pitch of the SoundInstance
this.modulateSoundForTransition();
this.modulateSoundForStress();
}
// you can also add sound modulation methods here,
// which should be only accessible to this
// specific SoundInstance
}
DynamicSoundManager
Abbiamo discusso poco fa come si riproduce e interrompe una SoundInstance
. Per ripulire, centralizzare e gestire quelle interazioni, puoi creare un tuo gestore di SoundInstance
personalizzato basato su di esso.
Questa nuova classe DynamicSoundManager
gestirà le SoundInstance
personalizzate, quindi anch'essa sarà solo disponibile lato client. Oltre a ciò, un client dovrebbe permettere l'esistenza solo di un'istanza di questa classe. Gestori multipli di suoni per un client solo non hanno molto senso, e complicano ulteriormente le interazioni. Usiamo quindi il "Modello di Progettazione Singleton".
public class DynamicSoundManager implements SoundInstanceCallback {
// An instance of the client to use Minecraft's default SoundManager
private static final MinecraftClient client = MinecraftClient.getInstance();
// static field to store the current instance for the Singleton Design Pattern
private static DynamicSoundManager instance;
// The list which keeps track of all currently playing dynamic SoundInstances
private final List<AbstractDynamicSoundInstance> activeSounds = new ArrayList<>();
private DynamicSoundManager() {
// private constructor to make sure that the normal
// instantiation of that object is not used externally
}
// when accessing this class for the first time a new instance
// is created and stored. If this is called again only the already
// existing instance will be returned, instead of creating a new instance
public static DynamicSoundManager getInstance() {
if (instance == null) {
instance = new DynamicSoundManager();
}
return instance;
}
// This is where the callback signal of a finished custom SoundInstance will arrive.
// For now, we can just stop and remove the sound from the list, but you can add
// your own functionality too
@Override
public <T extends AbstractDynamicSoundInstance> void onFinished(T soundInstance) {
this.stop(soundInstance);
}
}
Dopo aver creato la corretta struttura basilare, puoi aggiungere i metodi necessari per interagire con il sistema sonoro.
// Plays a sound instance, if it doesn't already exist in the list
public <T extends AbstractDynamicSoundInstance> void play(T soundInstance) {
if (this.activeSounds.contains(soundInstance)) return;
client.getSoundManager().play(soundInstance);
this.activeSounds.add(soundInstance);
}
// Stops a sound immediately. in most cases it is preferred to use
// the sound's ending phase, which will clean it up after completion
public <T extends AbstractDynamicSoundInstance> void stop(T soundInstance) {
client.getSoundManager().stop(soundInstance);
this.activeSounds.remove(soundInstance);
}
// Finds a SoundInstance from a SoundEvent, if it exists and is currently playing
public Optional<AbstractDynamicSoundInstance> getPlayingSoundInstance(SoundEvent soundEvent) {
for (var activeSound : this.activeSounds) {
// SoundInstances use their SoundEvent's id by default
if (activeSound.getId().equals(soundEvent.id())) {
return Optional.of(activeSound);
}
}
return Optional.empty();
}
Invece di avere solo una lista di tutte le SoundInstance
attualmente in riproduzione, puoi anche tener traccia di quali fonti sonore stanno riproducendo quali suoni. Per esempio, un motore che riproduce due suoni di un motore allo stesso tempo non ha senso, però più motori, ciascuno dei quali riproduce i propri suoni, è un caso valido. Per semplicità abbiamo solo creato una List<AbstractDynamicSoundInstance>
, ma spesso una HashMap
di una DynamicSoundSource
e una AbstractDynamicSoundInstance
potrebbe essere una scelta migliore.
Per usare questo sistema sonoro, basta sfruttare i metodi di DynamicSoundManager
o di SoundInstance
. Usando onStartedTrackingBy
e onStoppedTrackingBy
delle entità, o anche solo networking S2C personalizzato, puoi ora avviare e interrompere le tue SoundInstance
dinamiche personalizzate.
private static void handleS2CEngineSoundPacket(EngineSoundInstancePacket packet, ClientPlayNetworking.Context context) {
ClientWorld world = context.client().world;
if (world == null) return;
DynamicSoundManager soundManager = DynamicSoundManager.getInstance();
if (world.getBlockEntity(packet.blockEntityPos()) instanceof EngineBlockEntity engineBlockEntity) {
if (packet.shouldStart()) {
soundManager.play(new EngineSoundInstance(engineBlockEntity,
CustomSounds.ENGINE_LOOP, SoundCategory.BLOCKS,
60, 30, 1.2f, 0.8f, 1.4f,
soundManager)
);
return;
}
}
if (!packet.shouldStart()) {
soundManager.getPlayingSoundInstance(CustomSounds.ENGINE_LOOP).ifPresent(AbstractDynamicSoundInstance::end);
}
}
Il prodotto finale può regolare il proprio volume in base alla fase di suono, per rendere fluide le transizioni, e cambiare il tono in base a un valore di stress che proviene dalla fonte sonora.
Potresti anche volendo aggiungere un altro valore alla tua fonte sonora, che tiene traccia di un valore "surriscaldamento", e fare in modo che una SoundInstance
di un fischio/sibilo aumenti lentamente se il valore è sopra a 0; o ancora aggiungere una nuova interfaccia alla tua SoundInstance
dinamica personalizzata, che assegni un valore di priorità ai tipi di suono, e permette di scegliere quale suono riprodurre se questi vanno in conflitto tra loro.
Con il sistema corrente, puoi facilmente gestire più SoundInstance
insieme e progettare l'audio secondo i tuoi bisogni.