Eylemlerin yan etkileri olduğu sıra tabanlı bir oyun tasarımı


19

Dominion oyununun bilgisayar versiyonunu yazıyorum . Aksiyon kartları, hazine kartları ve zafer puanı kartlarının bir oyuncunun kişisel destesinde toplandığı sıra tabanlı bir kart oyunudur. Sınıf yapısını oldukça iyi geliştirdim ve oyun mantığını tasarlamaya başladım. Python kullanıyorum ve daha sonra pygame ile basit bir GUI ekleyebilirim.

Oyuncuların sıra sırası çok basit bir durum makinesi tarafından yönetilir. Saat yönünde döner ve bir oyuncu bitmeden oyundan çıkamaz. Tek bir turun oynanışı da bir durum makinesidir; genel olarak, oyuncular bir "aksiyon aşaması", bir "satın alma aşaması" ve bir "temizleme aşaması" (bu sırayla) içinden geçer. Sıra tabanlı oyun motoru nasıl uygulanır? Sorusunun cevabına dayanarak ? , durum makinesi bu durum için standart bir tekniktir.

Benim sorunum, bir oyuncunun aksiyon aşamasında, kendisi veya diğer oyunculardan biri veya daha fazlası üzerinde yan etkileri olan bir aksiyon kartı kullanabilmesidir. Örneğin, bir aksiyon kartı, bir oyuncunun mevcut turun bitiminden hemen sonra ikinci bir tur atmasına izin verir. Başka bir aksiyon kartı diğer tüm oyuncuların elinden iki kart atmasına neden olur. Yine de başka bir aksiyon kartı mevcut dönüş için hiçbir şey yapmaz, ancak bir oyuncunun bir sonraki dönüşünde ekstra kart çekmesine izin verir. İşleri daha da karmaşık hale getirmek için, oyuna sıklıkla yeni kartlar ekleyen yeni genişletmeler var. Bana öyle geliyor ki her aksiyon kartının sonuçlarını oyunun devlet makinesine kodlamak hem çirkin hem de uyumsuz olacak. Sıra Tabanlı Strateji Döngüsünün Cevabı bu sorunu çözmek için tasarımları ele alan bir ayrıntı düzeyine girmez.

Dönüş yapmak için genel modelin dönüş içinde gerçekleşen eylemlerle değiştirilebileceği gerçeğini kapsamak için ne tür bir programlama modeli kullanmalıyım? Oyun nesnesi her aksiyon kartının etkilerini takip etmeli mi? Veya, kartların kendi etkilerini uygulaması gerekiyorsa (örneğin, bir arabirim uygulayarak), onlara yeterli güç sağlamak için hangi kurulum gerekir? Bu soruna birkaç çözüm düşündüm, ancak bunu çözmek için standart bir yol olup olmadığını merak ediyorum. Özellikle, her oyuncunun oynanan bir aksiyon kartının bir sonucu olarak yapması gereken eylemleri izlemek için hangi nesnenin / sınıfın / neyin sorumlu olduğunu ve bunun normal sıradaki geçici değişikliklerle nasıl ilişkili olduğunu bilmek istiyorum. dönüş durumu makinesi.


2
Merhaba Apis Utilis ve GDSE'ye hoş geldiniz. Sorunuz iyi yazılmış ve ilgili sorulara başvurmanız harika. Ancak, sorunuz birçok farklı sorunu kapsıyor ve tam olarak ele almak için bir sorunun muhtemelen çok büyük olması gerekir. Yine de iyi bir yanıt alabilirsiniz, ancak sorununuzu biraz daha bozarsanız kendiniz ve site bundan yararlanacaktır. Belki daha basit bir oyun inşa etmeye ve Dominion'u kurmaya başlayabilirsin?
michael.bartnett

1
Her karta oyunun durumunu değiştiren bir komut dosyası vermeye başladım ve garip bir şey olmazsa, varsayılan dönüş kurallarına geri dönün ...
Jari Komppa

Yanıtlar:


11

Jari Komppa ile kart efektlerini güçlü bir betik diliyle tanımlamanın yol olduğunu kabul ediyorum. Ancak, maksimum esnekliğin anahtarının senaryo etkinliği olay işleme olduğuna inanıyorum.

Kartların sonraki oyun etkinlikleriyle etkileşime girmesine izin vermek için, oyun aşamalarının başlangıcı ve bitişi veya oyuncuların gerçekleştirebileceği belirli eylemler gibi belirli etkinliklere "komut dosyası kancaları" eklemek için bir komut dosyası API'sı ekleyebilirsiniz. Bu, bir kart oynandığında yürütülen komut dosyasının, belirli bir aşamaya bir sonraki ulaşıldığında çağrılan bir işlevi kaydedebileceği anlamına gelir. Her olay için kaydedilebilecek işlev sayısı sınırsız olmalıdır. Birden fazla olduğunda, kayıt sırasına göre çağrılırlar (elbette farklı bir şey söyleyen çekirdek bir oyun kuralı yoksa).

