Thực hiện hành vi trong một trò chơi phiêu lưu đơn giản


11

Gần đây tôi đã giải trí bằng cách lập trình một trò chơi phiêu lưu dựa trên văn bản đơn giản và tôi bị mắc kẹt với những gì có vẻ như là một vấn đề thiết kế rất đơn giản.

Để cho một cái nhìn tổng quan ngắn gọn: trò chơi được chia thành Roomcác đối tượng. Mỗi người Roomcó một danh sách các Entityđồ vật trong căn phòng đó. Mỗi cái Entitycó một trạng thái sự kiện, đó là một chuỗi đơn giản-> bản đồ boolean và một danh sách hành động, đó là một bản đồ chuỗi-> chức năng.

Đầu vào của người dùng có dạng [action] [entity]. Việc Roomsử dụng tên thực thể để trả về Entityđối tượng thích hợp , sau đó sử dụng tên hành động để tìm đúng hàm và thực thi nó.

Để tạo mô tả phòng, mỗi Roomđối tượng hiển thị chuỗi mô tả của riêng mình, sau đó nối thêm chuỗi mô tả của mỗi Entity. Các Entitymô tả có thể thay đổi dựa trên trạng thái của nó ( "Cánh cửa mở cửa", "Cánh cửa được đóng lại", "Cánh cửa bị khóa", vv).

Đây là vấn đề: sử dụng phương pháp này, số lượng các chức năng mô tả và hành động tôi cần thực hiện nhanh chóng vượt khỏi tầm kiểm soát. Phòng khởi đầu của tôi một mình có khoảng 20 chức năng giữa 5 thực thể.

Tôi có thể kết hợp tất cả các hành động thành một chức năng duy nhất và if-other / chuyển qua chúng, nhưng đó vẫn là hai chức năng cho mỗi thực thể. Tôi cũng có thể tạo Entitycác lớp con cụ thể cho các đối tượng chung / chung như cửa và khóa, nhưng điều đó chỉ giúp tôi đến nay.

EDIT 1: Theo yêu cầu, ví dụ mã giả của các hàm hành động này.

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

Các hàm mô tả hoạt động theo cách tương tự, kiểm tra trạng thái và trả về chuỗi thích hợp.

EDIT 2: Sửa đổi từ ngữ câu hỏi của tôi. Giả sử rằng có thể có một số lượng đáng kể các đối tượng trong trò chơi không chia sẻ hành vi chung (phản hồi dựa trên trạng thái đối với các hành động cụ thể) với các đối tượng khác. Có cách nào để tôi có thể định nghĩa các hành vi độc đáo này theo cách sạch hơn, dễ bảo trì hơn là viết một hàm tùy chỉnh cho từng hành động cụ thể của thực thể không?


1
Tôi nghĩ bạn cần giải thích những "chức năng hành động" này làm gì và có thể đăng một số mã, bởi vì tôi không chắc bạn đang nói gì về điều đó.
giật

Đã thêm mã.
Eric

Câu trả lời:


5

Thay vì tạo một chức năng riêng cho mọi sự kết hợp của danh từ và động từ, bạn nên thiết lập một kiến ​​trúc trong đó có một giao diện chung mà tất cả các đối tượng trong trò chơi thực hiện.

Một cách tiếp cận ngoài đỉnh đầu của tôi sẽ là xác định một đối tượng Thực thể mà tất cả các đối tượng cụ thể trong trò chơi của bạn mở rộng. Mỗi Thực thể sẽ có một bảng (bất kỳ cấu trúc dữ liệu nào mà ngôn ngữ của bạn sử dụng cho các mảng kết hợp) liên kết các hành động khác nhau với các kết quả khác nhau. Các hành động trong bảng có thể sẽ là Chuỗi (ví dụ: "mở") trong khi kết quả được liên kết thậm chí có thể là một hàm riêng trong đối tượng nếu ngôn ngữ của bạn hỗ trợ các hàm hạng nhất.

