Scripting e Cinematica senza thread


12

Ho avuto difficoltà a implementare gli script nel mio motore di gioco. Ho solo alcuni requisiti: dovrebbe essere intuitivo, non voglio scrivere un linguaggio personalizzato, parser e interprete e non voglio usare il threading. (Sono certo che esiste una soluzione più semplice; non ho bisogno della seccatura di più thread di logica di gioco.) Ecco uno script di esempio, in Python (aka pseudocodice):

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    alice.walk_to(bob)
    if bob.can_see(alice):
        bob.say("Hello again!")
    else:
        alice.say("Excuse me, Bob?")

Quella parte epica della narrazione pone problemi di implementazione. Non posso semplicemente valutare l'intero metodo in una volta, perché walk_torichiede tempo di gioco. Se ritorna subito, Alice inizia a camminare verso Bob, e (nella stessa cornice) saluta (o si saluta). Ma se walk_toè una chiamata bloccante che ritorna quando raggiunge Bob, allora il mio gioco si blocca, perché sta bloccando lo stesso filo di esecuzione che farebbe camminare Alice.

Ho pensato di rendere ogni funzione accodata in un'azione: alice.walk_to(bob)spingevo un oggetto in una coda, che sarebbe saltato fuori dopo che Alice avesse raggiunto Bob, dovunque fosse. È più sottilmente rotto: il iframo viene valutato immediatamente, quindi Bob potrebbe salutare Alice anche se le si volge le spalle.

In che modo altri motori / persone gestiscono gli script senza creare thread? Sto iniziando a cercare idee in aree non sviluppate dal gioco, come catene di animazioni jQuery. Sembra che ci dovrebbero essere alcuni buoni schemi per questo tipo di problema.


1
+1 per la domanda ma soprattutto per "Python (aka pseudocode)" :)
Ricket,

Python è come avere una superpotenza.
ojrac,

Risposte:


3

Il modo in cui qualcosa come Panda fa questo è con i callback. Invece di bloccare sarebbe qualcosa di simile

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    def cb():
        if bob.can_see(alice):
            bob.say("Hello again!")
        else:
            alice.say("Excuse me, Bob?")
    alice.walk_to(bob, cb)

Avere una richiamata completa consente di concatenare questo tipo di eventi nel modo desiderato.

EDIT: esempio JavaScript poiché ha una sintassi migliore per questo stile:

function dramatic_scene(actors) {
    var alice = actors.alice;
    var bob = actors.bob;
    alice.walk_to(bob, function() {
        if(bob.can_see(alice)) {
            bob.say('Hello again!');
        } else {
            alice.say('Excuse me, Bob?');
        }
     });
}

Funziona abbastanza per un +1, penso solo che sia sbagliato per la progettazione di contenuti. Sono disposto a fare qualche lavoro extra da parte mia in modo che gli script appaiano semplici e chiari.
ojrac,

1
@ojrac: In realtà è meglio così, perché qui nella stessa sceneggiatura puoi dire a più attori di iniziare a camminare contemporaneamente.
Bart van Heukelom,

Uh, buon punto.
ojrac,

Per migliorare la leggibilità, tuttavia, la definizione di callback potrebbe essere nidificata all'interno della chiamata walk_to () o essere inserita dopo (per entrambi va: se la lingua supporta) in modo che il codice che viene chiamato in seguito sia visto più avanti nel sorgente.
Bart van Heukelom,

Sì, purtroppo Python non è davvero eccezionale per questo tipo di sintassi. Sembra molto più bello in JavaScript (vedi sopra, non è possibile utilizzare la formattazione del codice qui).
coderanger

13

Il termine che si desidera cercare qui è " coroutine " (e in genere la parola chiave lingua o il nome della funzione è yield).

Le coroutine sono componenti del programma che generalizzano le subroutine per consentire più punti di ingresso per sospendere e riprendere l'esecuzione in determinate posizioni.

L'implementazione dipenderà innanzitutto dalla tua lingua. Per un gioco vuoi che l'implementazione sia il più leggera possibile (più leggera dei fili o persino delle fibre). La pagina Wikipedia (collegata) contiene alcuni collegamenti a varie implementazioni specifiche della lingua.

Ho sentito che Lua ha il supporto integrato per le coroutine. Lo stesso vale per GameMonkey.

