Oyun Devleti 'Yığın'?


52

Oyun durumlarını oyunuma nasıl uygulayacağımı düşünüyordum. İstediğim başlıca şeyler:

  • Yarı saydam üst durumları-duraklama menüsü ile arkasındaki oyuna görmek mümkün

  • Bir şey OO-I bunu teoriyi kullanmak ve anlamak, bunun yanı sıra düzenlenmiş tutmak ve daha fazlasını eklemek için daha kolay buluyorum.



Bağlantılı bir liste kullanmayı planlıyordum ve bir yığın olarak görüyordum. Bu, yarı şeffaflık için aşağıdaki devlete erişebileceğim anlamına geliyor.
Plan: Devlet yığınının IGameStates'e bağlantılı bir işaretçi listesi olmasını sağlayın. Üst durum, kendi güncelleme ve giriş komutlarını yönetir ve daha sonra, altındaki durumun çizilip çizilmeyeceğine karar vermek için Şeffaf olan bir üyeye sahiptir.
Sonra yapabilirdim:

states.push_back(new MainMenuState());
states.push_back(new OptionsMenuState());
states.pop_front();

Müzikçaların yüklenmesini, ardından seçeneklere ve ardından ana menüye gidilmesini temsil etmek için.
Bu iyi bir fikir mi, yoksa ... Başka bir şeye bakmalı mıyım?

Teşekkürler.


MainMenuState'i OptionsMenuState'in arkasında görmek ister misiniz? Veya sadece OptionsMenuState arkasındaki oyun ekranı?
Skizz

Plan, devletlerin bir opaklığa / isTransparent değerine / bayrağına sahip olmasıydı. En yüksek durumun böyle olup olmadığını ve eğer öyleyse hangi değere sahip olduğunu kontrol eder ve görürüm. O zaman diğer devlete o kadar opaklıkla yap. Bu durumda, hayır olmazdım.
Komünist Ördek,

Günün geç saatlerinde olduğunu biliyorum, ancak gelecekteki okuyucular için: newörnek kodda gösterilen şekilde kullanmayın , sadece bellek sızıntıları veya diğer, daha ciddi hatalar istiyor.
Pharap

Yanıtlar:


44

Kodlayıcı ile aynı motorda çalıştım. Farklı bir bakış açım var. :)

İlk olarak, bir yığın FSM'miz yoktu - bir yığın devletimiz vardı. Durumların bir yığını tek bir FSM yapar. Bir yığın FSM'nin neye benzeyeceğini bilmiyorum. Muhtemelen pratik bir şeyler yapmak için fazla karmaşık.

Global Devlet Makinemiz ile ilgili en büyük sorunum, bir devletler dizisi değil, bir devletler yığını olmasıydı. Bu, örneğin, ... / MainMenu / Loading öğesinin ... / Loading / MainMenu'dan farklı olduğu anlamına gelir, ana ekranın yükleme ekranından önce mi yoksa sonra mı başlatıldığına bağlı olarak (oyun asenkron ve yükleme çoğunlukla sunucu tarafından yönlendirilir) ).

Bu çirkin yapılan şeylere iki örnek olarak:

  • Bu, örneğin, LoadingGameplay durumuna yol açtı, bu yüzden, normal yükleme durumundaki kodun çoğunu tekrar etmek zorunda kaldığınız, Gameplay durumu içinde yükleme yapmak için Base / Loading ve Base / Gameplay / LoadingGameplay kullandınız. ).
  • "Eğer karakter yaratıcısında oyuna giderse; oyunda karakter seçimine giderse; karakter seçiminde ise girişe geri dönecektim" gibi bir takım fonksiyonlarımız vardı, çünkü aynı arayüz pencerelerini farklı durumlarda göstermek istiyoruz fakat Geri / İleri yapmak düğmeleri hala çalışıyor.

