Basit bir macera oyununda davranışların uygulanması


11

Son zamanlarda basit bir metin tabanlı macera oyunu programlayarak eğlendim ve çok basit bir tasarım sorunu gibi görünen şeylere takılı kaldım.

Kısa bir bakış vermek için: oyun Roomnesnelere ayrılmıştır . Her Roombirinin Entityo odada bulunan nesnelerin bir listesi vardır. Her Entitybirinin basit bir string-> boolean haritası olan bir olay durumu ve string-> fonksiyon haritası olan bir eylem listesi vardır.

Kullanıcı girişi formu alır [action] [entity]. RoomUygun dönmek için işletme adını kullanır Entitysonra doğru işlevi bulmak işlemi adı kullanan nesneyi, ve çalıştırır.

Oda tanımını oluşturmak için her Roomnesne kendi açıklama dizesini görüntüler, ardından her birinin açıklama dizelerini ekler Entity. EntityAçıklaması kendi durumuna bağlı olarak değişebilir ( "kapı açık", vs, "Kapı kilitli" "Kapı kapalı olduğu").

Sorun şu: bu yöntemi kullanarak, hızlı bir şekilde uygulamak için gereken açıklama ve eylem işlevlerinin sayısı elden çıkıyor. Başlangıç ​​odamın tek başına 5 varlık arasında yaklaşık 20 işlevi var.

Tüm eylemleri tek bir işlevde birleştirebilir ve if-else / bunlar arasında geçiş yapabilirim, ancak bu hala varlık başına iki işlevdir. Ayrıca Entity, kapılar ve anahtarlar gibi ortak / genel nesneler için belirli alt sınıflar oluşturabilirim , ancak bu sadece beni şimdiye kadar alıyor.

DÜZENLEME 1: İstendiği gibi, bu eylem işlevlerinin sözde kod örnekleri.

string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
    if thisEntity["is_searched"] then
        return "There was nothing more in the bushes."
    else
        thisEntity["is_searched"] := true
        currentRoom.setEntity("dungeonDoorKey")
        return "You found a key in the bushes."
    end if

string dungeonDoorKeyUse(currentRoom, thisEntity, player)
    if getEntity("outsideDungeonDoor")["is_locked"] then
        getEntity("outsideDungeonDoor")["is_locked"] := false
        return "You unlocked the door."
    else
        return "The door is already unlocked."
    end if

Açıklama işlevleri, durumu kontrol ederek ve uygun dizeyi döndürerek hemen hemen aynı şekilde çalışır.

EDIT 2: Soru ifadelerimi revize etti. Diğer davranışlarla ortak davranışı paylaşmayan (belirli eylemlere devlet tabanlı yanıtlar) önemli sayıda oyun içi nesne olabileceğini varsayın. Bu benzersiz davranışları, her bir varlığa özgü eylem için özel bir işlev yazmaktan daha temiz ve daha sürdürülebilir bir şekilde tanımlamanın bir yolu var mı?


1
Ben bu "eylem fonksiyonları" ne açıklamak ve belki de bazı kod göndermek gerektiğini düşünüyorum, çünkü orada ne hakkında emin değilim.
2012'de

Kod eklendi.
Eric

Yanıtlar:


5

Her isim ve fiil kombinasyonu için ayrı bir işlev yapmak yerine, oyundaki tüm nesnelerin uyguladığı ortak bir arayüzün bulunduğu bir mimari kurmalısınız.

Başımın üst kısmındaki bir yaklaşım, oyununuzdaki tüm belirli nesnelerin genişlettiği bir Varlık nesnesi tanımlamak olacaktır. Her Varlığın farklı eylemleri farklı sonuçlarla ilişkilendiren bir tablosu (dilinizin ilişkilendirilebilir diziler için kullandığı veri yapısı ne olursa olsun) olacaktır. Tablodaki eylemler muhtemelen Dizeler (ör. "Açık") olurken, diliniz birinci sınıf işlevleri destekliyorsa ilişkili sonuç nesnede bile özel bir işlev olabilir.

