Làm cách nào để triển khai đối thoại phân nhánh trong javascript?


11

Tôi đang tạo một loại trò chơi tiểu thuyết trực quan rất cơ bản bằng JavaScript. Tôi là người mới bắt đầu, vì vậy tôi chỉ làm việc này để giải trí và học hỏi, và do kế hoạch không tốt, tôi đã gặp phải một chút vấn đề khi bạn đến một chi nhánh trong cuộc đối thoại.

Hiện tại tôi giữ tập lệnh cho trò chơi theo biến chuỗi và tôi chia mỗi cảnh bằng một thẻ như "# ~" thành các mảng nhỏ hơn để kịch bản trò chơi trông như thế này:

var script = "Hello World!#~How are you today?"
var gameText = script.split("#~");
//gameText[0]= Hello World!

Điều này hoạt động rất tốt cho các công cụ tuyến tính, nhưng tôi nên xử lý một nhánh trong cây đối thoại như thế nào? Phương pháp này có vẻ rất phức tạp, vì tôi sẽ phải biết chính xác mỗi con đường là bao lâu và sau đó nếu tôi cần thay đổi bất cứ điều gì thì đó sẽ là một vấn đề đau đầu.

Làm thế nào tôi có thể làm điều này một cách đơn giản hơn? Tôi đang cố gắng bám lấy JavaScript vanilla vì tôi muốn trò chơi hoạt động với Web Run Time.


Video này có thể cung cấp cho bạn một số ý tưởng: youtube.com/watch?v=XM2t5H7kY6Y
JCM

Gần đây tôi đã phải phát triển một cái gì đó cho việc này bằng Node và chọn cấu trúc tệp văn bản rất cơ bản. Bạn có thể xem mã kết quả và định dạng văn bản tại: github.com/scottbw/dialoguejs Mã là GPL, vui lòng sử dụng nó. Tôi chắc chắn sẽ không khó để thích nghi với trò chơi không phải là Node JS - thay thế phần "fs.load ()" bằng Ajax.
Scott Wilson