Bu kancaları tüm oyuncular veya sadece belirli oyuncular için kaydettirmek mümkün olmalıdır. Ayrıca, kancaların çağrılıp çağrılmayacakları konusunda kendileri için karar vermeleri olasılığını eklemenizi öneririm. Bu örneklerde bunu ifade etmek için hook fonksiyonunun (true veya false) dönüş değeri kullanılır.

Çift tur kartınız böyle bir şey yapar:

add_event_hook('cleanup_phase_end', current_player, function {
     setNextPlayer(current_player); // make the player take another turn
     return false; // unregister this hook afterwards
});

(Dominion'un "temizleme aşaması" gibi bir şeye sahip olup olmadığı hakkında hiçbir fikrim yok - bu örnekte, oyuncuların dönüşünün varsayımsal son aşaması)

Her oyuncunun beraberlik aşamasının başında ek bir kart çekmesine izin veren bir kart şöyle görünür:

add_event_hook('draw_phase_begin', NULL, function {
    drawCard(current_player); // draw a card
    return true; // keep doing this until the hook is removed explicitely
});

Hedef oyuncuyu bir kart oynadıklarında bir vuruş noktasını kaybettiren bir kart şöyle görünür:

add_event_hook('play_card', target_player, function {
    changeHitPoints(target_player, -1); // remove a hit point
    return true; 
});

Çizim kartları veya isabet puanlarını kaybetme gibi bazı oyun eylemlerini zor kodlamayacaksınız, çünkü tam tanımları - "kart çekmek" tam olarak ne demek - temel oyun mekaniğinin bir parçasıdır. Örneğin, bazı TCG'leri biliyorum, hangi nedenle olursa olsun bir kart çekmeniz ve desteniz boş olduğunda oyunu kaybedersiniz. Bu kural, kart çekmenizi sağlayan her karta basılmaz, çünkü kural kitabındadır. Bu nedenle, her kartın komut dosyasında da bu kayıp durumunu kontrol etmek zorunda kalmamalısınız. Bunun gibi şeyleri kontrol etmek, sabit kodlu drawCard()işlevin bir parçası olmalıdır (bu arada, aynı zamanda kancalanabilir bir olay için iyi bir aday olacaktır).

Bu arada: Gelecekte yapacağınız tüm belirsiz mekanik sürümler için önceden plan yapabilmeniz olası değildir , bu yüzden ne yaparsanız yapın, gelecekte bir sonraki sürümler için yeni işlevler eklemeniz gerekecektir (bu arada) durumda, bir konfeti atma mini oyunu).


1
Vay. Bu kaos konfeti.
Jari Komppa

Mükemmel cevap, @Philipp, ve bu Dominion'da yapılan çok şeyle ilgileniyor. Ancak, bir kart oynandığında hemen gerçekleşmesi gereken eylemler vardır, yani başka bir oyuncuyu kütüphanesinin üst kartını ters çevirmeye zorlayan ve mevcut oyuncunun "Tut" veya "At" ifadesini vermesine izin veren bir kart oynanır. Bu tür acil eylemlerle ilgilenmek için etkinlik kancaları yazar mısınız, yoksa kartlar için ek komut yazma yöntemleri bulmanız gerekir mi?
fnord

2
Bir şeyin hemen gerçekleşmesi gerektiğinde, komut dosyası uygun işlevleri doğrudan çağırmalı ve bir kanca işlevi kaydetmemelidir.
Philipp

@JariKomppa: Unglued seti kasten saçma ve anlamsız çılgın kartlarla doluydu. Benim favorim, belirli bir kelimeyi söylediklerinde herkesin hasar görmesini sağlayan bir karttı. 'Ben'i seçtim.
Jack Aidley

9

Bu sorunu - esnek bilgisayarlı kart oyunu motoru - bir süre önce düşündüm.

Öncelikle, Chez Geek veya Fluxx gibi karmaşık bir kart oyunu (ve inanıyorum ki, Dominion) kartların yazılabilir olmasını gerektiriyor. Temel olarak her kart, oyunun durumunu çeşitli şekillerde değiştirebilecek kendi senaryolarıyla gelir. Komut dosyaları şu anda aklınıza gelmeyecek şeyleri yapabileceğinden, ancak gelecekteki bir genişlemede gelebileceğinden, bu sisteme geleceğe yönelik bir kanıt sunmanıza izin verir.

İkincisi, katı "dönüş" sorunlara neden olabilir.

