TRANG CHỦ
CHUYÊN MỤC
HỌC HỎI
TAG
ABOUT
Tìm kiếm
Lập trình games với Love2D - Chương 11 - Các lớp
2023-10-22 07:48:40
Love2D
Học Lập Trình Lua
83 lượt xem
0 bình luận
Lớp giống như bản thiết kế. Bạn có thể tạo nhiều ngôi nhà với một bản thiết kế. Tương tự, chúng ta có thể tạo nhiều đối tượng trong một lớp.  Đối với lớp chúng ta sẽ sử dụng thư viện [classic](https://github.com/rxi/classic). Bấm vào ```classic.lua``` rồi bấm vào Raw và sao chép mã. Đi tới trình soạn thảo, tạo một tệp mới có tên ```classic.lua``` và dán mã. Bây giờ chúng ta phải ```require``` nó. ```lua function love.load() Object = require "classic" end ``` Và bây giờ chúng ta đã sẵn sàng để tạo một lớp. Tạo một tệp mới có tên ```rectangle.lua```, và nhập đoạn mã sau: ```lua --! file: rectangle.lua -- Truyền đối tượng làm đối số đầu tiên. Rectangle = Object.extend(Object) function Rectangle.new(self) self.test = math.random(1, 1000) end ``` Mọi thứ sẽ được giải thích. Nhưng trước tiên hãy đặt mã này vào tệp ```main.lua```. ```lua --! file: main.lua function love.load() Object = require "classic" --Đừng quên tải file require "rectangle" r1 = Rectangle() r2 = Rectangle() print(r1.test, r2.test) end ``` Khi bạn chạy trò chơi, bạn sẽ thấy 2 số ngẫu nhiên được in ra. Vì vậy, chúng ta hãy đi qua mã này từng bước một. Đầu tiên chúng ta tạo một lớp mới với ```Rectangle = Object.extend(Object)```. Điều này làm cho ```Rectangle``` trở thành một lớp. Đây sẽ là kế hoạch chi tiết của tôi. Ngược lại với các thuộc tính, các lớp thường được viết bằng ký tự viết hoa (UpperCamelCase hoặc PascalCase). Trong ```main.lua``` chúng ta nói ```r1 = Rectangle()```. Mặc dù ```Rectangle``` là một bảng nhưng chúng ta vẫn có thể gọi nó như thể nó là một hàm. Cách thức hoạt động sẽ dành cho một chương khác. Nhưng bằng cách gọi ```Rectangle()``` nó sẽ tạo ra một phiên bản mới. Điều đó có nghĩa là nó lấy bản thiết kế của chúng ta và tạo ra một đối tượng mới với tất cả các tính năng của lớp đó. Để chứng minh rằng đó ```r1``` là duy nhất, chúng ta tạo một phiên bản khác có tên là ```r2```. Cả hai đều có thuộc tính ```test```, nhưng chúng có giá trị khác nhau. Khi chúng ta gọi ```Rectangle()```, nó sẽ thực thi ```Rectangle.new```. Đây là những gì chúng ta gọi là hàm tạo . Tham số ```self```, là phiên bản chúng ta đang sửa đổi. Nếu chúng ta gõ ```Rectangle.test = math.random(0, 1000)```, chúng ta sẽ cấp thuộc tính cho bản thiết kế chứ không phải cho một phiên bản được tạo bằng bản thiết kế. Bây giờ hãy thực hiện một số thay đổi cho lớp của chúng ta: ```lua --! file: rectangle.lua Rectangle = Object.extend(Object) function Rectangle.new(self) self.x = 100 self.y = 100 self.width = 200 self.height = 150 self.speed = 100 end function Rectangle.update(self, dt) self.x = self.x + self.speed * dt end function Rectangle.draw(self) love.graphics.rectangle("line", self.x, self.y, self.width, self.height) end ``` Nó giống như đối tượng hình chữ nhật chuyển động mà chúng ta đã tạo ở Chương 8. Ngoại trừ lần này chúng ta đặt mã của chuyển động và phần vẽ vào đối tượng. Bây giờ chúng ta chỉ cần gọi ```update``` và ```draw``` trong ```main.lua```. ```lua --! file: main.lua function love.load() Object = require "classic" require "rectangle" r1 = Rectangle() r2 = Rectangle() end function love.update(dt) r1.update(r1, dt) end function love.draw() r1.draw(r1) end ``` Khi bạn chạy trò chơi, bạn sẽ thấy một hình chữ nhật chuyển động. Chúng ta đã tạo một lớp có tên là ```Rectangle```. Chúng ta cũng tạo một đối tượng của lớp đó được gọi là ```r1```. Vì vậy, bây giờ ```r1``` có các chức năng ```update``` và ```draw``` của lớp ```Rectangle```. Chúng ta chuyển chính ``` r1``` làm đối số. Đây là những gì ```self``` trở thành giá trị trong hàm. Tuy nhiên, thật khó chịu khi chúng ta phải bỏ qua ```r1``` mỗi lần gọi một trong các chức năng của nó. May mắn thay, Lua có cách viết tắt cho việc này. Khi chúng ta sử dụng dấu hai chấm (:), lệnh gọi hàm sẽ tự động chuyển đối tượng bên trái dấu hai chấm làm đối số đầu tiên. ```lua --! file: main.lua function love.update(dt) --Lua biến điều này thành: r1.update(r1, dt) r1:update(dt) end function love.draw() --Lua biến điều này thành: r1.draw(r1) r1:draw() end ``` Và chúng ta cũng có thể làm điều này với các hàm. ```lua --! file: rectangle.lua --Lua biến điều này thành: Object.extend(Object) Rectangle = Object:extend() --Lua biến điều này thành: Rectangle.new(self) function Rectangle:new() self.x = 100 self.y = 100 self.width = 200 self.height = 150 self.speed = 100 end --Lua biến điều này thành: Rectangle.update(self, dt) function Rectangle:update(dt) self.x = self.x + self.speed * dt end --Lua biến điều này thành: Rectangle.draw(self) function Rectangle:draw() love.graphics.rectangle("line", self.x, self.y, self.width, self.height) end ``` Chúng ta gọi đây là [Syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) Hãy thêm một số tham số vào ```Rectangle:new()``` ```lua --! file: rectangle.lua function Rectangle:new(x, y, width, height) self.x = x self.y = y self.width = width self.height = height self.speed = 100 end ``` Với điều này, chúng ta có thể cung cấp cho ```r1``` mỗi ```r2``` vị trí và kích thước riêng cho nó. ```lua --! file: main.lua function love.load() Object = require "classic" require "rectangle" r1 = Rectangle(100, 100, 200, 50) r2 = Rectangle(350, 80, 25, 140) end function love.update(dt) r1:update(dt) r2:update(dt) end function love.draw() r1:draw() r2:draw() end ``` Như vậy bây giờ chúng ta có 2 hình chữ nhật chuyển động. Đây là điều làm cho các lớp trở nên tuyệt vời. ```r1``` và ```r2``` giống nhau, nhưng chúng có giá trị riêng.  Một điều nữa làm cho các lớp trở nên tuyệt vời là tính kế thừa . ------ ### Sự kế thừa - Inheritance Với tính kế thừa, chúng ta có thể mở rộng lớp của mình. Nói cách khác, chúng ta tạo một bản sao của bản thiết kế và thêm các tính năng vào đó mà không cần chỉnh sửa bản thiết kế ban đầu.  Giả sử bạn có một trò chơi với quái vật. Mỗi quái vật đều có đòn tấn công riêng, chúng di chuyển khác nhau. Nhưng chúng cũng có thể bị sát thương và có thể chết. Những đặc điểm giống nhau này nên được đặt trong một lớp cơ sở đặt tính chung. Chúng cung cấp những tính năng mà tất cả quái vật đều có. Và sau đó, mỗi lớp quái vật có thể mở rộng lớp cơ sở này và thêm các tính năng của riêng chúng vào đó. Hãy tạo một hình dạng chuyển động khác, một hình tròn. Hình chữ nhật và hình tròn chuyển động của chúng ta sẽ có điểm chung gì? Vâng, cả hai đều sẽ di chuyển. Vì vậy, hãy tạo một lớp cơ sở cho cả hai hình dạng của chúng ta. Tạo một tệp mới có tên shape.lua, và nhập đoạn mã sau: ```lua --! file: shape.lua Shape = Object:extend() function Shape:new(x, y) self.x = x self.y = y self.speed = 100 end function Shape:update(dt) self.x = self.x + self.speed * dt end ``` Lớp cơ sở của chúng ta ```Shape``` bây giờ nó xử lý chuyển động. Chúng ta đã có một lớp cơ sở xử lý chuyển động của mình, chúng ta có thể tạo ```Rectangle``` phần mở rộng của ```Shape```, và xóa trình cập nhật của nó (update). Hãy chắc chắn ```require "shape"``` trước khi sử dụng nó. ```lua --! file: main.lua function love.load() Object = require "classic" require "shape" require "rectangle" r1 = Rectangle(100, 100, 200, 50) r2 = Rectangle(350, 80, 25, 140) end --! file: rectangle.lua Rectangle = Shape:extend() function Rectangle:new(x, y, width, height) Rectangle.super.new(self, x, y) self.width = width self.height = height end function Rectangle:draw() love.graphics.rectangle("line", self.x, self.y, self.width, self.height) end ``` Mã đầy đủ lúc này: | main.lua ```lua --! file: main.lua function love.load() Object = require "classic" require "shape" require "rectangle" r1 = Rectangle(100, 100, 200, 50) r2 = Rectangle(350, 80, 25, 140) end function love.update(dt) r1:update(dt) r2:update(dt) end function love.draw() r1:draw() r2:draw() end ``` | rectangle.lua ```lua --! file: rectangle.lua Rectangle = Shape:extend() function Rectangle:new(x, y, width, height) Rectangle.super.new(self, x, y) self.width = width self.height = height end function Rectangle:draw() love.graphics.rectangle("line", self.x, self.y, self.width, self.height) end ``` | shape.lua ```lua --! file: shape.lua Shape = Object:extend() function Shape:new(x, y) self.x = x self.y = y self.speed = 100 end function Shape:update(dt) self.x = self.x + self.speed * dt end ``` Với việc ```Rectangle = Shape:extend()``` chúng ta đã tạo ```Rectangle``` phần mở rộng của ```Shape```. ```Shape``` có chức năng riêng ```Shape:new()```. Bằng cách tạo ```Rectangle:new()```, chúng ta ghi đè chức năng ban đầu. Có nghĩa là khi chúng ta gọi ```Rectangle()```, nó sẽ không thực thi ```Shape:new()``` mà thay vào đó là ```Rectangle:new()```. Nhưng hình chữ nhật có thuộc tính ```super``` là lớp ```Rectangle``` được mở rộng từ đó. Với việc ```Rectangle.super``` chúng ta có thể truy cập vào các hàm của lớp cơ sở và chúng ta sử dụng nó để gọi ```Shape:new()```. Chúng ta phải ```self``` để chuyển làm đối số đầu tiên và không thể để Lua xử lý nó bằng dấu hai chấm (:), vì chúng ta không gọi hàm này làm đối tượng. Giờ đây chúng ta sẽ tạo một lớp hình tròn. Tạo một tệp mới có tên ```circle.lua```, và nhập đoạn mã sau. ```lua --! file: circle.lua Circle = Shape:extend() function Circle:new(x, y, radius) Circle.super.new(self, x, y) --Hình tròn không có chiều rộng hoặc chiều cao. Nó có bán kính. self.radius = radius end function Circle:draw() love.graphics.circle("line", self.x, self.y, self.radius) end ``` Chúng ta thực hiện lớp ```Circle``` có phần mở rộng của ```Shape```. Chúng ta chuyển ```x``` và ```y``` sang hàm ```new()``` của ```Shape``` với ```Circle.super.new(self, x, y)```. Chúng ta cung cấp cho lớp ```Circle``` chức năng vẽ riêng. Đây là cách bạn vẽ một vòng tròn. Vòng tròn không có chiều rộng hoặc chiều cao, chúng có bán kính. Và bây giờ trong ```main.lua``` tải ```shape.lua``` và ```circle.lua```, và thay đổi ```r2``` thành một vòng tròn. ```lua --! file: main.lua function love.load() Object = require "classic" --Đừng quên tải file require "shape" require "rectangle" --Đừng quên tải file require "circle" r1 = Rectangle(100, 100, 200, 50) --We make r2 a Circle instead of a Rectangle r2 = Circle(350, 80, 40) end ``` Bây giờ khi chạy trò chơi, bạn sẽ thấy một hình chữ nhật chuyển động và một hình tròn chuyển động.  #### Chúng ta hãy xem lại tất cả mã này một lần nữa. Đầu tiên chúng ta tải thư viện classic với ```require "classic"```. Việc tải thư viện này trả về một bảng và chúng ta lưu trữ bảng này bên trong ```Object```. Nó có những điều cơ bản cần thiết để mô phỏng một lớp. Bởi vì Lua không có lớp, nhưng bằng cách sử dụng thư viện classic chúng ta có được một lớp rất hay. Tiếp theo chúng ta tải ```shape.lua```. Trong file đó, chúng ta tạo một lớp mới gọi là ```Shape```. Chúng ta sẽ sử dụng lớp này làm lớp cơ sở cho ```Rectangle``` và ```Circle```. Hai điểm chung của các lớp này là chúng có thuộc tính ```x``` và ```y``` di chuyển theo chiều ngang. Những điểm tương đồng này là những gì chúng ta đưa vào ```Shape```. Tiếp theo chúng ta tạo lớp ```Rectangle```. Chúng ta tạo nó từ một phần mở rộng của lớp cơ sở ```Shape```. Bên trong hàm ```:new()``` (hàm tạo), chúng ta gọi lớp cơ sở ```Rectangle.super.new(self, x, y)```. Chúng ta chuyển ```self``` làm đối số đầu tiên, do đó, đối số đó ```Shape``` sẽ sử dụng phiên bản của bản thiết kế của chúng ta chứ không phải bản thân bản thiết kế đó. Chúng ta gán cho hình chữ nhật một thuộc tính ```width``` và ```height``` và gán cho nó một hàm vẽ ```draw()```. Tiếp theo chúng ta lặp lại những điều trên cho ```Circle```, ngoại trừ thay vì ```width``` và ```height``` chúng ta gán cho nó một ```radius``` thuộc tính. Bây giờ chúng ta đã chuẩn bị xong các lớp, chúng ta có thể bắt đầu tạo các lớp này. Với việc ```r1 = Rectangle(100, 100, 200, 50)``` chúng ta tạo một lớp Rectangle. Nó là một vật thể được tạo ra từ bản thiết kế của chúng ta chứ không phải bản thân bản thiết kế Rectangle. Bất kỳ thay đổi nào chúng ta thực hiện đối với bản thiết kế này sẽ không ảnh hưởng đến lớp Rectangle. Chúng ta cập nhật và vẽ bản thiết kế này và chúng ta sử dụng dấu hai chấm (:). Điều này là do chúng ta cần chuyển bản thiết kế của mình làm đối số đầu tiên và dấu hai chấm sẽ yêu cầu Lua làm cho chúng ta. Và cuối cùng chúng ta làm tương tự cho ```r2```, ngoại trừ việc chúng ta biến nó thành Circle. #### Bạn đang bối rối? Trên là rất nhiều thông tin cho 1 chương và tôi có thể tưởng tượng nếu bạn gặp khó khăn trong việc hiểu tất cả những điều này. Lời khuyên của tôi: Hãy tiếp tục làm theo hướng dẫn. Nếu bạn là người mới làm quen với lập trình, sẽ mất một thời gian để bạn hiểu được tất cả các khái niệm mới này, rồi bạn cũng sẽ quen với chúng. Tôi sẽ tiếp tục bổ sung thêm lời giải thích về các chủ đề cũ hơn trong khi nói về những chủ đề mới hơn. ### Bản tóm tắt Lớp giống như bản thiết kế. Chúng ta có thể tạo nhiều đối tượng trong 1 lớp. Để mô phỏng các lớp chúng ta sử dụng thư viện ```classic```. Bạn tạo một lớp với ```ClassName = Object:extend()```. Bạn tạo một đối tượng của một lớp với ```Name = ClassName()```. Điều này sẽ gọi hàm ```ClassName:new()```. Đây được gọi là hàm tạo. Mọi hàm của một lớp phải bắt đầu bằng tham số ```self``` để khi gọi hàm, bạn có thể chuyển đối tượng làm đối số đầu tiên. ```Name.functionName(Name)```. Chúng ta có thể sử dụng dấu hai chấm (:) để yêu cầu Lua làm điều này cho chúng ta. Chúng ta có thể mở rộng một lớp với ```ExtensionName = ClassName:extend()```. Điều này tạo ```ExtensionName``` một bản sao ```ClassName``` mà chúng ta có thể thêm thuộc tính mà không cần chỉnh sửa ```ClassName```. Nếu chúng ta đưa ra ```ExtensionName``` một hàm ```ClassName``` đã có sẵn, chúng ta vẫn có thể gọi hàm ban đầu bằng ```ExtensionName.super.functionName(self)```. ------ [Trước](/learn/detail?learnId=10) | [Mục lục](/learn/search?keyword=Lập%20trình%20games%20với%20Love2D) | [Kế tiếp](/learn/detail?learnId=12)
Gợi ý bài học liên quan
Awesome Love2D
Lập trình games với Love2D - Visual Studio Code
Lập trình games với Love2D - Chương 24
Lập trình games với Love2D - Chương 23
Lập trình games với Love2D - Chương 22