2011-03-16 18 views
8

En este ejemplo se ha utilizado en otra pregunta para ilustrar cómo co-rutinas pueden utilizarse para escenas de guión en un videojuego:Alternativas a corrutinas

bob.walkto(jane) 
bob.lookat(jane) 
bob.say("How are you?") 
wait(2) 
jane.say("Fine") 
... 

cada función de los rendimientos en el motor principal que hace la animación, el tiempo, etc. antes de reanudar la corutina. Una posible alternativa a corutinas sería una cola de eventos en lugar de código, pero luego uno tiene que implementar lógica de control y bucles como eventos. ¿Hay alguna otra alternativa a corutinas que se pueda utilizar para implementar este tipo de funcionalidad? He visto devoluciones de llamadas mencionadas en algunos artículos, pero no estoy seguro de cómo se vería el código.

+0

La alternativa a corutinas, FSM y programación basada en eventos son CSP (comunicación de procesos secuenciales).Consulte la implementación de LuaCSP (en la parte superior de coroutines) aquí: https://github.com/loyso/LuaCSP NB: lo que las personas suelen perder es el aspecto de coordinación y comunicación. –

Respuesta

3

Coroutines son ideales para esto, ya que puede mantener todas las variables de estado locales sin problemas. Es decir. sin tener que almacenarlo manualmente en un contexto en algún lugar.

Pero no veo un sistema de eventos como alternativa. Más como un complemento que probablemente desee tener además de un sistema de secuencias de comandos basado en Coroutine.

Ejemplo (en algo coherente C++):

Ha implementado un comportamiento usando corrutinas lo largo de estas líneas:

class EnterHouse : public NPCBehavior 
{ 
    EnterHouse(House theHouse) { _theHouse = theHouse; } 
    void Begin() { _theHouse.AddNPC(NPC()); } 
    void Update() 
    { 
     NPC().WalkTo(_theHouse.GetDoor().Position()); 
     NPC().FaceObject(_theHouse.GetDoor()); 
     NPC().PlayAnimation(NPC().Animations().Get(eAnimKnockOnDoor)); 
     Sleep(1.0f);  
     NPC().OpenDoor(_theHouse.GetDoor()); 
    } 
    void End() { /* Nothing */ } 

    private House _theHouse; 
} 

imaginar que los métodos en los NPCs a sí mismos crear objetos NPCBehavior, pulsadores ellos en algún tipo de pila de comportamiento y regresan de la llamada cuando esos comportamientos se completan.

La llamada Sleep(1.0f) dará lugar a su programador de scripts y permitirá que se ejecuten otros scripts. El WalkTo, FaceObject, PlayAnimation y OpenDoor también llamarán al Sleep para ceder. Ya sea basado en una duración de animación conocida, para despertarse periódicamente para ver si el Pathfinder y el sistema de locomoción terminan de caminar o lo que sea.

¿Qué sucede si el NPC encuentra una situación que tendrá que enfrentar de camino a la puerta? No desea tener que verificar todos estos eventos en todas partes en su código basado en coroutine. Tener un sistema de eventos que complemente las corutinas facilitará esto:

Un bote de basura se cae sobre: El bote de basura puede transmitir un evento a todos los NPC cercanos. El objeto NPC decide empujar un nuevo objeto de comportamiento en su pila para ir a arreglarlo. El comportamiento WalkTo está en una llamada Sleep rindiendo en alguna parte, pero ahora el comportamiento FixTrashcan se está ejecutando debido al evento. Cuando FixTrashcan complete el comportamiento WalkTo se despertará desde Sleep y nunca se sabe sobre el incidente de la papelera. Pero todavía estará en camino hacia la puerta, y debajo todavía estamos corriendo EnterHouse.

Una explosión ocurre: La explosión difunde un evento al igual que el cubo de basura, pero esta vez el objeto de la APN decide restablecer funcionando comportamientos y empujar un comportamiento FleeInPanic. Él no regresará al EnterHouse.

Espero que entiendan lo que quiero decir con eventos y corutinas viviendo juntos en un sistema de inteligencia artificial. Puedes usar coroutines para mantener el estado local mientras te rindes al programador de scripts, y puedes usar eventos para manejar las interrupciones y mantener la lógica para tratarlos centralizados sin contaminar tus comportamientos.

