- Đây là bài hướng dẫn mở rộng kiến trúc ECS và lập trình siêu ngôn ngữ trên môi trường phát triển game Common Lisp bằng một ví dụ dungeon crawler thực tế
- Sau khi đọc bản đồ XML của Tiled bằng cl-tiled, dữ liệu được chuyển sang các component ECS thay vì dùng trực tiếp đối tượng CLOS, qua đó tách riêng việc render, va chạm và quản lý bộ nhớ
- Kết hợp tile prefab, con trỏ hình ảnh, chỉ mục cha-con và finalizer để tránh tải trùng lặp và double free, đồng thời tận dụng thuộc tính tùy biến của Tiled như dữ liệu
- Người chơi và kẻ địch được xử lý bằng các hệ thống ECS để di chuyển, chuyển hoạt ảnh và xử lý va chạm; kẻ địch truy đuổi bằng tìm đường A* dựa trên cl-astar để né tường
- Hoàn thiện một ví dụ dungeon crawler nhỏ khoảng 500 dòng với UI dựa trên Nuklear, đối tượng tường thuật, tạm dừng và cả điều kiện chiến thắng
Bắt đầu dự án và chạy cơ bản
- Sử dụng kiến trúc Entity-Component-System và các kỹ thuật lập trình siêu ngôn ngữ đã đề cập ở Part 1 để tạo một dungeon crawler nhỏ có UI
- Demo binary có thể chạy và mã nguồn có tại ecs-tutorial-2 GitHub repository
- Môi trường phát triển giả định là môi trường phát triển game Common Lisp ở Part 1, và cập nhật bản phân phối Quicklisp trong SBCL REPL
(ql-util:without-prompting (ql:update-all-dists))
- Tạo dự án mới
ecs-tutorial-2bằng mẫu cookiecutter-lisp-game, và trong ví dụ chọnliballegrolàm backend - Liên kết thư mục dự án vào Quicklisp
local-projects, sau đó đổi kích thước cửa sổ trongsrc/main.lispthành 1280×800 - Chạy
(ql:quickload :ecs-tutorial-2)và(ecs-tutorial-2:main)sẽ hiển thị một cửa sổ đen ở độ phân giải đã chỉ định cùng bộ đếm FPS
Bản đồ Tiled và kho lưu trữ ECS
- Sử dụng trình biên tập bản đồ mã nguồn mở Tiled để tạo bản đồ dungeon
- Tiled là công cụ đa nền tảng, đa engine và lưu dữ liệu bản đồ ở dạng XML
- Trong Common Lisp, cl-tiled tải file Tiled thành các đối tượng Lisp
- Bộ tileset ví dụ sử dụng Dungeon Tileset II - Extended
- Vì các tile gốc 16×16 quá nhỏ, chúng được phóng to 200% bằng ImageMagick để dùng thành tile 32×32
level1.tmxvà file tileset có thể tải trongResources.zipdo bài hướng dẫn cung cấp
- Thêm phụ thuộc
cl-tiledvàoecs-tutorial-2.asd, tạo mớisrc/map.lispđể tách riêng mã tải và hiển thị bản đồ - Trong
src/package.lisp, đăng kýcl-tiledvới biệt danh cục bộ làtiled
Vì sao chuyển đối tượng CLOS sang component ECS
cl-tiledtrả về dữ liệu bản đồ dưới dạng đối tượng CLOS, nên rất thuận tiện để khám phá trong REPL- Nhưng nếu dùng trực tiếp các đối tượng này trong vòng lặp game, chi phí dispatch khi chạy có thể tăng cao
- Một cửa sổ 1280×800 lấp đầy bằng tile 32×32 sẽ cần ít nhất 40×25 = 1000 tile
- Trong một demo riêng, khi bật render bản đồ trên Ryzen 5 3600 12 lõi, FPS giảm từ 20.000 xuống 600
- Tức là tăng thêm khoảng
1/600 - 1/20000 = 0.0016giây mỗi frame, hay hơn 1,5ms
- Nếu chuyển dữ liệu do
cl-tiledđọc sang kho lưu trữcl-fast-ecs, có thể giảm dispatch và cải thiện khả năng tận dụng CPU cache - Thêm phụ thuộc
cl-fast-ecs, rồi gọiecs:make-storagetronginitvàecs:run-systemstrongupdate
Component bản đồ, tile và prefab
maplà tag component biểu thị entity bản đồ đã được nạpmap-tilebiểu thị từng tile riêng lẻ và có slot Booleanobstacleđể đánh dấu vật cản như tường hoặc cửa đóng- Component
parentcho biết tile và các đối tượng liên quan đến bản đồ là con của entity bản đồ nào- Chỉ định
:index childrencho slotentityđể tìm nhanh các entity con của một parent cụ thể - Chỉ mục này dựa trên bảng băm open-address, cung cấp tra cứu trung bình O(1) nhưng có chi phí cập nhật khi tạo hoặc xóa
- Chỉ định
- Thêm hook vào
ecs:*entity-deleting-hook*để khi xóa entity cha thì các entity con tìm được qua chỉ mụcchildrencũng bị xóa theo - Component
imagechỉ lưu con trỏ C trỏ tớiALLEGRO_BITMAP- Hình ảnh tileset được cắt thành các mảnh 32×32 bằng
al_create_sub_bitmaprồi lưu con trỏ
- Hình ảnh tileset được cắt thành các mảnh 32×32 bằng
map-tile-prefablà prefab tile mang ID toàn cụcgidcủa tile trong Tiled- Với
gid, chỉ định:index map-tile-prefab :unique tđể tìm một entity prefab duy nhất theo ID - Các tile thực trên bản đồ sẽ sao chép
imagev.v. từ prefab, còn vị trí được giữ riêng trong componentposition
- Với
- Finalizer của
imagechỉ gọial_destroy_bitmapkhi entity làmap-tile-prefab- Mục đích là tránh double free vì nhiều tile trên bản đồ cùng chia sẻ một con trỏ
ALLEGRO_BITMAP
- Mục đích là tránh double free vì nhiều tile trên bản đồ cùng chia sẻ một con trỏ
positionvàsizelưu tọa độ màn hình và kích thước dưới dạngsingle-float- Vì
liballegroxử lý tọa độ màn hình bằng số thực dấu chấm động đơn để tương thích OpenGL, nên ở đây cũng làm tương tự
- Vì
Render hình ảnh và tải bản đồ
- Hệ thống
render-imagesrender các entity cópositionvàimage- Bật và tắt sprite batching bằng
al_hold_bitmap_drawing - Vẽ hình tại tọa độ chỉ định bằng
al_draw_bitmap - Vì prefab không có
position, chúng không được xử lý trong hệ thống này
- Bật và tắt sprite batching bằng
load-bitmaplà hàm tải ảnh bọcal_load_bitmapbằngal:ensure-loadedtile->spectạo đặc tả đối tượng ECS để sinh prefab tile- Entity bản đồ cha
- Mảnh hình ảnh của tile
- ID tile toàn cục của Tiled
- Kích thước tile
load-tile-prefabkiểm tra prefab đã được nạp hay chưa qua chỉ mụcmap-tile-prefab, nếu chưa thì tạo bằngmake-objectload-tilekhi tạo entity tile thực trên bản đồ sẽ sao chép component từ prefab và thêmpositionload-mapduyệt tileset và layer từ đối tượng CLOS được đọc bằngtiled:load-map- Tải hình tileset và tạo prefab cho từng tile
- Tạo entity cho từng ô trong tile layer và sao chép dữ liệu prefab
- Thứ tự layer của Tiled được giữ nguyên theo thứ tự trong editor, và
make-entityđảm bảo số entity tăng dần- Vì hệ thống xử lý các entity cũ trước, tile của layer phía trên sẽ được vẽ sau và che layer phía dưới
- Cách lưu mọi tile thành entity riêng không phải là đáp án duy nhất; cũng có thể render trước bản đồ tĩnh vào một buffer
Hoạt ảnh tile
- Tiled hỗ trợ tile hoạt ảnh, nên có thể biểu diễn các yếu tố như đuốc hoặc đài phun ma thuật
- Thêm
common.lispvàanimation.lispđể tách các component dùng chung khỏi các component và system liên quan đến hoạt ảnh - Component
animation-framebiểu thị một frame của hoạt ảnhsequencelà tên hoạt ảnh và được lưu dưới kiểukeyword- Dùng chỉ mục
sequence-framesđể tìm các frame của một hoạt ảnh cụ thể durationlà thời lượng hiển thị frame tính bằng giây
animation-statelưu trạng thái hiện tại của tile hoạt ảnh thực tế trên bản đồsequencehiện tạiframehiện tạidurationcủa frame hiện tại- Thời gian
elapsedmà frame hiện tại đã được hiển thị
- Thêm dependency
let-plusđể viết mã chuyển frame gọn hơn - System
update-animationstăngelapsedlên theodt, và khi vượt quá thời lượng thì chuyển sang frame tiếp theo- Vì thời gian frame có thể nhỏ hơn
dtlớn, dùngfloorđể tính cần bỏ qua bao nhiêu frame - Dùng
truncateđể khi số frame vượt quá độ dài danh sách thì quay vòng về đầu - Đổi con trỏ bitmap của
imagesang bitmap của prefab frame kế tiếp
- Vì thời gian frame có thể nhỏ hơn
- Thời lượng hoạt ảnh được Tiled lưu theo mili giây, nên trong
animation->specsẽ được đổi sang đơn vị giây instantiate-animationtạoanimation-statecho tile entity thực tế, đồng thời khởi tạoelapsedbằng một số ngẫu nhiên giữa 0 vàdurationđể các hoạt ảnh giống nhau không đồng bộ hoàn toàn- Tile hoạt ảnh phải có thuộc tính Tiled
"sequence"- Nếu thiếu thuộc tính này, nó sẽ được tải với tên
NIL, không thể tìm bằng tên hoạt ảnh như mong đợi và có thể gây lỗi kiểu dữ liệu
- Nếu thiếu thuộc tính này, nó sẽ được tải với tên
Nhân vật người chơi và điều khiển
- Thêm
character.lispvà định nghĩa componentcharactercho các nhân vật có thể di chuyểnspeedlà tốc độ tính theo pixel mỗi giâytarget-x,target-ylà tọa độ đích di chuyển- Giá trị đích ban đầu được đặt là
single-float-nanđể nhân vật mới không tự nhiên di chuyển về góc trên bên trái
- Component tag
playerdùng slotbitvà:index player-entity :unique t- Đây là cấu trúc để tìm player entity trong O(1) bằng
(player-entity 1) - Không lưu player entity trong biến toàn cục
- Đây là cấu trúc để tìm player entity trong O(1) bằng
- Ở bản cài đặt ban đầu, ảnh orc được cắt từ tileset thành
player.png, rồi tạo người chơi bằngload-playertheo kiểu hardcode- Vị trí là
(64.0, 64.0) - Kích thước là 32×32
- Tốc độ là 100.0
- Vị trí là
- System
move-charactersdi chuyển nhân vật tới điểm đích- Nếu tọa độ đích là
NaNthì khởi tạo bằng vị trí hiện tại - Dùng
approx-equalthay vì so sánh trực tiếp số thực dấu phẩy động - Tính tọa độ mới bằng
atan,cos,sin, tốc độ vàdt
- Nếu tọa độ đích là
- System
control-playerđọc input phímW,A,S,Dđể cập nhật tọa độ đích- Dùng
al:with-current-keyboard-statevàal:key-down - Dùng
clampđể không đi ra ngoài biên màn hình - Chạy với
:after (move-characters)để tránh vấn đề khởi tạoNaNsau system di chuyển
- Dùng
Tải va chạm và object bằng thuộc tính Tiled
- Ban đầu, tường chỉ là ảnh thông thường giống tile sàn nên người chơi đi xuyên qua tường
- Tạo lớp
map-tilebằng custom type của Tiled và thêm member Booleanobstacle- Thêm thuộc tính
map-tilevào tile tường và đánh dấuobstacle
- Thêm thuộc tính
- Hàm
properties->specchuyển hash table thuộc tính của Tiled thành đặc tả object ECS- Custom class của Tiled được xử lý như component
- Member của class được xử lý như slot của component
- Ví dụ có dạng
((:map-tile :obstacle t))
load-tile-prefabđưa kết quảproperties->specvào đặc tả prefab- Nếu không có thuộc tính,
spec-adjoinsẽ thêm componentmap-tilemặc định vàobstaclenhận giá trị mặc địnhnil
- Nếu không có thuộc tính,
- Thêm slot
tile-hashvà chỉ mụctilesvào componentpositiontile-hashépx,ythành số nguyên rồi đóng gói thành một số nguyên 64-bit duy nhất- Có thể dùng chỉ mục
tilesđể tìm mọi entity nằm tại tọa độ góc trên bên trái của một tile cụ thể
tile-starttrả về tọa độ góc trên bên trái của tile lưới chứa một tọa độ bất kỳtile-obstacle-pkiểm tra xem trong các entity ở cùng tọa độ có tile nào làmap-tilevà cóobstaclelà true hay khôngobstaclepkiểm tra xem tile tại một tọa độ bất kỳ có phải vật cản hay khôngcontrol-playerkiểm tra các tile ở những góc liên quan của hình chữ nhật nhân vật theo hướng di chuyển, và nếu gặp vật cản thì trả tọa độ đích về vị trí hiện tại- Cách xử lý va chạm này chưa hoàn hảo
- Nếu thiết kế dựa trên tọa độ tâm nhân vật thì toán học và mã có thể đơn giản hơn, nhưng ví dụ giữ nguyên cách hiện tại để tránh tăng độ phức tạp
Tải người chơi và nhân vật hoạt ảnh từ bản đồ
- Thêm các custom class
charactervàplayertrong Tiledcharacterchỉ có member floatspeedtarget-x,target-yđược bỏ qua để dùng giá trị mặc địnhplayercó member intplayervới giá trị mặc định là 1
- Đặt nhân vật người chơi trong object layer của Tiled dưới dạng tile object, rồi gán thuộc tính
charactervàplayer load-mapđược mở rộng để xử lý cảtiled:object-layer- Thuộc tính object được chuyển thành component ECS bằng
properties->spec - Với
tiled:tile-object,load-tilesẽ sao chép dữ liệu tile và hoạt ảnh rồi thiết lập vị trí - Tọa độ object của Tiled lấy góc dưới bên trái làm chuẩn, nên cần trừ chiều cao object khỏi
yđể khớp với chuẩn góc trên bên trái
- Thuộc tính object được chuyển thành component ECS bằng
- Lời gọi
load-playerhardcode và chính hàm này đều được gỡ bỏ - Cấu trúc này đọc trực tiếp dữ liệu bản đồ Tiled thành object ECS, gần với lập trình hướng dữ liệu hơn
- Hoạt ảnh nhân vật dùng các sequence
orc-idlevàorc-runcủa orc được định nghĩa trong tileset change-animation-sequenceđổi hoạt ảnh hiện tại của entity- Nếu đã là cùng sequence thì không làm gì cả
- Tìm frame đầu tiên của sequence mới bằng chỉ mục
sequence-frames, rồi cập nhậtanimation-statevàimage-bitmap
move-characterschuyển sang:orc-idlekhi nhân vật đứng yên, và:orc-runkhi đang di chuyển
Kẻ địch, game over, tìm đường A*
- Component
enemycó hai slot cần cho hành vi của kẻ địchvision-range: khoảng cách bắt đầu nhìn thấy và phản ứng với người chơiattack-range: phạm vi tấn công
- Để kết thúc game, thêm biến toàn cục
*should-quit*, và vòng lặp chính sẽ thoát nếu giá trị này là true - Hệ thống
handle-enemieslấy tọa độ người chơi và so sánh với kẻ địch- Nếu người chơi ở trong tầm nhìn, đặt tọa độ mục tiêu của kẻ địch thành vị trí người chơi
- Nếu người chơi ở trong tầm tấn công, đặt
*should-quit*thành true và hiển thị hộp thoại thông báo nativeYou died
- Hoạt ảnh của kẻ địch dùng các sequence
demon-idle,demon-runmove-characterschọn hoạt ảnh orc cho người chơi và hoạt ảnh quỷ cho kẻ địch tùy theo kết quảhas-player-p
- Với cách đuổi trực tiếp, kẻ địch cũng đi xuyên tường nên thêm tìm đường A*
- Thêm cl-astar vào dependencies
- Thư viện này tạo các hàm tìm đường được tối ưu cho bài toán bằng macro
- Thay vì lưu đường đi trong mảng trong slot của component, mỗi điểm trên đường được biểu diễn bằng một entity riêng
path-pointcóx,y,traveller, vàtravellercó chỉ mụcpath-pointspathlưu đích cuối cùngdestination-x,destination-y- Tọa độ mục tiêu của
characterlà điểm đường đi kế tiếp, cònpathbiểu diễn đích cuối cùng
- Hệ thống
follow-pathlấy điểm đường đi đầu tiên và di chuyển nhân vật đến điểm đó- Khi đến điểm, xóa entity
path-pointtương ứng - Nếu không còn điểm nào nữa thì xóa component
path
- Khi đến điểm, xóa entity
find-pathđược định nghĩa bằnga*:define-path-finder- Kích thước world được tính bằng cách chia kích thước cửa sổ cho kích thước tile
- Dùng bộ đánh chỉ mục row-major
- Việc đã đến đích hay chưa được xác định bằng việc tọa độ tile có trùng nhau hay không
- Các láng giềng được liệt kê theo 8 hướng
- Vật cản hoặc di chuyển chéo cắt qua vật cản được gán chi phí
most-positive-single-floatđể khiến nó gần như bất khả thi - Heuristic dùng octile distance
- Nếu đã có đường đi cũ thì xóa các điểm đường đi và gán
pathmới - Mỗi điểm trong đường đi kết quả được tạo thành một entity có
path-pointvàparent
handle-enemiessẽ gọifind-pathkhi kẻ địch nhìn thấy người chơi mà đích của đường đi hiện tại khác vị trí người chơi- Sau thay đổi này, kẻ địch sẽ đuổi theo người chơi nhưng tránh vật cản khi di chuyển
UI game dựa trên Nuklear
- Cần GUI cho các yếu tố tường thuật, nhưng các thư viện GUI truyền thống như Qt hay GTK không phù hợp với UI game được vẽ trong graphics context của
liballegro - Dùng Nuklear làm thư viện UI
- Có binding Common Lisp cl-liballegro-nuklear để dùng cùng
liballegro - Binding này cũng cung cấp DSL cho giao diện khai báo
- Có binding Common Lisp cl-liballegro-nuklear để dùng cùng
- Thêm dependency
cl-liballegro-nuklear/declarativevà thêm file mớisrc/narrative.lisp - Trong package, đăng ký local nickname
uiđể tham chiếu ngắn gọn đếncl-liballegro-nuklear/declarative - Dùng Alegreya từ Google Fonts làm font UI, và đổi tên file thành
alegreya-sc.ttf ui:defwindow narrativeđịnh nghĩa hàm cửa sổ tường thuật- Vị trí cửa sổ được tính trong vùng trung tâm màn hình
- Hiển thị văn bản tự xuống dòng bằng
ui:label-wrap ui:button-label "Ok"trả về true khi được nhấn
- Nuklear là thư viện UI immediate mode
- Không giữ các object widget trong bộ nhớ như retained mode, mà render và xử lý ở mỗi frame
- Việc nhấn nút được xử lý bằng giá trị trả về và điều kiện ở mỗi frame chứ không phải callback
main.lisptải font UI và khởi tạo UI context bằngnk:allegro-init- Trong event loop, gọi
nk:input-begin,nk:allegro-handle-event,nk:input-end - Khi render, gọi
nk:allegro-render - Khi thoát, gọi
nk:allegro-shutdown,nk:allegro-font-del
- Trong event loop, gọi
Skin UI và object tường thuật
- UI mặc định khá đơn điệu nên dùng asset ảnh
fantasy-ui-borderscủa Kenney để tạo kiểu - Lưu các ảnh UI vào các biến toàn cục
*window-background*,*button-normal-background*,*button-hover-background*,*button-active-background* load-uitải ảnh bằngnk:allegro-create-image, cònunload-uigiải phóng resource ảnh phía C bằngnk:allegro-del-image- Gọi
load-uitronginit, và gọiunload-uikhi vòng lặp chính kết thúc - Đặt ảnh nền, ảnh theo trạng thái nút và màu chữ bằng đối số
:stylescủaui:defwindow - Component
narrativebiểu diễn các object dùng cho environmental storytellingtext: văn bản sẽ hiển thịshown: đã từng được hiển thị ít nhất một lần hay chưaactive: cửa sổ hiện có đang được kích hoạt hay khôngactivecó chỉ mụcactive-narratives
- Hệ thống
show-narrativehiển thị cửa sổ khi người chơi ở gần object tường thuật- Khoảng cách tương tác được tính từ
+interact-distance-factor+và kích thước tile của người chơi - Hiển thị nếu cửa sổ đã đang active, hoặc chưa từng được hiển thị, hoặc phím
Eđược nhấn - Có thể đóng cửa sổ bằng nút
Ok,Esc,SpacehoặcEnter
- Khoảng cách tương tác được tính từ
- Trong Tiled, tạo custom type
narrativevà thêm member stringtext - Để phát hiện va chạm và căn khớp chính xác, các object không thể đi xuyên phải được căn tọa độ theo lưới tile
- Vấn đề dungeon vẫn tiếp tục chuyển động khi cửa sổ tường thuật mở được ngăn bằng điều kiện chạy hệ thống
- Thêm
:when (null (active-narratives t))vàomove-charactersvàcontrol-player - Nếu có narrative đang active thì các hệ thống di chuyển và điều khiển sẽ không chạy
- Thêm
- Điều kiện chiến thắng được thêm bằng tag component
win- Nếu đóng cửa sổ trên object có cả
narrative, đặt*should-quit*thành true để kết thúc game
- Nếu đóng cửa sổ trên object có cả
Kết lại và phạm vi
- Ví dụ hoàn chỉnh dùng
cl-fast-ecs,cl-tiled,cl-astar,cl-liballegro-nuklearđể tạo một dungeon crawler kiểu Souls-like có environmental storytelling, AI kẻ địch và GUI - Quy mô triển khai khoảng 500 dòng code
- Toàn bộ code có trong kho GitHub, và ngoài code tutorial còn có thêm các khai báo kiểu tùy chọn dùng
declaim - Không đề cập đến sound design, cutscene, menu chính, chuyển level, hay “door problem”
- Autumn Lisp Game Jam 2024 sẽ diễn ra trên itch.io vào ngày 25 tháng 10 năm 2024, là sự kiện làm game trong 10 ngày bằng các phương ngữ Lisp rồi đánh giá và phản hồi lẫn nhau
- Phần này dựa trên tác phẩm dự thi Spring Lisp Game Jam 2023 Thoughtbound
- Ở phần tiếp theo, tác giả báo trước sẽ mở rộng quy mô và thêm AI nâng cao hơn để thử thách làm một game chiến thuật thời gian thực
1 bình luận
Ý kiến trên Hacker News
Ước gì mọi tutorial kỹ thuật đều được viết như thế này. Bố cục bài viết rất tốt, hầu như không có lỗi ngữ pháp, và mỗi khi giới thiệu một chủ đề mới thì đều giải thích với dung lượng vừa đủ, lại còn có ví dụ mã hoàn chỉnh và tư liệu trực quan cho thấy đoạn mã thực sự làm gì
Bài đủ dài để đi sâu vào tài liệu, nhưng cũng đủ độc lập để vẫn theo kịp ngay cả khi chưa đọc phần 1 và chỉ mới đụng Common Lisp vài tháng từ mấy năm trước. Tôi cũng từng dùng Clojure và Emacs Lisp khá nhiều
Bravo, awkravchuk/Andrew :^)
(Đăng chéo từ https://mxjn.me/2024/10/17/1)
Cũng có lợi thế là dễ lưu lại để dùng offline hoặc lưu trữ, có thể ghi chú, và tìm kiếm cũng dễ
Hiếm có điều gì trong giới kỹ thuật làm tôi xúc động như một dự án hay bài viết xuất sắc về Common Lisp. Đây thực sự là một món quà lớn
Tôi đã đọc phần 1 khi nó ra mắt, và thực sự rất mong được đọc phần này nữa. Xin gửi lời khen ngợi đến tác giả
Chỉ riêng
package.shvà toàn bộ phần quản lý build cho 3 hệ điều hành cũng đã là một lớp học bậc thầy rồi. Chỉ cần lướt qua kho GitHub thôi tôi cũng đã học được rất nhiềuThông thường tôi build ứng dụng dòng lệnh Common Lisp bằng SBCL hoặc LispWorks, nhưng lần tới có lẽ tôi sẽ thử với ECL. Việc có cả build cho macOS lẫn Linux thật tuyệt, và cũng có vẻ thú vị khi thử điều gì đó mới
Bài viết rất hay. Tôi đang phát triển một game bắn phép thuật góc nhìn người thứ ba nhiều người chơi, bằng Lisp, chính xác hơn là ClojureScript. Đây là một game 3D trên nền web, và tôi định sẽ viết blog về hành trình này, bao gồm cả các công cụ và lớp trừu tượng tôi đã tạo ra cho dự án
Nếu quan tâm thì bản demo ở đây: https://wizardmasters.io
Bản thân bài viết thì thực sự rất chắc tay, nhưng khi nhìn vào quy trình thiết lập ở phần 1, trải dài qua chính Common Lisp, Python, C và nhiều bước khác nhau, thì cũng phần nào cho thấy vì sao CL lại không quá phổ biến, đặc biệt với các lập trình viên trẻ
Thật đáng tiếc, và sẽ rất tốt nếu có ai đó chịu khó làm cho ngôn ngữ dễ tiếp cận hơn ở khía cạnh cài đặt
Theo tôi hiểu thì nó tập trung nhiều hơn vào chuyện có quá nhiều lựa chọn và các thiết lập mặc định lâu đời trông đã lỗi thời
Vòng lặp sự kiện là một ví dụ tuyệt vời cho thấy
looplà một ngôn ngữ chuyên biệt cho lặp nghiêm túc đến mức nào. Thích hay ghét thì cũng vậy ;)loop? Không có cú pháp lạ không phải S-expression, cũng không cầndođể quay về cú pháp LispCó thể dùng
if/whenthông thường mà không cóelse/endtrông khó chịu, và nhìn chung còn bổ sung thêm nhiều tính năng hữu íchloopđã trở thành một trong những thành phần CL tôi thích nhấtBài này làm tôi nhớ đến "Caves of Clojure": https://stevelosh.com/blog/2012/07/caves-of-clojure-01/
Đúng lúc tuần này tôi vừa bắt đầu làm một roguelike bằng Python, nên thử làm bằng Lisp nghe cũng hay đấy
Cảm giác như bị lừa vậy. Tôi đến để học cách làm một game đơn giản, rồi lại học được quá nhiều thứ về toàn bộ lĩnh vực điện toán
Thật sự rất tuyệt