Làm thế nào để Lua làm việc như một ngôn ngữ kịch bản trong các trò chơi?


67

Tôi hơi mơ hồ về chính xác Lua là gì và một trò chơi được lập trình trong C ++ sẽ sử dụng nó như thế nào. Tôi đang hỏi chủ yếu về cách nó được biên dịch và chạy.

Chẳng hạn, khi bạn sử dụng một chương trình được viết bằng C ++, sử dụng các tập lệnh Lua: mã trong Lua chỉ gọi các hàm trong chương trình chính được viết bằng C ++ và hoạt động như một lớp không biên dịch đang chờ được biên dịch và thêm vào đống bộ nhớ của C ++ chương trình?

Hay nó hoạt động như một tập lệnh bash trong Linux, nơi nó chỉ thực thi các chương trình tách biệt hoàn toàn với chương trình chính?

Câu trả lời:


90

Scripting là một sự trừu tượng hóa lập trình trong đó bạn (về mặt khái niệm) có một chương trình (script) chạy bên trong một chương trình khác (máy chủ lưu trữ). Trong hầu hết các trường hợp, ngôn ngữ mà bạn viết tập lệnh khác với ngôn ngữ mà máy chủ được viết, nhưng bất kỳ sự trừu tượng hóa chương trình nào bên trong chương trình đều có thể được coi là kịch bản.

Về mặt khái niệm, các bước phổ biến để kích hoạt tập lệnh là như sau (Tôi sẽ sử dụng giả cho máy chủ và giả cho tập lệnh. Đây không phải là các bước chính xác, nhưng giống như dòng chảy chung mà bạn kích hoạt tập lệnh)

  1. Tạo một máy ảo trong chương trình máy chủ:

    VM m_vm = createVM();
  2. Tạo một hàm và hiển thị nó cho VM:

    void scriptPrintMessage(VM vm)
    {
        const char* message = getParameter(vm, 0); // first parameter
        printf(message);
    }
    
    //...
    
    createSymbol(m_vm, "print", scriptPrintMessage);

    Lưu ý rằng tên mà chúng ta tiếp xúc với hàm ( print) không phải khớp với tên bên trong của chính hàm ( scriptPrintMessage)

  3. Chạy một số mã script sử dụng chức năng:

    const char* scriptCode = "print(\"Hello world!\")"; // Could also be loaded from a file though
    doText(m_vm, scriptCode);

Thats tất cả để có nó. Chương trình sau đó chảy theo cách sau:

  1. Bạn gọi doText(). Điều khiển sau đó được chuyển đến máy ảo, nó sẽ thực thi văn bản bên trong scriptCode.

  2. Mã script tìm thấy một biểu tượng được xuất trước đó print. Sau đó nó sẽ chuyển điều khiển đến chức năng scriptPrintMessage().

  3. Khi scriptPrintMessage()kết thúc, điều khiển sẽ chuyển trở lại máy ảo.

  4. Khi tất cả văn bản trong scriptCodeđã được thực thi, doText()sẽ kết thúc và điều khiển sẽ được chuyển trở lại chương trình của bạn trên dòng sau đó doText().

Vì vậy, nói chung, tất cả những gì bạn đang làm là chạy một chương trình bên trong một chương trình khác. Về mặt lý thuyết, không có gì bạn có thể làm với các tập lệnh mà bạn không thể làm mà không có chúng, nhưng sự trừu tượng này cho phép bạn thực hiện một số điều thú vị thực sự dễ dàng. Một số trong số họ là:

  • Tách biệt các mối quan tâm: đó là một mô hình phổ biến để viết một công cụ trò chơi bằng C / C ++ và sau đó là trò chơi thực tế bằng ngôn ngữ kịch bản như lua. Thực hiện đúng, mã trò chơi có thể được phát triển hoàn toàn độc lập với chính công cụ.

  • Tính linh hoạt: ngôn ngữ kịch bản thường được diễn giải và do đó, một sự thay đổi trong kịch bản sẽ không nhất thiết phải xây dựng lại toàn bộ dự án. Hoàn thành đúng, bạn thậm chí có thể thay đổi tập lệnh và xem kết quả mà không cần khởi động lại chương trình!

  • Tính ổn định và bảo mật: do tập lệnh đang chạy bên trong một máy ảo, nếu được thực hiện đúng, tập lệnh bị lỗi sẽ không làm hỏng chương trình máy chủ. Điều này đặc biệt quan trọng khi bạn cho phép người dùng viết kịch bản của riêng họ vào trò chơi của bạn. Hãy nhớ rằng bạn có thể tạo nhiều máy ảo độc lập như bạn muốn! (Tôi đã từng tạo một máy chủ MMO trong đó mỗi trận đấu chạy trên một máy ảo lua riêng)

  • Các tính năng ngôn ngữ: khi sử dụng ngôn ngữ tập lệnh, dựa trên sự lựa chọn của bạn cho ngôn ngữ máy chủ và ngôn ngữ tập lệnh, bạn có thể sử dụng các tính năng tốt nhất mà mỗi ngôn ngữ cung cấp. Đặc biệt, coroutines của lua là một tính năng rất thú vị rất khó thực hiện trong C hoặc C ++