Tương tự, trạng thái của đối tượng được lưu trữ trong các trường khác nhau của đối tượng. Vì vậy, ví dụ, bạn có thể có một mảng các công cụ trong Bush, và sau đó chức năng được liên kết với "tìm kiếm" sẽ hoạt động trên mảng đó, hoặc trả về đối tượng được tìm thấy hoặc chuỗi "Không còn gì nữa trong các bụi rậm".

Trong khi đó, một trong các phương thức công khai là một cái gì đó giống như Entity.actOn (Chuỗi hành động) Sau đó, trong phương thức đó so sánh hành động được truyền với bảng hành động cho đối tượng đó; nếu hành động đó nằm trong bảng thì trả về kết quả.

Bây giờ tất cả các chức năng khác nhau cần thiết cho từng đối tượng sẽ được chứa trong đối tượng, giúp dễ dàng lặp lại đối tượng đó trong các phòng khác (ví dụ: khởi tạo đối tượng Cửa trong mỗi phòng có cửa)

Cuối cùng, xác định tất cả các phòng trong XML hoặc JSON hoặc bất cứ điều gì để bạn có thể có nhiều phòng duy nhất mà không cần phải viết mã riêng cho mỗi phòng. Tải tệp dữ liệu này khi trò chơi bắt đầu và phân tích dữ liệu để khởi tạo các đối tượng cư trú trong trò chơi của bạn. Cái gì đó như:

<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.

BỔ SUNG: aha, tôi vừa đọc câu trả lời của FxIII và bit này gần cuối đã nhảy ra với tôi:

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

Mặc dù tôi không đồng ý rằng bẫy lửa được kích hoạt là điều chỉ xảy ra một lần (tôi có thể thấy bẫy này được sử dụng lại cho nhiều đối tượng khác nhau) Tôi nghĩ cuối cùng tôi cũng hiểu ý bạn nói về các thực thể phản ứng duy nhất với đầu vào của người dùng. Tôi có lẽ sẽ giải quyết những thứ như làm một cánh cửa trong ngục tối của bạn có một quả cầu lửa bằng cách xây dựng tất cả các thực thể của tôi với một kiến ​​trúc thành phần (được giải thích chi tiết ở nơi khác).

Bằng cách này, mỗi thực thể Cửa được xây dựng như một gói các thành phần và tôi có thể linh hoạt trộn và kết hợp các thành phần giữa các thực thể khác nhau. Ví dụ, phần lớn các cửa sẽ có cấu hình giống như

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

nhưng một cánh cửa với một quả cầu lửa sẽ là

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

và sau đó mã duy nhất tôi sẽ phải viết cho cánh cửa đó là thành phần FireballTrap. Nó sẽ sử dụng các thành phần Khóa và Cổng giống như tất cả các cửa khác, và nếu sau đó tôi quyết định sử dụng FireballTrap trên rương kho báu hoặc thứ gì đó đơn giản như thêm thành phần FireballTrap vào rương đó.

Việc bạn có xác định tất cả các thành phần trong mã được biên dịch hay trong một ngôn ngữ kịch bản lệnh riêng biệt không phải là một sự khác biệt lớn trong tâm trí của tôi (dù bạn sẽ viết mã ở đâu đó ) nhưng điều quan trọng là bạn có thể giảm đáng kể số lượng mã duy nhất bạn cần viết. Heck, nếu bạn không quan tâm đến tính linh hoạt cho các nhà thiết kế / modder cấp độ (rốt cuộc bạn đang viết trò chơi này), bạn thậm chí có thể tạo tất cả các thực thể kế thừa từ Entity và thêm các thành phần trong hàm tạo thay vì tệp cấu hình hoặc tập lệnh hoặc bất cứ điều gì:

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
Điều đó làm việc cho các mục phổ biến, lặp lại. Nhưng những gì về các thực thể đáp ứng duy nhất cho đầu vào của người dùng? Tạo một lớp con Entitychỉ cho một đối tượng duy nhất nhóm mã lại với nhau nhưng không làm giảm số lượng mã tôi phải viết. Hay đó là một cạm bẫy không thể tránh khỏi trong vấn đề này?
Eric