"Özel kartları" içeren bir tür "dönüş yığını" gerekir, örneğin "2 kart" atın. Yığın boş olduğunda, varsayılan normal dönüş devam eder.

Fluxx'da, bir dönüşün aşağıdaki gibi bir şeye gitmesi tamamen mümkündür:

  • N kart seç (mevcut kurallarda belirtildiği gibi, kartlarla değiştirilebilir)
  • Play N kartları (mevcut kurallarda belirtildiği gibi, kartlarla değiştirilebilir)
    • Kartlardan biri "3 tane al, 2 tane oyna" olabilir
      • Bu kartlardan biri "başka bir dönüş yap" olabilir
    • Kartlardan biri "atın ve çekin"
  • Sıranız başladığında yaptığınızdan daha fazla kart seçmek için kuralları değiştirirseniz, daha fazla kart seçin
  • Daha az kart için kuralları değiştirirseniz, diğer herkes kartları derhal atmalıdır
  • Sıranız sona erdiğinde, N kartınız olana kadar kartları tekrar atın (tekrar kartlarla değiştirilebilir), daha sonra başka bir tur atın (yukarıdaki karışıklıkta bazen "başka bir tur" kartı oynadıysanız).

..ve böyle devam eder. Bu nedenle, yukarıdaki kötüye kullanımı ele alabilecek bir dönüş yapısı tasarlamak oldukça zor olabilir. Buna "ne zaman" kartlarıyla ("chez geek" gibi) çok sayıda oyun ekleyin, burada "ne zaman" kartları normal akışı kesintiye uğratabilir, örneğin en son oynanan kartı iptal ederek ..

Bu yüzden temelde çok esnek bir dönüş yapısı tasarlamaya başladım, bir komut dosyası olarak tanımlanabilecek şekilde tasarladım (her oyunun temel oyun yapısını işleyen kendi "ana komut dosyasına" ihtiyaç duyacağı gibi). Daha sonra, herhangi bir kart yazılabilir olmalıdır; kartların çoğu muhtemelen garip bir şey yapmaz, ama diğerleri yapar. Kartların çeşitli özellikleri de olabilir - el altında tutulması, "ne zaman" oynanması, varlık olarak saklanıp depolanamayacakları (fluxx 'bekçileri' veya yiyecek gibi 'chez geek' gibi çeşitli şeyler) ...

Asla bunlardan hiçbirini uygulamaya başlamadım, bu yüzden pratikte başka zorluklar da bulabilirsiniz. Başlamanın en kolay yolu, uygulamak istediğiniz sistem hakkında bildiklerinizle başlamak ve bunları yazılabilir yollarla uygulamak, mümkün olduğunca az taş koymaktır, bu nedenle bir genişleme geldiğinde revize etmeniz gerekmez. temel sistem - çok. =)


Bu harika bir cevap ve eğer yapabilseydim ikisini de kabul ederdim. Daha düşük şöhrete sahip kişinin cevabını kabul ederek kravat kırdım :)
Apis Utilis

Hayır prob, şu ana kadar alışkınım .. =)
Jari Komppa

0

Hearthstone işleri göreli olarak yapıyor gibi görünüyor ve dürüstçe esneklik elde etmenin en iyi yolunun veri odaklı bir tasarıma sahip bir ECS motorudur. Bir kalbi klonu yapmaya çalışıyorum ve aksi takdirde imkansız olduğu kanıtlandı. Tüm kenar durumlarda. Bu garip kenar vakalarının birçoğuyla karşı karşıyaysanız, muhtemelen bu konuda en iyi yol. Bu tekniği deneyen son deneyimlerimden oldukça yanlıyım.

Düzenleme: İstediğiniz esneklik ve optimizasyon türüne bağlı olarak ECS bile gerekli olmayabilir. Bunu başarmanın sadece bir yolu. DOD Yanlışlıkla yordamsal programlama olarak düşünmüştüm ama çok ilgili. Demek istediğim ... dir. OOP ile tamamen veya en azından ortadan kaldırmayı düşünmelisiniz ve bunun yerine dikkatinizi verilere ve nasıl düzenlendiğine odaklanmalısınız. Kalıtım ve yöntemlerden kaçının. Bunun yerine kart verilerinizi değiştirmek için genel işlevlere (sistemlere) odaklanın. Her eylem, şablonlanmış bir şey veya herhangi bir tür mantık değil, bunun yerine ham verilerdir. Sistemlerinizin mantığı gerçekleştirmek için kullandığı yer. Tamsayı anahtar durumu veya bir dizi işlev işaretçisi erişmek için bir tamsayı kullanmak, giriş verilerinden istenen mantığı verimli bir şekilde bulmaya yardımcı olur.