Kiểm tra Ink , một ngôn ngữ kịch bản câu chuyện phân nhánh, được phát triển bởi Inkle Studio . Có nhiều tích hợp Ink lập trình khác nhau (Java, Javascript, C #) và nhiều tài nguyên của bên thứ 3 . Ink cũng đã được sử dụng trong nhiều game thương mại. Cuối cùng, có một trình soạn thảo trên máy tính để bàn, Inky có thể kiểm tra cú pháp và 'phát' các hộp thoại phân nhánh của bạn.
Big Rich

Câu trả lời:


8

Câu trả lời của Philipp đã cho thấy hướng đi đúng đắn. Tôi chỉ nghĩ rằng cấu trúc dữ liệu là dài dòng không cần thiết. Các văn bản ngắn hơn sẽ dễ dàng hơn để viết và đọc.

Ngay cả khi các văn bản ngắn hơn sẽ làm cho thuật toán phức tạp hơn một chút, điều đó đáng để thực hiện, bởi vì bạn chỉ viết thuật toán một lần, nhưng phần lớn thời gian của bạn sẽ được dành để viết và duy trì câu chuyện. Do đó tối ưu hóa để làm cho phần dễ dàng hơn bạn sẽ dành nhiều thời gian để làm.

var story = [
  { m: "Hi!" },
  { m: "This is my new game." },
  { question: "Do you like it?", answers: [
    { m: "yes", next: "like_yes" },
    { m: "no", next: "like_no" },
  ] },
  { label: "like_yes", m: "I am happy you like my game!", next: "like_end" },
  { label: "like_no", m: "You made me sad!", next: "like_end" },
  { label: "like_end" },
  { m: "OK, let's change the topic" }
];

Một số giải thích cho thiết kế này:

Toàn bộ câu chuyện được viết trong một mảng. Bạn không phải cung cấp số, chúng được cung cấp tự động theo cú pháp mảng: mục đầu tiên có chỉ số 0, mục tiếp theo có chỉ mục 1, v.v.

Trong hầu hết các trường hợp, không cần thiết phải viết số của bước sau. Tôi giả định rằng hầu hết các dòng văn bản không phải là chi nhánh. Hãy đặt "bước tiếp theo là mục sau" thành một giả định mặc định và chỉ tạo ghi chú khi nó khác.

Đối với bước nhảy, sử dụng nhãn , không phải số. Sau đó, nếu sau này bạn thêm hoặc xóa một vài dòng, logic của câu chuyện sẽ được giữ nguyên và bạn không phải điều chỉnh các con số.

Tìm một sự thỏa hiệp hợp lý giữa sự rõ ràng và ngắn gọn. Ví dụ, tôi đề nghị viết "m" thay vì "message", bởi vì đó sẽ là lệnh được sử dụng thường xuyên nhất từ ​​trước đến nay, vì vậy việc viết ngắn sẽ khiến văn bản dễ đọc hơn. Nhưng không cần phải rút ngắn các từ khóa còn lại. (Tuy nhiên, hãy làm như bạn muốn. Điều quan trọng là làm cho nó dễ đọc nhất đối với bạn . Ngoài ra, bạn có thể hỗ trợ cả "m" và "message" làm từ khóa hợp lệ.)

Thuật toán cho trò chơi nên giống như thế này:

function execute_game() {
  var current_line = 0;
  while (current_line < story.length) {
    var current_step = story[current_line];
    if (undefined !== current_step.m) {

      display_message(current_step.m);
      if (undefined !== current_step.next) {
        current_line = find_label(current_step.next);
      } else {
        current_line = current_line + 1;
      }

    } else if (undefined !== current_step.question) {

      // display the question: current_step.question
      // display the answers: current_step.answers
      // choose an answer
      // and change current_line accordingly

    }
  }
}

Nhân tiện, những ý tưởng này được lấy cảm hứng từ Ren'Py , đây không phải là chính xác những gì bạn muốn (không phải JavaScript, không phải web), nhưng dù sao cũng có thể cung cấp cho bạn một số ý tưởng hay.


Cảm ơn bạn đã giải thích sâu sắc, tôi không biết rằng các mảng có thể hoạt động theo cách bạn và Philipp thể hiện, tôi nghĩ rằng họ chỉ có thể giữ Chuỗi hoặc số.
Người lập bản đồ im lặng

1
Tôi đã cố gắng thực hiện giải pháp của bạn và nó hoạt động khá tốt, mặc dù tôi nghĩ ở một số nơi ({ label: "like_yes"; m: "I am happy you like my game!"; next: "like_end" },)nên có ',' không phải là ';'. Ngoài ra chính xác những gì trong các dấu ngoặc nhọn được gọi là? Có phải đó là một đối tượng trong mảng? Nếu tôi muốn biết thêm thông tin về cách sử dụng cái này thì tôi sẽ tìm kiếm cái gì?
Người lập bản đồ im lặng

Vâng, {...}là một đối tượng. Trong JavaScript, đối tượng là một mảng kết hợp khóa-giá trị (với một số chức năng bổ sung, không được sử dụng trong ví dụ này), tương tự như mảng trong PHP hoặc Map trong Java. Để biết thêm thông tin, hãy xem các bài viết Wikipedia về JavaScript và ECMAScript và tài liệu được liên kết từ đó, đặc biệt là tài liệu ECMAScript chính thức.
Viliam Búr

1
btw lưu ý rằng cấu trúc dữ liệu mà ông đề xuất ở đây về cơ bản là JSON. Cá nhân tôi khuyên bạn nên chuyển sang JSON (chủ yếu là thêm dấu ngoặc nhọn và "cây": xung quanh toàn bộ, như {"cây": [etc]}) và sau đó bạn có thể lưu trữ các cây đối thoại của mình trong các tệp bên ngoài. Đưa dữ liệu của bạn vào các tệp bên ngoài mà trò chơi của bạn tải linh hoạt và mô đun hơn nhiều (do đó, tại sao cách tiếp cận đó là cách thực hành tốt nhất).
jhocking

5

Tôi muốn giới thiệu bạn để tạo ra một loạt các sự kiện hộp thoại. Mỗi sự kiện là một đối tượng chứa văn bản mà NPC nói và một loạt các phản hồi có thể có của người chơi, lần lượt là các đối tượng có văn bản phản hồi và chỉ mục của sự kiện theo sau phản hồi này.

var event = []; // create empty array

// create event objects and store them in the array
event[0] = { text: "Hello, how are you?",
             options: [    { response: "Bad", next: 1 },
                           { response: "Good", next: 2 }
                      ]
           };
event[1] = { text: "Why, what's wrong?",
             options: [    { response: "My dog ran away", next: 3},
                           { response: "I broke up with my girlfriend", next: 4}
                      ]
           };
event[2] = { text: "That's nice",

...

2

Bạn nên sử dụng một cách tiếp cận khác. JavaScript hỗ trợ các mảng và đối tượng, vậy tại sao không sử dụng một mục trên mỗi mục, tiết kiệm cho bạn tất cả các phần tách và cũng làm cho văn bản thực tế dễ dàng chỉnh sửa / đọc hơn?

Nếu bạn muốn, bạn có thể xem một số nguyên mẫu tôi đã thực hiện trong vài giờ cho # 1gam . Nguồn này được sử dụng miễn phí theo GPLv3 (Tôi hoàn toàn ổn nếu bạn không sử dụng GPL, nếu bạn chỉ sử dụng nó để lấy cảm hứng. Nhưng hãy cho tôi biết khi trò chơi của bạn kết thúc.). Chỉ không mong đợi văn bản tuyệt vời hoặc bất cứ điều gì như thế. ;)

Để đưa ra một lời giải thích ngắn về cách hoạt động của mã, hãy bỏ qua những thứ hoạt hình CSS và những thứ tương tự:

  • var data về cơ bản chứa toàn bộ câu chuyện với tất cả các lựa chọn có thể, v.v.
  • Mỗi "vị trí" (hoặc trang / mục) được xác định bằng ID. ID đầu tiên trong danh sách là start, thứ hai cwait, v.v.
  • Mỗi vị trí chứa hai yếu tố bắt buộc: Chú thích cũng như văn bản thực tế. Liên kết cho các quyết định được viết trong một số đánh dấu đơn giản theo mẫu [target location:display text].
  • Toàn bộ "ma thuật" đang diễn ra bên trong navigate(): Chức năng này làm cho các liên kết Markup trở nên nhấp chuột. Nó dài hơn một chút, vì tôi cũng đang xử lý một số văn bản tĩnh cho Hết chết ở đó. Phần quan trọng là hai cuộc gọi đầu tiên đến replace().
  • Các mục cuối tùy chọn xác định màu nền mới để hòa trộn, hỗ trợ tâm trạng chung của trò chơi.
  • Thay vì xác định các màu này, bạn cũng có thể thêm các liên kết trỏ đến các vị trí khác (lưu ý rằng mã này không được xử lý bởi mã của tôi; đó chỉ là một số ý tưởng để chứng minh điều này):

    'start': ['Waking up', 'You wake...', 'cwait:yell for help', 'cwait: wait a bit', 'clook: look around']

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.