Kịch bản không hoàn hảo mặc dù. Có một số nhược điểm phổ biến khi sử dụng tập lệnh:

  • Việc gỡ lỗi trở nên rất khó khăn: thông thường, các trình gỡ lỗi có trong các IDE thông thường không được thiết kế để gỡ lỗi mã bên trong các tập lệnh. Bởi vì điều này, gỡ lỗi theo dõi giao diện điều khiển là phổ biến hơn nhiều so với tôi muốn.

    Một số ngôn ngữ kịch bản lệnh như lua có các phương tiện gỡ lỗi có thể được tận dụng trong một số IDE như Eclipse. Làm điều này là rất khó, và thật lòng tôi chưa bao giờ thấy gỡ lỗi kịch bản hoạt động cũng như gỡ lỗi gốc.

    Nhân tiện, sự thiếu quan tâm cực độ của các nhà phát triển Unity trong vấn đề này là sự chỉ trích chính của tôi đối với công cụ trò chơi của họ và lý do chính khiến tôi không sử dụng nó nữa, nhưng tôi lạc đề.

  • Tích hợp IDE: Không chắc là IDE của bạn sẽ biết các chức năng nào đang được xuất từ ​​chương trình của bạn và do đó, các tính năng như IntelliSense và các tính năng tương tự không có khả năng hoạt động với các tập lệnh của bạn.

  • Hiệu năng: Là các chương trình thường được hiểu và có nghĩa là cho một máy ảo trừu tượng có kiến ​​trúc có thể khác với phần cứng thực, các tập lệnh thường được thực thi chậm hơn so với mã gốc. Một số máy ảo như luaJIT và V8 thực hiện công việc rất tốt. Điều này có thể đáng chú ý nếu bạn sử dụng một số tập lệnh rất nặng.

    Thông thường, thay đổi ngữ cảnh (máy chủ-tập lệnh và tập lệnh thành máy chủ) rất tốn kém, vì vậy bạn có thể muốn giảm thiểu chúng nếu bạn gặp vấn đề về hiệu suất.

Làm thế nào bạn sử dụng các kịch bản của bạn là tùy thuộc vào bạn. Tôi đã thấy các tập lệnh được sử dụng cho những thứ đơn giản như tải cài đặt, phức tạp như tạo toàn bộ trò chơi bằng ngôn ngữ kịch bản với một công cụ trò chơi rất mỏng. Tôi thậm chí đã từng thấy một công cụ trò chơi rất kỳ lạ pha trộn kịch bản lua và JavaScript (thông qua V8).

Viết kịch bản chỉ là một công cụ. Làm thế nào bạn sử dụng nó để làm cho các trò chơi tuyệt vời là hoàn toàn tùy thuộc vào bạn.


Tôi thực sự mới với điều này, nhưng tôi sử dụng Unity. Bạn đã đề cập vài điều về Unity, bạn có thể nói rõ hơn về điều đó không? Tôi có nên sử dụng cái gì khác?
Tokamocha

1
Tôi sẽ không sử dụng printftrong ví dụ của bạn (hoặc sử dụng ít nhất printf("%s", message);)
ratchet freak

1
@ratchetfreak: Dấu chấm phẩy ở cuối tin nhắn của bạn kèm theo dấu ngoặc đơn đang nháy mắt với tôi ...
Panda Pajama

1
Một trong những câu trả lời hay nhất tôi từng thấy trên trang này trong một thời gian dài; nó xứng đáng với mọi upvote nó nhận được. Hoàn thành rất tốt.
Steven Stadnicki

