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 14 - Bắn kẻ địch
2023-10-22 07:49:16
Love2D
Học Lập Trình Lua
40 lượt xem
0 bình luận
Hãy sử dụng mọi thứ chúng ta đã học cho đến nay để tạo ra một trò chơi đơn giản. Bạn có thể đọc về lập trình và tạo trò chơi nếu muốn, nhưng để thực sự học nó, bạn sẽ phải làm điều đó. Một trò chơi về cơ bản là một loạt các vấn đề mà bạn phải giải quyết. Khi bạn yêu cầu một lập trình viên có kinh nghiệm tạo PONG, anh ta sẽ không tra cứu Cách tạo PONG . Họ có thể chia PONG thành các vấn đề riêng biệt và biết cách giải quyết từng vấn đề đó. Chương này hướng dẫn bạn cách chia trò chơi thành nhiều nhiệm vụ. Trò chơi chúng ta sẽ thực hiện rất đơn giản: Kẻ thù đang đập vào tường. Chúng ta phải bắn nó. Mỗi lần chúng ta bắn, kẻ địch sẽ nhanh hơn một chút. Khi bạn bỏ lỡ, trò chơi sẽ kết thúc và bạn sẽ phải bắt đầu lại.  Đối với trò chơi này, chúng tôi sẽ sử dụng hình ảnh. Bạn có thể tự do sử dụng hình ảnh của riêng mình, nhưng tôi sẽ sử dụng 3 hình ảnh sau: | Player | Enemy | Bullet | | --------------------------- | -------------------------- | --------------------------- | |  |  |  | Những hình ảnh này được thực hiện bởi [Kenney](https://kenney.nl/assets) , người tạo ra rất nhiều nội dung miễn phí mà bất kỳ ai cũng có thể sử dụng trong trò chơi của mình. Bây giờ, bạn tải 3 hình ảnh trên và thêm vào nơi chứa file ```main.lua``` của mình, và hãy bắt đầu với 3 lệnh gọi lại chính và tải thư viện ```classic``` , thư viện mà chúng ta sử dụng để tạo các lớp. ```lua function love.load() Object = require "classic" end function love.update(dt) end function love.draw() end ``` Hãy bắt đầu với người chơi. Tạo một tệp mới có tên ```player.lua```. Chúng ta có thể tạo một lớp cơ sở cho tất cả các đối tượng của mình, nhưng vì đây là một trò chơi đơn giản và ngắn gọn nên chúng ta sẽ thực hiện mà không cần một lớp nào. Mặc dù vậy, tôi khuyến khích bạn cải thiện mã ở cuối chương này bằng cách tự thêm một lớp cơ sở. ### Nhiệm vụ: Tạo di chuyển Người chơi (Player) Tạo một lớp Player: ```lua --! file: player.lua Player = Object:extend() function Player:new() end ``` Tôi thêm hình ảnh panda cho người chơi của mình. ```lua function Player:new() self.image = love.graphics.newImage("panda.png") end function Player:draw() love.graphics.draw(self.image) end ``` Tiếp theo, hãy làm cho người chơi có thể di chuyển bằng các phím mũi tên. ```lua function Player:new() self.image = love.graphics.newImage("panda.png") self.x = 300 self.y = 20 self.speed = 500 self.width = self.image:getWidth() end function Player:update(dt) if love.keyboard.isDown("left") then self.x = self.x - self.speed * dt elseif love.keyboard.isDown("right") then self.x = self.x + self.speed * dt end end function Player:draw() love.graphics.draw(self.image, self.x, self.y) end ``` Và bây giờ chúng ta có thể di chuyển người chơi của mình. Hãy quay lại ```main.lua``` và tải trình phát của chúng ta. ```lua --! file: main.lua function love.load() Object = require "classic" require "player" player = Player() end function love.update(dt) player:update(dt) end function love.draw() player:draw() end ``` Như bạn có thể thấy, chúng ta có thể di chuyển người chơi của mình. Nhưng người chơi của chúng ta có thể di chuyển ra ngoài khung hình. Hãy khắc phục điều này bằng câu lệnh ```if```. ```lua --! file: player.lua function Player:update(dt) if love.keyboard.isDown("left") then self.x = self.x - self.speed * dt elseif love.keyboard.isDown("right") then self.x = self.x + self.speed * dt end -- Lấy chiều rộng của khung hình local window_width = love.graphics.getWidth() -- Nếu x ở quá xa bên trái thì.. if self.x < 0 then --Set x to 0 self.x = 0 -- Ngược lại, nếu x quá xa về bên phải thì.. elseif self.x > window_width then -- Đặt x theo chiều rộng của khung hình. self.x = window_width end end ``` Rất tiếc, người chơi của chúng ta vẫn có thể di chuyển quá xa về bên phải. Chúng ta cần tính chiều rộng, kiểm tra xem người chơi có chạm đúng bức tường hay không. ```lua -- Nếu phía bên trái người chơi quá xa bên trái khung hình thì.. if self.x < 0 then -- Đặt x thành 0 self.x = 0 --Ngược lại, nếu phía bên phải người chơi quá xa về phía bên phải khung hình thì.. elseif self.x + self.width > window_width then -- Đặt phía bên phải người chơi theo chiều rộng khung hình. self.x = window_width - self.width end ``` Mã đầy đủ: | player.lua ```lua --! file: player.lua Player = Object:extend() function Player:new() self.image = love.graphics.newImage("panda.png") self.x = 300 self.y = 20 self.speed = 500 self.width = self.image:getWidth() end function Player:update(dt) if love.keyboard.isDown("left") then self.x = self.x - self.speed * dt elseif love.keyboard.isDown("right") then self.x = self.x + self.speed * dt end -- Lấy chiều rộng của khung hình local window_width = love.graphics.getWidth() -- Nếu phía bên trái người chơi quá xa bên trái khung hình thì.. if self.x < 0 then -- Đặt x thành 0 self.x = 0 --Ngược lại, nếu phía bên phải người chơi quá xa về phía bên phải khung hình thì.. elseif self.x + self.width > window_width then -- Đặt phía bên phải người chơi theo chiều rộng khung hình. self.x = window_width - self.width end end function Player:draw() love.graphics.draw(self.image, self.x, self.y) end ``` | main.lua ```lua --! file: main.lua function love.load() Object = require "classic" require "player" player = Player() end function love.update(dt) player:update(dt) end function love.draw() player:draw() end ``` Và bây giờ nó đã được sửa. Người chơi của chúng ta không thể di chuyển ra khỏi khung hình nữa. ## Nhiệm vụ: Tạo di chuyển Kẻ thù (Enemy) Bây giờ hãy tạo lớp Enemy. Tạo một tệp mới có tên ```enemy.lua```, và gõ như sau: ```lua --! file: enemy.lua Enemy = Object:extend() function Enemy:new() end ``` Tôi sẽ cho kẻ thù hình ảnh con rắn và khiến nó tự di chuyển. ```lua function Enemy:new() self.image = love.graphics.newImage("snake.png") self.x = 325 self.y = 450 self.speed = 100 end function Enemy:update(dt) self.x = self.x + self.speed * dt end function Enemy:draw() love.graphics.draw(self.image, self.x, self.y) end ``` Bây giờ, chúng ta quay lại file ```main.lua```. ```lua --! file: main.lua function love.load() Object = require "classic" require "player" require "enemy" player = Player() enemy = Enemy() end function love.update(dt) player:update(dt) enemy:update(dt) end function love.draw() player:draw() enemy:draw() end ``` Được rồi, bây giờ chúng ta có thể thấy kẻ thù di chuyển và chúng ta có thể thấy nó di chuyển ra khỏi khung hình của chúng ta. Hãy đảm bảo rằng nó không di chuyển ra khỏi khung hình của chúng ta như đã làm với Player. ```lua function Enemy:new() self.image = love.graphics.newImage("snake.png") self.x = 325 self.y = 450 self.speed = 100 self.width = self.image:getWidth() self.height = self.image:getHeight() end function Enemy:update(dt) self.x = self.x + self.speed * dt local window_width = love.graphics.getWidth() if self.x < 0 then self.x = 0 elseif self.x + self.width > window_width then self.x = window_width - self.width end end ``` Kẻ thù của chúng ta dừng lại ở bức tường, nhưng chúng ta muốn làm cho nó nảy lên. Chúng ta sẽ làm điều đó như thế nào? Khi nó chạm vào bức tường bên phải, nó nên chuyển sang hướng khác. Làm thế nào để chúng ta làm cho nó chuyển sang hướng khác? Bằng cách thay đổi giá trị của ```speed``` bằng ```-speed```. Và nếu nó chạm vào bức tường bên trái thì sao? Chúng ta lại đảo giá trị ```-speed``` bằng ```speed```. ```lua function Enemy:update(dt) self.x = self.x + self.speed * dt local window_width = love.graphics.getWidth() if self.x < 0 then self.x = 0 self.speed = -self.speed elseif self.x + self.width > window_width then self.x = window_width - self.width self.speed = -self.speed end end ``` Được rồi, chúng ta có một người chơi và một kẻ thù đang di chuyển, giờ tất cả những gì còn lại là viên đạn. ### Nhiệm vụ: Người chơi có thể bắn đạn Tạo một tệp mới có tên ```bullet.lua```, và viết đoạn mã sau: ```lua --! file: bullet.lua Bullet = Object:extend() function Bullet:new() self.image = love.graphics.newImage("bullet.png") end function Bullet:draw() love.graphics.draw(self.image) end ``` Các viên đạn nên di chuyển theo chiều dọc thay vì ngang. ```lua -- Chúng ta chuyển x và y của người chơi. function Bullet:new(x, y) self.image = love.graphics.newImage("bullet.png") -- Chúng ta thêm vào 50, 80 là số cân chỉnh vị trí ban đầu của viên đạn. self.x = x + 50 self.y = y + 80 self.speed = 700 -- Chúng ta sẽ cần những thứ này để kiểm tra va chạm self.width = self.image:getWidth() self.height = self.image:getHeight() end function Bullet:update(dt) self.y = self.y + self.speed * dt end function Bullet:draw() love.graphics.draw(self.image, self.x, self.y) end ``` Bây giờ chúng ta cần khả năng bắn đạn của người chơi. Trong ```main.lua``` tải tệp và tạo bảng. ```lua --! file: main.lua function love.load() Object = require "classic" require "player" require "enemy" require "bullet" player = Player() enemy = Enemy() listOfBullets = {} end ``` Bây giờ chúng tôi cung cấp cho Player một chức năng (function) tạo viên đạn khi nhấn dấu cách. ```lua --! file: player.lua function Player:keyPressed(key) --Nếu nhấn phím cách if key == "space" then --Thêm một bản mới của Bullet bên trong listOfBullets. table.insert(listOfBullets, Bullet(self.x, self.y)) end end ``` Và chúng ta cần gọi hàm này trong hàm ```love.keypressed```. ```lua --! file: main.lua function love.keypressed(key) player:keyPressed(key) end ``` Và bây giờ chúng ta cần duyệt qua bảng và cập nhật/vẽ tất cả. ```lua function love.load() Object = require "classic" require "player" require "enemy" require "bullet" player = Player() enemy = Enemy() listOfBullets = {} end function love.update(dt) player:update(dt) enemy:update(dt) for i,v in ipairs(listOfBullets) do v:update(dt) end end function love.draw() player:draw() enemy:draw() for i,v in ipairs(listOfBullets) do v:draw() end end ``` Mã đầy đủ lúc này: | player.lua ```lua --! file: player.lua Player = Object:extend() function Player:new() self.image = love.graphics.newImage("panda.png") self.x = 300 self.y = 20 self.speed = 500 self.width = self.image:getWidth() end function Player:keyPressed(key) --Nếu nhấn phím cách if key == "space" then --Thêm một bản mới của Bullet bên trong listOfBullets. table.insert(listOfBullets, Bullet(self.x, self.y)) end end function Player:update(dt) if love.keyboard.isDown("left") then self.x = self.x - self.speed * dt elseif love.keyboard.isDown("right") then self.x = self.x + self.speed * dt end -- Lấy chiều rộng của khung hình local window_width = love.graphics.getWidth() -- Nếu phía bên trái người chơi quá xa bên trái khung hình thì.. if self.x < 0 then -- Đặt x thành 0 self.x = 0 --Ngược lại, nếu phía bên phải người chơi quá xa về phía bên phải khung hình thì.. elseif self.x + self.width > window_width then -- Đặt phía bên phải người chơi theo chiều rộng khung hình. self.x = window_width - self.width end end function Player:draw() love.graphics.draw(self.image, self.x, self.y) end ``` | enemy.lua ```lua --! file: enemy.lua Enemy = Object:extend() function Enemy:new() self.image = love.graphics.newImage("snake.png") self.x = 325 self.y = 450 self.speed = 100 self.width = self.image:getWidth() self.height = self.image:getHeight() end function Enemy:update(dt) self.x = self.x + self.speed * dt local window_width = love.graphics.getWidth() if self.x < 0 then self.x = 0 self.speed = -self.speed elseif self.x + self.width > window_width then self.x = window_width - self.width self.speed = -self.speed end end function Enemy:draw() love.graphics.draw(self.image, self.x, self.y) end ``` | bullet.lua ```lua --! file: bullet.lua Bullet = Object:extend() -- Chúng ta chuyển x và y của người chơi. function Bullet:new(x, y) self.image = love.graphics.newImage("bullet.png") -- Chúng ta thêm vào 50, 80 là số cân chỉnh vị trí ban đầu của viên đạn. self.x = x + 50 self.y = y + 80 self.speed = 700 -- Chúng ta sẽ cần những thứ này để kiểm tra va chạm self.width = self.image:getWidth() self.height = self.image:getHeight() end function Bullet:update(dt) self.y = self.y + self.speed * dt end function Bullet:draw() love.graphics.draw(self.image, self.x, self.y) end ``` | main.lua ```lua --! file: main.lua function love.load() Object = require "classic" require "player" require "enemy" require "bullet" player = Player() enemy = Enemy() listOfBullets = {} end function love.keypressed(key) player:keyPressed(key) end function love.update(dt) player:update(dt) enemy:update(dt) for i,v in ipairs(listOfBullets) do v:update(dt) end end function love.draw() player:draw() enemy:draw() for i,v in ipairs(listOfBullets) do v:draw() end end ``` Tuyệt vời, giờ đây người chơi của chúng ta có thể bắn đạn. ### Nhiệm vụ: Làm đạn ảnh hưởng tới tốc độ của kẻ địch Bây giờ chúng ta cần làm sao cho kẻ địch có thể bị trúng đạn. Chúng ta cung cấp cho Bullet chức năng phát hiện va chạm. ```lua --! file: bullet.lua function Bullet:checkCollision(obj) end ``` Bạn vẫn biết cách làm điều đó? Bạn vẫn biết 4 điều kiện cần phải đúng để đảm bảo xảy ra va chạm? Thay vì trả về đúng và sai, chúng ta tăng tốc độ của kẻ địch. Chúng ta cung cấp thuộc tính ```dead``` cho dấu đầu dòng mà chúng ta sẽ sử dụng để xóa nó khỏi danh sách. ```lua function Bullet:checkCollision(obj) local self_left = self.x local self_right = self.x + self.width local self_top = self.y local self_bottom = self.y + self.height local obj_left = obj.x local obj_right = obj.x + obj.width local obj_top = obj.y local obj_bottom = obj.y + obj.height if self_right > obj_left and self_left < obj_right and self_bottom > obj_top and self_top < obj_bottom then self.dead = true --Tăng tốc độ của kẻ thù obj.speed = obj.speed + 50 end end ``` Bây giờ chúng ta cần gọi ```checkCollision``` trong main.lua. ```lua function love.update(dt) player:update(dt) enemy:update(dt) for i,v in ipairs(listOfBullets) do v:update(dt) -- Mỗi viên đạn kiểm tra xem có va chạm với kẻ thù không v:checkCollision(enemy) end end ``` Và tiếp theo chúng ta cần tiêu diệt những viên đạn đã chết. ```lua function love.update(dt) player:update(dt) enemy:update(dt) for i,v in ipairs(listOfBullets) do v:update(dt) v:checkCollision(enemy) --Nếu viên đạn có thuộc tính chết và sự thật thì.. if v.dead then --Xóa nó khỏi danh sách table.remove(listOfBullets, i) end end end ``` Điều cuối cùng cần làm là khởi động lại trò chơi khi chúng ta bắn trượt kẻ thù. Chúng ta cần kiểm tra xem viên đạn có ra khỏi màn hình hay không. ```lua --! file: bullet.lua function Bullet:update(dt) self.y = self.y + self.speed * dt -- Nếu viên đạn ra khỏi màn hình if self.y > love.graphics.getHeight() then -- Khởi động lại trò chơi love.load() end end ``` Và hãy chạy nó. Bạn có thể nhận thấy rằng khi bạn đánh kẻ thù khi nó đang di chuyển sang trái, nó sẽ chậm lại. Điều này là do tốc độ của kẻ thù lúc đó là số âm. Vì vậy, bằng cách tăng tốc độ kẻ thù sẽ làm nó chậm lại. Để khắc phục điều này chúng ta cần kiểm tra xem tốc độ của kẻ địch có phải là số âm hay không. ```lua function Bullet:checkCollision(obj) local self_left = self.x local self_right = self.x + self.width local self_top = self.y local self_bottom = self.y + self.height local obj_left = obj.x local obj_right = obj.x + obj.width local obj_top = obj.y local obj_bottom = obj.y + obj.height if self_right > obj_left and self_left < obj_right and self_bottom > obj_top and self_top < obj_bottom then self.dead = true --Increase enemy speed if obj.speed > 0 then obj.speed = obj.speed + 50 else obj.speed = obj.speed - 50 end end end ``` Và với điều đó trò chơi của chúng ta đã kết thúc. Bạn nên thử tự mình thêm các tính năng vào trò chơi. Hoặc tạo một trò chơi hoàn toàn mới. Không thành vấn đề miễn là bạn tiếp tục học hỏi và tiếp tục làm trò chơi! Mã đầy đủ: | player.lua ```lua --! file: player.lua Player = Object:extend() function Player:new() self.image = love.graphics.newImage("panda.png") self.x = 300 self.y = 20 self.speed = 500 self.width = self.image:getWidth() end function Player:keyPressed(key) --Nếu nhấn phím cách if key == "space" then --Thêm một bản mới của Bullet bên trong listOfBullets. table.insert(listOfBullets, Bullet(self.x, self.y)) end end function Player:update(dt) if love.keyboard.isDown("left") then self.x = self.x - self.speed * dt elseif love.keyboard.isDown("right") then self.x = self.x + self.speed * dt end -- Lấy chiều rộng của khung hình local window_width = love.graphics.getWidth() -- Nếu phía bên trái người chơi quá xa bên trái khung hình thì.. if self.x < 0 then -- Đặt x thành 0 self.x = 0 --Ngược lại, nếu phía bên phải người chơi quá xa về phía bên phải khung hình thì.. elseif self.x + self.width > window_width then -- Đặt phía bên phải người chơi theo chiều rộng khung hình. self.x = window_width - self.width end end function Player:draw() love.graphics.draw(self.image, self.x, self.y) end ``` | enemy.lua ```lua --! file: enemy.lua Enemy = Object:extend() function Enemy:new() self.image = love.graphics.newImage("snake.png") self.x = 325 self.y = 450 self.speed = 100 self.width = self.image:getWidth() self.height = self.image:getHeight() end function Enemy:update(dt) self.x = self.x + self.speed * dt local window_width = love.graphics.getWidth() if self.x < 0 then self.x = 0 self.speed = -self.speed elseif self.x + self.width > window_width then self.x = window_width - self.width self.speed = -self.speed end end function Enemy:draw() love.graphics.draw(self.image, self.x, self.y) end ``` | bullet.lua ```lua --! file: bullet.lua Bullet = Object:extend() -- Chúng ta chuyển x và y của người chơi. function Bullet:new(x, y) self.image = love.graphics.newImage("bullet.png") -- Chúng ta thêm vào 50, 80 là số cân chỉnh vị trí ban đầu của viên đạn. self.x = x + 50 self.y = y + 80 self.speed = 700 -- Chúng ta sẽ cần những thứ này để kiểm tra va chạm self.width = self.image:getWidth() self.height = self.image:getHeight() end function Bullet:checkCollision(obj) local self_left = self.x local self_right = self.x + self.width local self_top = self.y local self_bottom = self.y + self.height local obj_left = obj.x local obj_right = obj.x + obj.width local obj_top = obj.y local obj_bottom = obj.y + obj.height if self_right > obj_left and self_left < obj_right and self_bottom > obj_top and self_top < obj_bottom then self.dead = true --Increase enemy speed if obj.speed > 0 then obj.speed = obj.speed + 50 else obj.speed = obj.speed - 50 end end end function Bullet:update(dt) self.y = self.y + self.speed * dt -- Nếu viên đạn ra khỏi màn hình if self.y > love.graphics.getHeight() then -- Khởi động lại trò chơi love.load() end end function Bullet:draw() love.graphics.draw(self.image, self.x, self.y) end ``` | main.lua ```lua --! file: main.lua function love.load() Object = require "classic" require "player" require "enemy" require "bullet" player = Player() enemy = Enemy() listOfBullets = {} end function love.keypressed(key) player:keyPressed(key) end function love.update(dt) player:update(dt) enemy:update(dt) for i,v in ipairs(listOfBullets) do v:update(dt) v:checkCollision(enemy) --Nếu viên đạn có thuộc tính chết và sự thật thì.. if v.dead then --Xóa nó khỏi danh sách table.remove(listOfBullets, i) end end end function love.draw() player:draw() enemy:draw() for i,v in ipairs(listOfBullets) do v:draw() end end ``` ### Bản tóm tắt Một trò chơi cơ bản với một loạt các vấn đề cần được giải quyết. ------ [Trước](/learn/detail?learnId=13) | [Mục lục](/learn/search?keyword=Lập%20trình%20games%20với%20Love2D) | [Kế tiếp](/learn/detail?learnId=15)
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