İsmine rağmen, çok "küresel" değildi. Çoğu iç oyun sistemi, iç durumlarını izlemek için kullanmadılar, çünkü devletlerinin diğer sistemlerle dalga geçmesini istemiyorlardı. Diğerleri, örneğin UI sistemi, onu ancak devleti kendi yerel devlet sistemlerine kopyalamak için kullanabilir. (UI durumları için sisteme karşı özellikle dikkatli olurum. UI durumu bir yığın değil, gerçekten bir DAG ve üzerinde herhangi bir yapıyı zorlamaya çalışmak sadece kullanmak için can sıkıcı UI'ler yapacak.)

Bunun için iyi olan şey, oyun akışının gerçekte nasıl yapılandırıldığını bilmeyen altyapı programcılarından kod entegre etme görevlerini izole etmektir, böylece yamayı yazan adama "kodunuzu Client_Patch_Update" e koyup, grafikleri yazan adama söyleyebilirsiniz. loading "kodunuzu Client_MapTransfer_OnEnter" a koyunca, bazı mantık akışlarını çok fazla sorun yaşamadan değiştirebiliriz.

Bir yan projede, bir devlet ile daha iyi şans oldu set bir yerine yığın alakasız sistemler için birden fazla makine yapmak korkmak, ve kendimi gerçekten bir "küresel devlet" olma tuzağına, içine düşmesine izin reddederek değil, şeyleri global değişkenler arasında senkronize etmenin karmaşık bir yolu - Tabi, bir süre bitmek üzere yapmayı başaracaksınız, ancak bununla hedefiniz olarak tasarlamayın . Temel olarak, bir oyundaki durum bir yığın değildir ve bir oyundaki durumların hepsi birbiriyle ilişkili değildir.

GSM ayrıca, işlev göstericileri ve yerel olmayan davranışlar yapma eğiliminde olduğundan, bu tür büyük devlet geçişlerinde hata ayıklamak da bizim için çok eğlenceli değildi. Devlet yığınları yerine devlet setleri buna gerçekten yardımcı olmuyor, ama bunun farkında olmalısınız. İşlev işaretçileri yerine sanal işlevler bunu biraz hafifletebilir.


Harika cevap, teşekkürler! Sanırım görevinizden ve geçmiş deneyimlerinizden çok şey alabilirim. : D + 1 / Kene.
Komünist Ördek

Bir hiyerarşi ile ilgili güzel şey, sadece en üste itilen ve başkalarının ne olduğu hakkında endişelenmenize gerek kalmayan yardımcı devletler oluşturabilmenizdir.
coderanger

Bunun kümelerden ziyade bir hiyerarşi için nasıl bir argüman olduğunu anlamıyorum. Aksine, bir hiyerarşi tüm devletlerarası iletişimi daha karmaşık hale getirir, çünkü nereye itildikleri hakkında hiçbir fikriniz yoktur.

Kullanıcı arayüzlerinin aslında DAG olduğu konusu çok iyi anlaşılıyor, ancak kesinlikle bir yığında temsil edilebileceği konusunda aynı fikirde değilim. Bağlantılı herhangi bir yönlendirilmiş asiklik grafiği (ve bunun bağlı bir DAG olmayacağı bir durumu düşünemiyorum) bir ağaç olarak gösterilebilir ve bir yığın temel olarak bir ağaçtır.
Ed Ropple

2
Yığınlar, tüm grafiklerin alt kümesi olan DAG'lerin bir alt kümesi olan ağaçların bir alt kümesidir. Tüm istifler ağaç, tüm ağaçlar DAG, fakat çoğu DAG ağaç değil ve çoğu ağaç yığın değil. DAG'lar onları bir yığında saklamanıza izin verecek topolojik bir sıralamaya sahiptir (örneğin, örneğin bağımlılık çözümü için), ancak bir kez yığına tıktığınızda değerli bilgileri kaybettiniz. Bu durumda, eğer bir kardeşi varsa, bir ekran ve ebeveyn arasında gezinme yeteneği.

11

İşte çok faydalı bulduğum bir gamestate yığınının örnek bir uygulaması: http://creators.xna.com/en-US/samples/gamestatemanagement

C # ile yazılmış ve onu derlemek için XNA çerçevesine ihtiyacınız var, ancak fikir edinmek için kodu, belgeleri ve videoyu kontrol edebilirsiniz.

Durum geçişlerini, saydam durumları (kalıcı mesaj kutuları gibi) ve yükleme durumlarını (mevcut durumların boşaltılmasını ve bir sonraki durumun yüklenmesini yöneten) destekleyebilir.

Aynı kavramları (C # olmayan) hobi projelerimde şimdi kullanıyorum (verilen, daha büyük projeler için uygun olmayabilir) ve küçük / hobi projeler için kesinlikle yaklaşımı önerebilirim.


5

Bu, kullandığımıza, bir FSM yığınına benzer. Temel olarak her bir duruma bir enter, exit ve tick işlevini verin ve sırayla arayın. Yükleme gibi şeylerin üstesinden gelmek için çok iyi çalışıyor.


3

"Oyun Programlama Mücevherleri" hacimlerinden biri, oyun durumları için tasarlanan bir durum makinesi uygulamasına sahipti; http://emergent.net/Global/Documents/textbook/Chapter1_GameAppFramework.pdf , küçük bir oyun için nasıl kullanılacağına dair bir örneğe sahiptir ve okunabilir olması için Gamebryo'ya özgü olmamalıdır.


"DirectX ile Oyun Oynama Rolü Oynama" nın ilk bölümü aynı zamanda bir devlet sistemi (ve bir süreç sistemi - çok ilginç bir ayrım) uygular.
Ricket

Bu harika bir doktor ve bunu neredeyse tam olarak nasıl uyguladığımı açıklıyor, örneklerde kullandıkları gereksiz nesne hiyerarşisi dışında.
dash-tom-bang

3

Tartışmaya biraz standardizasyon eklemek için, bu tür veri yapıları için klasik CS terimi bir aşağı itme otomatıdır .


Devlet yığınlarının gerçek dünyadaki herhangi bir uygulamasının bir itme otomatına neredeyse eşdeğer olduğundan emin değilim. Diğer cevaplarda belirtildiği gibi, pratik uygulamalar her zaman "iki devlet pop", "bu ülkeleri değiştir" veya "bu verileri yığının dışındaki bir sonraki duruma geçirin" gibi komutlarla sonuçlanır. Ve bir otomat bir otomatondur - bir bilgisayar - veri yapısı değil. Hem durum yığınları hem de pushdown otomataları veri yapısı olarak bir yığın kullanır.

1
“Devlet yığınlarının gerçek dünyadaki herhangi bir uygulamasının bir itme otomatına neredeyse eşdeğer olduğundan emin değilim.” Fark ne? Her ikisi de sonlu bir durum kümesine, bir durum tarihine ve zorlamak ve zorlamak için ilkel işlemlere sahiptir. Bahsettiğiniz diğer faaliyetlerin hiçbiri, ondan fon olarak farklı değildir. "İki devlet pop" sadece iki kez patlıyor. "takas" bir pop ve bir itmedir. Veri iletimi ana fikrin dışında, ancak bir "FSM" kullanan her oyun, artık geçerli bir isim gibi hissetmeden ek verilere de dokunuyor.
munificent

Bir pushdown otomasyonunda, geçişinizi etkileyebilecek tek durum en üstteki durumdur. Ortada iki devletin yer değiştirmesine izin verilmez; Ortadaki devletlere bakmak bile yasaktır. "FSM" teriminin anlamsal genişlemesinin makul olduğunu ve fayda sağladığını hissediyorum (ve hala en kısıtlı anlam için "DFA" ve "NFA" terimlerimiz var), ancak "pushdown otomat" kesinlikle bir bilgisayar bilimi terim ve orada sadece her bir yığın tabanlı sisteme uygularsak, sadece kafa karışıklığı bekliyor.

Her şeyi etkileyebilecek tek durumun üstte olan durum olduğu uygulamalarını tercih ediyorum, ancak bazı durumlarda durum girişini filtrelemek ve işlemeyi "düşük" duruma geçirmek kullanışlı. (Örneğin, denetleyici girişi işleme bu yönteme göre eşleşir, en üstteki durum umursadığı bitleri alır ve muhtemelen onları temizler ve ardından yığını yığındaki bir sonraki duruma geçirir.)
dash-tom-bang

1
İyi nokta, sabit!
munificent

1

Devlet sisteminin işlevselliğini sınırlandırmanın yanı sıra bir yığının tamamen gerekli olduğundan emin değilim. Bir yığın kullanarak, bir durumdan birkaç olasılıktan birine 'çıkış' yapamazsınız. "Ana Menü" den başladığınızı ve ardından "Oyun Yükle" ye başladığınızı söyleyin, kaydedilen oyunu başarıyla yükledikten sonra "Duraklat" durumuna geçmek ve kullanıcı yükü iptal ederse "Ana Menü" ye geri dönmek isteyebilirsiniz.

Sadece devletin, çıkarken takip edeceği durumu belirtmesini istiyorum.

Geçerli durumdan önceki durumuna geri dönmek istediğiniz durumlar için, örneğin "Ana Menü-> Seçenekler-> Ana Menü" ve "Duraklat-> Seçenekler-> Duraklat", yalnızca duruma bir başlangıç ​​parametresi olarak iletin geri dönmek için devlet.


Belki de soruyu yanlış anladım.
Skizz

Hayır yapmadın. Sanırım aşağı seçmen yaptı.
Komünist Ördek

Bir yığın kullanılması, açık durum geçişlerinin kullanılmasını engellemez.
dash-tom-bang

1

Geçişler ve bu gibi şeylerin bir başka çözümü de, hedefin ve kaynak durumun yanı sıra, "motor" ile bağlantılı olabilecek durum makinesi ile birlikte, bunların ne olursa olsun sağlanmasıdır. Gerçek şu ki, çoğu devlet makinesi muhtemelen eldeki projeye uyarlanmak zorunda kalacak. Bir çözüm bu veya bu oyuna yarar sağlayabilir, diğer çözümler onu engelleyebilir.

class StateMachine
{
public:
    StateMachine(Engine *);
    void Push(State *);
    State *Pop();
    void Update();
    Engine *GetEngine();

private:
    std::stack<State *> _states;
    Engine *_engine;
};

Durumlar geçerli duruma ve makineye parametre olarak itilir.

void StateMachine::Push(State *state)
{
    State *from = 0;
    if (!_states.empty()) from = _states.top();
    _states.push(state);
    state->Enter(this, from);
}

Devletler aynı şekilde atıldı. Enter()Alt tarafta arayacağınız Statebir uygulama sorusudur.

State *StateMachine::Pop()
{
    _ASSERT(!_states.empty());
    State *state = _states.top();
    State *to = 0;
    _states.pop();
    if (!_states.empty()) to = _states.top();
    state->Exit(this, to);
    return state;
}

Girerken, güncellerken veya çıkarken, Stateihtiyaç duyduğu tüm bilgileri alır.

void SomeGameState::Enter(StateMachine *sm, State *from)
{
    Engine *eng = sm->GetEngine();
    eng->GetKeyboard()->KeyDown.Bind(this, &SomeGameState::KeyDown);
    LoadLevelState *state = new LoadLevelState();
    state->SetLevel(eng->GetSaveGame()->GetLevelName());
    state->Load.Bind(this, &SomeGameState::OnLevelLoaded);
    sm->Push(state);
}

void SomeGameState::Update(StateMachine *sm)
{
    Engine *eng = sm->GetEngine();
    float time = eng->GetFrameTime();
    if (shouldExit)
        sm->Pop();
}

void SomeGameState::Exit(StateMachine *sm, State *from)
{
    Engine *eng = sm->GetEngine();
    eng->GetKeyboard()->KeyDown.UnsubscribeAll(this);
}

0

Çeşitli oyunlarda çok benzer bir sistem kullandım ve birkaç istisna dışında mükemmel bir UI modeli olarak hizmet ettiğini gördüm.

Karşılaştığımız tek meseleler, bazı durumlarda yeni bir duruma geçmeden önce birden fazla devlet açmanın (genellikle kötü bir kullanıcı arayüzünün bir işareti olduğu için gerekliliği ortadan kaldırmak için kullanıcı arayüzünü yeniden akıttık) ve sihirbaz stilini oluşturmamızın istendiği durumlardı. Doğrusal akışlar (verileri bir sonraki duruma geçirerek kolayca çözülebilir).

Kullandığımız uygulama aslında yığını sardı ve güncelleme ve oluşturma işlemlerinin yanı sıra yığındaki işlemler için mantığı kullandı. Yığındaki her işlem, gerçekleşen işlemi bildirmeleri için durumlardaki olayları tetikledi.

Swap (Pop & Push, doğrusal akışlar için) ve Sıfırlama (ana menüye geri dönmek veya akışı sonlandırmak) gibi genel görevleri basitleştirmek için birkaç yardımcı işlev de eklenmiştir.


Bir UI modeli olarak bu biraz mantıklı. "Ana Menü", "Seçenekler Menüsü", "Oyun Ekranı" ve "Ekranı Duraklat" daha yüksek bir seviyedeyken, kafamda bunu ana oyun motorunun içindekilerle ilişkilendirdiğim için durumları çağırmakta tereddüt ediyorum. ve çoğu zaman yalnızca çekirdek oyunun iç durumu ile hiçbir etkileşimi yoktur ve basitçe "Duraklat", "Unpause", "Yük Seviyesi 1", "Başlama Seviyesi", "Yeniden Başlatma Seviyesi" formunun ana motoruna komutlar gönderir, "Kaydet" ve "Geri Yükle", "Ses düzeyi 57'yi ayarla", vb. Açıkçası bu oyun tarafından önemli ölçüde değişse de.
Kevin Cathcart

0

Neredeyse tüm projelerim için aldığım yaklaşım bu, çünkü inanılmaz derecede iyi çalışıyor ve son derece basit.

En yeni projem Sharplike , kontrol akışını bu şekilde ele alıyor. Devletlerimiz, devletler değiştiğinde çağrılan bir dizi olay işlevine bağlanır ve aynı durum makinesi içinde birden fazla sayıda devlet yığınına sahip olabileceğiniz ve bunlar arasında dallanabileceğiniz "adlandırılmış yığın" konseptine sahiptir. aracı ve gerekli değil, ancak olması kullanışlı.

Skizz'in önerdiği paradigmaya, "denetleyiciye bu durumun ne zaman bitmesi gerektiğini söylemesi gerektiğini söyleyin" ifadesine karşı uyarırdım: yapısal olarak sağlam değil ve iletişim kutularına benzer şeyler yapıyor (standart yığın-durumu paradigmasında yeni bir tane yaratmayı içerir) yeni üyelere sahip alt sınıfı belirtin, sonra çağıran duruma geri döndüğünüzde okumaya başlamanız gerekenden çok daha zor.


0

Temel olarak bu kesin sistemi ortogonal olarak birkaç sistemde kullandım; örneğin, ön uç ve oyun içi menü (aka "duraklama") durumlarının kendi devlet yığınlarına sahip olduğu belirtiliyor. Oyun içi kullanıcı arayüzü de, devlet değişiminin renklendirebileceği ancak devletler arasında ortak bir şekilde güncellenen "küresel" yönlere (sağlık çubuğu ve harita / radar gibi) sahip olmasına rağmen, böyle bir şey kullandı.

Oyun içi menü, bir DAG tarafından temsil edilen "daha iyi" olabilir, ancak üstü kapalı bir durum makinesiyle (başka bir ekrana giden her menü seçeneği oraya nasıl gidileceğini bilir ve geri düğmesine her zaman en üst durumu getirdi) tam olarak aynı.

Bu diğer sistemlerden bazıları "üst durum yerine" işlevselliğine de sahipti, ancak bu tipik olarak StatePop()takip edildiği şekilde uygulandı StatePush(x);.

Bellek kartının kullanımı benzerdi çünkü işlem sırasına bir ton "işlem" i zorladım (işlevsel olarak Yığın ile aynı şeyi yapmıştı, LIFO yerine FIFO gibi); bu tür bir yapı kullanmaya başladığınızda ("şu anda olan bir şey var ve bittiğinde kendiliğinden ortaya çıkıyor") kodun her alanını etkilemeye başlar. AI bile böyle bir şey kullanmaya başladı; AI "ipucu" idi, sonra oyuncu sesler çıkarıp görünmediği zaman "dikkatli" olarak değiştirildi ve daha sonra oyuncuyu görünce nihayet "aktif" seviyesine yükseldi (ve daha az oyunun aksine, saklanamazdın) Bir karton kutu içinde ve düşmanı seni unutturun! Acı değilim ...).

GameState.h:

enum GameState
{
   k_frontend,
   k_gameplay,
   k_inGameMenu,
   k_moviePlayback,
   k_numStates
};

void GameStatePush(GameState);
void GameStatePop();
void GameStateUpdate();

GameState.cpp:

// k_maxNumStates could be bigger, but we don't need more than
// one of each state on the stack.
static const int k_maxNumStates = k_numStates;
static GameState s_states[k_maxNumStates] = { k_frontEnd };
static int s_numStates = 1;

static void (*s_startupFunctions)()[] =
   { FrontEndStart, GameplayStart, InGameMenuStart, MovieStart };
static void (*s_shutdownFunctions)()[] =
   { FrontEndStop, GameplayStop, InGameMenuStop, MovieStop };
static void (*s_updateFunctions)()[] =
   { FrontEndUpdate, GameplayUpdate, InGameMenuUpdate, MovieUpdate };

static void GameStateStart(GameState);
static void GameStateStop(GameState);

void GameStatePush(GameState gs)
{
   Assert(s_numStates < k_maxNumStates);
   GameStateStop(s_states[s_numStates - 1])
   s_states[s_numStates] = gs;
   s_numStates++;
   GameStateStart(gs);
}

void GameStatePop()
{
   Assert(s_numStates > 1);  // can't pop last state
   s_numStates--;
   GameStateStop(s_states[s_numStates]);
   GameStateStart(s_states[s_numStates - 1]);
}

void GameStateUpdate()
{
   GameState current = s_states[s_numStates - 1];
   s_updateFunctions[current]();
}

void GameStateStart(GameState gs)
{
   s_startupFunctions[gs]();
}

void GameStateStop(GameState gs)
{
   s_shutdownFunctions[gs]();
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.