Boa pergunta! Antes de chegar às perguntas específicas que você fez, direi: não subestime o poder da simplicidade. Tenpn está certo. Lembre-se de que tudo o que você está tentando fazer com essas abordagens é encontrar uma maneira elegante de adiar uma chamada de função ou desacoplar o chamador do chamado. Posso recomendar corotinas como uma maneira surpreendentemente intuitiva de aliviar alguns desses problemas, mas isso é um pouco fora de tópico. Às vezes, é melhor chamar a função e viver com o fato de que a entidade A é acoplada diretamente à entidade B. Veja YAGNI.
Dito isto, eu usei e fiquei satisfeito com o modelo de sinal / slot combinado com a simples passagem de mensagens. Usei-o em C ++ e Lua para um título de iPhone bastante bem-sucedido que tinha um cronograma muito apertado.
Para o caso do sinal / slot, se eu quiser que a entidade A faça algo em resposta a algo que a entidade B fez (por exemplo, destranque uma porta quando algo morre), posso ter a entidade A se inscrever diretamente no evento de morte da entidade B. Ou, possivelmente, a entidade A se inscreveria em cada um de um grupo de entidades, incrementaria um contador em cada evento disparado e abriria a porta após a morte de N deles. Além disso, "grupo de entidades" e "N deles" normalmente seriam definidos pelo designer nos dados do nível. (Além disso, essa é uma área em que as corotinas podem realmente brilhar, por exemplo, WaitForMultiple ("Dying", entA, entB, entC); door.Unlock ();)
Mas isso pode se tornar complicado quando se trata de reações fortemente associadas ao código C ++ ou a eventos inerentemente efêmeros do jogo: causar dano, recarregar armas, depurar, feedback de IA baseado em localização orientado pelo jogador. É aqui que a passagem de mensagens pode preencher as lacunas. Essencialmente, tudo se resume a algo como "diga a todas as entidades nesta área que sofram danos em 3 segundos" ou "sempre que você completar a física para descobrir quem eu atirei, diga a eles para executar esta função de script". É difícil descobrir como fazer isso bem usando publicação / assinatura ou sinal / slot.
Isso pode facilmente ser um exagero (versus o exemplo da tenpn). Também pode ser um inchaço ineficiente se você tiver muita ação. Mas, apesar de suas desvantagens, essa abordagem de "mensagens e eventos" combina muito bem com o código do jogo com script (por exemplo, em Lua). O código de script pode definir e reagir a suas próprias mensagens e eventos sem que o código C ++ seja cuidadoso. E o código de script pode facilmente enviar mensagens que acionam o código C ++, como alterar níveis, tocar sons ou até mesmo deixar uma arma definir quanto dano a mensagem TakeDamage causa. Isso me salvou uma tonelada de tempo, porque eu não estava tendo que constantemente brincar com luabind. E isso me permitiu manter todo o meu código luabind em um só lugar, porque não havia muito dele. Quando acoplado corretamente,
Além disso, minha experiência com o caso de uso nº 2 é que é melhor lidar com isso como um evento na outra direção. Em vez de perguntar qual é a saúde da entidade, dispare um evento / envie uma mensagem sempre que a saúde fizer uma alteração significativa.
Em termos de interfaces, btw, acabei com três classes para implementar tudo isso: EventHost, EventClient e MessageClient. Os EventHosts criam slots, os EventClients se inscrevem / se conectam a eles e os MessageClients associam um delegado a uma mensagem. Observe que o destino delegado de um MessageClient não precisa necessariamente ser o mesmo objeto que possui a associação. Em outras palavras, os MessageClients podem existir apenas para encaminhar mensagens para outros objetos. FWIW, a metáfora host / cliente é meio inapropriada. Source / Sink podem ser melhores conceitos.
Desculpe, eu meio que divaguei lá. É a minha primeira resposta :) Espero que faça sentido.