Si aún no ha visto this article by Thomas Tong sobre cómo implementar corutinas de subproceso único en C/C++, lo recomiendo encarecidamente.

Utiliza solo el más mínimo bit de ensamblaje en línea (una sola instrucción) para guardar el puntero de la pila, y el código es fácilmente transferible a un montón de plataformas. Lo he estado ejecutando en Wintel, Xbox 360, PS3 y Wii.

Otra cosa agradable acerca de la configuración del programador/script es que resulta trivial matar de hambre a los caracteres AI/ajenos a la pantalla o lejanos si necesita los recursos para otra cosa. Simplemente combínelo con un sistema de prioridad en su programador y estará listo.

+0

¡Ejemplo interesante con la papelera y la explosión! No había visto el artículo, pero hay otro que [implementa corutinas en C usando una máquina de estado y algunos trucos de preprocesador para ocultar los detalles] (http://www.chiark.greenend.org.uk/~sgtatham/coroutines .html) (no ensamblado en línea). – absence

2

devoluciones de llamada (C# pseudocode al estilo):

bob.walkto(jane,() => { 
    bob.lookat(jane),() => { 
     bob.say..... 
    }) 
}) 
Definitivamente no

la forma más conveniente.

Un enfoque diferente es Futuros (también conocido como promesas):

futureChain = bob.walkto(jane) 
    .whenDone(bob.lookAt(jane)) 
    .whenDone(...) 
    .after(2 seconds, jane.Say("fine")); 

futureChain.run(); 

Un lenguaje interesante a tener en cuenta es E - se ha incorporado en el apoyo a los futuros, con una sintaxis más agradable que antes.

+0

Gracias por el ejemplo de devolución de llamada. Además de ser inconveniente, un bucle causaría un desbordamiento de pila. – absence

3

Usted no ha mencionado qué idioma se estaba utilizando, así que voy a estar escribiendo esto en Lua con orientación a objetos proporcionada por la clase media - https://github.com/kikito/middleclass (exención de responsabilidad: Soy creador de clase media)

Otra opción estaría dividiendo tus escenas en "listas de acciones". Esto probablemente se combinará mejor con su código, si ya tiene un bucle de juego que invoca un método de "actualización" en listas de objetos.

De esta manera:

helloJane = CutScene:new(
    WalkAction:new(bob, jane), 
    LookAction:new(bob, jane), 
    SayAction:new(bob, "How are you?"), 
    WaitAction:new(2), 
    SayAction:new(jane, "Fine") 
) 

acciones tendrían un atributo status con tres valores posibles: 'new', 'running', 'finished'. Todas las "clases de acción" serían subclases de Action, que definirían los métodos start y stop, así como también inicializarían el estado a 'new' de forma predeterminada. Habría también un método predeterminado update que arroja un error

Action = class('Action') 

function Action:initialize() self.status = 'new' end 

function Action:stop() self.status = 'finished' end 

function Action:start() self.status = 'running' end 

function Action:update(dt) 
    error('You must re-define update on the subclasses of Action') 
end 

Las subclases de acción puede mejorar esos métodos, y poner en práctica update. Por ejemplo, aquí está WaitAction:

WaitAction = class('WaitAction', Action) -- subclass of Action 

function WaitAction:start() 
    Action.start(self) -- invoke the superclass implementation of start 
    self.startTime = os.getTime() -- or whatever you use to get the time 
end 

function WaitAction:update(dt) 
    if os.getTime() - self.startTime >= 2 then 
    self:stop() -- use the superclass implementation of stop 
    end 
end 

La parte que falta es la aplicación única escena. Una escena se tienen principalmente tres cosas: * Una lista de acciones a ejecutar * Una referencia a la acción actual, o el índice de esa acción en la lista de acciones * Un método de actualización como la siguiente:

function CutScene:update(dt) 
    local currentAction = self:getCurrentAction() 
    if currentAction then 
    currentAction:update(dt) 
    if currentAction.status == 'finished' then 
     self:moveToNextAction() 
     -- more refinements can be added here, for example detecting the end of actions 
    end 
    end 
end 

Con esta estructura, lo único que necesita es su bucle de juego llamando al helloJane:update(dt) en cada iteración de bucle. Y eliminas la necesidad de corutinas.