1
Tôi đã giải quyết các ví dụ bạn đã đưa ra. Tôi không thể đọc được suy nghĩ của bạn; những đối tượng và đầu vào nào bạn muốn có?
jhocking

Chỉnh sửa bài viết của tôi để giải thích rõ hơn ý định của tôi. Nếu tôi hiểu đúng ví dụ của bạn, có vẻ như mỗi thẻ thực thể tương ứng với một số lớp con Entityvà các thuộc tính xác định trạng thái ban đầu của nó. Tôi đoán các thẻ con của thực thể đóng vai trò là tham số cho bất kỳ hành động nào mà thẻ đó được liên kết, phải không?
Eric

vâng, đó là ý tưởng.
jhocking

Tôi nên đã tìm ra rằng các thành phần sẽ là một phần của giải pháp. Cảm ơn đã giúp đỡ.
Eric

1

Vấn đề kích thước bạn giải quyết là khá bình thường và gần như không thể tránh khỏi. Bạn muốn tìm một cách để thể hiện các thực thể của mình vừa trùng khớp vừa linh hoạt .

Một "thùng chứa" (bụi cây trong câu trả lời jhocking) là một cách trùng hợp nhưng bạn thấy nó không đủ linh hoạt .

Tôi không khuyên bạn nên cố gắng tìm một giao diện chung và sau đó sử dụng file cấu hình để xác định hành vi, bởi vì bạn sẽ luôn có cảm giác khó chịugiữa một tảng đá (đơn vị tiêu chuẩn và nhàm chán, dễ dàng để mô tả) và một nơi khó khăn ( thực thể tuyệt vời độc đáo nhưng quá dài để thực hiện).

Đề nghị của tôi là sử dụng một ngôn ngữ thông dịch để mã hóa các hành vi.

Hãy suy nghĩ về ví dụ về bụi cây: đó là một thùng chứa nhưng bụi cây của chúng ta cần phải có các vật phẩm cụ thể bên trong; đối tượng container có thể có:

  • một phương pháp để người kể chuyện thêm mục,
  • một phương thức để động cơ hiển thị mục mà nó chứa,
  • một phương pháp để người chơi chọn một vật phẩm.

Một trong những vật phẩm này có một sợi dây kích hoạt một cỗ máy mà lần lượt đốt một ngọn lửa đốt cháy bụi cây ... (bạn thấy đấy, tôi có thể đọc được suy nghĩ của bạn để tôi biết những thứ bạn thích).

Bạn có thể sử dụng một tập lệnh để mô tả ống lót này thay vì tệp cấu hình đặt mã bổ sung có liên quan vào một hook mà bạn thực thi từ chương trình chính của mình mỗi khi ai đó chọn một mục từ một container.

Bây giờ bạn có rất nhiều lựa chọn kiến ​​trúc: bạn có thể định nghĩa các công cụ hành vi là các lớp cơ sở bằng ngôn ngữ mã của bạn hoặc ngôn ngữ kịch bản (những thứ như các thùng chứa, giống như cửa, v.v.). Các mục đích của điều theese là để cho bạn mô tả các thực thể easely tập hợp các hành vi đơn giảncấu hình chúng bằng các ràng buộc trên một ngôn ngữ kịch bản .

Tất cả các thực thể phải có thể truy cập được vào tập lệnh: bạn có thể liên kết một mã định danh với từng thực thể và đặt chúng vào một thùng chứa được xuất trong phần mở rộng của tập lệnh ngôn ngữ kịch bản.

Sử dụng các chiến lược kịch bản cho phép bạn giữ cho cấu hình của mình đơn giản (không có những thứ như thế <item triggerFlamesOnPicking="true">bạn sẽ sử dụng chỉ một lần) trong khi cho phép bạn thể hiện các đèn hiệu kỳ lạ (những thứ thú vị) thêm một số dòng mã

Trong vài từ: tập lệnh như tập tin cấu hình có thể chạy mã.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.