İzlenecek temel kurallar, mantığı doğrudan verilerle birlikte bağlamaktan kaçınmanız, verilerin birbirine olabildiğince bağlı olmasını (istisnalar geçerli olabilir) ve erişilemeyen esnek mantık istediğinizde ... Verilere dönüştürmeyi düşünün.

Bunu yapmanın faydaları var. Her kartın eylem (ler) ini temsil etmek için bir numaralandırma değeri veya dizesi olabilir. Bu stajyer, kartları metin veya json dosyaları aracılığıyla tasarlamanızı ve programın otomatik olarak içe aktarmasını sağlar. Oyuncu eylemlerini bir veri listesi yaparsanız, özellikle bir kart, Hearthstone gibi geçmiş mantığa bağlıysa veya oyunu veya herhangi bir noktada bir oyunu tekrar oynamak istiyorsanız daha da esneklik sağlar. Yapay zekayı daha kolay yaratma potansiyeli var. Özellikle "davranış ağacı" yerine "yardımcı sistemler" kullanıldığında. Ağ oluşturma da daha kolay hale gelir, çünkü tüm polimorfik nesnelerin tel üzerinden nasıl aktarılacağını ve bunun ardından serileştirmenin nasıl kurulacağını anlamaya ihtiyaç duymak yerine, oyun nesnelerinize sahip olmanız, basit verilerden başka bir şey olamaz ve bu da hareket etmeyi gerçekten kolaylaştırır. Ve son fakat kesinlikle en az değil, bu daha kolay optimize etmenizi sağlar, çünkü kod hakkında endişelenmek için zaman kaybetmek yerine verilerinizi daha iyi organize edebilmenizi sağlar, böylece işlemci onunla daha kolay zaman geçirir. Python'un burada sorunları olabilir, ancak "önbellek hattı" na ve oyun geliştiriciyle nasıl ilişkili olduğuna bakın. Belki prototipleme için önemli değil ama yolda çok kullanışlı olacak.

Bazı yararlı bağlantılar.

Not: ECS, çalışma zamanında değişkenlerin (bileşenler olarak adlandırılır) dinamik olarak eklenmesine / kaldırılmasına izin verir. ECS'nin nasıl görünebileceğine dair örnek bir program (bunu yapmanın tonlarca yolu vardır).

unsigned int textureID = ECSRegisterComponent("texture", sizeof(struct Texture));
unsigned int positionID = ECSRegisterComponent("position", sizeof(struct Point2DI));
for (unsigned int i = 0; i < 10; i++) {
    void *newEnt = ECSGetNewEntity();
    struct Point2DI pos = { 0 + i * 64, 0 };
    struct Texture tex;
    getTexture("test.png", &tex);
    ECSAddComponentToEntity(newEnt, &pos, positionID);
    ECSAddComponentToEntity(newEnt, &tex, textureID);
}
void *ent = ECSGetParentEntity(textureID, 3);
ECSDestroyEntity(ent);

Doku ve konum verileri içeren bir grup varlık oluşturur ve sonunda doku bileşeni dizisinin üçüncü dizininde bulunan bir doku bileşenine sahip bir varlığı yok eder. İlginç görünüyor ama bir şeyler yapmanın bir yolu. İşte bir doku bileşeni olan her şeyi nasıl işleyeceğinize bir örnek.

unsigned int textureCount;
unsigned int positionID = ECSGetComponentTypeFromName("position");
unsigned int textureID = ECSGetComponentTypeFromName("texture");
struct Texture *textures = ECSGetAllComponentsOfType(textureID, &textureCount);
for (unsigned int i = 0; i < textureCount; i++) {
    void *parentEntity = ECSGetParentEntity(textureID, i);
    struct Point2DI *drawPos = ECSGetComponentFromEntity(positionID, parentEntity);
    if (drawPos) {
        struct Texture *t = &textures[i];
        drawTexture(t, drawPos->x, drawPos->y);
    }
}

1
Bu yanıt , veri odaklı ECS'nizi kurmanızı ve bu sorunu çözmek için uygulamayı nasıl önereceğiniz hakkında daha ayrıntılı bir ayrıntıya girerse daha iyi olurdu.
DMGregory

Güncellediğiniz için teşekkür ederiz.
Blue_Pyro

Genel olarak, birisine bu tür bir yaklaşımı nasıl kuracağını "söylemek" ama bunun yerine kendi çözümlerini tasarlamasına izin vermek kötü olduğunu düşünüyorum. Hem pratik yapmak için iyi bir yol hem de soruna potansiyel olarak daha iyi bir çözüm sağlar. Verileri bu şekilde mantıktan daha fazla düşünürken, aynı şeyi başarmanın birçok yolu olduğu ve her şeyin uygulamanın ihtiyaçlarına bağlı olduğu ortaya çıkar. Programcı zamanı / bilgisi kadar.
Blue_Pyro
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.