Sau khi đã đi qua các phần 9 và phần 10, chúng ta đã làm quen với một số ví dụ cụ thể và hiểu phần nào về cách hoạt động của MAXScript. Qua những ví dụ đó, có lẽ bạn đã bắt đầu nắm bắt được các khái niệm cơ bản và cách MAXScript được sử dụng để thực hiện các tác vụ trong 3ds Max.
Trong phần này, chúng ta cùng tìm hiểu về một khái niệm mới quan trọng trong MAXScript. Đó chính là struct (cấu trúc). Đây là một công cụ mạnh mẽ trong MAXScript, giúp bạn quản lý và tổ chức code của mình một cách hiệu quả hơn. Với struct, chúng ta có thể gom các biến và hàm liên quan lại với nhau, giúp việc truy xuất và thao tác trên dữ liệu trở nên dễ dàng và trực quan.
Điều này đặc biệt quan trọng khi bạn làm việc với các dự án lớn, có nhiều biến và hàm, và cần sự phối hợp của nhiều script nhỏ khác nhau. Lúc này, việc giữ code gọn gàng và có tổ chức là điều cần thiết để đảm bảo tính ổn định và dễ bảo trì của dự án. Hãy cùng bắt đầu.
MỤC LỤC
Vì sao bạn cần struct?
Với người mới, struct sẽ hơi phức tạp và gây ra nhầm lẫn lúc đầu. Điều này có thể khiến bạn bỏ cuộc và chọn các cách làm thủ công hơn. Hãy tin tôi, giữ bình tĩnh và kiên nhẫn! Với những dự án nhỏ, bạn sẽ không cần đến nó. Nhưng nếu bạn nghiêm túc với MAXScript, đây chắc chắn sẽ là thứ không thể thiếu.
Vì vậy, trước khi vào nội dung chính, tôi sẽ lấy một số ví dụ để bạn hiểu được giá trị của struct.
Ví dụ #1
Giả sử tôi có thông tin của 5 người như sau:
- Lan: mã nhân viên 87, số phòng 15, nặng 55kg, 32 tuổi.
- Tuấn: mã nhân viên 19, số phòng 30, nặng 68kg, 25 tuổi.
- Mai: mã nhân viên 63, số phòng 11, nặng 60kg, 40 tuổi.
- Minh: mã nhân viên 45, số phòng 27, nặng 85kg, 35 tuổi.
- Phương: mã nhân viên 72, số phòng 22, nặng 50kg, 28 tuổi.
Tôi cần viết một MAXScript, để dùng các số liệu này cho việc tính toán kích thước khi thiết kế. Tôi có thể khai báo các biến bằng Array như sau:
lan
= #(87, 15, 55, 32)
tuan
= #(19, 30, 68, 25)
mai
= #(63, 11, 60, 40)
minh
= #(45, 27, 85, 35)
phuong
= #(72, 22, 50, 28)
Giả sử, trong một đoạn code nào đó, tôi cần dữ liệu về cân nặng của Mai. Chúng ta sẽ làm như thế nào? Đúng vậy, chúng ta sẽ viết là mai[3]. Tức là lấy phần tử số 3 của array, tương ứng với cân nặng. Tuy nhiên, hãy chú ý vào thông tin của biến mai.
mai = #(63, 11, 60, 40)
Với các con số trong array này, đâu sẽ là số phòng của Mai? Có phải bạn vừa phải nhìn lại định nghĩa để ra được kết quả không? Bạn thấy đấy, nó rất bất tiện truy cập theo cách tổ chức thông thường, bởi bạn phải ghi nhớ thứ tự sắp xếp của các biến. Và đây mới chỉ là 1 ví dụ đơn giản với 4 tham số. Hãy tưởng tượng độ phức tạp khi có nhiều tham số hơn.
Ví dụ #2
Giả sử tôi có 10 đối tượng trong scene: Obj0, Obj1, Obj2,…, Obj9. Chúng có các thuộc tính riêng là center và color. Nếu khai báo theo cách thông thường, tôi sẽ phải tạo ra 20 biến với tên tương ứng cho từng đối tượng. Ví dụ:
ColorOfObj0 = black CenterOfObj0 = [120,50,30] ColorOfObj1 = yellow CenterOfObj4 = [160,90,70] ... ColorOfObj9 = gray CenterOfObj9 = [210,140,120]
Và cứ mỗi lần tạo ra một đối tượng mới, tôi sẽ phải khai báo thêm 2 biến tương ứng với nó. Điều này thật sự bất tiện nếu tôi làm việc với rất nhiều đối tượng, và số lượng biến lớn.
Ví dụ #3
Giả sử dự án của tôi cần viết 3 script khác nhau. Các script này chứa các function gần giống nhau về chức năng, chỉ hơi khác một chút về đối tượng sử dụng. Ví dụ, tôi có script dành cho khối Box, khối Shpere và khối Pyramid. Ta tạm gọi chúng là b.ms, s.ms và p.ms. Trong script dành cho khối Box b.ms, tôi đã viết một function có tên là calcArea để tính diện tích khối Box, và nó không nằm trong bất kỳ một rollout nào.
calcArea
b = (2 * (b.width * b.length + b.width * b.height + b.length * b.height))
Khi bắt tay vào viết function tương ứng cho khối Shpere, tức s.ms, vấn đề đã xảy ra. Tôi sẽ đặt tên cho function mới này như thế nào?
Nếu tôi đặt tên là calcArea giống như b.ms, khi chạy script cho khối Shpere, hàm calcArea sẽ bị định nghĩa lại. Lúc này, hàm calcArea khi tính cho khối Box sẽ cho ra kết quả sai. Vậy theo cách thông thường, tôi sẽ phải nghĩ một cái tên đặc biệt cho hàm calcArea tương ứng cho mỗi script. Ví dụ, calcAreaBox và calcAreaShphere. Và khi viết đến hàng trăm function, những cái tên này sẽ phải ngày càng dài hơn để tránh việc bị trùng lặp.
Bạn sẽ phải đau đầu ngay từ việc nghĩ tên, khi còn chưa viết dòng code nào.
Struct ứng dụng như thế nào?
Sau khi đi qua phần ví dụ, bây giờ, chúng ta sẽ thử dùng struct để giải quyết các vấn đề trên. Struct (cấu trúc) cho phép bạn định nghĩa các lớp (class) mới của giá trị mà sau đó bạn có thể tạo và sử dụng trong code của mình. Cú pháp để định nghĩa một struct là:
struct <tên_struct> ( <thành_phần_1>, <thành_phần_2>... )
Mỗi thành phần trong struct là một tên gọi thuộc tính, hoặc một function. Ví dụ:
struct nhanvien (maNhanVien, soPhong, canNang, tuoi)
Đoạn code trên sẽ tạo ra một class có tên là nhanvien. Class này sẽ có các thuộc tính là mã nhân viên (maNhanVien), số phòng (soPhong), cân nặng (canNang), tuổi (tuoi). Để dễ hiểu hơn, bây giờ, chúng ta sẽ dùng nó để áp dụng vào tình huống ở ví dụ 1.
Xử lý ví dụ 1
Sau khi đã khai báo struct nhanvien, bây giờ công việc của chúng ta sẽ dễ dàng hơn nhiều. Thay vì dùng array, chúng ta sẽ dùng struct nhanvien để khai báo thuộc tính cho các biến. Cụ thể:
struct nhanvien (maNhanVien, soPhong, canNang, tuoi) lan = nhanvien maNhanVien:87 soPhong:15 canNang:55 tuoi:32 tuan = nhanvien maNhanVien:19 soPhong:30 canNang:68 tuoi:25 mai = nhanvien maNhanVien:63 soPhong:11 canNang:60 tuoi:40 minh = nhanvien maNhanVien:45 soPhong:27 canNang:85 tuoi:35 phuong = nhanvien maNhanVien:72 soPhong:22 canNang:50 tuoi:28
Bây giờ để truy xuất cân nặng của Mai, tôi sẽ viết là:
mai.canNang
3ds Max sẽ trả về kết quả là 60. Quá tiện lợi! Vậy tuổi thì sao? Thật đơn giản, chỉ cần viết mai.tuoi. Cách viết này thân thiện hơn rất nhiều so với việc phải viết mai[1], mai[2]. Tôi không còn cần phải nhớ số thứ tự và ý nghĩa của từng biến, bởi tên biến đã thể hiện luôn ý nghĩa của nó rồi. Tôi cũng không sợ khai báo sai thứ tự mỗi khi thêm một người mới nữa.
Và kể cả nếu tôi quên từ khóa cho các biến này, tôi có thể truy xuất lại dễ dàng bằng cách dùng lệnh getPropNames:
getPropNames nhanvien
-- Kết quả trả về #(#canNang, #tuoi, #maNhanVien, #soPhong)
Vậy, struct giúp chúng ta tạo và sử dụng các thuộc tính một cách dễ dàng hơn.
Xử lý ví dụ 2
Tương tự, chúng ta sẽ tạo một lớp mới chứa các biến cần thiết cho mỗi vật thể. Ví dụ, tôi sẽ khai báo struct mới tên là dovat như sau:
struct dovat (color, center)
Vậy là chúng ta đã có class mới tên là dovat, với 2 thuộc tính là color và center. Chúng ta có thể khai báo các đối tượng theo 2 cách:
Obj1 =
dovat
color:black center:[120,50,30]
Bạn có thấy điều gì quen thuộc không? Hãy nhớ lại phần 4, khi chúng ta làm việc với lệnh box(), chúng ta cũng có thể viết là:
Box pos:[0,0,0] isSelected:off width:150 length:150 height:150
Đúng vậy, class dovat của chúng ta hoàn toàn tương đương với class box có sẵn trong 3ds Max. Vì thế, chúng ta cũng có thể dùng struct dovat giống như cách dùng class Box. Chúng ta có thể khai báo thuộc tính như sau:
Obj1 =
dovat
() Obj1.color = black Obj1.center = [120,50,30]
Việc truy xuất các thuộc tính dữ liệu sẽ dễ dàng hơn nhiều, bởi chỉ có 1 biến được tạo ra là Obj1, class là dovat. Còn color và center là thuộc tính của Obj1, không phải là một biến mới. Hãy phân biệt điều này.
Vậy, struct giúp chúng ta tiết kiệm số lượng biến phải khai báo.
Xử lý ví dụ 3
Như đã nói ở trên, các thành phần trong struct có thể là tên gọi, hoặc một function. Các function này sẽ không bị ghi đè dù có trùng tên với các function ở nơi khác. Nó sẽ chỉ biến mất khi tên struct bị ghi đè. Vì thế, thay vì đau đầu khi nghĩ tên riêng biệt cho từng function để tránh “đụng hàng”, bạn chỉ cần nghĩ cái tên riêng biệt cho struct là đủ.
Ví dụ, tôi sẽ tạo struct như sau:
struct bfn
(
fn calcArea b = (2 * (b.width * b.length + b.width * b.height + b.length * b.height)),
fn calcVolume b = (b.width * b.length * b.height)
)
Hãy chú ý tới dấu phảy tôi bôi màu đỏ trong code trên, nó rất quan trọng. Nếu không có dấu phảy này, việc khai báo struct sẽ báo lỗi.
Từ giờ, khi cần dùng function calcArea cho khối box, tôi sẽ viết là bfn.calcArea. Tiền tố bfn làm cho hàm này trở nên riêng biệt và không bị ghi đè bởi các script khác. Tức là thay vì phải đổi tên tất cả các function để tránh bị trùng tên, ví dụ calcAreaBox, calcVolumeBox,…, tôi chỉ cần khai báo 1 struct với cái tên riêng biệt.
Vậy, struct giúp chúng ta giảm công sức phòng tránh việc trùng lặp tên.
Kết luận
Trên đây là những giới thiệu cơ bản về struct trong MaxScript. Ta có thể kết luận rằng, struct được hiểu nôm na như một chiếc “khóa” tổng, giúp bạn gom nhiều thứ riêng lẻ lại thành một. Nó cho phép bạn tổ chức các biến và hàm liên quan thành một đơn vị duy nhất, dễ dàng quản lý và truy xuất. Thay vì phải xử lý từng biến riêng lẻ, bạn có thể gói gọn chúng vào một struct để làm việc với chúng một cách tiện lợi và logic hơn.
Bài viết này mới chỉ mang tính giới thiệu, vì thế hôm nay sẽ không có bài tập thực hành nào cho bạn. Như đã nói ở phần trên, với người mới, bạn sẽ không cần dùng ngay đến công cụ này. Nhưng hãy ghi nhớ về nó! Hãy biết rằng nó tồn tại. Khi bạn bắt đầu phát triển những dự án lớn hơn, hãy tìm lại bài viết này để tham khảo.
Chúc các bạn thành công!