Benzer şekilde, nesnenin durumu nesnenin çeşitli alanlarında saklanır. Örneğin, bir Bush'da bir dizi öğeye sahip olabilirsiniz ve daha sonra "arama" ile ilişkili işlev, bulunan nesneyi döndürerek ya da "Çalılarda daha fazla bir şey yoktu" dizesini etkiler.

Bu arada, genel yöntemlerden biri Entity.actOn (String action) gibi bir şeydir. Sonra bu yöntemde iletilen eylemi o nesnenin eylemler tablosu ile karşılaştırın; bu eylem tablodaysa, sonucu döndürün.

Şimdi, her nesne için gereken tüm farklı işlevler nesnenin içinde yer alacak ve bu nesneyi diğer odalarda tekrar etmeyi kolaylaştıracaktır (örneğin, bir kapıya sahip her odada Kapı nesnesini örnekleme)

Son olarak, XML veya JSON'daki tüm odaları veya her birini tanımlayın, böylece her bir oda için ayrı kod yazmanıza gerek kalmadan çok sayıda benzersiz odaya sahip olabilirsiniz. Oyun başladığında bu veri dosyasını yükleyin ve oyunu dolduran nesneleri somutlaştırmak için verileri ayrıştırın. Gibi bir şey:

<rooms>
  <room id="room1">
    <description>Outside the dungeon you see some bushes and a heavy door over the entrance.</description>
    <entities>
      <bush>
        <description>The bushes are thick and leafy.</description>
        <contains>
          <key />
        </contains>
      </bush>
      <door connection="room2" isLocked="true">
        <description>It's an oak door with stout iron clasps.</description>
      </door>
    </entities>
  </room>

  <room id="room2">
    etc.

İLAVE: aha, sadece FxIII'ın cevabını okudum ve sonuna yakın bu bit bana atladı:

(no things like <item triggerFlamesOnPicking="true"> that you will use just once)

Her ne kadar bir alev tuzağının tetiklenmesinin sadece bir kez gerçekleşeceğine katılmama rağmen (bu tuzağın birçok farklı nesne için yeniden kullanıldığını görebiliyordum) Sonunda kullanıcı girişine benzersiz tepki veren varlıklar hakkında ne demek istediğini anladım. Muhtemelen zindanınızdaki bir kapıyı, tüm varlıkları bir bileşen mimarisiyle (başka bir yerde ayrıntılı olarak açıklanmıştır) inşa ederek bir ateş topu tuzağına sahip olmak gibi şeylere hitap ederim.

Bu şekilde, her Kapı öğesi bir bileşen grubu olarak oluşturulur ve bileşenleri farklı varlıklar arasında esnek bir şekilde karıştırabilir ve eşleştirebilirim. Örneğin, kapıların çoğunda,

<entity name="door">
  <description>It's an oak door with stout iron clasps.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room2" />
  </components>
</entity>

ama ateş topu tuzağına sahip tek kapı

<entity name="door">
  <description>There are strange runes etched into the wood.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room7" />
    <fireballTrap />
  </components>
</entity>

ve o kapı için yazmam gereken tek benzersiz kod FireballTrap bileşenidir. Diğer tüm kapılarla aynı Kilit ve Portal bileşenlerini kullanırdı ve daha sonra bir hazine sandığında FireballTrap'ı veya bu sandığa FireballTrap bileşenini eklemek kadar basit bir şey kullanmaya karar verirsem.

Derlenmiş koddaki veya ayrı bir komut dosyası oluşturma dilindeki tüm bileşenleri tanımlayıp tanımlamam, zihnimde büyük bir ayrım değildir (her iki şekilde de kodu bir yere yazacaksınız ), ancak önemli olan, yazmanız gereken benzersiz kod miktarı. Heck, seviye tasarımcıları / modülleri için esneklik konusunda endişelenmiyorsanız (sonuçta bu oyunu kendiniz yazıyorsunuz), tüm varlıkları Entity'den miras alabilir ve bir yapılandırma dosyası veya komut dosyası yerine kurucuya bileşenler ekleyebilir veya her neyse:

Door extends Entity {
  public Door() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
  }
}