1
Đứa trẻ áp phích cho Lua trong các trò chơi là World of Warcraft, trong đó phần lớn giao diện người dùng được viết bằng Lua và hầu hết có thể được thay thế bởi người chơi. Tôi ngạc nhiên khi bạn không đề cập đến nó.
Michael Hampton

7

Nói chung, bạn liên kết hoặc hiển thị một số chức năng gốc cho Lua (thường sử dụng thư viện tiện ích để làm như vậy, mặc dù bạn có thể làm điều đó bằng tay). Điều đó cho phép mã Lua thực hiện cuộc gọi thành mã C ++ bản địa của bạn khi trò chơi của bạn thực thi mã Lua đó. Theo nghĩa này, giả định của bạn rằng mã Lua chỉ gọi vào mã gốc là đúng (mặc dù Lua có thư viện chức năng tiêu chuẩn riêng có sẵn; bạn không cần phải gọi mã gốc của mình cho mọi thứ).

Bản thân mã Lua được diễn giải bởi thời gian chạy Lua, là mã C mà bạn liên kết như một thư viện (thường) trong chương trình của riêng bạn. Bạn có thể đọc thêm về cách Lua hoạt động tại trang chủ Lua . Cụ thể, Lua không phải là "một lớp không biên dịch" như bạn phỏng đoán, đặc biệt nếu bạn đang nghĩ về một lớp C ++, bởi vì C ++ gần như không bao giờ được biên dịch động trong thực tế. Tuy nhiên, thời gian chạy Lua và các đối tượng được tạo bởi các tập lệnh Lua mà trò chơi của bạn chạy sẽ tiêu tốn dung lượng trong bộ nhớ hệ thống của trò chơi.


5

Các ngôn ngữ script như Lua có thể được sử dụng theo nhiều cách. Như bạn đã nói, bạn có thể sử dụng Lua để gọi các hàm trong chương trình chính nhưng bạn cũng có thể chỉ có các hàm Lua được gọi từ phía C ++ nếu bạn muốn. Nói chung, bạn xây dựng một giao diện để cho phép một số linh hoạt với ngôn ngữ kịch bản bạn chọn để bạn có thể sử dụng ngôn ngữ kịch bản trong một loạt các kịch bản. Điều tuyệt vời về các ngôn ngữ script như Lua là chúng được hiểu thay vì được biên dịch để bạn có thể sửa đổi các tập lệnh Lua một cách nhanh chóng để bạn không phải ngồi chờ đợi trò chơi của bạn biên dịch chỉ để bạn biên dịch lại nếu bạn là người biên dịch Đã làm không phù hợp với thị hiếu của bạn.

Các lệnh Lua chỉ được gọi khi chương trình C ++ muốn thực thi chúng. Như vậy, mã sẽ chỉ được giải thích khi nó được gọi. Tôi đoán bạn có thể coi Lua là một tập lệnh bash thực thi tách biệt với chương trình chính.

Nói chung, bạn muốn sử dụng Ngôn ngữ kịch bản cho những thứ bạn có thể muốn cập nhật hoặc lặp đi lặp lại xuống dòng. Tôi đã thấy rất nhiều công ty sử dụng nó cho GUI nên nó cung cấp rất nhiều tùy chỉnh cho giao diện. Cung cấp cho bạn biết họ đã tạo giao diện Lua như thế nào, bạn cũng có thể tự sửa đổi GUI. Nhưng có rất nhiều con đường khác bạn có thể sử dụng Lua, như logic AI, thông tin về vũ khí, đối thoại của các nhân vật, v.v.


3

Hầu hết các ngôn ngữ kịch bản, bao gồm Lua, hoạt động trên Máy ảo ( VM ), về cơ bản là một hệ thống để ánh xạ một lệnh tập lệnh tới lệnh gọi CPU "thực" hoặc lệnh gọi hàm. VM Lua thường chạy trong cùng tiến trình với ứng dụng chính. Điều này đặc biệt đúng với các trò chơi sử dụng nó. API Lua cung cấp cho bạn một số chức năng mà bạn gọi trong ứng dụng gốc để tải và biên dịch các tệp script. Ví dụ, luaL_dofile()biên dịch tập lệnh đã cho thành Lua bytecode và sau đó chạy nó. Mã byte này sau đó sẽ được ánh xạ bởi VM chạy bên trong API thành các lệnh gọi và chức năng của máy gốc.