UnrealScript implementa questo con ciò che chiama "stati" e "funzioni latenti".

Se usi C # puoi guardare questo post sul blog di Nick Gravelyn.

Inoltre, l'idea delle "catene di animazioni", sebbene non sia la stessa cosa, è una soluzione praticabile allo stesso problema. Anche Nick Gravelyn ha implementato questo C # .


Bella cattura, Tetrad;)
Andrew Russell

Questo è davvero buono, ma non sono sicuro che mi faccia arrivare al 100%. Sembra che le coroutine ti permettano di cedere al metodo di chiamata, ma voglio un modo per cedere da uno script Lua, fino allo stack fino al codice C # senza scrivere mentre (walk_to ()! = Done) {yield}.
ojrac,

@ojrac: Non so di Lua, ma se stai usando il metodo C # di Nick Gravelyn potresti restituire un delegato (o un oggetto che ne contiene uno) che detiene la condizione che il tuo gestore di script verifichi (il codice di Nick restituisce solo una volta che è implicitamente un condizionale). Potresti anche avere le stesse funzioni latenti che restituiscono il delegato, così puoi scrivere: yield return walk_to();nel tuo script.
Andrew Russell,

Il rendimento in C # è fantastico, ma sto ottimizzando la mia soluzione per uno scripting semplice e difficile. Avrò più tempo a spiegare i callback che il rendimento, quindi accetterò l'altra risposta. Farei +2 se potessi.
ojrac,

1
Normalmente non è necessario spiegare la chiamata di rendimento - ad esempio, è possibile racchiuderla nella funzione "walk_to".
Kylotan,

3

non andare in thread è intelligente.

La maggior parte dei motori di gioco funziona come una serie di fasi modulari con elementi in memoria che guidano ogni fase. Per la tua "passeggiata verso l'esempio", di solito hai una fase AI in cui i tuoi personaggi camminati sono in uno stato in cui non dovrebbero cercare nemici da uccidere, una fase di animazione in cui dovrebbero eseguire l'animazione X, una fase di fisica (o fase di simulazione) in cui viene aggiornata la loro posizione effettiva, ecc.

nel tuo esempio sopra, 'alice' è un attore composto da pezzi che vivono in molte di queste fasi, quindi un attore bloccante.walk_to call (o una coroutine che chiami next () su una volta per frame) probabilmente non avrebbe il giusto contesto prendere molte decisioni.

Invece, una funzione 'start_walk_to' probabilmente farebbe qualcosa del genere:

def start_cutscene_walk_to(actor,target):
    actor.ai.setbrain(cutscene_brain)
    actor.physics.nocoll = 1
    actor.anims.force_anim('walk')
    # etc.

Quindi, il tuo loop principale esegue il suo tick ai, il tick della fisica, il tick dell'animazione e il tick del filmato, e il filmato aggiorna lo stato per ciascuno dei sottosistemi nel tuo motore. Il sistema del filmato dovrebbe sicuramente tenere traccia di ciò che sta facendo ciascuno dei suoi filmati, e un sistema guidato dal coroutine per qualcosa di lineare e deterministico come un custscene potrebbe avere un senso.

Il motivo di questa modularità è che mantiene le cose belle e semplici e per alcuni sistemi (come la fisica e l'IA), è necessario conoscere lo stato di tutto allo stesso tempo per risolvere le cose correttamente e mantenere il gioco in uno stato coerente .

spero che sia di aiuto!


Mi piace quello che stai cercando, ma in realtà sono fortemente convinto del valore di actor.walk_to ([un altro attore, una posizione fissa o persino una funzione che restituisce una posizione]). Il mio obiettivo è fornire strumenti semplici e comprensibili e gestire tutta la complessità lontano dalla parte di creazione del contenuto del gioco. Mi hai anche aiutato a capire che tutto ciò che voglio davvero è un modo di trattare ogni script come una macchina a stati finiti.
ojrac,

felice di poterti aiutare! Mi è sembrato che la mia risposta fosse un po 'fuori tema :) sono decisamente d'accordo con il valore di una funzione attore.walk_per il raggiungimento dei tuoi obiettivi, non vedo l'ora di sapere della tua implementazione.
Aaron Brady,

Sembra che andrò con un mix in stile jQuery di callback e funzioni concatenate. Vedi risposta accettata;)
ojrac,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.