TrappedDoor extends Entity {
  public TrappedDoor() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
    addComponent(new FireballTrap());
  }
}

1
Bu, ortak, tekrarlanabilir öğeler için geçerlidir. Ancak, kullanıcı girdilerine benzersiz şekilde yanıt veren varlıklar ne olacak? EntityYalnızca tek bir nesne için bir alt sınıf oluşturmak , kodu birlikte gruplandırır, ancak yazmam gereken kod miktarını azaltmaz. Yoksa bu kaçınılmaz bir tuzak mı?
Eric

1
Verdiğiniz örnekleri ele aldım. Aklını okuyamıyorum; hangi nesnelere ve girdilere sahip olmak istersiniz?
2012'de

Niyetlerimi daha iyi açıklamak için görevimi düzenledim. Örneğinizi doğru anlarsam, her varlık etiketi bazı alt sınıflarına karşılık gelir Entityve öznitelikler başlangıç ​​durumunu tanımlar. Varlığın alt etiketleri bu etiketle ilişkilendirilmiş herhangi bir eylem için parametre olarak hareket ediyorum, değil mi?
Eric

evet, fikir bu.
jhocking

Bileşenlerin çözümün bir parçası olacağını düşünmeliydim. Yardım için teşekkürler.
Eric

1

Ele aldığınız boyutsal problem oldukça normal ve neredeyse kaçınılmaz. Hem senin varlıkları ifade etmek için bir yol bulmak istiyorum coincise ve esnek .

Bir "konteyner" (jhocking cevabındaki çalı) tesadüfidir ama bunun yeterince esnek olmadığını görürsünüz .

Her zaman olacaktır, çünkü genel arabirimi bulmak ve davranışlarını belirlemek için yapılandırma dosyalarını kullanmayı denemek için önermiyoruz nahoş bir his olması bir kayanın arasına (standart ve sıkıcı kişiler, kolay tarif etmek) ve sert bir yerde ( benzersiz fantastik varlıklar ancak uygulamak için çok uzun).

Benim önerim davranışları kodlamak için yorumlanmış bir dil kullanmaktır .

Çalı örneğini düşünün: bir kaptır, ancak çalımızın içinde belirli öğeler olması gerekir; kap nesnesinde şunlar olabilir:

  • hikaye anlatıcısının öğe eklemesi için bir yöntem,
  • motorun içerdiği öğeyi göstermesi için bir yöntem,
  • oyuncunun bir öğe seçmesi için bir yöntem.

Bu eşyalardan birinde, çalıyı yakan bir alev yakan bir mekanizmayı tetikleyen bir ip var ... (görüyorsun, aklını okuyabilirim, böylece sevdiğin şeyleri biliyorum).

Birisi bir kaptan her öğe seçtiğinde , ilgili ekstra kodu ana programınızdan yürüttüğünüz bir kancaya yerleştiren bir yapılandırma dosyası yerine bu çalıyı tanımlamak için bir komut dosyası kullanabilirsiniz .

Artık birçok mimari seçeneğiniz var: davranış araçlarını kod dilinizi veya komut dosyası dilini (kapsayıcılar, kapı benzeri vb.) Kullanarak temel sınıflar olarak tanımlayabilirsiniz. Bu şeylerin amacı , basit davranışları yavaşça toplayan varlıkları tanımlamanıza ve bunları bir komut dosyası dilindeki bağları kullanarak yapılandırmanıza izin vermektir .

Tüm varlıkların komut dosyası tarafından erişilebilir olması gerekir: her varlıkla bir tanımlayıcı ilişkilendirebilir ve bunları komut dosyası dili komut dosyasının uzantısı içinde dışa aktarılan bir kaba koyabilirsiniz.

Komut dosyası oluşturma stratejilerini <item triggerFlamesOnPicking="true">kullanmak, tek bir kod satırı ekleyerek garip beaviourları (eğlenceli olanlar) ifade etmenizi sağlarken , yapılandırmanızı basit tutmanızı sağlar (böyle bir şey kullanmazsınız).

Birkaç kelimeyle: kod çalıştırabilen yapılandırma dosyası olarak komut dosyaları.

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.