Quá trình kết nối một ngôn ngữ bản địa, chẳng hạn như C ++, với một ngôn ngữ kịch bản được gọi là ràng buộc . Trong trường hợp Lua, API của nó cung cấp các hàm giúp bạn hiển thị các hàm riêng cho mã tập lệnh. Vì vậy, ví dụ bạn có thể định nghĩa hàm C ++ say_hello()và làm cho hàm này có thể gọi được từ tập lệnh Lua. API Lua cũng cung cấp các phương thức để tạo các biến và bảng thông qua mã C ++ sẽ hiển thị cho các tập lệnh khi chúng được chạy. Bằng cách kết hợp các tính năng này, bạn có thể hiển thị toàn bộ các lớp C ++ cho Lua. Điều ngược lại cũng có thể, API Lua cho phép người dùng sửa đổi các biến Lua và gọi các hàm Lua từ mã C ++ gốc.

Hầu hết nếu không phải tất cả các ngôn ngữ script đều cung cấp API để tạo điều kiện ràng buộc mã script với mã gốc. Hầu hết cũng được biên dịch thành mã byte và chạy trong VM, nhưng một số có thể được hiểu theo từng dòng.

Hy vọng điều này sẽ giúp làm rõ một số câu hỏi của bạn.


3

Vì không ai đề cập đến điều này, tôi sẽ thêm nó ở đây cho những người quan tâm. Có cả một cuốn sách về chủ đề này có tên là Game Scripting Mastery . Đây là một văn bản tuyệt vời đã được viết cách đây khá lâu, nhưng nó vẫn hoàn toàn có liên quan đến ngày hôm nay.

Cuốn sách này sẽ không chỉ cho bạn thấy ngôn ngữ kịch bản phù hợp với mã gốc như thế nào, nó còn dạy cho bạn cách thực hiện ngôn ngữ kịch bản lệnh của riêng bạn. Mặc dù điều này sẽ quá mức cần thiết cho 99% người dùng, không có cách nào tốt hơn để hiểu điều gì đó hơn là thực sự thực hiện nó (ngay cả ở dạng rất cơ bản).

Nếu bạn muốn tự viết một công cụ trò chơi (hoặc bạn chỉ làm việc với một công cụ kết xuất), văn bản này là vô giá để hiểu làm thế nào một ngôn ngữ kịch bản có thể được kết hợp tốt nhất vào công cụ / trò chơi của bạn.

Và nếu bạn muốn tạo ngôn ngữ kịch bản của riêng mình, đây là một trong những nơi tốt nhất để bắt đầu (theo như tôi biết).


2

Thứ nhất, ngôn ngữ kịch bản là USUALLY không được biên dịch . Đó là một phần lớn của những gì thường định nghĩa chúng là ngôn ngữ kịch bản. Họ thường, thay vào đó, "giải thích." Điều này về cơ bản có nghĩa là có một ngôn ngữ khác (một ngôn ngữ được biên dịch, thường xuyên hơn không) đang đọc trong văn bản, trong thời gian thực và thực hiện các hoạt động, từng dòng một.

Sự khác biệt giữa ngôn ngữ này và các ngôn ngữ khác là ngôn ngữ kịch bản có xu hướng đơn giản hơn (thường được gọi là "cấp độ cao hơn"). Tuy nhiên, chúng cũng có xu hướng chậm hơn một chút, vì trình biên dịch có xu hướng tối ưu hóa rất nhiều vấn đề đi kèm với "yếu tố con người" của mã hóa, và kết quả là nhị phân có xu hướng nhỏ hơn và nhanh hơn để đọc cho máy. Ngoài ra, có ít chi phí hơn từ một chương trình khác cần được chạy để đọc mã đang được chạy, với các chương trình được biên dịch.

Bây giờ, bạn có thể nghĩ, "Ồ, tôi hiểu rằng nó dễ hơn một chút, nhưng tại sao một người nào đó sẽ từ bỏ tất cả hiệu suất đó để dễ sử dụng hơn một chút?"

Bạn sẽ không đơn độc trong giả định này, tuy nhiên mức độ dễ dàng bạn có được với các ngôn ngữ kịch bản, tùy thuộc vào những gì bạn đang làm với chúng, có thể rất đáng để hy sinh trong hiệu suất.

Về cơ bản: Đối với các trường hợp tốc độ phát triển quan trọng hơn tốc độ của chương trình đang chạy - sử dụng ngôn ngữ kịch bản. Có rất nhiều tình huống như thế này trong phát triển trò chơi. Đặc biệt là khi xử lý những việc nhỏ nhặt như xử lý sự kiện cấp cao.

Chỉnh sửa: Lý do lua có xu hướng khá phổ biến trong phát triển trò chơi là vì nó được cho là một trong những ngôn ngữ kịch bản công khai nhanh nhất (nếu không phải là nhanh nhất) trên trái đất. Tuy nhiên, với tốc độ tăng thêm này, nó đã hy sinh một số tiện lợi. Điều đó đang được nói, nó vẫn thuận tiện hơn nhiều so với làm việc với C hoặc C ++.

Chỉnh sửa quan trọng: Sau khi nghiên cứu sâu hơn, tôi thấy rằng có nhiều tranh cãi hơn về định nghĩa của ngôn ngữ kịch bản (xem phần phân đôi của Ousterhout ). Sự chỉ trích chính của việc định nghĩa một ngôn ngữ là "ngôn ngữ kịch bản" là nó không có ý nghĩa đối với cú pháp cũng như ngữ nghĩa của ngôn ngữ mà nó được giải thích hoặc biên dịch.

Mặc dù các ngôn ngữ thường được coi là "ngôn ngữ kịch bản" thường được hiểu theo truyền thống, thay vì được biên dịch, nhưng định nghĩa dài và ngắn của chúng là "ngôn ngữ kịch bản" thực sự phụ thuộc vào cách mọi người xem chúng và cách người tạo ra chúng định nghĩa chúng.

Nói chung, một ngôn ngữ có thể dễ dàng được coi là ngôn ngữ kịch bản (giả sử bạn đồng ý với sự phân đôi của Ousterhout) nếu nó đáp ứng các tiêu chí sau (theo bài viết được liên kết ở trên):

  • Chúng được gõ động
  • Họ có ít hoặc không có điều khoản cho các cấu trúc dữ liệu phức tạp
  • Các chương trình trong đó (script) được diễn giải

Ngoài ra, người ta thường chấp nhận rằng một ngôn ngữ là ngôn ngữ kịch bản nếu nó được thiết kế để tương tác và hoạt động cùng với ngôn ngữ lập trình khác (thường là ngôn ngữ không được coi là ngôn ngữ kịch bản).


1
Tôi không đồng ý với bạn ở dòng đầu tiên nơi bạn đã viết "ngôn ngữ kịch bản không được biên dịch". Lua trên thực tế được biên dịch thành mã byte trung gian trước khi thực hiện bởi trình biên dịch JIT. Một số tất nhiên được giải thích từng dòng, nhưng không phải tất cả.
glampert

Tôi không đồng ý với định nghĩa của bạn về "ngôn ngữ kịch bản." Có những ngôn ngữ có cách sử dụng kép (như Lua hoặc C #) được sử dụng tương đối phổ biến ở dạng biên dịch của chúng, thay vì dạng tập lệnh (hoặc ngược lại, như trường hợp của C #). Khi một ngôn ngữ được sử dụng làm ngôn ngữ kịch bản, nó được định nghĩa nghiêm ngặt là ngôn ngữ được diễn giải và không được biên dịch.
Gurgadurgen

Đủ công bằng, định nghĩa của bạn phù hợp hơn với Wikipedia: "Ngôn ngữ kịch bản hoặc ngôn ngữ kịch bản là ngôn ngữ lập trình hỗ trợ các tập lệnh, chương trình được viết cho một môi trường thời gian chạy đặc biệt có thể diễn giải (thay vì biên dịch) ...", đừng bận tâm Nhận xét của tôi rồi.
glampert

1
Ngay cả Lua thông thường cũng được biên dịch hoàn toàn thành mã byte, trình biên dịch JIT thậm chí có thể tạo ra nhị phân nguyên gốc. Vì vậy, không, Lua không được giải thích.
Oleg V. Volkov

Phần đầu tiên là iffy, tôi rất muốn downvote. Anh ta đúng là người cuối cùng được giải thích và @ OlegV.Volkov biên dịch JIT không tạo ra thứ gì đó được biên dịch. Biên dịch được xác định bởi thời gian biên dịch, mà Lua có, mã byte Lua không (JIT hoặc không có JIT). Cho phép không nhận được hạn của chúng tôi nhầm lẫn.
Alec Teal
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.