You are on page 1of 186

BỘ CÔNG THƢƠNG

TRƢỜNG ĐẠI HỌC SAO ĐỎ


*****

TÀI LIỆU HỌC TẬP

LẬP TRÌNH GAME 3D


Dùng cho sinh viên đại học chính quy
Ngành đào tạo: Công nghệ thông tin
(Tài liệu lưu hành nội bộ)

NĂM 2023
MỤC LỤC

LỜI NÓI ĐẦU ................................................................................................................1


Phần 1. LÝ THUYẾT ....................................................................................................2
Chƣơng 1. TỔNG QUAN VỀ LẬP TRÌNH UNITY ..................................................2
1.1. Phân loại Game ....................................................................................................... 2
1.1.1. Game hành động ............................................................................................2
1.1.2. Game nhập vai ...............................................................................................2
1.1.3. Game chiến thuật ...........................................................................................2
1.1.4. Game thể thao ................................................................................................3
1.1.5. Game mê cung, câu đố ..................................................................................3
1.1.6. Game mô phỏng ............................................................................................3
1.1.7. Game phiêu lƣu .............................................................................................3
1.2. Quy trình phát triển game ..................................................................................... 4
1.3. Thành phần cơ bản của game ............................................................................... 5
1.4. Giới thiệu chung về Unity ...................................................................................... 6
1.4.1. Tính năng của Unity ......................................................................................7
1.4.2. Kiến trúc Engine Unity..................................................................................7
1.4.3. Thao tác với Project Unity ..........................................................................10
1.4.4. Giao diện của Unity .....................................................................................14
1.4.5. Các thành phần cơ bản trong Unity .............................................................20
1.5. Địa hình trong Unity ............................................................................................. 26
1.6. Một số lớp và hàm trong Unity ........................................................................... 33
1.6.1. Lớp Input .....................................................................................................33
1.6.2. Lớp Debug ...................................................................................................36
1.6.3. Lớp Vectơ [1] ..............................................................................................38
1.6.4. Các hàm xử lý Script nâng cao [2] ..............................................................44
CÂU HỎI ÔN TẬP CHƢƠNG 1 ............................................................................... 52
Chƣơng 2. XỬ LÝ HÌNH ẢNH VÀ GIAO DIỆN ....................................................53
2.1. Cấu hình đối tƣợng đồ họa .................................................................................. 53
2.2. Xử lý các hình ảnh ................................................................................................ 57
2.3. Cắt hình ảnh .......................................................................................................... 59
2.4. Các thành phần cơ bản của UI ............................................................................ 61
2.4.1. Canvas .........................................................................................................61
2.4.2. Text ..............................................................................................................64
2.4.3. Button ..........................................................................................................66
2.4.4. Input Field ...................................................................................................69
2.4.5. Image ...........................................................................................................70

ii
2.5. Chuyển cảnh các Scene ........................................................................................ 72
CÂU HỎI ÔN TẬP CHƢƠNG 2 ............................................................................... 75
Chƣơng 3. SỰ KIỆN VÀ HIỆU ỨNG TRONG GAME ..........................................76
3.1. Lập trình chuyển động ......................................................................................... 76
3.2. Xử lý va chạm đối tƣợng ...................................................................................... 79
3.3. Đối tƣợng dựng sẵn .............................................................................................. 85
3.4. Di chuyển đối tƣợng ............................................................................................. 86
3.4.1. Di chuyển bằng bàn phím ............................................................................86
3.4.2. Di chuyển bằng chuột ..................................................................................92
3.4.3. Di chuyển trên điện thoại ............................................................................93
3.5. Tạo các diễn hoạt .................................................................................................. 94
3.6. Xử lý Particle [5] ................................................................................................. 101
3.7. Sound [6].............................................................................................................. 104
3.8. Design Pattern trong game ................................................................................ 112
3.9. Truyền giá trị giữa các Script ........................................................................... 124
3.10. Slider, Timer ..................................................................................................... 125
3.11. Dừng, thoát game.............................................................................................. 127
CÂU HỎI ÔN TẬP CHƢƠNG 3 .............................................................................129
Chƣơng 4. THIẾT KẾ GAME NHIỀU NGƢỜI CHƠI ........................................132
4.1. Tổng quan............................................................................................................ 132
4.2. Thƣ viện Mirror networking [3] ....................................................................... 132
4.3. Mô hình Client - Server [4] ................................................................................ 133
4.4. Quản lý và điều khiển mạng .............................................................................. 135
4.5. Đồng bộ hóa dữ liệu ............................................................................................ 137
4.6. Gọi phƣơng thức từ xa ....................................................................................... 138
4.7. Cơ chế ủy quyền.................................................................................................. 139
4.8. Thành phần Network Transform ..................................................................... 140
CÂU HỎI ÔN TẬP CHƢƠNG 4 .............................................................................161
PHẦN 2. THỰC HÀNH ............................................................................................162
TÀI LIỆU THAM KHẢO.........................................................................................183

iii
LỜI NÓI ĐẦU

Ngày nay, công nghiệp giải trí hiện đang phát triển trên thế giới và mang lại
nhiều lợi nhuận trong đó có ngành công nghiệp game. Game là lĩnh vực giải trí tƣơng
tác quan trọng hàng đầu trong ngành công nghiệp số với doanh thu cao, thu hút hàng
triệu lao động là các chuyên gia phát triển.
Lập trình game 3D đƣợc biên soạn làm tài liệu học tập cho sinh viên ngành
Công nghệ thông tin để thiết kế game trên máy tính với sự trợ giúp mạnh mẽ từ công
cụ phát triển game hàng đầu Unity, phiên bản 2021.3.19f1, ra mắt tháng 02 năm 2023
và Unity Hub 3.5.0. Ngƣời học sẽ khám phá những khía cạnh cơ bản nhất trong việc
tạo ra game, từ việc xử lý hình ảnh và giao diện, tạo ra các sự kiện và hiệu ứng cho
đến việc thiết kế game nhiều ngƣời chơi. Tài liệu đƣợc chia thành 4 chƣơng:
Chƣơng 1. Tổng quan về lập trình Unity giới thiệu về công cụ Unity và tổng
quan về lập trình game 3D, ngƣời học sẽ tìm hiểu về giao diện và cách thức làm việc
Unity để tiếp cận các chƣơng sau.
Chƣơng 2. Xử lý hình ảnh và giao diện hƣớng dẫn cách xử lý hình ảnh, tạo
giao diện tƣơng tác ngƣời dùng. Qua việc tìm hiểu về các thành phần trong giao diện
game, ngƣời học sẽ biết cách tùy chỉnh và thiết kế các thành phần này để mang lại trải
nghiệm cho ngƣời chơi trong game.
Chƣơng 3. Sự kiện và hiệu ứng trong game trình bày cách xử lý sự kiện,
chuyển động và va chạm giữa các đối tƣợng game và tạo hiệu ứng trong game.
Chƣơng 4. Thiết kế game nhiều ngƣời chơi tập trung vào việc phát triển
game mà nhiều ngƣời có thể tham gia cùng nhau. Chƣơng này sẽ giới thiệu các khái
niệm cơ bản về đồng bộ hóa dữ liệu và tƣơng tác giữa nhiều ngƣời chơi, giúp ngƣời
học tạo ra game đa ngƣời chơi trên mạng.
Trong quá trình biên soạn tài liệu này, tác giả nhận đƣợc nhiều ý kiến đóng góp
quý báu về nội dung chuyên môn của các phản biện cũng nhƣ đồng nghiệp trong khoa
Công nghệ thông tin, trƣờng Đại học Sao Đỏ. Tác giả xin chân thành cảm ơn sự giúp
đỡ quý giá đó. Mặc dù đã hết sức cố gắng nhƣng sẽ không tránh khỏi những thiếu sót,
rất mong nhận đƣợc sự góp ý của các sinh viên, đồng nghiệp và các độc giả để nâng
cao chất lƣợng tài liệu. Mọi đóng góp xin gửi về: Khoa Công nghệ thông tin, trƣờng
Đại học Sao Đỏ, số 76 Đƣờng Nguyễn Thị Duệ, phƣờng Sao Đỏ, thành phố Chí Linh,
tỉnh Hải Dƣơng.

1
Phần 1. LÝ THUYẾT
Chƣơng 1. TỔNG QUAN VỀ LẬP TRÌNH UNITY
Chƣơng 1 đề cập đến các khía cạnh liên quan đến phát triển game nhƣ phân loại
game, tóm tắt quy trình phát triển game, thành phần cơ bản của game; giới thiệu về
giao diện của Unity và cách hỗ trợ việc phát triển game đa nền tảng; giới thiệu địa hình
cũng nhƣ một số lớp và hàm quan trọng trong Unity.
1.1. Phân loại game
1.1.1. Game hành động
Game hành động gồm tất cả game có liên quan đến hành động, đề cao khả năng
chiến đấu của nhân vật trong từng khu vực rộng lớn, ít sử dụng câu đố hay thông điệp
phức tạp. Dạng phổ biến nhất là các game góc nhìn ngƣời thứ nhất (first-person point-
of-view) nhƣ Half Life 2 hoặc góc nhìn ngƣời thứ ba (third-person point-of-view) là
ngƣời chơi quan sát từ phía trên hoặc từ sau lƣng nhân vật và vị trí quan sát có thể thay
đổi nhƣ Prince of Persia, Lost Planet. Ngoài ra, một số game hành động còn cho phép
ngƣời chơi trang bị các loại vũ khí, phƣơng tiện chiến đấu nhƣ xe tăng, máy bay,…
1.1.2. Game nhập vai
Trong game nhập vai (Role Playing Game - RPG), ngƣời chơi vào vai một nhân
vật hoặc một nhóm để tham gia các sự kiện, giao tiếp với các nhân vật, tham gia các
cuộc chiến với một quá trình xây dựng nhân vật thông qua việc tăng cấp độ, phát triển
các kỹ năng, tính cách,... Các hoạt động phổ biến nhƣ luyện level, thu lƣợm vật phẩm,
luyện kỹ năng, lập team với nhiều nhân vật khác. Nhập vai hành động (Action RPG)
nhấn mạnh sự khám phá các vùng đất và áp dụng kiểu chiến đấu theo thời gian thực, ví
dụ Two Worlds. Nhập vai chiến thuật (Tactical RPG) nhấn mạnh vị trí, tốc độ, tầm
đánh của nhân vật trong trận chiến, ví dụ Final Fantasy Tactics, Disgaea.
1.1.3. Game chiến thuật
Game chiến thuật là tên gọi đơn giản của dòng game chiến thuật thời gian thực,
loại game phổ biến nhất trong thể loại này trên cả nền tảng offline hay online. Trong
game này, hành động của ngƣời chơi, quân địch và những thay đổi trong môi trƣờng
game đều diễn ra liên tục và đồng thời. Các tựa game điển hình của thể loại game
chiến thuật thời gian thực thƣờng có tính năng thu thập tài nguyên, xây dựng căn cứ,
bồi dƣỡng quân đội hiệu quả để chiếm đất và phòng thủ thành trì.
Trong game chiến thuật thời gian thực, ngƣời chơi cần phải quan sát tổng thể
đƣợc tình hình của toàn bộ bản đồ game, gồm các đơn vị, công trình kiến trúc, các đội
quân chịu sự kiểm soát của ngƣời chơi; vừa phải thu thập tài nguyên, xây dựng, bảo vệ
và phát triển các công trình kiến trúc, vừa phải tấn công kẻ địch, phá hủy tài sản của
đối thủ. Ngoài ra còn phải nghiên cứu chỉ số binh lực, trận hình áp dụng và tƣớng lĩnh
phò trợ để đƣa ra những quyết định chiếm đánh hay phòng thủ mang tính quyết đoán.
Do đó, game này có nhịp độ nhanh, liên tục và đòi hỏi sự phản xạ nhanh từ ngƣời chơi
khi đƣa ra các quyết định tấn công, phòng thủ hay rút lui.

2
1.1.4. Game thể thao
Game thể thao có nội dung về các môn thể thao, trong đó chia thành nhiều phân
khúc nhỏ. Ngƣời chơi sẽ có cơ hội hóa thân thành vận động viên hoặc điều khiển một
đội nhóm, phù hợp với ngƣời mê thể thao nhƣng không có cơ hội chơi các môn thể
thao ngoài thực tế.
Trong dòng game thể thao thì bóng đá và đua xe nổi bật hơn so với các bộ môn
khác. Đại diện tƣơng ứng cho các game bóng đá là FIFA, PES, Asphalt và Need For
Speed. Game thuộc thể loại này thƣờng tập trung vào những thử thách vật lý, chiến
thuật và độ chính xác. Game thể thao là game mô phỏng nên những gì ngƣời chơi cảm
nhận đƣợc ngoài đời thực sẽ đƣợc đƣa toàn bộ vào game. Ngƣời chơi sẽ phải tính toán
nhƣ đang trải nghiệm bộ môn thể thao đó.
1.1.5. Game mê cung, câu đố
Game mê cung, câu đố là thể loại game phổ biến trên thị trƣờng, đặc biệt là thị
trƣờng game dành cho mobile, dùng để chỉ những tựa game có các màn chơi ngắn với
cách chơi đơn giản, thiên về giải đố, sắp xếp, logic hoặc chạy, nhảy, né chƣớng ngại
vật. Thể loại game này phù hợp cho thƣ giãn, luyện nhanh tay, nhanh mắt, nhanh phản
xạ. Trong game mê cung cần phải tìm đƣờng đi qua một mê cung vật lý, trong đó các
tuyến đƣờng đƣợc xác định bởi các bức tƣờng và rào cản khác. Game giải đố thƣờng
giống nhƣ game mê cung nhƣng thay vào đó vấn đề cần đƣợc giải quyết là các rào cản
vật lý để tìm cách vƣợt qua. Câu đố giải quyết vấn đề gián tiếp mà ngƣời chơi phải tạo
ra một loạt sự việc xảy ra theo thứ tự để kích hoạt hành động tiếp theo và tiến lên.
Nhiều game giải đố sử dụng chế độ giải quyết vấn đề trực tiếp, trong đó câu đố đƣợc
trình bày một cách trực quan. Sau đó, cần thao tác với các biểu tƣợng trên màn hình
hoặc các điều khiển theo trình tự chính xác để giải quyết vấn đề.
1.1.6. Game mô phỏng
Mục tiêu của một game mô phỏng là tái tạo một tình huống trong thế giới thực
một cách chính xác. Các trình giả lập đều nhấn mạnh vào độ trung thực của hình ảnh,
âm thanh và vật lý của game, nghĩa là trong môi trƣờng game nhƣng ngƣời chơi có
cảm giác đang ở thế giới thực nhƣ lái máy bay chiến đấu hoặc lái xe đua.
1.1.7. Game phiêu lưu
Game phiêu lƣu hay game mạo hiểm là thể loại game giả định ngƣời chơi là
nhân vật chính trong một câu chuyện có tính tƣơng tác để khám phá và vƣợt qua thử
thách. Game thuộc thể loại này tập trung vào xây dựng cốt truyện, đƣợc diễn dịch sang
loại hình truyền thông mang tính tƣờng thuật nhƣ sách vở, điện ảnh hay văn chƣơng.
Để có một game phiêu lƣu hấp dẫn, các nhà phát triển tập trung vào yếu tố đồ họa, âm
thanh. Tuy nhiên, yếu tố làm cho ngƣời chơi cảm thấy lôi cuốn, hấp dẫn trong game
này là cốt truyện và khám phá câu đố đòi hỏi phải tƣ duy. Ví dụ nhƣ Uninvited và
Shadowgate có cốt truyện là ngƣời chơi đang ở lâu đài của kẻ xấu, cần tìm và tiêu diệt
kẻ xấu đó. Game phiêu lƣu luôn bắt đầu bởi một câu chuyện liên quan đến sự bí ẩn và
để làm điều đó ngƣời chơi cần phải khám phá thế giới. Ví dụ, ngƣời chơi có thể khám

3
phá những bí ẩn đằng sau câu chuyện để hiểu đƣợc bản chất của một nhân vật, hoặc
khám phá ra đối tƣợng mà ngƣời chơi cần tìm kiếm.
1.2. Quy trình phát triển game
Bƣớc 1. Thành lập ý tƣởng. Ý tƣởng của một tựa game bao gồm 3 phần:
- High Concept: Trả lời câu hỏi “Tựa game nói về cái gì?” tức nội dung mà tựa
game hƣớng tới.
- Pitch: Bản mô tả về ý tƣởng làm ra tựa game. Bản này không quá dài, đủ hấp
dẫn và trả lời cho 2 cầu hỏi tiên quyết là game sẽ ăn khách ở điểm nào và có khả năng
sinh lời hay không?
- Concept Document (game plan): Kế hoạch làm game đƣợc tạo ra. Concept
Document sâu và nhiều thông tin hơn so với Pitch ở trên, bao gồm cả đối tƣợng khách
hàng đƣợc hƣớng đến, thời gian biểu dự kiến, định hƣớng marketing, yêu cầu về nhân
sự cũng nhƣ đánh giá rủi ro của dự án.
Bƣớc 2. Tìm kiếm công cụ để làm game, là nghiên cứu và chọn một công cụ
phát triển game phù hợp nhƣ Unity, Unreal Engine hoặc các công cụ khác dựa trên yêu
cầu cụ thể của dự án.
Bƣớc 3. Chọn nền tảng hỗ trợ, quyết định nền tảng mà game sẽ hỗ trợ, ví dụ
Android, iOS, Web, PS4, Xbox để có chiến lƣợc phát triển và triển khai phù hợp.
Bƣớc 4. Chọn thể loại game, các tƣơng tác, game 2D hay 3D, đặc tả các yếu tố
tƣơng tác nhƣ điều khiển, cơ chế gameplay.
Bƣớc 5. Thiết kế gameplay, xác định cách chơi game, quy tắc và môi trƣờng.
Đảm bảo sự cân bằng và sự hấp dẫn cho ngƣời chơi.
Bƣớc 6. Viết kịch bản, đặt ra cốt truyện và các sự kiện trong game. Xác định
cách ngƣời chơi tƣơng tác với cốt truyện.
Bƣớc 7. Thiết kế, vẽ, sáng tạo nhân vật, bản đồ, Assets. Tạo các tài nguyên nhƣ
hình ảnh, âm thanh, và mô hình 3D cho nhân vật, bản đồ.
Bƣớc 8. Hiện thực hóa bản thiết kế sử dụng công cụ phát triển game, gồm xây
dựng bản đồ, ánh xạ vật liệu và triển khai nhân vật.
Bƣớc 9. Liệt kê các tính năng cần thiết cho game nhƣ chế độ chơi nhiều ngƣời,
hỗ trợ âm thanh, đồ họa cao cấp,...
Bƣớc 10. Phát triển cốt truyện và các chi tiết liên quan, đảm bảo tính logic và
sự liên kết trong game.
Bƣớc 11. Xây dựng và ráp nối các phân đoạn, lập kế hoạch và xây dựng các
phân đoạn của game, kết nối chúng để tạo thành một trải nghiệm chơi game liền mạch.
Bƣớc 12. Viết tài liệu và kiểm thử game, hƣớng dẫn và mô tả cho ngƣời chơi,
nhà phát triển và sửa lỗi.
Bƣớc 13. Thử nghiệm game để đảm bảo game hoạt động mƣợt mà và không
gặp lỗi.
Bƣớc 14. Phát hành game và tiến hành các chiến lƣợc quảng bá nhƣ tạo trang
web, sử dụng mạng xã hội và quảng cáo để thu hút ngƣời chơi.

4
1.3. Thành phần cơ bản của game
a. Game engine
Game engine còn đƣợc gọi là công cụ game hay phần mềm game, là một phần
mềm dùng để xây dựng và thiết kế game.
Một game engine thông thƣờng gồm kết xuất đồ họa cho hình ảnh 2D hay 3D,
công cụ vật lý, hoạt hình, trí tuệ nhân tạo cho nhân vật, phân luồng, tạo dòng dữ liệu
xử lý, quản lý bộ nhớ, dựng ảnh đồ thị,... Từ một game engine có thể phát triển nhiều
game khác nhau. Đây là giải pháp tiết kiệm thời gian và chi phí cho các nhà sản xuất.
Game engine chính là thành phần cốt lõi làm nền móng xây dựng nên game. Sự khác
biệt giữa game engine và bản thân game tƣơng tự nhƣ sự khác biệt giữa động cơ và
chiếc xe. Một game engine là một phần không phụ thuộc vào game, có thể đƣợc thay
đổi độc lập với game mà không làm thay đổi cấu trúc logic của game. Việc lựa chọn
một game engine trƣớc khi phát triển sẽ giúp giảm thiểu những sai sót không đáng có
trong quá trình phát triển phần mềm. Có một số tiêu chí cần chú ý nhƣ sau:
- Giá thành: Một số game engine có giá thành cao, tuy có rất nhiều chức năng
và khả năng hỗ trợ tốt, do vậy ngƣời dùng cần xem xét đến vấn đề này trƣớc tiên.
- Hiệu năng: Trƣớc khi lựa chọn một engine cần kiểm tra hiệu năng của engine
này trên nền tảng mà ngƣời dùng hƣớng đến. Ví dụ một engine có hiệu năng cao trên
PC nhƣng lại cho hiệu năng thấp hoặc không hỗ trợ đầy đủ trên Android hoặc một
engine chỉ cho 1 đến 2 FPS (Frame Per Second) trong khi một engine khác cho 30 FPS
với cùng một cách lập trình.
- Cộng đồng: Lƣợng ngƣời sử dụng thể hiện chất lƣợng của engine. Khi có
nhiều lập trình viên sẽ hỗ trợ các vấn đề phát sinh khi sử dụng engine tốt hơn.
- Tài liệu: Tài liệu cụ thể sẽ giúp rất nhiều khi mới làm quen với engine để giải
quyết các vấn đề về thiết kế cũng nhƣ các vấn đề xảy ra trong khi lập trình.
- Mã nguồn: Khi các tài liệu không đủ thông tin thì cần tìm hiểu mã nguồn để
hiểu cách xử lý của engine. Khi mã nguồn của engine không phù hợp với yêu cầu thì
ngƣời dùng cần sửa đổi. Mã nguồn mở giúp việc sửa lỗi nhanh hơn do có nhiều ngƣời
đóng góp hơn.
- Các tính năng: Mục đích khi sử dụng engine là tiết kiệm thời gian phát triển,
dễ phát triển và phù hợp trong các ứng dụng.
Các thành phần trong game engine:
Graphics engine: Là thành phần quan trọng nhất của game engine, xử lý quá
trình render trong game (là quá trình chuyển đổi dữ liệu ngƣời dùng thành hình ảnh
hiển thị trên màn hình cho ngƣời chơi), cung cấp các API giúp việc lập trình trở nên
đơn giản và có cấu trúc chặt chẽ hơn. Một số tính năng của graphics engine nhƣ quản
lý khung cảnh, đổ bóng, ánh sáng, load mô hình, hoạt cảnh, địa hình và hiệu ứng.
Sound Engine: Hỗ trợ phát nhạc nền và hiệu ứng âm thanh, tối ƣu hóa dữ liệu
và đa luồng.
Input Engine: Là các công cụ để tƣơng tác game nhƣ bàn phím, gamepad,
joystick, Kinect,…

5
Physics Engine: Là các mô phỏng vật lý trong game. Lập trình game càng phát
triển thì càng nhiều các mô phỏng vật lý phức tạp.
Network engine: Networking đặc biệt quan trọng đối với các game nhiều ngƣời
chơi trực tuyến (Massively Multiplayer Online - MMO), ví dụ NetDog, RakNet, GNE
- Scripting giúp việc lập trình game trở nên mềm dẻo.
Trí tuệ nhân tạo: Một số giải thuật trí tuệ nhân tạo có thể xây dựng thành các
framework để sử dụng trong game engine, ví dụ tìm đƣờng đi ngắn nhất,…
Hỗ trợ cross-platform: Giúp ngƣời dùng có thể viết game và build ở các hệ điều
hành khác nhau một cách dễ dàng.
b. Script
Công cụ game thực hiện tất cả công việc kết xuất đồ họa, kết nối mạng,…
Ngƣời dùng gắn tất cả các tính năng này với các kịch bản (Script). Các game đầy đủ
tính năng sẽ khó tạo nếu không có khả năng viết kịch bản. Các kịch bản đƣợc sử dụng
để đƣa các thành phần của công cụ lại với nhau, cung cấp các chức năng nhƣ ghi điểm,
quản lý ngƣời chơi, xác định hành vi của ngƣời chơi, phƣơng tiện và kiểm soát giao
diện ngƣời dùng đồ họa GUI (Graphical User Interface).
c. GUI
GUI là sự kết hợp của đồ họa và các kịch bản mang lại sự xuất hiện trực quan
của game và chấp nhận các đầu vào điều khiển của ngƣời dùng nhƣ hiển thị điểm số
ngƣời chơi có đƣợc. Các menu khởi động chính, menu cài đặt hoặc tùy chọn, hộp thoại
và các hệ thống tin nhắn trong game.
d. Model
Nhân vật của ngƣời chơi là một mô hình. Tất cả các tòa nhà, cây cối, cột đèn và
phƣơng tiện trong thế giới game gọi chung là các mô hình. Thế giới trong game mà
nhân vật đi trong đó đƣợc gọi là địa hình.
e. Họa tiết
Trong game 3D, kết cấu là một phần quan trọng của kết xuất các mô hình trong
cảnh. Họa tiết xác định sự xuất hiện trực quan của tất cả các mô hình thành một game.
f. Âm thanh
Âm thanh cung cấp điểm thu hút theo ngữ cảnh trong game, cung cấp tín hiệu
âm thanh cho các sự kiện và âm thanh nền cho môi trƣờng và bối cảnh cũng nhƣ tín
hiệu định vị cho ngƣời chơi. Việc sử dụng hợp lý các hiệu ứng âm thanh phù hợp là
cần thiết để tạo ra một sản phẩm game tốt.
g. Nhạc
Một số game, đặc biệt là game nhiều ngƣời chơi thƣờng ít sử dụng nhạc. Đối
với các game khác, ch ng hạn nhƣ game phiêu lƣu một ngƣời chơi cần có âm nhạc để
thiết lập tâm trạng dòng câu chuyện và tín hiệu theo ngữ cảnh cho ngƣời chơi.
1.4. Giới thiệu chung về Unity
Unity 3D đƣợc dùng để làm game 3D hoặc những nội dung có tính tƣơng tác
nhƣ thể hiện kiến trúc, hoạt hình 3D thời gian thực. Unity là một trong những engine

6
phát triển game đa nền, trình biên dịch có thể chạy trên Windows, Mac OS và có thể
xuất ra game cho Windows, Mac, Wii, iOS, Android. Game cũng có thể chơi trên trình
duyệt web thông qua plugin Unity Web Player.
1.4.1. Tính năng của Unity
a. Kết xuất hình ảnh - Rendering: Giống nhƣ tất cả các engine hoàn chỉnh khác,
Unity hỗ trợ đầy đủ khả năng kết xuất hình ảnh cùng nhiều hỗ trợ cho phép áp dụng
các công nghệ phổ biến trong lĩnh vực đồ họa 3D nhằm cải thiện chất lƣợng hình ảnh.
Các phiên bản gần đây nhất của Unity đƣợc xây dựng lại thuật toán nhằm cải thiện
hiệu suất kết xuất hình ảnh đồng thời tăng cƣờng chất lƣợng hình ảnh.
Ánh sáng - Lighting: Unity cung cấp nhiều giải pháp đa dạng cho phép áp dụng
ánh sáng vào môi trƣờng trong game với nhiều loại nguồn sáng nhƣ ánh sáng có
hƣớng (Directional Light), ánh sáng điểm (Point Light),...
Địa hình - Terrains: Terrains còn gọi chung là địa hình bao gồm phần đất nền
của môi trƣờng trong game cùng các đối tƣợng gắn liền nhƣ cây cỏ. Unity cung cấp
công cụ Terrains Tools cho phép thiết kế địa hình với các công cụ vẽ dƣới dạng Brush
với nhiều thông số tùy chỉnh để tạo hình và lát Texture cho địa hình. Cùng với Terrain
Tools là Tree Creator phép tạo cây cối với hình dạng, kích thƣớc và kiểu cách đa dạng.
Texture thông minh - Substances: Substances là dạng tùy biến Textures làm đa
dạng trong điều kiện môi trƣờng khác nhau. Unity cung cấp khả năng này thông qua
các API dựng sẵn trong thƣ viện, hỗ trợ ngƣời dùng tuỳ biến hình ảnh đƣợc kết xuất
của Texture.
Vật lý - Physics: PhysX là một Engine mô phỏng và xử lý vật lý đƣợc phát triển
bởi NVIDIA. Một số vấn đề vật lý đƣợc hỗ trợ bởi Unity nhƣ: Soft Bodies, Rigit
bodies, Ragdolls, Joints, Cars,…
Tìm đường - Pathfinding: Đây là tính năng mới từ phiên bản Unity 3.5 trở đi.
Với phiên bản này, Unity hỗ trợ tính năng Pathfinding cho phép tạo ra khả năng tìm
đƣờng cho AI nhờ vào khái niệm lƣới định hƣớng (NavMesh).
Âm thanh - Audio: Unity tích hợp FMOD hỗ trợ nhập và sử dụng nhiều định
dạng tập tin âm thanh khác nhau.
Lập trình - Programming: Lập trình là một trong những yếu tố quan trọng nhất
trong phát triển game. Lập trình cho phép nhà phát triển tạo nên khả năng tƣơng tác và
yếu tố gameplay cho game.
Networking: Networking cho phép tạo ra các game trực tuyến. Tính năng này
hỗ trợ đầy đủ để tạo nên các khía cạnh phổ biến trong game online nhƣ chat và tƣơng
tác thời gian thực nhƣ: State Synchronization, Realtime Networking, Remote
Procedure Calls, Backend Connectivity, Web Browser Integration, Web Connectivity.
1.4.2. Kiến trúc Engine Unity
a. Kiến trúc tổng quan
Engine Unity hỗ trợ UnityAPI để viết Script game. UnityAPI chứa các đối
tƣợng và phƣơng thức hỗ trợ hầu hết các thành phần trong Unity. Trong một Scene

7
thƣờng có nhiều đối tƣợng game. Mỗi đối tƣợng này có thể có hoặc không có đoạn
Script nào gắn vào. Script điều chỉnh hành vi đối tƣợng game bằng cách sử dụng các
lớp, các hàm và biến của UnityAPI. Để gắn Script vào đối tƣợng cần kế thừa lớp đó từ
lớp MonoBehaviour của UnityAPI và tên lớp phải trùng với tên file Script.
Mỗi Script khi gắn lên đối tƣợng game đều đƣợc đối tƣợng game xem nhƣ một
thành phần bên trong và đƣợc cấp phát vùng nhớ khi chạy.

Hình 1.1. Tổng quan kiến trúc Unity


Một số lớp của UnityAPI:
- MonoBehaviour: Tất cả các Script muốn gắn vào một đối tƣợng game bắt
buộc phải kế thừa từ lớp này.
- GameObject: Lớp cha của tất cả các thực thể trong Scene.
- Component: Lớp cha của tất cả các thành phần có thể gắn vào đối tƣợng.
- Transform: Hỗ trợ thay đổi vị trí, xoay, biến đổi tỷ lệ mô hình.
- Audio: Hỗ trợ lập trình với âm thanh.
- Camera: Hỗ trợ lập trình camera.
- Light: Hỗ trợ tạo ánh sáng trong game.
- Projector: Hỗ trợ chiếu texture lên bề mặt vật thể.
- ParticleEmitter: Hỗ trợ tạo các hiệu ứng particle.
- Input: Hỗ trợ lập trình với chuột, cảm ứng đa điểm, cảm biến gia tốc.
- Animation: Hỗ trợ chuyển động của mô hình nhân vật.
- Rigidbody: Hỗ trợ tạo hiệu ứng vật lý liên quan đến trọng lực.
- CharacterController: Hỗ trợ điều khiển nhân vật di chuyển theo độ cao.
- Collider: Hỗ trợ lập trình va chạm giữa các vật thể.
- GUI: Hỗ trợ lập trình giao diện ngƣời dùng trên Unity.

8
b. Chu kỳ sống của thành phần Script gắn trên đối tượng game
Chu kỳ sống của thành phần Script đƣợc tính từ khi Scene đƣợc chạy cho đến
lúc Scene bị tắt hoặc chuyển sang Scene khác. Tùy theo trạng thái của Scene mà sự
kiện tƣơng ứng sẽ đƣợc gọi.

Hình 1.2. Chu kỳ sống của thành phần Script


Awake(): Đƣợc gọi khi Script load xong. Khi game khởi động, tất cả các đối
tƣợng game đƣợc khởi tạo. Sau khi đối tƣợng game đƣợc khởi tạo thì thể hiện của
Script đƣợc nạp. Hàm Awake() đƣợc gọi một lần trong suốt vòng đời của Script khi thể
hiện của Script đƣợc nạp để thiết lập cho game play. Thứ tự gọi hàm Awake() là ngẫu
nhiên, vì vậy không dùng hàm này để gửi thông tin từ đối tƣợng này sang đối tƣợng
khác vì đối tƣợng có thể chƣa sẵn sàng. Awake() sử dụng khi khởi tạo giá trị biến, lấy
thành phần nào đó của game object.
Start(): Đƣợc gọi khi Script load xong và cũng chỉ đƣợc gọi một lần khi Script
kích hoạt, việc này chỉ diễn ra sau khi hàm Awake() đƣợc gọi trên tất cả các đối tƣợng
trong Scene, nghĩa là hàm Start() sử dụng khi cho phép một giá trị đó làm gì.
Update (): Là sự kiện vẽ lại màn hình, đƣợc sử dụng nhiều nhất và đƣợc gọi
liên tục từng frame. Sau khi Script đƣợc kích hoạt và hàm Start() đã đƣợc gọi, hàm
Update() đƣợc gọi trong mỗi khung hình (frame) mà game render sau đó. Đây là hàm
thực thi hành vi của game. Render một game tức là vẽ game ra màn hình theo một
frame, ở một tốc độ đủ nhanh để tạo cảm giác chuyển động trong game. Trƣớc khi
render mỗi frame, engine unity phải tính toán các thay đổi so với frame trƣớc đó dựa
trên hành vi của đối tƣợng game đã đƣợc quy định bởi các component của mỗi đối
tƣợng game, bao gồm cả Script. Mã nguồn viết trong hàm Update() sẽ điều khiển hành
động tiếp theo của game. Hàm Update() sử dụng khi di chuyển nhân vật mà không có
thành phần vật lý, khi di chuyển nhân vật bằng transform.
FixedUpdate(): Gọi 2, 3 lần trên mỗi frame, thƣờng sử dụng khi di chuyển nhân
vật có Physics. Hàm này có thể thay thế vị trí hàm Update ().

9
LateUpdate(): Là sự kiện chạy sau Update() và đƣợc gọi liên tục từng frame,
thƣờng sử dụng khi di chuyển camera theo nhân vật.
OnGUI(): Là sự kiện vẽ GUI và đƣợc gọi liên tục từng frame, dùng để tạo giao
diện ngƣời dùng của UnityAPI.
OnDestroy(): Đƣợc gọi khi thành phần Script bị hủy khỏi bộ nhớ.
Khi tạo một Project mới thì Unity tự tạo các hàm Start() và Update() rỗng cho
Script này. Engine chạy các hàm này trên mọi đối tƣợng game bất kể có liên kết Script
với đối tƣợng hay không. Tuy nhiên, cần xác định đƣợc thời điểm mà game engine sẽ
gọi các hàm khác nhau để từ đó quyết định chính xác hàm nào cần phải đƣợc ghi đè
sao cho các đối tƣợng game xử lý hành động tƣơng ứng.
1.4.3. Thao tác với Project Unity
a. Tạo Project
Bƣớc 1. Khởi động phần mềm Unity, trong tài liệu này sử dụng phần mềm
Unity 3D phiên bản 2021.3.19f1, Unity Hub 3.5.0.

Hình 1.3. Chọn phiên bản 2021 3.19f1


Bƣớc 2. Cửa sổ Unity hiển thị ra, chọn New project.

Hình 1.4. Tạo Project mới

Hình 1.5. Game góc nhìn thứ nhất

10
Hình 1.6. Game góc nhìn thứ ba

Hình 1.7. Game FPS

Hình 1.8. Microgame


Bƣớc 3. Trong thẻ Projects, nhập các thông tin.

Hình 1.9. Tạo Project


Project name: Tên dự án, giả sử là Bai1.
Location: Khu vực lƣu trữ dự án Bai1.
Bƣớc 4. Trong phần Templates, chọn nền tảng game.
Bƣớc 5. Nhấn Create project để tạo mới 1 dự án game.

11
Hình 1.10. Giao diện màn hình Unity
b. Mở một Project có sẵn
Khi đang mở trình soạn thảo Unity, nhấp Menu File, chọn Open Project.

Hình 1.11. Mở Project


Nếu đang ở màn hình Home sẽ xuất hiện danh sách các Project đã tạo trƣớc đó,
nhấp vào Project muốn mở trong danh sách. Trƣờng hợp Project không có trong danh
sách, cần chỉ ra đƣờng dẫn chứa Project: Chọn Open bên góc phải màn hình, tìm đến
nơi lƣu trữ Project cần mở, nhấp Open.

Hình 1.12. Mở Project từ màn hình Home

12
Hình 1.13. Kết quả mở Project
c. Thực thi Project
Để thực thi Project, vào Edit, chọn Play hoặc nhấn nút Play (mũi tên hƣớng
phải) ở góc trên bên trái của cửa sổ Unity hoặc nhấn tổ hợp phím Ctrl + P trong chế
độ xem của Unity.

Hình 1.14. Thực thi game

Hình 1.15. Kết quả thực thi game

13
Để dừng Project, vào Edit, chọn Pause hoặc nhấn nút Pause (dấu th ng) ở góc
trên bên trái của cửa sổ Unity hoặc nhấn tổ hợp phím Ctrl + Shift + P trong chế độ
xem của Unity. Để thực thi từng bƣớc Project, vào Edit, chọn Step hoặc nhấn nút Step
(mũi tên hƣớng phải và gạch th ng) ở góc trên bên trái của cửa sổ Unity hoặc nhấn tổ
hợp phím Ctrl + Alt + P trong chế độ xem của Unity.
1.4.4. Giao diện của Unity
Giao diện (Layout) của Unity có thể tùy chỉnh. Layout của Unity bao gồm
nhiều tab khác nhau và có thể bật tắt.

5
1

Hình 1.16. Giao diện Editor của Unity


Hình 1.16 có 5 khung khác nhau:
Scene [1]: Nơi xây dựng game.
Hierarchy [2]: Danh sách các game object trong một Scene game.
Inspector [3]: Màn hình cài đặt cho tài nguyên và đối tƣợng đang đƣợc chọn.
Game [4]: Cửa sổ xem trƣớc game, hoạt động ở chế độ chơi (khi nhấn Play).
Project [5]: Danh sách các tài nguyên trong Project và có vai trò nhƣ thƣ viện.
a. Cửa sổ Scene
Cửa sổ Scene là nơi xây dựng toàn bộ các đối tƣợng trong game. Cửa sổ cung
cấp nhiều góc nhìn khác nhau, có thể nhìn dạng phối cảnh hoặc dạng song song. Nếu
xây dựng game 3D, ở góc trên bên phải của khung Scene sẽ xuất hiện biểu tƣợng
Gizmo (hệ trục tọa độ Oxyz), là nơi hiển thị góc nhìn hiện tại của khung Scene, giúp
thay đổi góc nhìn một cách nhanh chóng. Mỗi màu sắc thể hiện một trục tọa độ khác
nhau, có thể click chuột vào các trục để thay đổi góc nhìn tƣơng ứng với trục đó.

Hình 1.17. Hệ trục tọa độ

14
Khi chọn một đối tƣợng trong cửa sổ Scene, đối tƣợng này sẽ đƣợc tự động
chọn trong cửa sổ Hierarchy và ngƣợc lại.

Hình 1.18. Chọn đối tƣợng trong Scene và Hierarchy


Thanh công cụ điều khiển khung Scene:

Hình 1.19. Thanh công cụ điều khiển Scene


Thanh công cụ này cung cấp khả năng hiển thị khung Scene dƣới nhiều chế độ
khác nhau. Ngoài ra còn cung cấp tùy chỉnh về ánh sáng, âm thanh, hiệu ứng,…
b. Hierarchy
Khung nhìn Hierarchy chứa danh sách các đối tƣợng hiện có trong Scene và
đƣợc hiển thị theo thứ tự bảng chữ cái. Các đối tƣợng có thể đƣợc nhóm lại thành các
đối tƣợng cha và con tƣơng ứng.

Hình 1.20. Khung nhìn Hierarchy

15
c. Inspector
Inspector hiển thị thông tin, các thành phần trong đối tƣợng game đang chọn và
cho phép điều chỉnh các biến của thành phần này. Cửa sổ Inspector nhƣ cửa sổ
Properties khi thiết kế giao diện Winform trên Visual Studio.
Ngoài ra, Inspector cũng thể hiện các thông số Import Setting của Asset đang
làm việc nhƣ hiển thị mã nguồn của Script, thông số Animation,…

Hình 1.21. Cửa sổ Inspector


d. Cửa sổ game
Cửa sổ game hiển thị cửa sổ Scene và sẽ hoạt động khi nhấn nút Play. Trong
cửa sổ này có thể chọn các kích cỡ hiển thị khác nhau để build cho các loại máy khác
nhau, có thể chơi thử game khi nhấn nút Play. Chú ý: Khi cửa sổ game hoạt động thì
mọi chỉnh sửa trên cửa sổ Scene và cài đặt cho các đối tƣợng chỉ là tạm thời. Khi nhấn
nút Stop, cửa sổ này về trạng thái tĩnh thì mọi chỉnh sửa trƣớc đó không còn.

16
Hình 1.22. Cửa sổ game
Thanh công cụ trong cửa sổ game cung cấp các tùy chỉnh về độ phân giải màn
hình, thông số (stats), gizmos, tùy chọn bật và tắt các component,…
Ngƣời dùng có thể tự tạo độ phân giải màn hình game tùy theo ứng dụng cụ thể
bằng cách chọn mục Add New Item nhƣ sau:

Hình 1.23. Tạo độ phân giải màn hình game


Sau đó xác định tên, chiều cao và chiều rộng cho độ phân giải cần đặt:

Hình 1.24. Tạo độ phân giải màn hình game

17
e. Cửa sổ Project
Cửa sổ Project thể hiện nội dung trong thƣ mục tài nguyên (Assets) của Project.
Khi thêm tài nguyên vào thƣ mục Assets chúng sẽ tự động cập nhật vào Project Unity.

Hình 1.25. Cửa sổ Project


Cột bên trái hiển thị Assets và các mục yêu thích dƣới dạng cây thƣ mục tƣơng
tự nhƣ Windows Explorer. Khi click vào một nhánh trên cây thƣ mục thì toàn bộ nội
dung của nhánh đó sẽ đƣợc hiển thị ở khung bên phải.
Có thể tạo ra các thƣ mục mới bằng cách nhấp chuột phải, Create, Folder hoặc
nhấn vào nút Create ở góc trên bên trái cửa sổ Project và chọn Folder. Phía trên cây
thƣ mục là mục Favorites, giúp truy cập nhanh vào những tài nguyên thƣờng sử dụng,
ngoài ra có thể đƣa các tài nguyên vào Favorites bằng cách kéo thả.

Hình 1.26. Mục Favorites và thanh tìm kiếm


Khi game càng lớn, số lƣợng Assets càng nhiều, thanh tìm kiếm sẽ là công cụ
cần thiết để xác định Assets cần tìm. Ngoài ra, thanh công cụ của Unity gồm 5 thành
phần. Mỗi phần có ý nghĩa và mục đích khác nhau.
Bảng 1.1. Một số chức năng trong thanh công cụ

Công cụ Chức năng

Thao tác với các game object trong cửa sổ Scene, gồm di
chuyển các object, di chuyển camera chính, di chuyển từng
game object và một số tính năng phóng to, thu nhỏ, xoay,…
Truy cập từ bàn phím bằng cách sử dụng các phím Q, W, E, và
R. Các phím thực hiện các hoạt động sau đây:
- Công cụ bàn tay [Q]: Cho phép di chuyển trong cửa sổ
Scene, xoay góc nhìn, phóng to, thu nhỏ góc nhìn.

18
Công cụ Chức năng

- Công cụ di chuyển [W]: Cho phép di chuyển một đối tƣợng.


- Công cụ xoay [E]: Cho phép xoay nhân vật theo một trục
trong không gian.
- Công cụ tỷ lệ [R]: Cho phép tăng giảm tỷ lệ kích thƣớc của
đối tƣợng.
- Công cụ Transform Tool: Thực hiện các phép về Transform
đối tƣợng.
- Công cụ Edit bounding: Thay đổi kích thƣớc Collider.

Thay đổi khung nhìn Scene bằng cách biến đổi chốt Gizmo.

Công cụ giúp thực thi game trƣớc khi build sang các nền tảng
khác, dừng game hoặc tiếp tục.

Tùy chỉnh các đối tƣợng xuất hiện trong khung Scene.

Bố trí lại màn hình làm việc. Unity cung cấp sẵn một số cách
bố trí màn hình sau:
- 2 by 3: Cho phép xem cả chế độ Scene và chế độ game cùng
với Inspector. Chế độ Project và Hierarchy ở bên phải.
- 4 Split: Xem các mô hình 3D từ các góc độ khác nhau.
- Tall: Chế độ xem Scene và Inspector rõ ràng.
- Wide: Đẩy phân cấp, cửa sổ Project và Assets xuống dƣới
cùng của Editor.
- Default: Chế độ xem Scene hoặc game chiếm phần lớn
Editor, có Assets, Hierarchy và Inspector ở hai bên.
Ngoài ra có thể tự bố trí lại màn hình làm việc bằng thao tác
kéo thả các cửa sổ.

Hình 1.27. Minh họa công cụ Translate, Rotate

19
Cửa sổ Console hiển thị đầu ra của game trong suốt quá trình phát triển nhƣ các
thông báo, cảnh báo và lỗi. Để hiển thị cửa sổ Console, vào Window, Console hoặc tổ
hợp phím Ctrl+Shift+C.

Hình 1.28. Minh họa cửa sổ Console


1.4.5. Các thành phần cơ bản trong Unity
a. Assert
Assert là kho tài nguyên cho việc xây dựng game trong một Project của Unity.
Các tài nguyên này có thể là hình ảnh, âm thanh, hoặc một mô hình 3D có sẵn. Unity
sẽ tham chiếu đến các tập tin để tạo ra các tài nguyên cho game. Do đó, trong bất kỳ
thƣ mục chứa Project sử dụng Unity thì tất cả các tập tin tài nguyên đƣợc lƣu trữ trong
một thƣ mục con tên là Asset.

Hình 1.29. Asset trong Unity


b. Scene
Trong Unity coi Scene là các màn chơi, cảnh chơi, cấp độ chơi riêng lẻ, hoặc
các vùng của nội dung game. Bằng cách xây dựng game với nhiều cảnh thì có thể phân
phối thời gian tải và thử nghiệm các phần khác nhau của game riêng lẻ một cách nhanh
chóng và chính xác.

Hình 1.30. Các Scene của Unity

20
c. Game object
Khi một tài nguyên đƣợc sử dụng trong một Scene thì tài nguyên này là một
game object mới. Mỗi game object phải chứa ít nhất một thành phần, đó là thành phần
Transform. Transform chứa các phép để biến đổi góc quay, tỷ lệ hay tịnh tiến của
đối tƣợng.

Hình 1.31. Kéo tài nguyên vào Scene để sử dụng


Bất kỳ đối tƣợng nào trong Scene đều là game object, kể cả các đối tƣợng vô
hình (là đối tƣợng không đƣợc vẽ lên hoặc trong suốt). Unity dựa vào thông số
Transform và vị trí camera để tính toán xem vẽ game object trên Scene nhƣ thế nào.
Game object không nhất thiết phải đƣợc đặt sẵn trên Scene mà có thể đƣợc tạo ra và
thêm vào Scene trong quá trình thực thi game. Unity cho phép tạo ra các object nguyên
thủy đơn giản trong một Scene, ví dụ hình cầu, hình hộp, hình trụ,… Để tạo một game
object, chọn GameObject, Create Other, Cube, hoặc GameObject, Create Other,
Sphere, hoặc GameObject, Create Other, Cylinder.

Hình 1.32. Object nguyên thủy


Có thể chọn khối lập phƣơng, nhấn Ctrl+D trên bàn phím hoặc nhấn khối lập
phƣơng tại cửa sổ Hierarchy và chọn Duplicate để tạo nhiều game object.
d. Component
Component là các thành phần trong một game object của Unity. Bằng cách đính
kèm các thành phần vào đối tƣợng, ngƣời dùng có thể áp dụng ngay các phần mới của
game engine. Thông thƣờng các thành phần này đƣợc Unity xây dựng sẵn nhƣ ánh

21
sáng, camera, particle, hiệu ứng vật lý,… Ví dụ một nhân vật trong game có các
component chứa thông tin hoặc các phản ứng của nhân vật đó. Bằng cách gắn thêm
các component vào game object, ngƣời dùng có thể làm phong phú thêm các tính chất
của game object nhằm đáp ứng yêu cầu của gameplay.

Hình 1.33. Các thành phần trong game object


Một số thành phần cơ bản trong cửa sổ Inspector của Unity:
Transform (Biến đổi): Đây là thành phần cơ bản của mọi game object, bao gồm
thông tin về vị trí, tỷ lệ và góc xoay của đối tƣợng trong không gian 3D.
Renderer (Bộ dựng): Thành phần này quyết định cách thức mà đối tƣợng sẽ
đƣợc hiển thị trong game, chứa các thông tin về vật liệu (material) và ánh sáng
(lighting) liên quan đến đối tƣợng.
Collider (Trình va chạm): Để đối tƣợng có thể tƣơng tác với các đối tƣợng khác
trong game cần thêm thành phần Collider xác định ranh giới va chạm của đối tƣợng.
Rigidbody (Cơ thể cứng): Nếu muốn đối tƣợng có thể tham gia vào hiệu ứng
vật lý cần thêm thành phần Rigidbody.
Animator (Hoạt cảnh): Thành phần này sử dụng để tạo và quản lý các hoạt cảnh
của đối tƣợng, làm cho đối tƣợng có thể chuyển động một cách hợp lý.
AudioSource (Nguồn âm thanh): Khi muốn thêm âm thanh vào đối tƣợng cần
sử dụng thành phần AudioSource để điều khiển phát âm thanh.
Canvas: Thành phần này đƣợc sử dụng trong giao diện ngƣời dùng (UI) của
game để định nghĩa khu vực chứa các yếu tố UI nhƣ nút bấm, văn bản, hình ảnh,…

22
Collider 2D (Trình va chạm 2D): Tƣơng tự nhƣ Collider nhƣng đƣợc sử dụng
trong không gian 2D.
Rigidbody 2D (Cơ thể cứng 2D): Tƣơng tự nhƣ Rigidbody nhƣng dùng cho các
hiệu ứng vật lý trong không gian 2D.
Particle System (Hệ thống hạt): Thành phần này đƣợc sử dụng để tạo các hiệu
ứng hạt trong game nhƣ lửa, nƣớc, khói,...
Box Collider (Trình va chạm hình hộp): Một loại Collider có dạng hình hộp
dùng để xác định vùng va chạm của đối tƣợng.
Mesh Collider (Trình va chạm lưới): Một loại Collider dùng để xác định vùng
va chạm của đối tƣợng dựa trên hình dạng mesh của đối tƣợng đó.
e. Script
Script là thành phần quan trọng nhất trong Unity. Ngƣời dùng có thể viết kịch
bản cho game bằng ngôn ngữ lập trình mà Unity hỗ trợ (ví dụ C#).
Tạo Script:
Trình soạn thảo Unity là nơi xây dựng game một cách trực quan. MonoDevelop
là trình soạn thảo Script để viết mã nguồn. Mã nguồn này sẽ cho các đối tƣợng game
hành xử, ví dụ nhƣ phản hồi các thao tác đầu vào của ngƣời chơi hoặc phản hồi giữa
các đối tƣợng game với nhau. Khi tạo mới một dự án, Unity sẽ cung cấp một bộ khung
của game. Bộ khung này có các yếu tố cần thiết cơ bản của một game, do đó khi nhấn
nút Play thì sẽ chỉ khởi động một game rỗng. Giả sử đã tạo đƣợc game object tên là
player_0:

Hình 1.34. Tạo game object


Tại Project, nhấp chuột phải vào Assets, Create, C# Script:

Hình 1.35. Tạo Script

23
Hình 1.36. Các file Script
Muốn đổi tên Script đã tạo, chọn Script và nhấn phím F2 hoặc nhấp chuột phải
tại Script và chọn Rename.
Gắn Script cho game object:
Để gắn Script cho game object, thực hiện nhƣ sau:
Cách 1. Nhấn vào Script (Move) và kéo thả vào game object (player_0) đã tạo
tại cửa sổ Hierarchy.
Cách 2. Kéo Script (Move) và thả vào cửa sổ Inspector.
Cách 3. Chọn game object (player_0), tại cửa sổ Inspector chọn Add
Component và nhập tên Script vào ô tìm kiếm.

Hình 1.37. Gắn Script cho game object


Khi đó, Script đã đƣợc liệt kê dạng một Component trong Inspector.
Ngoài ra, có thể xóa Component bằng biểu tƣợng xóa Component hoặc thiết lập
lại các giá trị thuộc tính của Component (reset).

Hình 1.38. Xóa bỏ hoặc thiết lập lại các giá trị thuộc tính của Component

24
Để mở Script này, nhấp đúp chuột vào Script hoặc chọn Script và mở tại cửa sổ
Inspector, Script đƣợc kế thừa class MonoBehaviour là một class đƣợc Unity xây dựng
sẵn để hỗ trợ làm game. Mỗi file Script có tên class phải trùng với tên file Script. Code
C# mặc định khi tạo Script nhƣ sau:
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4. public class Move : MonoBehaviour
5. {
6. // Start is called before the first frame update
7. void Start()
8. {
9. …
10. }
11. // Update is called once per frame
12. void Update()
13. {
14. …
15. }
16. }
f. Prefab
Prefab dùng để sử dụng lại các đối tƣợng giống nhau có trong game mà chỉ cần
khởi tạo lại các giá trị vị trí, tỷ lệ biến dạng và góc quay từ một đối tƣợng ban đầu. Ví
dụ các đối tƣợng là đồng tiền trong game Mario đều có xử lý giống nhau, nên chỉ cần
tạo ra một đối tƣợng ban đầu, các đồng tiền còn lại sẽ sử dụng prefab, khi lát gạch cho
một nền nhà, các viên gạch cũng đƣợc sử dụng là prefab hoặc trong game tránh đá, các
viên đá sử dụng prefab.
g. Shader và Material
Shader quy định cách thức render của chất liệu trên bề mặt vật thể. Material sử
dụng Shader để làm chất liệu cho mô hình. Giữa Material và Shader có mối liên hệ với
nhau. Shader quy định các thuộc tính cần để Shader làm việc. Còn Material cho phép
gán hình ảnh vào các thuộc tính đó từ Assets.

Hình 1.39. Shader và Material

25
1.5. Địa hình trong Unity
Để tạo địa hình trong Unity, chọn menu GameObject, 3D object, Terrain.

Hình 1.40. Tạo Terrain


Chọn Terrain vừa tạo, cửa sổ Inspector nhƣ sau:

Hình 1.41. Địa hình Terrain


Trong đó:
Create Neighbor Terrains: Tạo các Terrain xung quanh.
Paint Terrain: Vẽ Terrain lên địa hình.
Place Trees: Đặt cây lên bề mặt địa hình.
Paint Details: Đặt các chi tiết nhỏ nhƣ cỏ cây lên bề mặt địa hình.
Terrain Settings: Thay đổi các thiết lập của địa hình.
Để tạo cây trong Unity, chọn menu GameObject, 3D object, Tree:

Hình 1.42. Thêm cây

26
Các bƣớc thực hiện nhƣ sau:
Bƣớc 1. Trong cửa sổ Hierarchy, chọn Create Empty hoặc tạo một game object
mới bằng cách nhấp chuột phải vào cửa sổ Hierarchy và chọn Create Empty.
Bƣớc 2. Chọn game object đã tạo và chọn tab Inspector.
Bƣớc 3. Trong cửa sổ Inspector, nhấn Add Component.
Bƣớc 4. Trong cửa sổ tìm kiếm, nhập từ khóa Terrain để tìm kiếm thành phần
Terrain để thêm vào game object.

Hình 1.43. Thêm Terrain bằng Add Component


Bƣớc 5. Chỉnh sửa địa hình bằng công cụ Terrain.
- Raise/Lower Terrain: Nâng/hạ địa hình.
- Texture Terrain: Vẽ bề mặt địa hình.
- Set Height: Thiết lập chiều cao địa hình.
Bƣớc 6. Để sử dụng công cụ Texture Terrain cần thêm các texture vào địa hình
Terrain sau đó sử dụng công cụ Paint Texture để vẽ texture lên địa hình.

Hình 1.44. Thêm Paint Terrain

27
Ví dụ thiết kế hòn đảo nhƣ sau:

Hình 1.45. Minh họa Rise and lower the terrain height
Texture Unity cung cấp một số các texture và các tài nguyên khác cho việc thiết
kế địa hình. Để sử dụng những tài nguyên này chọn menu Assets, Import Package,
Terrain Assets. Ngoài ra có thể sử dụng các texture tự tạo nếu muốn.
Chọn công cụ Paint Texture, nhấn vào button Edit Textures và chọn Add
Texture. Sau đó lần lƣợt thêm vào các texture Grass (Hill), Good Dirt, Grass & Rock
và Cliff (Layered Rock). Mục Normal map để trống vì không sử dụng Normal map. Để
mặc định giá trị size của texture là (15, 15) và Cliff (Layered Rock) chọn (70, 70).

Hình 1.46. Minh họa Add Texture


Sau khi thêm vào texture đầu tiên thì toàn bộ địa hình sẽ đƣợc phủ texture đó.
Vì địa hình phần lớn là cỏ xanh nên thêm texture Glass (Hill). Tiếp theo, chọn texture

28
Good Dirt để tô vào những vùng xung quanh hòn đảo bằng Brush thích hợp. Lƣu ý là
có thể chỉnh Brush size, Opacity và Target Strength của Brush. Kết quả thu đƣợc
tƣơng tự nhƣ hình 1.47:

Hình 1.47. Minh họa Add Texture tô màu


Tiếp theo, sử dụng texture Grass&Rock để vẽ ở những vùng núi cao, theo tƣ
tƣởng là ở các vùng núi cao cỏ sẽ khó mọc hơn vùng đồng bằng:

Hình 1.48. Minh họa sử dụng texture Grass&Rock


Cuối cùng, dùng texture Cliff (Layered Rock) để vẽ lên đỉnh núi cao nhất. Khi
đó có đƣợc một hòn đảo nhƣ sau:

Hình 1.49. Minh họa sử dụng texture Cliff

29
Tạo cây:
Chọn địa hình vừa tạo, nhấn vào công cụ Place Tree. Nhấn vào Edit Trees… và
chọn Add Tree.
Trong cửa sổ Add Tree hiện ra, nhấn vào vòng tròn bên cạnh mục Tree và chọn
mô hình cây bất kỳ. Ở đây chọn mô hình Palm có sẵn trong Terrain Asset. Ngoài ra có
thể thay đổi giá trị Bend Factor là mức độ tác động của gió lên độ nghiêng của cây:

Hình 1.50. Minh họa sử dụng Add tree


Việc đặt cây lên địa hình cũng tƣơng tự nhƣ việc vẽ texture lên địa hình:

Hình 1.51. Kết quả Add tree


Vẽ các cỏ, cây, hoa, lá lên địa hình: Chọn công cụ Paint Details trong thanh
công cụ của địa hình. Cũng tƣơng tự nhƣ vẽ cây, có thể dùng công cụ này để vẽ các
chi tiết nhỏ nhƣ cỏ, hoa, lá. Các chi tiết này sử dụng công nghệ billboarding, thực chất
là các texture 2D nhƣng luôn quay về phía ngƣời chơi tạo cảm giác 3D. Tƣơng tự,
nhấn vào Edit Details… và chọn Add Grass Texture. Tiếp theo, chọn Detail Texture là
Grass, chọn màu sắc Healthy Color và Dry Color cho phù hợp màu sắc của địa hình:

Hình 1.52. Add Grass Texture

30
Skybox:
Chọn menu Assets, Import Package, Skyboxes. Chọn menu Edit, Render
Settings… Tại Inspector nhấn Skybox Material và chọn skybox phù hợp cho cảnh.

Hình 1.53. Add Skybox


Địa hình vừa tạo là cảnh ngoài trời, do đó sẽ sử dụng Directional Light để làm
nguồn ánh sáng chính cho khung cảnh. Với các loại ánh sáng khác cũng làm tƣơng tự.
Chọn menu GameObject, Create Other, Directional Light. Ngƣời dùng có thể chọn
đối tƣợng ánh sáng này và quay hƣớng cho phù hợp. Mặc dù có thể thay đổi vị trí của
đối tƣợng nhƣng việc này không có tác dụng trong game vì ánh sáng chiếu của
Directional Light là ánh sáng song song.
Tạo Flare cho ánh sáng:
Hiệu ứng Light Flare là hiệu ứng đƣợc sử dụng nhiều trong game, khi ngƣời
chơi nhìn vào nguồn ánh sáng thì những quầng ánh sáng mô phỏng ánh sáng chiếu vào
ống kính máy quay sẽ đƣợc tạo ra. Để sử dụng hiệu ứng này, trƣớc tiên import các tài
nguyên tạo ánh sáng. Chọn menu Assets, Import Package, Light Flares. Sau đó chọn
nguồn ánh sáng, nhƣ vừa tạo ở trên là Directional Light. Trong cửa sổ Inspector, nhấn
vào mục Flare và chọn là Sun. Nếu sử dụng Sun Flare thì lƣu ý đến hƣớng của ánh
sáng chiếu vào cho phù hợp với skybox đã sử dụng, đặc biệt là đối với các skybox có
hình ảnh mặt trời.

31
Hình 1.54. Flare
Tạo nƣớc:
Sử dụng địa hình đã tạo để tạo biển bao quanh hòn đảo. Chọn menu Assets,
Import Package, Water (Basic). Chọn Daylight Simple Water trong thƣ mục Standard
Assets/Water (Basic) và thả vào cửa sổ Scene. Chỉnh vị trí sao cho độ cao y cao hơn
mặt đất và tâm đặt tại chính giữa địa hình. Sau đó phóng to đối tƣợng Water để bao
trùm cả địa hình. Kết quả thu đƣợc nhƣ hình sau:

Hình 1.55. Tạo nƣớc

32
Hình 1.56. Kết quả tạo địa hình đảo
1.6. Một số lớp và hàm trong Unity
1.6.1. Lớp Input
Trong Unity, lớp Input đƣợc sử dụng để xử lý dữ liệu đầu vào từ ngƣời chơi
nhƣ sự kiện từ bàn phím, chuột, hoặc các thiết bị đầu vào khác nhƣ cảm ứng, joystick.
Lớp Input cung cấp một cách thuận tiện để theo dõi và phản ứng với các sự kiện đầu
vào khác nhau trong game.
OnMouse event:
Đây là một sự kiện cơ bản thƣờng đƣợc sử dụng trong Unity cho phép dùng
chuột máy tính để tƣơng tác với các đối tƣợng trong game. Giả sử trong game có một
game object, ví dụ nhƣ một quả bom và mong muốn khi ngƣời chơi click chuột vào thì
quả bom đó sẽ phát nổ. Để làm đƣợc điều này, sử dụng hàm OnMouseDown() đƣợc
hiện thực sẵn bởi Unity. Hàm này sẽ đƣợc tự động gọi mỗi khi ngƣời dùng click vào
đối tƣợng. Vậy chỉ cần hiện thực một hàm thể hiện một vụ nổ và trong hàm
OnMouseDown() có một lời gọi đến hàm này. Các hàm trong OnMouse event gồm:
OnMouseDown: Xảy ra khi ngƣời dùng click vào đối tƣợng.
OnMouseUp: Xảy ra khi ngƣời dùng thả chuột khi đã click chuột trƣớc đó.
OnMouseEnter: Xảy ra khi ngƣời dùng di chuyển chuột vào phạm vi Collider
của game object.
OnMouseExit: Xảy ra khi ngƣời dùng di chuyển chuột ra khỏi phạm vi Collider
của game object.
Ví dụ 1.1. Minh họa khi ngƣời dùng click vào game object là một Cube thì tại
cửa sổ Console sẽ in ra dòng chữ “Click me!”.
1. using System.Collections;
2. using System.Collections.Generic;

33
3. using UnityEngine;
4. public class ClickMe : MonoBehaviour {
5. void Start() {
6.
7. }
8. void Update()
9. {
10.
11. }
12. void OnMouseDown()
13. {
14. Debug.Log("Click me!");
15. }
16. }
Chú ý: Lớp Debug trình bày tại 1.6.2.
GetMouseButton:
Các sự kiện OnMouse thƣờng đƣợc sử dụng để phát hiện và xử lý khi ngƣời
dùng click chuột. Giả sử muốn dùng chuột để di chuyển quả bom đi một vị trí khác và
sau đó mới kích nổ sử dụng các hàm Input.GetMouseButton(x):
Input.GetMouseButtonDown(0): Nhấn chuột trái.
Input.GetMouseButtonDown (1): Nhấn chuột phải.
Input.GetMouseButtonDown (2): Nhấn chuột giữa.
Chú ý: Các hàm trên cần phải đƣợc gọi liên tục sau mỗi frame để sự kiện đƣợc
kiểm tra một cách chính xác nhất.
Ví dụ 1.2. Minh họa GetMouseButton.
1. // Update is called once per frame
2. void Update()
3. {
4. if(Input.GetMouseButtonDown(0))
5. Debug.Log("Mouse left click!");
6. if (Input.GetMouseButtonDown(1))
7. Debug.Log("Mouse right click!");
8. if (Input.GetMouseButtonDown(2))
9. Debug.Log("Mouse center click!");
10. }
Nhập dữ liệu từ bàn phím:
Đây là một dạng input thƣờng xuyên sử dụng khi lập trình các game trên
Windows sử dụng bàn phím vật lý. Những hành động thƣờng gặp là chạy, nhảy, tấn
công đối thủ sẽ đƣợc gắn vào các phím trên bàn phím. Để lấy input từ bàn phím có 2
cách: GetKey (hay GetButton) và GetAxis.
GetKey và GetButton:
Trong Unity, GetKey hay GetButton là cách nhận input từ bàn phím thông qua
class Input. Điểm khác biệt giữa chúng là GetKey sử dụng KeyCode đƣợc quy định sẵn
trong Unity. Mỗi KeyCode đại diện cho một phím bấm. GetButton sẽ sử dụng tên do

34
chính ngƣời dùng quy định để đại diện cho một phím bất kỳ. Ch ng hạn có thể đặt
“DauCach” đại diện cho phím Space, khi đó “DauCach” tƣơng đƣơng với Key.Space.
Để thêm một tên đại diện cho một phím bất kỳ, vào Edit, Project Settings, Input
menu. Với Name là tên thay thế cho phím và PostiveButton là phím cần đại diện.

Hình 1.57. Thêm một tên đại diện cho một phím
Ví dụ 1.3. Minh họa GetKey và GetButton.
1. void Update (){
2. // Lấy Input theo GetKey
3. bool down = Input.GetKeyDown(KeyCode.Space);
4. bool held = Input.GetKey(KeyCode.Space);
5. bool up = Input.GetKeyUp(KeyCode.Space);
6. // Lấy Input theo GetButton
7. bool down = Input.GetButtonDown("Jump");
8. bool held = Input.GetButton("Jump");
9. bool up = Input.GetButtonUp("Jump");
10. }
GetAxis:
Điểm khác biệt giữa GetAxis so với các phƣơng thức GetKey hay GetButton là
không trả về giá trị true/false nhƣ thông thƣờng mà sẽ trả về một giá trị float có phạm
vi nằm trong khoảng [-1; 1] phụ thuộc vào thời gian nhấn giữ của một phím.
GetAxis thƣờng làm việc cùng lúc với 2 phím đồng thời đƣợc khai báo cụ thể
trong Input Manager. Một phím gọi là Negative Button, phím còn lại gọi là Positive
Button, có giá trị lần lƣợt là -1 và 1. Ở trạng thái bình thƣờng, cả 2 phím này không
đƣợc nhấn hàm sẽ trả về giá trị 0. Khi trạng thái của phím PositiveButton thay đổi,
hàm sẽ tăng giá trị từ 0 lên đến 1. Khi nhả phím ra sẽ giảm giá trị về 0. Tƣơng tự nhƣ
vậy với Negative Button với khoảng [-1, 0].

Hình 1.58. GetAxis

35
Thời gian thay đổi các cặp giá trị [0, 1] [1,0] , [0, -1] [-1,0] có thể đƣợc điều
chỉnh bằng thuộc tính Gravity thông qua Input Manager. Ngƣời dùng có thể thiết lập
thời gian từ 0 đến 1 sẽ tăng dần đều, từ 1 trở về 0 sẽ giảm dần đều để phù hợp với từng
trƣờng hợp cụ thể của game.
Input.GetAxis sẽ trả về giá trị từ -1 đến 1, nếu không bấm gì sẽ trả về 0. Do đó,
khi sử dụng GetAxis để làm chuyển động cho nhân vật sẽ mƣợt mà hơn. Ví dụ khi
dùng float move = Input.GetAxis("Horizontal");
Khi chạy game và bấm phím A, move không phải có giá trị -1 liền mà kéo từ 0
xuống từ từ đến 1. Tƣơng tự bấm D thì giá trị của move sẽ tăng từ từ lên đến 1 nên
nhân vật sẽ chuyển động mƣợt mà hơn.
Input.GetAxisRaw trả về một trong 3 giá trị -1 hoặc 0 hoặc 1. Ví dụ khi dùng:
float move = Input.GetAxisRaw("Horizontal");
Khi chạy game và bấm phím A, move sẽ có giá trị -1.
GetAxis đƣợc sử dụng rất nhiều trong việc di chuyển Player trong game theo
thời gian thực. Giá trị của hàm phụ thuộc vào thời gian nhấn giữ phím do đó có thể
dùng để di chuyển nhân vật nhanh hoặc chậm theo ý muốn.
1.6.2. Lớp Debug
Để phát hiện lỗi, các bộ công cụ phát triển phần mềm hiện nay đa phần đều tích
hợp chức năng debug, giúp ngƣời dùng tìm kiếm, sửa lỗi đƣợc dễ dàng và hiệu quả
hơn. Tuy nhiên, nếu không có công cụ debugger, có thể tự phát hiện và sửa lỗi bằng
cách in một thông điệp nào đó ra màn hình tƣơng ứng với đoạn code.
Kiểm tra tính thực thi:
Trƣớc khi sửa lỗi chƣơng trình phải khoanh vùng đƣợc đoạn code bị lỗi, phạm
vi càng thu hẹp càng tốt. Sau khi đã khoanh vùng phần code lỗi, cần xem xét tính thực
thi của đoạn code bằng cách phát ra một thông điệp nào đó.

Hình 1.59. Kỹ thuật Debug


Nếu thông điệp không đƣợc hiển thị, có thể xảy ra các trƣờng hợp sau:
- Script chƣa đƣợc gắn vào một game object nào trong chƣơng trình.
- Hàm chứa đoạn code thông điệp không đƣợc gọi.
Thông điệp nằm trong nhánh code không đƣợc thực thi (thƣờng gặp với các cấu
trúc rẽ nhánh if... else, switch... case,...).

36
Kiểm tra tính logic:
Khi chắc chắn đoạn code đƣợc thực thi nhƣng chƣơng trình vẫn chạy không
mong muốn thì cần tiến hành quan sát sự thay đổi của các đối tƣợng liên quan theo
thời gian, dùng hàm Log() thuộc lớp Debug có thể theo dõi một hoặc nhiều đối tƣợng
cụ thể và dần tìm ra nguyên nhân gây ra lỗi.
Một số hàm chức năng hỗ trợ debug tiện dụng thuộc lớp Debug:
Log:
Debug.Log là hàm đƣợc sử dụng nhiều nhất trong việc kiểm tra và sửa lỗi
chƣơng trình. Hàm Log có chức năng hiển thị ra màn hình Console (Windows,
Console) các thông tin về một đối tƣợng trong game. Kiểu dữ liệu mà hàm nhận vào là
Object, do đó với mọi loại dữ liệu thì hàm đều có thể in ra cửa sổ Console của Unity
Editor, tùy theo cách hiển thị của kiểu dữ liệu. Các kiểu dữ liệu thƣờng đƣợc sử dụng
với hàm Log bao gồm int, float, string, dữ liệu kiểu mảng. Các kiểu dữ liệu do Unity
định nghĩa nhƣ Vector2, Vector3, Quaternion,... cũng đƣợc hiển thị chi tiết.
Draw:
Ngoài việc hiển thị ra màn hình Console giá trị của một đối tƣợng, Unity còn hỗ
trợ việc vẽ một đƣờng th ng lên màn hình. Khi cần xem xét cụ thể thì sử dụng hàm
DrawLine() để vẽ. Hàm có nguyên mẫu nhƣ sau:
public static void DrawLine(Vector3 start, Vector3 end, Color color =
Color.white, float duration = 0.0f, bool depthTest = true);
Trong đó các giá trị color, duration và depthTest đều mang sẵn giá trị mặc định.
Một đƣờng th ng sẽ đƣợc vẽ trong cửa sổ Scene với hai đầu mút đƣợc quy định rõ.
Trong trƣờng hợp cần quan sát hƣớng của đối tƣợng, sử dụng hàm DrawRay().
Cách sử dụng hàm tƣơng tự nhƣ với hàm DrawLine():
public static void DrawRay(Vector3 start, Vector3 dir, Color color =
Color.white, float duration = 0.0f, bool depthTest = true);
Một đƣờng th ng sẽ đƣợc vẽ từ điểm bắt đầu cho đến start + dir. Ray (tia) là
một đƣờng th ng vô hạn không có điểm kết thúc.
Chú ý: Hai hàm Draw() thuộc lớp Debug sẽ vẽ lên cửa sổ Scene. Nếu muốn
hiển thị trực tiếp lên màn hình game, mở tùy chọn hiển thị Gizmo ở cửa sổ game.
Break:
Trong một số trƣờng hợp cần chƣơng trình tạm dừng lại để xem xét sự thay đổi
của các đối tƣợng trong game. Hàm Break() trong lớp Debug sẽ giúp thực hiện công
việc này. Hàm Break() thực sự trở nên hữu ích khi cần dừng chƣơng trình tại một thời
điểm cụ thể nào đó. Ngƣời dùng có thể sử dụng công cụ Debugger có sẵn trong Visual
Studio hay MonoDevelop nhƣng sẽ không xem xét các đối tƣợng một cách trực quan.
isDebugBuild:
Khi đƣa sản phẩm hoàn thiện ra thị trƣờng sẽ không cần đến các đoạn code
Debug do đó nên loại bỏ các đoạn Debug này ra khỏi sản phẩm. Với hàng chục, thậm

37
chí hàng trăm file Script với nhiều dòng code, việc xóa từng dòng Debug thủ công là
điều gây lãng phí thời gian và công sức. Trong lớp Debug, Unity cung cấp một thuộc
tính để kiểm tra trạng thái của sản phẩm là thuộc tính isDebugBuild. Thuộc tính này
trả về true nếu nhƣ sản phẩm vẫn đang trong quá trình hoàn thiện (thƣờng gọi là Beta
Release). Trạng thái của sản phẩm có thể đƣợc tìm thấy tại cửa sổ Build Setting (Ctrl +
Shift + B), checkbox Development Build. Tại cửa sổ Unity Editor, thuộc tính này luôn
mang giá trị true.
1.6.3. Lớp vectơ [1]
a. Hệ tọa độ
World Coordinate: Là hệ tọa độ của thế giới thực trong game. Hệ tọa độ này
tuân theo quy tắc bàn tay trái (hình 1.60), chiều x dƣơng là bên phải, chiều y dƣơng
hƣớng lên trên, chiều z dƣơng hƣớng vào bên trong màn hình.

Hình 1.60. Quy tắc bàn tay trái


Tọa độ của đối tƣợng đƣợc thể hiện trong thành phần Transform chính là tọa độ
trong World coordinate. Các đối tƣợng con còn có một tọa độ khác đƣợc lƣu trữ tại
các thuộc tính localPosition, localRotation và localScale của Transform.
Viewport Coordinate: Là hệ tọa độ của camera. Các trục tọa độ của World
space và Viewport space trùng nhau nhƣng khác nhau về đơn vị của các trục. Trong
camera, góc dƣới bên trái có tọa độ (0, 0), góc trên bên phải có tọa độ (1, 1). Các giá
trị này đƣợc chỉnh sửa thông qua Viewport Rect trong Inspector của đối tƣợng camera.
Screen Coordinate: Là hệ tọa độ của màn hình hiển thị. Đơn vị của các trục là
pixel, góc dƣới bên trái màn hình có tọa độ (0, 0) trong khi góc trên bên phải là
(screenWidth - 1, screenHeight - 1).
UI Coordinate: Là hệ tọa độ của đối tƣợng UI, giá trị của các trục tọa độ nằm
trong khoảng (0, 1). Góc trên bên trái có tọa độ (0, 0) và góc dƣới bên phải là (1, 1).
Chuyển đổi giữa các hệ tọa độ trong Unity: Trong khi thao tác với các đối
tƣợng thông qua Script, nhiều trƣờng hợp nhận đƣợc input từ các sự kiện click chuột,
touch,… dùng để thao tác với các đối tƣợng. Thông thƣờng các sự kiện này sẽ trả về
tọa độ của Screen space. Unity hỗ trợ sẵn các hàm chuyển đổi giữa các Coordinate
system. Các hàm này đƣợc gắn vào camera chính đang đƣợc sử dụng (trong trƣờng
hợp game có nhiều hơn một camera).

38
Camera.WorldToScreenPoint: Chuyển đổi một điểm trong không gian 3D
(worldPosition) thành tọa độ màn hình 2D. Tọa độ màn hình thƣờng đƣợc thể hiện
dƣới dạng cặp (x, y) với (0, 0) ở góc trái dƣới và giá trị tăng dần khi đi lên và sang
phải trên màn hình.
Camera.WorldToViewportPoint: Chuyển đổi một điểm trong không gian 3D
thành tọa độ trong không gian viewport 2D. Tọa độ viewport đƣợc biểu diễn bằng cặp
(x, y) với giá trị nằm trong khoảng từ 0 đến 1, trong đó (0, 0) là góc dƣới bên trái và
(1, 1) là góc trên bên phải của viewport.
Camera.ScreenToViewportPoint: Chuyển đổi một điểm trong không gian màn
hình 2D (screenPosition) thành tọa độ trong không gian viewport 2D. Tọa độ màn hình
đƣợc thể hiện bằng cặp (x, y) và tọa độ viewport cũng đƣợc biểu diễn bằng cặp (x, y)
nằm trong khoảng từ 0 đến 1.
Camera.ScreenToWorldPoint: Chuyển đổi một điểm trong không gian màn
hình 2D (screenPosition) thành tọa độ trong không gian 3D. Tọa độ màn hình đƣợc thể
hiện bằng cặp (x, y), và kết quả trả về là tọa độ không gian 3D tƣơng ứng với điểm
trên màn hình.
Camera.ViewportToScreenPoint: Chuyển đổi một điểm trong không gian
viewport 2D (viewportPosition) thành tọa độ trong không gian màn hình 2D. Tọa độ
viewport đƣợc thể hiện bằng cặp (x, y) nằm trong khoảng từ 0 đến 1 và tọa độ màn
hình đƣợc thể hiện bằng cặp (x, y).
Camera.ViewportToWorldPoint: Chuyển đổi một điểm trong không gian
viewport 2D (viewportPosition) thành tọa độ trong không gian 3D. Tọa độ viewport
đƣợc thể hiện bằng cặp (x, y) nằm trong khoảng từ 0 đến 1 và kết quả trả về là tọa độ
không gian 3D tƣơng ứng với điểm trong viewport.
Các hàm trên sẽ nhận vào một Vector3 và trả về một Vector3 tƣơng ứng với hệ
tọa độ cần chuyển. Đối với UI Coordinate, Unity hỗ trợ chuyển từ UI sang Screen
Coordinate và ngƣợc lại. Do đó cần thực hiện hai thao tác liên tiếp, các hàm nhƣ sau:
- GUIUtility.ScreenToGUIPoint(Vector2 screenPoint): Hàm này chuyển đổi
một điểm trong không gian màn hình thành tọa độ trong không gian GUI của Unity.
Tọa độ màn hình đƣợc biểu diễn bằng cặp (x, y) với (0, 0) ở góc trái dƣới và giá trị
tăng dần khi đi lên và sang phải trên màn hình. Trong khi đó, tọa độ GUI cũng đƣợc
thể hiện bằng cặp (x, y) nhƣng có gốc tọa độ (0, 0) nằm ở góc trái trên và giá trị tăng
dần khi đi xuống và sang phải. Hàm này giúp chuyển đổi tọa độ từ không gian màn
hình sang không gian GUI để sử dụng trong việc vẽ các phần tử giao diện ngƣời dùng.
- GUIUtility.GUIToScreenPoint(Vector2 guiPoint): Ngƣợc lại với hàm trên,
hàm này chuyển đổi một điểm trong không gian GUI thành tọa độ trong không gian
màn hình của Unity. Ngƣời dùng cung cấp tọa độ GUI bằng cặp (x, y) với gốc tọa độ
(0, 0) ở góc trái trên và giá trị tăng dần khi đi xuống và sang phải. Hàm này trả về tọa
độ tƣơng ứng trong không gian màn hình, cho phép tính toán vị trí của các phần tử
GUI và hiển thị chúng đúng vị trí trên màn hình.

39
b. Vectơ trong Unity
Không gian 2 chiều:
Unity sử dụng tọa độ Decac để mô tả các điểm trong không gian sử dụng trục x
và trục y vuông góc với nhau. Gốc tọa độ là điểm giao nhau, tại đó giá trị của x và y
bằng không hay (0, 0). Trong Unity, Vector2 là một cấu trúc chứa giá trị x và y để mô
tả vị trí và vecto trong không gian hai chiều. Tọa độ (x, y) cho biết vị trí của một điểm
trong không gian 2D. Vecto đƣợc mô tả dạng đƣờng th ng so với gốc tọa độ (0, 0).
Trong trình soạn thảo MonoDevelop, khai báo Vector2 nhƣ sau:
Vector2 d = new Vector2(7, 8);
Unity cung cấp một số biến tĩnh thƣờng dùng trong Scripting Reference. Các
biến đƣợc liệt kê trong bảng sau:
Bảng 1.2. Một số biến tĩnh của lớp vectơ
Biến Ý nghĩa

down Vector2(0, -1).

left Vector2(-1, 0).

one Vector2(1, 1).

right Vector2(1, 0).

up Vector2(0, 1).

zero Vector2(0, 0).

magnitude Độ dài (kích thƣớc) của một vectơ.

normalized Độ dài của vectơ bằng 1.

sqrMagnitude Độ dài bình phƣơng của vectơ.

Truy cập thành phần x hoặc y bằng cách sử dụng [0] hoặc [1]
this[int]
tƣơng ứng.

x Thành phần x của vectơ.

y Thành phần x của vectơ.


Bảng 1.3. Một số hàm của lớp vectơ
Hàm Ý nghĩa

Normalize() Gán vectơ có độ dài là 1.

Thiết lập giá trị x, y cho vectơ:


Set()
public void Set(float new_x, float new_y)

ToString() Trả về kiểu chuỗi của vectơ: public string ToString(string format)

40
Trong môi trƣờng game 3D, một vị trí hay vectơ đƣợc biểu diễn bằng 3 tham
số, đại diện cho 3 chiều không gian tƣơng ứng. Trong Unity, lớp Vector3 đƣợc sử
dụng để biểu diễn điểm hoặc vectơ ba chiều. Ngoài ra, Unity còn tích hợp một số hàm
và thuộc tính hữu ích hỗ trợ lập trình viên.
Các vecto đơn vị:
Trong một số trƣờng hợp cần sử dụng một Vector3 (0, 0, 0) làm giá trị ban đầu
cho một đối tƣợng nào đó. Tƣơng tự khi cần thay đổi trên một trục của đối tƣợng sử
dụng Vector3(x, 0, 0) hoặc các Vector3 tƣơng tự. Đáp ứng nhu cầu sử dụng thƣờng
xuyên của ngƣời dùng, Unity hỗ trợ sẵn các vectơ đơn vị với tên gọi dễ nhớ để thuận
tiện trong các thao tác.
Bảng 1.4. Danh sách các vectơ đơn vị

Tên thuộc tính Chức năng

zero Đại diện cho Vector3(0, 0, 0).

one Đại diện cho Vector3(1, 1, 1).

left Đại diện cho Vector3(-1, 0, 0).

right Đại diện cho Vector3(1, 0, 0).

up Đại diện cho Vector3(0, 1, 0).

down Đại diện cho Vector3(0, -1, 0).

back Đại diện cho Vector3(0, 0, -1).

forward Đại diện cho Vector3(0, 0, 1).

Ví dụ 1.4. Minh họa vectơ đơn vị.


1. transform.position = Vector3.zero;
2. // Di chuyển đối tượng theo trục z
3. transform.Translate(Vector3.forward * Time.deltaTime);
Các thuộc tính cơ bản:
Đối với Vector3, ngƣời dùng có thể thay đổi từng thành phần bên trong của lớp
và truy xuất các thành phần bằng thuộc tính x, y, z tƣơng ứng hoặc sử dụng mảng this.
Đối với mảng this, các chỉ số [0], [1], [2] sẽ tƣơng ứng với các thành phần x, y, z.
Ví dụ 1.5. Minh họa thuộc tính vectơ.
1. Vector3 stdioLogoPosition = Vector3.zero;
2. stdioLogoPosition.x += 10;
3. stdioLogoPosition[2] += 5; // trục z

41
Tính toán độ dài:
Thuộc tính normalize hỗ trợ chuyển đổi vectơ thành vectơ đơn vị với độ dài là
1, tức là chỉ quan tâm đến chiều của vectơ. Vectơ ban đầu sẽ đƣợc giữ lại giá trị cũ và
thuộc tính sẽ trả về một vectơ mới.
Chú ý:
- Đối với vectơ có độ dài quá nhỏ sẽ trả về Vector3.zero thay vì vectơ cần tìm.
- Thuộc tính magnitude sẽ trả về độ dài của vectơ (tính bằng công thức). Một
thuộc tính khác là sqrMagnitude sẽ trả về giá trị bình phƣơng của magnitude.
- Thuộc tính magnitude và sqrMagnitude thƣờng đƣợc sử dụng để xác định vận
tốc ban đầu cho một số đối tƣợng.
Ví dụ: Xử lý hành vi của đối tƣợng trong môi trƣờng game. Nếu đối tƣợng gần
vị trí mục tiêu thì sẽ đƣợc đƣa về vị trí đó. Sau đó, nếu đối tƣợng không còn di chuyển
thì sẽ bị phá hủy (destroyed). Game object đƣợc đƣa về vị trí mặc định khi gần vị trí
mục tiêu nếu khoảng cách giữa vị trí hiện tại của đối tƣợng (transform.position) và vị
trí mục tiêu (position) nhỏ hơn hoặc bằng 0.1 đơn vị (rất gần). Điều này có nghĩa là khi
đối tƣợng đang rất gần với vị trí mục tiêu, đối tƣợng sẽ đƣợc đƣa về vị trí mục tiêu.
Ví dụ 1.6. Minh họa hủy đối tƣợng.
1. if ((transform.position - position).magnitude <= 0.1f)
2. transform.position = position;
3. // Kiểm tra và hủy đối tượng khi nó không còn di chuyển nữa
4. if(gameObject.GetComponent<Rigidbody2D>().velocity.magnitude < 0.1f)
5. Destroy(gameObject);
Nếu vận tốc của đối tƣợng (tính bằng độ lớn của vector vận tốc) nhỏ hơn 0.1
đơn vị (rất chậm hoặc gần bằng 0). Điều này có nghĩa là khi đối tƣợng không còn di
chuyển nữa sẽ hủy đối tƣợng.
Danh sách các hàm hỗ trợ:
- Hàm so sánh:
public static Vector3 Max(Vector3 lhs, Vector3 rhs);
public static Vector3 Min(Vector3 lhs, Vector3 rhs);
Hàm Max() sẽ so sánh từng thành phần của hai vectơ và trả về một vectơ mới
với các thành phần là lớn nhất giữa hai vectơ đó.
Hàm Min() cũng có chức năng tƣơng tự hàm Max(), điểm khác là trả về một
vectơ mới với các thành phần là nhỏ nhất giữa hai vectơ.
Ví dụ 1.7. Minh họa hàm Max(), Min().
1. public Vector3 a = new Vector3(1, 2, 3);
2. public Vector3 b = new Vector3(4, 3, 2);
3. public Vector3 c = new Vector3(0, 5, 1);
Khi đó tại hàm Update() nhƣ sau:
1. Debug.Log(Vector3.Max(a, b));
2. Debug.Log(Vector3.Min(a, c));
3. Debug.Log(Vector3.Min(a, Vector3.Max(b, c)));

42
- Hàm di chuyển vectơ:
public static Vector3 MoveTowards(Vector3 current, Vector3 target, float
maxDistanceDelta);
public static Vector3 RotateTowards(Vector3 current, Vector3 target, float
maxRadiansDelta, float maxMagnitudeDelta);
Hai hàm có chức năng di chuyển một vectơ đến một vị trí khác trong không
gian. Đối với MoveTowards(), sau mỗi vòng lặp đối tƣợng sẽ di chuyển tuyến tính về
target với khoảng cách tối đa đƣợc quy định trƣớc. Nếu vị trí hiện tại gần đích hơn là
khoảng cách tối đa, hàm sẽ trả về giá trị là target và kết thúc di chuyển.
RotateTowards() sẽ quay một vectơ trong không gian đến vị trí target với góc quay tối
đa là maxRadianDelta.
Chú ý: Nếu maxDistanceDelta và maxRadianDelta nhận giá trị âm, vectơ ban
đầu sẽ dần dần di chuyển xa khỏi target. Khi hai vectơ hoàn toàn ngƣợc nhau cả về độ
lớn thì hàm sẽ dừng lại.
- Hàm nội suy:
public static Vector3 Lerp(Vector3 from, Vector3 to, float t);
public static Vector3 Slerp(Vector3 from, Vector3 to, float t);
Với t = 0, hàm sẽ trả về vectơ from. Với t = 1, hàm sẽ trả về vectơ to. Với t
thuộc khoảng (0, 1), hàm sẽ nội suy để tìm ra vị trí giữa from và to.
Hai hàm có chức năng tƣơng tự nhau, đều thực hiện nội suy giữa hai vectơ và
trả về một vectơ mới tƣơng ứng với thành phần t trong hàm.
- Hàm tính tích:
float dotProduct = Vector3.Dot(vector1, vector2);
Vector3 crossProduct = Vector3.Cross(vector1, vector2);
Hàm Dot() trả về tích vô hƣớng giữa hai vectơ, hàm Cross() trả về tích có
hƣớng giữa hai vectơ đó.
Bảng 1.5. Một số hàm khác của vectơ

Hàm Chức năng

Angle() Trả về góc đo giữa hai vectơ tính bằng độ.

Distance() Trả về khoảng cách giữa hai vectơ.

Chuẩn hóa vectơ và gán lại giá trị. Vectơ sẽ mang giá trị mới sau khi
Normalize()
sử dụng hàm Normalize.

Thực hiện chiếu một vectơ lên một vectơ khác. Tham số onNormal
Project()
của hàm là hƣớng của vectơ mới.

43
Hàm Chức năng

Reflect Thực hiện phép phản xạ một vectơ lên một mặt ph ng chiếu.

Thu phóng một vectơ với một vectơ khác. Từng thành phần của hai
Scale
vectơ sẽ đƣợc nhân với nhau tạo thành vectơ mới.

Ngoài các hàm và thuộc tính nhƣ trên, Unity cũng cung cấp một số toán tử để
thực hiện tính toán giữa các vectơđƣợc thuận tiện hơn. Danh sách các toán tử nhƣ sau:
Bảng 1.6. Danh sách toán tử

Toán tử Chức năng

operator+ Phép cộng giữa hai vectơ.

operator- Phép trừ giữa hai vectơ.

operator* Nhân một vectơ với một số.

operator/ Chia một vectơ cho một số.

operator!= So sánh và trả về true nếu hai vectơ khác nhau.

operator== So sánh và trả về true nếu hai vectơ giống nhau.

1.6.4. Các hàm xử lý Script nâng cao [2]


a. Lớp Mathf
Mathf chứa các hàm xử lý toán học đƣợc cung cấp sẵn để sử dụng trong game.
Abs(): Là hàm trả về giá trị tuyệt đối của tham số float và int nhƣ sau:
public static float Abs(float value);
public static int Abs(int value);
Asin(), Acos(), Atan(), Sin(), Cos(), Tan(): Các hàm hỗ trợ tính toán giá trị của
các hàm lƣợng giác và lƣợng giác ngƣợc. Cú pháp cho hàm Sin() nhƣ sau:
public static float Sin(float f);
Các hàm lƣợng giác khác cũng đƣợc sử dụng tƣơng tự.
Chú ý: Giá trị f truyền vào là góc lƣợng giác tính bằng radian. Do đó sử dụng
hằng số Mathf.PI để tính toán trên radian hoặc chuyển đổi sang radian bằng cách nhân
góc với hằng số Mathf.Deg2Rag.
Max(), Min()
Hàm Max() sẽ trả về giá trị lớn nhất trong danh sách tham số, trong khi hàm
Min() sẽ trả về giá trị nhỏ nhất. Danh sách tham số có thể bao gồm hai hoặc nhiều các
giá trị. Kiểu dữ liệu của các giá trị có thể là số thực hoặc số nguyên.

44
Đối với việc so sánh hai giá trị, có các hàm sau:
public static float Min( float a, float b);
public static float Max( float a, float b);
Nếu có nhiều hơn hai giá trị cần so sánh, sử dụng mảng để chứa các giá trị đó
và sử dụng hàm đƣợc override nhƣ sau:
public static float Min(params float[] values);
public static float Max(params float[] values);
Clamp():
Hàm Clamp() có tác dụng giới hạn giá trị của một đối tƣợng trong phạm vi cho
phép. Tùy vào nhu cầu để sử dụng hàm Clamp() nhằm giới hạn bất cứ khả năng nào
của một đối tƣợng. Ví dụ về ứng dụng của hàm Clamp: Giới hạn tọa độ của một đối
tƣợng trong phạm vi màn hình, giới hạn góc quay của một khẩu súng, giới hạn tốc độ,
damage, các chỉ số sức mạnh,... của một nhân vật trong game.
Cú pháp của hàm nhƣ sau:
public static float Clamp(float value, float min, float max);
Trong đó, min và max là hai giá trị cận. Giá trị trả về của hàm sẽ luôn nằm giữa
min và max, bất kể giá trị của biến value.
PingPong():
PingPong() là một hiệu ứng mà một đối tƣợng luôn di chuyển ở giữa hai đầu
mút giống trò chơi bóng bàn khi quả bóng đƣợc đánh qua lại giữa hai ngƣời chơi.
Cú pháp của hàm nhƣ sau:
public static float PingPong(float t, float length);
Trong đó, length là giá trị lớn nhất mà hàm có thể trả về. Giá trị trả về này sẽ
nằm trong khoảng [0, length].
Ngoài các hàm trên, lớp Mathf còn một số hằng và hàm khác nhƣ bảng sau:
Bảng 1.7. Một số hằng và hàm toán học khác

Hàm Ý nghĩa

PI Hằng số PI, giá trị là 3.1415926535...

Deg2Rag Hằng số chuyển đổi từ độ thông thƣờng sang radian.

Rag2Deg Hằng số chuyển đổi từ radian sang độ thông thƣờng.

Approximately() So sánh giá trị của hai số thực nếu tƣơng đƣơng nhau.

45
Hàm Ý nghĩa

IsPowerOfTwo() Trả về true nếu giá trị là lũy thừa của 2.

ClosetPowerOfTwo() Trả về giá trị là lũy thừa của 2 gần nhất.

NextPowerOfTwo() Trả về giá trị là lũy thừa của 2 lớn hơn gần nhất.

Log() Trả về giá trị logarit của một số với cơ số tùy ý.

Log10() Trả về giá trị logarit của một số với cơ số 10.

Round() Trả về số nguyên gần nhất với giá trị truyền vào.

Sqrt() Trả về căn bậc hai của một số.

Pow() Trả về lũy thừa của một số với số mũ bất kỳ.

b. Lớp Time
Time là một lớp đƣợc định nghĩa sẵn bởi Unity, cung cấp các thuộc tính lƣu trữ
và xử lý các thông tin về thời gian. Lớp Time không có phƣơng thức nhƣng có một số
thuộc tính có thể chỉnh sửa để tƣơng thích và phù hợp với chƣơng trình:
time:
Thời gian tính bằng giây (kiểu float), trả về thời điểm bắt đầu của frame hiện
tại. Thuộc tính này không thể chỉnh sửa mà sẽ trả về giá trị nhƣ nhau dù đƣợc gọi ở
các thời điểm khác nhau trong cùng một vòng lặp. Chú ý: Time.time sẽ trả về giá trị
của Time.fixedTime nếu đƣợc gọi trong hàm FixedUpdate().
deltaTime:
Thuộc tính này trả về khoảng thời gian (tính bằng giây) cần thiết để xử lý hết
frame trƣớc đó. Thuộc tính deltaTime đƣợc ứng dụng rất nhiều trong các hàm thuộc
lớp Mathf khi cần di chuyển đối tƣợng một cách mƣợt mà nhất. Thuộc tính deltaTime
không thể chỉnh sửa.
Ví dụ 1.8. Minh họa di chuyển đối tƣợng dùng deltaTime, hệ số tỷ lệ để điều
khiển tốc độ di chuyển.
1. void Update()
2. {
3. float translation =Time.deltaTime*10;
4. transform.Translate(0, 0, translation); //Di chuyển theo trục z
5. }

46
Khi đƣợc gọi trong FixedUpdate(), thuộc tính sẽ trả về giá trị của
fixedDeltaTime thuộc lớp Time.
timeScale:
timeScale là thuộc tính mô tả độ co giãn của thời gian. Khi nhận giá trị 1.0, thời
gian trôi qua đúng bằng thời gian thực. Khi muốn chƣơng trình chạy chậm đi hai lần,
cần gán giá trị 0.5 cho timeScale. Đặc biệt khi timeScale nhận giá trị 0, chƣơng trình sẽ
đóng băng hoàn toàn. Thuộc tính timeScale sẽ ảnh hƣởng lên mọi đối tƣợng đƣợc quản
lý bởi lớp Time, ngoại trừ thuộc tính realTimeSinceStartup.
Ví dụ 1.9. Minh họa timeScale.
1. void Update()
2. {
3. if (Input.GetButtonDown("Fire1"))
4. {
5. if (Time.timeScale == 1.0f)
6. Time.timeScale = 0.7f;
7. else Time.timeScale = 1.0f;
8. Time.fixedDeltaTime = 0.02f * Time.timeScale;
9. }
10. }
Hàm trên sẽ làm chậm hoặc tăng tốc độ chạy game khi ngƣời chơi nhấn nút
"Fire1". Nếu game đang chạy bình thƣờng (Time.timeScale == 1.0f), thì sẽ chuyển
sang chế độ di chuyển chậm với tốc độ 0.7f, ngƣợc lại khi đang ở chế độ di chuyển
chậm (Time.timeScale != 1.0f) thì sẽ chuyển về chế độ di chuyển bình thƣờng với tốc
độ 1.0f. Chú ý: Hàm FixedUpdate() sẽ không đƣợc gọi khi timeScale nhận giá trị 0.
maximumDeltaTime:
Là khoảng thời gian tối đa mà một frame có thể đƣợc cập nhật. Thuộc tính này
thƣờng đƣợc sử dụng để tránh việc FPS bị giảm đột ngột do tiến trình thu dọn rác và
các thao tác xử lý vật lý tốn tài nguyên gây ra. Nếu việc xử lý giá trị tốn nhiều thời
gian, các hiệu ứng và vật lý sẽ có ít thời gian hơn để cập nhật. Việc này sẽ làm giảm
chất lƣợng game, tuy nhiên sẽ tránh đƣợc hiện tƣợng giật khi chơi game.
fixedTime và fixedDeltaTime:
Hai thuộc tính này có chức năng tƣơng tự nhƣ Time.time và Time.deltaTime
nhƣng đƣợc sử dụng trong hàm FixedUpdate() để tính toán các thao tác vật lý đƣợc
đơn giản hơn. Các thao tác vật lý thƣờng sử dụng các công thức phức tạp nên việc làm
tròn các giá trị là khá quan trọng giúp giảm việc hao tốn tài nguyên.
smoothDeltaTime:
Là thuộc tính đƣợc sử dụng để giảm thay đổi đột ngột thời gian. Giả sử tại một
hoặc một vài vòng lặp liên tiếp trong game, các giá trị cần xử lý tăng đột biến khiến
cho deltaTime tăng nhanh đột ngột. Điều này gây ảnh hƣởng không nhỏ đến độ chính
xác của các thao tác trong các lần lặp tiếp theo. Thuộc tính smoothDeltaTime đƣợc sử
dụng để làm giảm việc thay đổi đột ngột thời gian.

47
frameCount:
Tổng số lƣợng frame đã đƣợc xử lý kể từ khi chƣơng trình bắt đầu chạy. Thuộc
tính này không thể thay đổi.
realtimeSinceStartup:
Trả về thời gian tính bằng giây kể từ khi chƣơng trình đƣợc khởi chạy. Ngoại
trừ khi thay đổi timeScale, có thể sử dụng Time.time tƣơng đƣơng trong mọi trƣờng
hợp còn lại. Khi chƣơng trình đƣợc tạm dừng ở Unity Editor, thuộc tính này vẫn sẽ
đƣợc cập nhật liên tục. Phụ thuộc vào nền tảng và thiết bị phần cứng, thuộc tính này có
thể trả về giá trị giống nhau trong vài frame liên tiếp.
timeSinceLevelLoad:
Thuộc tính sẽ trả về khoảng thời gian bắt đầu từ khi level cuối cùng đƣợc khởi
chạy cho đến thời gian bắt đầu của frame hiện tại.
c. Lớp Random
Lớp Random trong Unity cung cấp các phƣơng thức để tạo ra các giá trị ngẫu
nhiên. Đƣợc sử dụng chủ yếu trong việc tạo ngẫu nhiên trong game, ví dụ nhƣ vị trí
ban đầu của đối tƣợng, giá trị ban đầu của các tham số hoặc các sự kiện ngẫu nhiên.
Số ngẫu nhiên:
Hàm Range():
public static float Range(float min, float max);
public static int Range(int min, int max);
Hàm sẽ trả về một giá trị ngẫu nhiên trong khoảng min và max với kiểu dữ liệu
tƣơng ứng. Hàm có thể trả về giá trị bằng với min, nhƣng không trả về giá trị max.
Thuộc tính value:
Thuộc tính này trả về một giá trị thực ngẫu nhiên thuộc đoạn [0.0, 1.0]. Khác
với Random.Range, thuộc tính có thể trả về giá trị của cả hai đầu mút 0.0 và 1.0.
Thuộc tính này thƣờng đƣợc sử dụng để lấy ngẫu nhiên màu sắc, hoặc dùng với các
thuộc tính có giá trị dao động trong đoạn [0.0, 1.0].
Vectơ ngẫu nhiên:
insideUnitCircle, insideUnitSphere:
insideUnitCircle trả về một giá trị Vector2 là điểm nằm trong đƣờng tròn đơn vị
có bán kính là 1. Đối với insideUnitSphere, giá trị trả về là một Vector3 nằm trong
hình cầu có bán kính 1. Tâm của đƣờng tròn và hình cầu là gốc tọa độ tƣơng ứng với
không gian 2D hoặc 3D. Hai thuộc tính này thƣờng đƣợc sử dụng trong các hiệu ứng
rung của các đối tƣợng. Ví dụ khi cần ngƣời chơi chú ý vào một đối tƣợng trong game
cần cho đối tƣợng đó lắc ngẫu nhiên để thu hút sự chú ý.
onUnitSphere:
Thuộc tính này trả về một giá trị nằm trên mặt cầu đơn vị có bán kính 1. Nói
cách khác, khoảng cách tính từ gốc tọa độ đến điểm trả về luôn có độ dài bằng 1. Giả
sử không gian game là một hình cầu, ch ng hạn nhƣ một quả địa cầu thu nhỏ. Các
enemy sẽ xuất hiện ngẫu nhiên trên đó, cần sử dụng onUnitSphere để xác định ngẫu

48
nhiên một tọa độ chính xác và đúng với yêu cầu một cách dễ dàng nhất. Khi cần tạo
một vận tốc nhƣ nhau cho một đối tƣợng với bất kỳ hƣớng nào cần thao tác.
Ví dụ 1.10. Minh họa onUnitSphere.
1. public float m_speed;
2. void RandomDirection()
3. {
4. gameObject.GetComponent<Rigidbody>().velocity = Random.onUnitSphere *
m_speed;
5. }
Góc ngẫu nhiên:
Hai thuộc tính Random.rotation và Random.rotationUniform đều trả về một góc
quay ngẫu nhiên (Quaternion). Chúng đều trả về một kiểu dữ liệu giống nhau, đồng
thời giá trị trả về cũng thay đổi theo thời gian. Với mục đích sử dụng thông thƣờng,
hầu nhƣ ngƣời dùng không nhận thấy sự khác biệt giữa hai thuộc tính này. Tuy nhiên,
rotateUniform có xác suất xuất hiện các góc là nhƣ nhau, còn rotation sẽ có một sự
phân cụm các giá trị.
Thiết lập máy sinh số ngẫu nhiên:
Trong các máy sinh số ngẫu nhiên (Random Number Generator - RNG), có một
giá trị quy định cách thức giá trị sẽ đƣợc trả về trong các lần gọi một thành phần
Random. Giá trị này đƣợc gọi là seed (hạt nhân của RNG). Hạt nhân này sẽ quy định
trình tự phát sinh các số ngẫu nhiên giả. Các giá trị đƣợc trả về dựa trên trình tự này.
Đối với hầu hết các RNG, trƣớc khi sử dụng thì chƣơng trình sẽ tự quy định giá
trị seed dựa trên thời gian của hệ thống. Mục đích của việc này là để các lần chạy
chƣơng trình khác nhau thu đƣợc các trình tự ngẫu nhiên khác nhau. Tuy nhiên trong
một số trƣờng hợp, việc tự quản lý các trình tự ngẫu nhiên này rất hữu ích. Ví dụ ứng
dụng việc quản lý thiết kế màn chơi. Thông thƣờng, mỗi màn chơi sẽ có các tính chất
giống nhau ngay cả khi chơi đi chơi lại nhiều lần. Do đó, cần đảm bảo các giá trị ngẫu
nhiên sẽ giống nhau bằng cách thiết lập giá trị seed khi load tài nguyên của level.
Trong Unity, giá trị seed đƣợc thiết lập nhƣ sau:
Random.seed = value;
Trong đó, value có kiểu dữ liệu là số nguyên.
Khi đó, việc quản lý level sẽ gồm cả việc xử lý biến seed, đảm bảo từng màn
chơi sẽ giữ đúng tính chất ở các lần chơi khác nhau.
Quaternion
Trong Unity, Quaternionđƣợc sử dụng để biểu diễn phép quay của mọi đối
tƣợng. Quaternion đƣợc xây dựng dựa trên số phức toán học và đƣợc biểu diễn bởi 4
thành phần x, y, z, w với công thức:
Q = w + xi + yj + zk
Với i, j, k là các thành phần ảo, đƣợc xem nhƣ ba vectơ đơn vị của các trục tọa
độ tƣơng ứng. Mọi phép quay của đối tƣợng đều đƣợc lƣu trữ và thể hiện bằng
Quaternion với 4 thành phần. Để truy xuất các thành phần đó, sử dụng các kênh x, y,
z, w tƣơng ứng hoặc sử dụng nhƣ một mảng 4 phần tử với thứ tự nhƣ trên.

49
Ví dụ 1.11. Minh họa Quaternion.
1. void Example(){
2. public Quaternion p;
3. p[3] = 0.5f;
4. p.x = 0.75f;
5. }
Ngoài ra còn thiết lập giá trị cho các kênh thông tin của Quaternion nhƣ sử
dụng Constructor hay hàm Set. Các cách này chỉ có thể chỉnh sửa cùng lúc cả 4 kênh
của Quaternion.
eulerAngles:
Thuộc tính này trả về một Vector3, tƣơng ứng với góc quay của từng trục trên
đối tƣợng.
identity:
Thuộc tính này trả về một Quaternion tƣơng ứng không có bất kỳ phép quay
nào, tƣơng ứng với Vector3(0, 0, 0) đối với cách biểu diễn dƣới dạng eulerAngles. Đối
tƣợng đƣợc gán identity sẽ có các trục tọa độ trùng với hệ trục tọa độ thế giới (World
axes) hoặc đối tƣợng chứa nó (Parent axes).
Ví dụ: transform.rotation = Quaternion.identity;
LookRotation():
Hàm LookRotation() sẽ tạo ra một phép quay với hƣớng xác định, giá trị trả về
là một Quaternion.
public static Quaternion LookRotation(Vector3 forward, Vector3 upwards =
Vector3.up);
Trong đó forward là một Vector3 đƣợc sử dụng để xác định hƣớng quay của
đối tƣợng. Trục z của đối tƣợng sẽ trùng với hƣớng quay. Tham số upwards nhận giá
trị mặc định là Vector3.up, nghĩa là trục y sẽ hƣớng lên trên so với forward.
Angle():
Hàm Angle() trả về một góc (tính bằng độ) là góc giữa hai Quaternion.
public static float Angle(Quaternion a, Quaternion b);
Euler():
Hàm Euler() điều chỉnh góc quay của đối tƣợng thông qua chính ba trục tọa độ
của đối tƣợng đó. Cú pháp của hàm nhƣ sau:
public static Quaternion Euler(float x, float y, float z);
Trong đó, x, y, z là ba góc quay tƣơng ứng với ba trục tọa độ của đối tƣợng.
Các góc quay sử dụng đơn vị là độ. Ví dụ:
Public Quaternion rotation = Quaternion. Euler(0, 30, 0);
Lerp(), Slerp():
Lerp() và Slerp() là hai hàm nội suy phép quay giữa hai Quaternion và hiển thị
việc quay ra màn hình theo thời gian.
public static Quaternion Slerp(Quaternion from, Quaternion to, float t);

50
public static Quaternion Lerp(Quaternion from, Quaternion to, float t);
Với các phép quay lớn, góc quay rộng, hàm Lerp() sẽ cho chất lƣợng hiển thị
kém hơn Slerp(). Tuy nhiên, về mặt tốc độ thì Lerp() nhanh hơn do không thực hiện
nội suy một cách phức tạp.
Ví dụ: transform.rotation = Quaternion. Lerp(from. rotation, to. rotation,
Time.time*speed);

51
CÂU HỎI ÔN TẬP CHƢƠNG 1
Câu 1.1. Cài đặt công cụ lập trình game Unity.
Câu 1.2. Tạo một dự án Unity mới, thao tác với giao diện dự án và thực thi
game mẫu trong dự án.
Câu 1.3. Tạo một đối tƣợng đơn giản trong Scene và viết mã nguồn để di
chuyển đối tƣợng này bằng sử dụng các phím mũi tên (lên, xuống, trái, phải). Đồng
thời, khi nhấn phím Space, đối tƣợng sẽ di chuyển theo trục y.
Câu 1.4. Tạo một Script và sử dụng lớp Debug để in ra màn hình thông tin vị trí
của đối tƣợng trong Scene sau mỗi frame.
Câu 1.5. Tạo một hàm tùy chỉnh trong Script để tính tổng của hai Vector3 và in
kết quả ra màn hình sử dụng lớp Debug.
Câu 1.6. Tạo một Script và sử dụng hàm xử lý toán học trong Unity để di
chuyển một đối tƣợng từ vị trí A đến vị trí B trong thời gian nhất định, đồng thời in ra
màn hình thông báo khi di chuyển hoàn tất.
Câu 1.7. Tạo một hình cầu trong Scene và viết mã nguồn để khi nhấn một phím
bất kỳ, hình cầu sẽ bắt đầu xoay xung quanh trục y với tốc độ chậm dần theo thời gian.
Sử dụng lớp Time để kiểm soát tốc độ xoay.
Câu 1.8. Tạo một Script để tạo ngẫu nhiên các hình cầu với kích thƣớc và màu
sắc ngẫu nhiên trong một vùng nhất định của Scene sau mỗi khoảng thời gian. Sử dụng
lớp Random để tạo các giá trị ngẫu nhiên.
Câu 1.9. Tạo một đối tƣợng trong Scene và viết mã nguồn để làm cho đối tƣợng
xoay quanh trục x, y, z theo các góc ngẫu nhiên sau mỗi khoảng thời gian nhất định.
Sử dụng lớp Quaternion để quản lý xoay.
Câu 1.10. Tạo một đối tƣợng trong Scene và viết mã để khi ngƣời chơi nhấn các
phím mũi tên (lên, xuống, trái, phải), đối tƣợng sẽ xoay quanh trục y dựa trên hƣớng
của phím đƣợc nhấn. Sử dụng lớp Input để xác định hƣớng di chuyển và lớp
Quaternion để quản lý xoay.
Câu 1.11. Tạo một hiệu ứng nổ cho đối tƣợng trong Scene. Viết mã nguồn để
khi ngƣời chơi nhấn một phím bất kỳ, một số lƣợng ngẫu nhiên các hiệu ứng nổ xuất
hiện xung quanh đối tƣợng. Sử dụng lớp Random để tạo vị trí ngẫu nhiên cho các hiệu
ứng nổ và lớp Time để điều chỉnh tốc độ xuất hiện của chúng.
Câu 1.12. Tạo một game đơn giản với một đối tƣợng di chuyển trong Scene.
Ngƣời chơi sẽ điều khiển đối tƣợng này bằng cách sử dụng các phím mũi tên. Đồng
thời, viết mã nguồn để đếm thời gian ngƣời chơi hoàn thành trò chơi và in ra màn hình
thông báo khi trò chơi kết thúc.
Câu 1.13. Tạo một đối tƣợng trong Scene và viết mã nguồn để làm cho đối
tƣợng này di chuyển theo một quỹ đạo trong một khoảng thời gian nhất định. Sử dụng
lớp vectơ để xác định hƣớng di chuyển và lớp Time để điều chỉnh tốc độ chuyển động,
giúp đối tƣợng di chuyển mƣợt mà và tự nhiên hơn.

52
Chƣơng 2. XỬ LÝ HÌNH ẢNH VÀ GIAO DIỆN
Chƣơng 2 trình bày kỹ thuật trong xử lý hình ảnh nhƣ cắt tỷ lệ, quản lý thành
phần giao diện ngƣời dùng nhƣ sử dụng các thành phần nhƣ Button, Text,… để tạo ra
một giao diện ngƣời dùng tƣơng tác và dễ sử dụng và quá trình chuyển đổi giữa các
Scene trong phát triển game.
2.1. Cấu hình đối tƣợng đồ họa
Trong Unity, đối tƣợng đồ họa (Graphic Object) không phải là một loại đối
tƣợng chính thức do ngƣời dùng tạo ra, mà bao gồm các thành phần, lớp và đối tƣợng
khác nhau đƣợc sử dụng để hiển thị đồ họa trong game. Trong đó, các hình ảnh và diễn
hoạt là nguồn dữ liệu và tài nguyên mà ngƣời dùng sử dụng để hiển thị đồ họa và tạo
hiệu ứng đồ họa. Ví dụ, tại Assets có thể tạo các thƣ mục về font: Xử lý về font chữ
trong game, Sprites: Xử lý hình ảnh trong game, Animation: Quản lý hiệu ứng của đối
tƣợng. Có thể tạo các đối tƣợng này nhƣ sau:
Cách 1. Nhấp chuột phải tại Assets, chọn Create Folder và tạo các thƣ mục.

Hình 2.1. Tạo thƣ mục cách 1


Trong thƣ mục Sprites tạo các thƣ mục để xử lý riêng về hình ảnh nhƣ BG
Sprites: Hình nền trong game, game Sprites: Hình ảnh về nhân vật, UI: Hình ảnh về
các Button trong game,… Sau đó kéo và thả hình ảnh vào các thƣ mục này.
Cách 2. Tạo thƣ mục chứa các ảnh bên ngoài sau đó kéo và thả vào Assets.

Hình 2.2. Tạo thƣ mục cách 2

53
Sau khi tạo thƣ mục, có nhiều cách để đƣa hình ảnh vào Unity:
- Kéo và thả hình ảnh vào cửa sổ Project: Đây là cách đơn giản và phổ biến
nhất để import hình ảnh vào Unity. Ngƣời dùng chỉ cần mở cửa sổ Project trong Unity,
sau đó kéo và thả hình ảnh từ trình quản lý tệp của máy tính vào cửa sổ Project. Sau
khi kéo và thả, hình ảnh sẽ đƣợc import vào dự án và hiển thị trong thƣ mục Assets.

Hình 2.3. Ảnh trong thƣ mục Assets


- Sử dụng nút Import New Asset: Trong cửa sổ Project, sử dụng nút Import New
Asset để import hình ảnh vào Unity. Chọn nút Import New Asset và sau đó tìm đến vị
trí hình ảnh trên máy tính. Chọn hình ảnh và nhấn Open để import.

Hình 2.4. Thêm ảnh sử dụng nút Import New Asset


Cách 3. Sử dụng menu ngữ cảnh.
Trong cửa sổ Project, nhấp chuột phải và chọn Import New Asset từ menu ngữ
cảnh. Sau đó, tìm đến vị trí hình ảnh trên máy tính và chọn để import. Sau khi import
hình ảnh vào Unity, hình ảnh sẽ đƣợc lƣu trong thƣ mục Assets của dự án. Ngƣời dùng
sử dụng hình ảnh này để tạo sprite, texture, UI image, background,… trong game.
Cách 4. Thêm từ Unity Asset Store.
Bƣớc 1. Truy cập Unity Asset Store.
Truy cập trang Unity Asset Store: https://assetstore.unity.com/
Đăng nhập vào tài khoản Unity hoặc tạo một tài khoản mới nếu chƣa có.

54
Bƣớc 2. Tìm tài nguyên.
Sử dụng thanh tìm kiếm và các danh mục trong Unity Asset Store để tìm tài
nguyên muốn sử dụng trong dự án. Khi tìm thấy tài nguyên phù hợp, chọn nút Add to
My Assets.

Hình 2.5. Thêm ảnh từ Unity Asset Store

Hình 2.6. Chọn Add to My Assets


Bƣớc 3. Tải và import tài nguyên vào dự án Unity.
Sau khi tải thành công, truy cập trang My Assets trong Unity Asset Store. Tại
đây, ngƣời dùng sẽ thấy danh sách các tài nguyên đã tải. Tìm tài nguyên muốn thêm
vào dự án và chọn nút Import hoặc Download.
Một cửa sổ xác nhận sẽ hiển thị, hỏi ngƣời dùng xác nhận mở Unity và import
tài nguyên. Chọn Open in Unity để mở dự án Unity và tiến hành import tài nguyên.

55
Hình 2.7. Chọn Import hoặc Download
Bƣớc 4. Xử lý tài nguyên trong dự án.
Unity sẽ tự động import tài nguyên vào dự án sau khi chọn Open in Unity.
Sau khi import, ngƣời dùng có thể sử dụng tài nguyên trong dự án của mình
bằng cách kéo và thả chúng vào cửa sổ Scene hoặc Project hoặc tham chiếu đến chúng
thông qua Script.

Hình 2.8. Mở tài nguyên trong Unity

Hình 2.9. Tài nguyên đƣợc thêm vào thƣ mục Assets

56
2.2. Xử lý các hình ảnh
Xử lý hình ảnh trong Unity là quá trình thực hiện các thao tác và hiệu chỉnh trên
hình ảnh để tạo ra các hiệu ứng hoặc cải thiện ngoại hình của đối tƣợng đồ họa trong
game. Để thực hiện xử lý ảnh, mở thƣ mục Sprites chứa các thƣ mục về hình ảnh đã
tạo nhƣ sau:

Hình 2.10. Các thƣ mục chứa hình ảnh


Giả sử để xử lý hình ảnh background, khi nhấn vào background sẽ xuất hiện
phần Window Inspector là khung dùng để chỉnh sửa thuộc tính của game object.

Hình 2.11. Thuộc tính xử lý hình ảnh


- Texture Type: Mặc định là Sprite (2D and UI).
- Sprite Mode: Có 2 chế độ là Single và Multiple.
- Single: Hình ảnh chỉ có một ảnh duy nhất.
- Multiple: Hình ảnh có nhiều ảnh nhỏ.
- Generate Mip Map: Tích chọn nếu dùng Project 3D.

57
Tại đây có một số nền để chọn phát triển, ví dụ Default là mặc định nên có thể
phát triển các nền khác nhau, Web, Android, IOS,…
- Max Size: Chọn một giá trị lớn hơn size của ảnh hiện hành, nếu chọn giá trị
nhỏ hơn size của ảnh thì ảnh có thể giảm chất lƣợng hoặc không đủ màu.
- Compression: Chọn chất lƣợng ảnh.

Hình 2.12. Max size hình ảnh và chất lƣợng ảnh


Nhấp chọn Apply để chấp nhận thay đổi.
Nếu hình ảnh gồm nhiều ảnh nhỏ, tại Sprite Mode chọn chế độ là Multiple.

Hình 2.13. Ảnh gồm nhiều ảnh nhỏ

58
2.3. Cắt hình ảnh
Mục đích cắt hình ảnh khi ảnh lớn chứa nhiều thông tin và ngƣời dùng muốn
hiển thị chỉ một phần cụ thể của ảnh đó hoặc mục đích cắt hình ảnh để tạo các frame
cho một Animation để tạo các hoạt ảnh động bằng cách chuyển đổi giữa các frame
khác nhau. Điều kiện là hình ảnh phải là một Sprite Sheet và Sprite Mode chọn là
Multiple. Để cắt hình ảnh, nhấp vào Sprite Editor tại cửa sổ Inspector.

Hình 2.14. Chọn thông số để cắt hình ảnh

Hình 2.15. Cài đặt gói 2D Sprite

Hình 2.16. Chọn cài đặt gói 2D Sprite


59
Hộp thoại hiển thị nhƣ sau:

Hình 2.17. Hộp thoại Sprite Editor


Để cắt, nhấn vào nút Slice trên góc trái màn hình, các thông số để mặc định và
nhấn Slice và Apply.

Hình 2.18. Cắt hình ảnh


Trong phần Assets, hình ảnh ban đầu đã đƣợc cắt thành các ảnh con đƣợc đánh
số từ 0 đến 5 tùy thuộc số lƣợng ảnh nhỏ trong hình ảnh.

Hình 2.19. Hình ảnh sau khi cắt

60
2.4. Các thành phần cơ bản của UI
UI (User Interface) trong Unity là một khía cạnh quan trọng trong việc thiết kế
và phát triển game. UI đảm nhận vai trò tƣơng tác giữa ngƣời chơi và game, cung cấp
thông tin, điều khiển và trải nghiệm thú vị cho ngƣời dùng. UI tập trung vào ba phần
chính: Canvas, Components và Layout.
Canvas: Canvas là một thành phần quan trọng trong UI của Unity. Canvas đại
diện cho bề mặt 2D trong game, nơi các thành phần UI khác nhau đƣợc hiển thị.
Canvas là nơi gốc cho tất cả các thành phần UI và có thể đƣợc thiết lập để tự động co
dãn hoặc giữ nguyên kích thƣớc tùy chỉnh cho phù hợp với kích thƣớc màn hình của
ngƣời dùng.
Components: Unity cung cấp nhiều Components UI có sẵn để tạo các thành
phần tƣơng tác và hiển thị thông tin. Một số Components UI nhƣ sau:
- Button: Là đối tƣợng nút bấm.
- Image: Là thành phần đồ họa hình ảnh chính cho các đối tƣợng nhƣ Button,
Panel, Slider,...
- Text: Dùng để hiển thị hình ảnh văn bản, tên Button, Label,...
- Transitions: Thành phần này giúp tạo ra các Animation, chuyển đổi giữa các
trạng thái.
- Scroll Rect: Cuộn theo chiều dọc và chiều ngang.
- Scrollbar: Thanh cuộn nội dung.
- Mark: Dùng để ẩn một phần đối tƣợng UI image, tạo một không gian để tạo
hình ảnh động của đối tƣợng image.
Layout: Layout trong Unity UI giúp tự động xếp chồng và sắp xếp các thành
phần UI trên Canvas một cách dễ dàng và linh hoạt. Unity hỗ trợ các loại Layout khác
nhau nhƣ Vertical Layout Group, Horizontal Layout Group và Grid Layout Group,
giúp tự động sắp xếp các thành phần UI theo hƣớng dọc, ngang hoặc dạng lƣới.
Unity UI hỗ trợ cơ chế Event System để xử lý các sự kiện tƣơng tác từ ngƣời
dùng nhƣ nhấn nút, di chuyển chuột, chạm màn hình,... Ngoài ra, Animator Controller
cho phép tạo các hiệu ứng chuyển động và thay đổi trạng thái của các thành phần UI.
- Rect Transform: Đây là thành phần định vị cho tất cả các đối tƣợng GUI trong
Canvas, giúp điều chỉnh vị trí, điểm neo, trụ,… của đối tƣợng GUI.
- Events and Event Triggers: Sự kiện trong các đối tƣợng UI.
2.4.1. Canvas
Canvas là thành phần chính và không thể thiếu trong việc thiết kế UI. Các thành
phần UI khác khi đƣợc khởi tạo bắt buộc phải nằm trong một Canvas. Khi khởi tạo
một thành phần UI (Text, Image,...), Unity sẽ tự động tạo ra một Canvas nếu chƣa tồn
tại một Canvas nào trong Scene.
Để khởi tạo một canvas, trong cửa sổ Hierarchy, chọn Create, UI, Canvas. Các
đối tƣợng UI khác cũng đƣợc khởi tạo tƣơng tự.

61
Hình 2.20. Tạo các đối tƣợng đồ họa
Các đối tƣợng con của một Canvas sẽ đƣợc render theo thứ tự từ trên xuống
dƣới trong cửa sổ Hierarchy. Đối tƣợng nào ở trên sẽ đƣợc render trƣớc và có thể bị
che khuất bởi đối tƣợng phía dƣới.
Khi tạo UI (Text, Button, Image, Panel,…) thì tất cả đều là con của Canvas, là
nơi chứa tất cả các phần tử liên quan đến giao diện ngƣời dùng. Tại đây, chỉnh sửa
khung Canvas nhƣ sau:

Hình 2.21. Thành phần của Canvas

62
Kéo Main Camera tại Hierarchy vào Render Camera của cửa sổ Inspector:

Hình 2.22. Camera trong Render Mode


Nhƣ vậy khung Canvas đang đè lên khung Main Camera.
Các chức năng quan trọng của Canvas:
Render mode
Có 3 tùy chọn hiển thị Canvas:
- Screen Space - Overlay: Canvas sẽ đƣợc vẽ lên layer cao nhất của màn hình và
nằm trên mọi game object khác. Canvas với render mode này hoàn toàn không phụ
thuộc vào camera.
- Screen Space - Camera: Đối với mode này, cần chỉ định một camera cho
Canvas và sẽ đƣợc render theo camera. Nếu không có camera đƣợc chỉ định thì
Canvas và các thành phần bên trong sẽ không đƣợc render.
- World Space: Với tùy chọn này, đối tƣợng canvas sẽ đƣợc xem nhƣ một game
object thông thƣờng. Tùy chọn này sử dụng event camera thay vì render camera.
Ngoài các chức năng nhƣ render camera, event camera còn có thêm chức năng bắt sự
kiện, dựa trên thứ tự render, tọa độ z,... của các đối tƣợng UI.

Hình 2.23. Tùy chọn hiển thị Canvas

63
Đối với các tùy chọn render theo Screen Space, Unity cung cấp tính năng Pixel
Perfect, tăng khả năng hiển thị sắc nét và khử vết mờ.
Rect Transform
Tƣơng tự nhƣ thành phần Transform trong các game object khác. Rect
Transform đƣợc sử dụng để xác định kích thƣớc, vị trí của giao diện. Đối với các tùy
chọn render mode Screen Space - Overlay và Screen Space - Camera, thành phần Rect
Transform sẽ đƣợc khóa lại và không thể tùy chỉnh. Canvas sẽ điều chỉnh các thông số
một cách tự động để phù hợp với độ phân giải màn hình game.

Hình 2.24. Rect Transform của Canvas


Graphic Raycast
Graphic Raycast hỗ trợ bắt sự kiện. Khi nhận tín hiệu (mouse click, touch,...),
sẽ đƣợc tạo ra một tia nhìn tại vị trí tƣơng tác. Bằng cách này sẽ dễ dàng xác định
đƣợc đối tƣợng mà ngƣời chơi muốn tƣơng tác, thông qua tọa độ z của đối tƣợng.

Hình 2.25. Graphic Raycast của Canvas


2.4.2. Text
Trong Unity, Text là một thành phần UI cho phép hiển thị văn bản lên màn hình
trong game. Với Text, ngƣời dùng có thể hiển thị các thông điệp, hƣớng dẫn, điểm số,
tên nhân vật và các nội dung khác.

64
Thao tác với Text thực hiện các bƣớc sau:
Bƣớc 1. Tạo một Text.
Trong Hierarchy, nhấp chuột phải và chọn UI, Text hoặc vào GameObject, UI,
Text để tạo một đối tƣợng Text mới. Đối tƣợng Text sẽ đƣợc tạo ra bên trong Canvas.

Hình 2.26. Tạo Text


Bƣớc 2. Điều chỉnh nội dung của Text.
Chọn đối tƣợng Text trong Hierarchy để hiển thị các thuộc tính trong Inspector.
Trong phần Text của Inspector có thể điều chỉnh nội dung của Text bằng cách nhập
vào các văn bản cần hiển thị.

Hình 2.27. Nhập nội dung Text


Bƣớc 3. Định dạng Text.
Ngƣời dùng có thể thay đổi kiểu chữ, kích thƣớc, màu sắc, chế độ chữ in đậm
(bold), chế độ nghiêng (italic) và nhiều tùy chọn định dạng khác của Text trong phần
Text của Inspector. Để điều chỉnh kích thƣớc của Text có thể kéo thả cạnh của đối
tƣợng Text trên Scene. Tùy chọn Best Fit sẽ tự động điều chỉnh kích thƣớc font chữ
phù hợp với kích thƣớc đƣợc quy định trong Rect Transform.

65
Hình 2.28. Định dạng Text
Bƣớc 4. Hiển thị Text trong game.
Khi điều chỉnh nội dung và định dạng của Text sẽ tự động hiển thị trên màn
hình trong game hoặc ứng dụng khi chạy.
Bƣớc 5. Cập nhật văn bản trong mã nguồn.
Nếu muốn cập nhật nội dung của Text trong mã nguồn cần tham chiếu đến đối
tƣợng Text thông qua Script. Sau đó, sử dụng các phƣơng thức của Text để cập nhật
nội dung một cách tự động.
Ví dụ 2.1. Hiển thị số giây lên màn hình.
Đầu tiên cần khai báo thƣ viện UI:
1. using UnityEngine.UI;
Sau đó khai báo các biến đối tƣợng Text và giá trị cần hiển thị:
1. public Text scoretext;
2. private float timeScore;
Cuối cùng xây dựng hàm đếm thời gian thực hiện tăng và hiển thị giá trị lên đối
tƣợng Text:
1. timeScore += 0.01f;
2. scoretext.text = timeScore.ToString();
2.4.3. Button
UI Button là một thành phần quan trọng giúp ngƣời chơi tƣơng tác với game.
Một số button quen thuộc có thể thấy trong nhiều game là Play, Pause, Resume,
Replay,... Tƣơng tự nhƣ tạo Text, vào GameObject, UI, Button. UI Button là một thành
phần quan trọng, giúp ngƣời chơi tƣơng tác với game. Một số button quen thuộc có thể
thấy trong nhiều game là Play, Pause, Resume, Replay,...
Tƣơng tự, dùng Button để thiết kế menu nhƣ sau:

Hình 2.29. Thiết kế menu dùng Button

66
Unity cung cấp nhiều tính năng giúp hiển thị và thao tác với Button. Về hiển
thị, có 4 tùy chọn hiển thị trong phần Transition:

Hình 2.30. Tùy chọn hiển thị trong phần Transition


- None: Sử dụng hình ảnh Button đƣợc truyền vào thành phần Image. Với tùy
chọn này, hình hiển thị Button sẽ không thay đổi mỗi khi ngƣời chơi thao tác.
- Color Tint, Sprite Swap và Animation: Có các tùy chọn hiển thị riêng cho từng
trạng thái của button. Với Color Tint, button sẽ thay đổi màu sắc theo từng trạng thái,
Sprite Swap hỗ trợ thay đổi sprite theo trạng thái, Animation hỗ trợ thêm các đặc tính
nhƣ scale, transform,… Tùy vào yêu cầu của game để lựa chọn phù hợp.
Khi tạo Button mặc định sẽ tạo sẵn một cái Text là con của Button, có thể đổi
tên và màu cho Text tùy ý.

Hình 2.31. Hiển thị Button

67
Có thể thay đổi hình dạng button tại Source Image.

Hình 2.32. Thay đổi hình dạng Button


Thay đổi màu Button khi chạy và khi nhấn vào Button:
- Highlighted Color: Màu Button khi chạy.
- Pressed Color: Màu khi nhấn button.

Hình 2.33. Kết quả thay đổi màu khi chạy và nhấn Button
Ví dụ 2.2. Minh họa bắt sự kiện cho Button.
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4. public class SkienButon : MonoBehaviour
5. {
6. public void nhan()
7. {
8. Debug.Log("Nhấn Button");
9. }
10. }
Nhấp Button tại cửa sổ Hierarchy, tại On Click trong cửa sổ Inspector kéo
Object Button vào Runtime Only và chọn Function của Script.

Hình 2.34. Chọn sự kiện cho Button

68
Hình 2.35. Gắn Button vào Runtime Only

Hình 2.36. Chọn Function cho sự kiện của Button

Hình 2.37. Kết quả sự kiện Button


2.4.4. Input Field
Input Field cho phép nhập liệu vào khung, để tạo Input Field, nhấp
GameObjecy, UI, Input Field. Nhấp vào Input Field, mặc định tạo Input Field thì có
hai con của game object là PlaceHoler và Text.
- PlaceHoler: Hiển thị chữ Enter text,…
- Text: Hiển thị giá trị cần nhập.

Hình 2.38. PlaceHoler Input Field

69
Hình 2.39. Text của Input Field
Để bắt sự kiện cho Input Field tƣơng tự nhƣ Button nhƣng ở Input Field sẽ có 2
giá trị:
- On Value Change: Sự kiện này đƣợc gọi khi nội dung của đoạn text thay đổi.
- End Edit: Sự kiện này đƣợc gọi khi ngƣời dùng hoàn tất việc chỉnh sửa text
bằng cách nhấn Enter hoặc click vào vị trí nào đó.

Hình 2.40. Bắt sự kiện cho Input Field


2.4.5. Image
Trong Unity, Image là một thành phần UI cho phép hiển thị hình ảnh hoặc
sprite lên màn hình trong game. Image đƣợc sử dụng rất phổ biến nhƣ thiết kế
Background, Button, Title,... Thao tác với Image thực hiện nhƣ sau:
Bƣớc 1. Tạo một Image.
Trong Hierarchy, tƣơng tự nhƣ Text hay Button, nhấp chuột phải và chọn UI,
Image để tạo đối tƣợng Image mới. Đối tƣợng Image sẽ đƣợc tạo ra bên trong Canvas.
Bƣớc 2. Chọn hình ảnh hoặc sprite cho Image.
Chọn đối tƣợng Image trong Hierarchy để hiển thị các thuộc tính của Image
trong Inspector. Trong phần Image của Inspector, kéo và thả hình ảnh hoặc sprite từ
cửa sổ Project vào trƣờng Source Image để hiển thị lên Image.

70
Hình 2.41. Chọn hình ảnh hoặc Sprite
Bƣớc 3. Điều chỉnh kích thƣớc và vị trí của Image.
Kích thƣớc của Image điều chỉnh bằng cách kéo cạnh của Image trên Scene. Để
di chuyển Image sử dụng các thuộc tính Transform (Position, Rotation, Scale) trong
Inspector.
Bƣớc 4. Hiển thị Image trong game.
Khi chọn hình ảnh hoặc Sprite và điều chỉnh kích thƣớc, Image sẽ tự động hiển
thị trên màn hình trong game hoặc ứng dụng khi chạy.
Bƣớc 5. Cập nhật hình ảnh hoặc Sprite trong mã nguồn.
Để cập nhật hình ảnh hoặc Sprite của Image trong mã nguồn cần tham chiếu
đến đối tƣợng Image thông qua Script. Sau đó sử dụng các thuộc tính của Image để
cập nhật hình ảnh hoặc Sprite. Cách sử dụng đơn giản hơn ở bƣớc 1 và 2 là ngƣời
dùng chỉ cần chọn hình ảnh và kéo thả vào khung Source Image.

Hình 2.42. Kết quả tạo Image

71
2.5. Chuyển cảnh các Scene
Đối với đa số game hiện nay đều có nhiều hơn một màn chơi hay cấp độ để
thách thức ngƣời chơi, tăng tính thú vị của game gọi là các Scene. Khi chơi hết một
màn có thể chuyển sang màn chơi mới cần thực hiện chuyển màn. Để chuyển đổi giữa
các Scene trong game cần tạo hai hoặc nhiều Scene và tạo menu để chuyển đổi giữa
các Scene. Giả sử có hai Scene main và Man1 nhƣ sau:

Hình 2.43. Scene main

Hình 2.44. Scene Man1


Giả sử khi nhấn Button Play, game sẽ chuyển sang Man1, tạo Script gắn vào
Button Play và sử dụng phƣơng thức LoadScene() trong thƣ viện SceneManager.
- SceneManager.LoadScene (int SceneBuildIndex);

72
Khi đó sẽ chuyển qua Scene có số thứ tự build là SceneBuildIndex.
Ví dụ: SceneManager.LoadScene(1);
- SceneManager.LoadScene (string SceneName);
Sẽ chuyển qua Scene có tên là SceneName.
Ví dụ: SceneManager.LoadScene("GamePlay");
Ví dụ 2.3. Minh họa chuyển Scene.
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4. using UnityEngine.SceneManagement;
5. public class SkienPlay : MonoBehaviour
6. {
7. public void playgame()
8. {
9. SceneManager.LoadScene(1);
10. }
11. }
Ngoài ra, Unity còn cung cấp một số phƣơng thức chuyển Scene nhƣ sau:
- SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
Load lại từ đầu Scene đang chạy theo số thứ tự build (không phải tất cả Scene,
chỉ duy nhất Scene đang chạy) thƣờng dùng để nhân vật chơi lại màn đó khi bị chết.
- SceneManager.LoadScene(SceneManager.GetActiveScene().name);
Load lại từ đầu Scene đang chạy theo tên Scene build (không phải tất cả Scene,
chỉ duy nhất Scene đang chạy) thƣờng dùng để nhân vật chơi lại màn đó khi bị chết.
- SceneManager.LoadScene(SceneManager.SceneCountInBuildSettings);
Đếm số Scene có trong bảng Build Setting, bắt đầu đến từ 1 tăng dần. Ví dụ có
hai Scene là main và Man1 thì sẽ đếm bằng 2.
Cấu hình Build Setting:
Sau khi thực hiện các thao tác trên, nếu chạy thử thì thấy các thao tác chuyển
Scene vẫn chƣa hoạt động. Các Scene có thể chuyển qua lại khi và chỉ khi chúng đƣợc
thêm vào Build Setting. Lúc này Unity sẽ lƣu trữ index và name của Scene phục vụ
cho việc chuyển đổi Scene.
Để mở menu Build Setting, thực hiện hai cách sau:
- Vào menu File, Build Setting.
- Nhấn tổ hợp phím Ctrl + Shift + B.
Giao diện Build Setting hiện ra, thực hiện kéo thả tất cả các Scene liên quan vào
mục Scene In Build. Mỗi Scene sẽ có một index và có thể kéo thả nhiều lần. Do đó
tránh lãng phí tài nguyên khi build lại những Scene trùng lặp.

73
Hình 2.45. Cấu hình Build Setting
Ví dụ 2.4. Minh họa chuyển Scene.

Hình 2.46. Xử lý chuyển màn hình dùng Button


Giả sử ngƣời dùng nhấn Button Play thì chuyển về màn chính, sử dụng sự kiện
của Button nhƣ đã trình bày ở phần trƣớc để gắn với hàm choilai1(), hàm này thực
hiện chuyển sang màn 1 trong bảng Build Setting.
1. public void choilai1()
2. {
3. SceneManager.LoadScene(1);
4. }

Hình 2.47. Cửa sổ Build Setting

74
CÂU HỎI ÔN TẬP CHƢƠNG 2
Câu 2.1. Xây dựng game tránh đá trong đó ngƣời chơi sẽ điều khiển một đối
tƣợng và cố gắng tránh các đá rơi từ ngẫu nhiên trên xuống.
Câu 2.2. Xây dựng game hứng trứng trong đó ngƣời chơi sẽ điều khiển một cái
rổ để hứng trứng rơi từ trên xuống.
Câu 2.3. Xây dựng game SpiderCave trong đó ngƣời chơi sẽ điều khiển một
nhân vật nhƣ con nhện đang bò qua một hang động, tránh các chƣớng ngại vật. Các
chƣớng ngại vật (đá, bức tƣờng,…) xuất hiện trên đƣờng đi của nhện.
Câu 2.4. Xây dựng game SpaceShooter trong đó ngƣời chơi sẽ điều khiển một
chiến đấu cơ vũ trụ để tiêu diệt đối thủ và tránh các vật thể nguy hiểm.
Câu 2.5. Xây dựng game FlappyBird trong đó ngƣời chơi sẽ điều khiển một chú
chim vƣợt qua các ống để ghi điểm. Tạo ra các ống hiển thị trên màn hình và di
chuyển từ phải sang trái đảm bảo có khoảng trống giữa các ống để chú chim đi qua.
Câu 2.6. Xây dựng game mê cung trong đó ngƣời chơi sẽ khám phá một mê
cung và cố gắng tìm đƣờng để đến đích. Tạo ra một mê cung với các phòng, hành lang
để tạo nên bản đồ. Đặt đối tƣợng cần tìm vào một vị trí ngẫu nhiên trong mê cung.
Câu 2.7. Xây dựng các màn chơi và sử dụng các Button để chuyển màn chơi
trong các game ở câu 2.1 đến 2.6.
Câu 2.8. Bổ sung các Text vào game để hiển thị điểm số, hƣớng dẫn trong các
game ở câu 2.1 đến 2.6.
Câu 2.9. Tạo game đoán từ với giao diện Text và xử lý Script. Ngƣời chơi phải
đoán từ đƣợc hiển thị trên giao diện và nhập câu trả lời.
Câu 2.10. Tạo ứng dụng ghi chú với Input Field và quản lý dữ liệu ghi chú.

75
Chƣơng 3. SỰ KIỆN VÀ HIỆU ỨNG TRONG GAME
Chƣơng 3 trình bày tổng quan về cách lập trình chuyển động, xử lý va chạm, di
chuyển đối tƣợng, tạo diễn hoạt, sử dụng slider và timer cùng với tích hợp âm thanh
trong quá trình phát triển game.
3.1. Lập trình chuyển động
Trong game, các đối tƣợng đều có sự chuyển động. Các bƣớc cơ bản để lập
trình chuyển động trong Unity nhƣ sau:
Bƣớc 1. Tạo đối tƣợng di chuyển.
Đầu tiên cần tạo một đối tƣợng để điều khiển chuyển động. Đối tƣợng này có
thể là một nhân vật hoặc một phƣơng tiện muốn di chuyển. Ví dụ trong game tránh đá,
ngƣời dùng đã tạo nhân vật ngƣời nhƣ đã trình bày trong chƣơng 2 (Kéo nhân vật
player_0 từ thƣ mục Sprites của cửa sổ Project lên màn hình Scene).

Hình 3.1. Tạo đối tƣợng di chuyển


Bƣớc 2. Áp dụng chuyển động vật lý cho đối tƣợng.
Vật lý chuyển động cho đối tƣợng là Rigidbody, đây là thành phần hỗ trợ các
thao tác về vật lý nhƣ tác dụng lực, trọng lực trái đất, ma sát,... Để thêm thành phần vật
lý cần thực hiện nhƣ sau: Ở Hierarchy, chọn đối tƣợng cần thêm (player_0), vào
Component, chọn Physics/Physics 2D, Rigidbody/ Rigidbody 2D.

Hình 3.2. Thêm thành phần vật lý Rigidbody 2D


Ngoài ra, có thể thêm thành phần Rigidbody/Rigidbody 2D bằng cách Add
Component. Chọn đối tƣợng cần thêm thành phần (player_0), tại cửa sổ Inspector của
Add Component, tìm đến Rigidbody/Rigidbody 2D của nhóm Physics.

76
Hình 3.3. Thêm Rigidbody 2D bằng Add Component
Khi chọn đối tƣợng player_0, tại cửa sổ Inspector sẽ thêm một Component nữa
là Rigidbody 2D nhƣ sau:

Hình 3.4. Thành phần Rigidbody 2D của đối tƣợng


Các thuộc tính của Rigidbody đƣợc liệt kê trong bảng 3.1:
Bảng 3.1. Các thuộc tính của Rigidbody
Thuộc tính Ý nghĩa
Vật liệu liên quan đến cách đối tƣợng tƣơng tác với các bề mặt khác
và các vật lý khác nhau. Mỗi Rigidbody có thể đƣợc gán một vật
liệu vật lý (Physics Material). Vật liệu vật lý có thể xác định các
Material
thuộc tính nhƣ ma sát và đàn hồi.
- Ma sát (Friction): Xác định mức độ ma sát giữa đối tƣợng (khi di
chuyển, quay) và bề mặt khi chúng tiếp xúc.
Xác định cách tƣơng tác của đối tƣợng với vật lý và các đối tƣợng
khác trong môi trƣờng. Có ba loại body chính trong Unity:
- Dynamic (Động): Đối tƣợng với loại body này sẽ chịu ảnh hƣởng
Body Type
của lực vật lý nhƣ trọng lực, áp lực và va chạm. Đối tƣợng có thể di
chuyển và gây va chạm với các đối tƣợng khác. Đây là loại body
mặc định khi thêm Rigidbody vào một đối tƣợng.

77
Thuộc tính Ý nghĩa
- Kinematic (Cơ học): Đối tƣợng với loại body này sẽ không bị ảnh
hƣởng bởi các lực vật lý, nhƣng có thể điều khiển chuyển động
bằng cách thay đổi vận tốc và vận tốc góc thông qua mã nguồn.
Dạng này thƣờng đƣợc sử dụng cho các đối tƣợng cần đƣợc điều
khiển bằng mã nguồn mà không cần tƣơng tác vật lý với các đối
tƣợng khác.
- Static (Tĩnh): Đối tƣợng với loại body này sẽ không di chuyển và
không tƣơng tác vật lý với bất kỳ đối tƣợng nào khác. Đối tƣợng
tĩnh thƣờng đƣợc sử dụng cho các vật thể cố định trong môi trƣờng
nhƣ tƣờng, sàn,…
- Đàn hồi (Bounciness/Elasticity): Xác định mức độ đàn hồi của đối
tƣợng khi va chạm với bề mặt. Đàn hồi cao sẽ khiến đối tƣợng bật
lại cao sau khi va chạm.
Là khối lƣợng của đối tƣợng, đơn vị thƣờng là kilogam. Mass ảnh
Mass hƣởng đến cách đối tƣợng phản ứng với lực vật lý. Đối tƣợng nặng
hơn sẽ khó di chuyển và cần lực lớn hơn để thay đổi tốc độ.
Xác định mức độ ma sát tuyến tính đƣợc áp dụng lên vận tốc của
Linear Drag đối tƣợng khi di chuyển. Giá trị càng cao, đối tƣợng sẽ dừng lại
nhanh hơn.
Ma sát góc tƣơng tự nhƣ ma sát tuyến tính nhƣng dành cho quá
Angular Drag
trình quay của đối tƣợng.
Tỷ lệ trọng lực thay đổi cƣờng độ của trọng lực tác động lên
Gravity Scale Rigidbody. Ngƣời dùng có thể sử dụng giá trị này để làm cho đối
tƣợng có trọng lực mạnh hơn hoặc yếu hơn so với mặc định.
Phát hiện va chạm cho đối tƣợng, gồm các tùy chọn nhƣ Discrete
Collision (xác định va chạm trong các bƣớc tƣơng tác), Continuous (xác định
Detection va chạm liên tục trong quá trình di chuyển) và Continuous Dynamic
(tƣơng tự Continuous nhƣng chỉ áp dụng cho đối tƣợng động).
Chế độ ngủ xảy ra khi Rigidbody ít tƣơng tác với môi trƣờng hoặc
Sleep Mode
vật lý nên chuyển vào chế độ ngủ để tiết kiệm tài nguyên.
Xác định cách Unity nội suy giữa các vị trí và góc của đối tƣợng
Interpolate giữa các khung hình. Interpolation có thể giúp giảm giật hoặc hiệu
ứng giật trong chuyển động của đối tƣợng.
Khi tích chọn, đối tƣợng sẽ bị ảnh hƣởng bởi trọng lực và sẽ rơi tự
Use Gravity
do nếu không có lực nào khác tác động.
Khi tích chọn, đối tƣợng sẽ không bị ảnh hƣởng bởi lực vật lý nhƣ
va chạm hoặc áp lực. Điều này thƣờng đƣợc sử dụng cho các đối
Is Kinematic
tƣợng mà ngƣời dùng muốn điều khiển di chuyển bằng code thay vì
dựa vào vật lý.

78
Thuộc tính Ý nghĩa
Freeze Position Ngăn chặn chuyển động của Rigidbody.
Freeze Rotation Ngăn chặn xoay của Rigidbody.
Ví dụ để nhân vật không bị xoay khi rơi xuống cạnh của đám mây, thực hiện
chọn Freeze Rotation không cho xoay đối tƣợng.

Hình 3.5. Thuộc tính Freeze Rotation

Hình 3.6. Kết quả không chọn thuộc tính Freeze Rotation

Hình 3.7. Kết quả chọn thuộc tính Freeze Rotation


Sau khi thêm thành phần vật lý vào, nhấn nút play ta sẽ thấy đối tƣợng sẽ từ từ
rơi xuống (do Gravity scale > 0).
Bƣớc 3. Lập trình.
Lập trình chuyển động bằng cách sử dụng Script để điều khiển chuyển động của
đối tƣợng (xem mục 3.4).
3.2. Xử lý va chạm đối tƣợng
Để xử lý va chạm đối tƣợng cần sử dụng các Collider (bề mặt va chạm) và các
hàm xử lý va chạm trong Script. Cách xử lý va chạm đối tƣợng trong Unity:
Bƣớc 1. Đặt Collider cho các đối tƣợng.
Để thêm thành phần bề mặt va chạm thực hiện nhƣ sau: Ở Hierarchy, chọn đối
tƣợng cần thêm (player_0), vào Component, chọn Physics/Physics 2D, chọn Collider.

79
Hình 3.8. Chọn kiểu Collider từ Menu Component
Ngoài ra, có thể thêm thành phần Collider bằng cách Add Component. Chọn đối
tƣợng cần thêm thành phần player_0, tại cửa sổ Inspector của Add Component, tìm
đến Collider. Unity hỗ trợ nhiều loại collider nhƣ Box Collider, Sphere Collider,
Capsule Collider, Mesh Collider,... Chọn loại Collider phù hợp với hình dáng và kích
thƣớc của đối tƣợng.

Hình 3.9. Chọn kiểu Collider từ Add Component


Khi đó ở cửa sổ Inspector khi chọn đối tƣợng player_0 sẽ thêm một Component
nữa là Collider 2D nhƣ sau:

Hình 3.10. Collider của đối tƣợng trong cửa sổ Inspector

80
Các thuộc tính của Collider đƣợc liệt kê trong bảng 3.2:
Bảng 3.2. Các thuộc tính của Collider
Thuộc tính Ý nghĩa
Điều chỉnh hình dạng và vùng va chạm của một Collider để phù
Edit Collider hợp với hình dạng thực tế của đối tƣợng hoặc để tƣơng thích với các
yêu cầu cụ thể.
Collider đƣợc gắn với vật liệu vật lý để xác định các thuộc tính nhƣ
Material
ma sát và đàn hồi khi đối tƣợng va chạm với các bề mặt khác.
Khi tích chọn, Collider sẽ không gây ra va chạm vật lý, nhƣng sẽ
thay vào đó kích hoạt sự kiện thông qua hàm xử lý va chạm. Điều
Is Trigger
này thƣờng đƣợc sử dụng để xử lý va chạm mà không ảnh hƣởng
đến vật lý của đối tƣợng.
Used By Áp dụng cho Edge Collider và 2D Collider, cho phép Collider
Effector tƣơng tác với các Effector trong hệ thống vật lý 2D.
Xác định kích thƣớc của Collider. Kích thƣớc thƣờng đƣợc xác định
Size
theo tọa độ x, y, z tƣơng ứng với chiều dài, chiều rộng và chiều cao.
Xác định xem Collider của Tile nào trong Tilemap đƣợc sử dụng để
tạo nên Collider toàn cục của Tilemap. Khi tích chọn, các Collider
Used By
của Tile trong Tilemap sẽ đƣợc tự động thêm vào Composite
Composite
Collider 2D để tạo ra một Collider duy nhất cho Tilemap. Điều này
hữu ích để tối ƣu hóa hiệu suất vật lý cho Tilemap lớn.
Tự động điều chỉnh các Tile sao cho chúng phù hợp khi xây dựng
Auto Tiling
môi trƣờng.
Sử dụng để tạo sự thay đổi vị trí, địa điểm hoặc hƣớng của một thứ
Offset
gì đó mà không cần thay đổi giá trị tuyệt đối.
Đối với các nhân vật, thƣờng phải loại bỏ các phần tử dƣ thừa bao quanh khung
của nhân vật, chọn đối tƣợng cần loại bỏ và chọn Edit Collider:

Hình 3.11. Điều chỉnh khung Collider của Box và Polygon Collider

81
Ví dụ để player_0 đứng đƣợc trên nền (base) thì tạo Collider cho base nhƣ sau:

Hình 3.12. Sử dụng Box Collider cho đối tƣợng


Đối với các đối tƣợng mà sự va chạm tác động vào tâm, sử dụng kiểu va chạm
là Circle Collider. Trong trƣờng hợp này chọn kiểu va chạm là Circle Collider để
tránh trƣờng hợp nền ví dụ đối tƣợng Ground có độ cao không đều, nhấp nhô dẫn đến
không di chuyển đƣợc nhân vật.

Hình 3.13. Sử dụng Circle Collider cho đối tƣợng


Đối với Circle Collider có các thuộc tính tâm và bán kính để xác định vùng xử
lý va chạm:
- Radius: Bán kính của vùng xử lý va chạm.
- Center: Tâm của vùng xử lý va chạm.
Bƣớc 2. Viết Script xử lý va chạm.
Có nhiều hàm và phƣơng thức đƣợc sử dụng để xử lý va chạm giữa các đối
tƣợng trong không gian 2D và 3D:
Collider 2D:
OnCollisionEnter2D(Collision2D collision): Đƣợc gọi khi có va chạm giữa hai
Collider 2D, dùng để xử lý sự kiện va chạm bắt đầu.

82
OnCollisionStay2D(Collision2D collision): Đƣợc gọi trong suốt quá trình va
chạm giữa hai Collider 2D, thƣờng đƣợc sử dụng để xử lý va chạm liên tục.
OnCollisionExit2D(Collision2D collision): Đƣợc gọi khi va chạm giữa hai
Collider 2D kết thúc, dùng để xử lý sự kiện va chạm kết thúc.
Trigger 2D:
OnTriggerEnter2D(Collider2D other): Đƣợc gọi khi một Collider 2D trigger
bắt đầu xâm nhập vào vùng trigger của Collider 2D khác.
OnTriggerStay2D(Collider2D other): Đƣợc gọi khi Collider 2D trigger tiếp tục
xâm nhập vào vùng trigger của Collider 2D khác.
OnTriggerExit2D(Collider2D other): Đƣợc gọi khi Collider 2D trigger thoát ra
khỏi vùng trigger của Collider 2D khác.
Collider 3D:
OnCollisionEnter(Collision collision): Tƣơng tự nhƣ OnCollisionEnter2D,
nhƣng dành cho Collider 3D.
OnCollisionStay(Collision collision): Tƣơng tự nhƣ OnCollisionStay2D, nhƣng
dành cho Collider 3D.
OnCollisionExit(Collision collision): Tƣơng tự nhƣ OnCollisionExit2D, nhƣng
dành cho Collider 3D.
Trigger 3D:
OnTriggerEnter(Collider other): Tƣơng tự nhƣ OnTriggerEnter2D, nhƣng
dành cho Collider 3D.
OnTriggerStay(Collider other): Tƣơng tự nhƣ OnTriggerStay2D, nhƣng dành
cho Collider 3D.
OnTriggerExit(Collider other): Tƣơng tự nhƣ OnTriggerExit2D, nhƣng dành
cho Collider 3D.
Các hàm va chạm này đƣợc triệu hồi tự động khi có sự kiện va chạm diễn ra
giữa các Collider hoặc Trigger.
Ví dụ 3.1. Xử lý va chạm sử dụng hàm OnCollisionEnter() để phát hiện khi có
va chạm giữa đối tƣợng này và các đối tƣợng khác:
1. using UnityEngine;
2. public class CollisionHandler : MonoBehaviour
3. {
4. private void OnCollisionEnter(Collision collision)
5. {
6. Debug.Log("Có va chạm với: " + collision.gameObject.name);
7. }
8. }
Ví dụ 3.2. Xử lý va chạm giữa nhân vật và đá trong game tránh đá.
1. private void OnCollisionEnter2D(Collision2D target)
2. {
3. if (target.gameObject.CompareTag("Rock"))
4. {
5. BornRock stone = target.gameObject.GetComponent<BornRock>();

83
6. if (stone && !stone.Ground)
7. {
8. audioSource.PlayOneShot(Over, 1f);
9. audioSource.PlayOneShot(soundOver, 1f);
10. heath--;
11. if (heath <= 0)
12. {
13. GameOver();
14. }
15. }
16. else { }
17. }
18. }
Sự va chạm xảy ra làm cho đối tƣợng khác biến mất (ví dụ game chặn bóng) thì
hiệu ứng biến mất là Destroy (phá hủy) và inactive (ngƣng kích hoạt). Destroy sẽ xóa
bỏ toàn bộ các đối tƣợng con và các Component của chúng khỏi Scene. Giải pháp an
toàn hơn là inactive giống nhƣ ta kiểm active trong bảng lệnh Inspector.
Ví dụ 3.3. Xóa đối tƣợng bằng hàm SetActive(false).
1. private void OnTriggerEnter(Collider other)
2. {
3. if (other.gameObject.CompareTag("pick up"));
4. other.gameObject.SetActive(false);
5. }
Khi đối tƣợng hình cầu va chạm làm hình hộp biến mất, Tag của hình hộp là
Pick Up, khi đó gắn other.gameObject.SetActive giá trị false.

Hình 3.14. Tạo Tag và Collider


Bƣớc 3. Xử lý logic va chạm trong Script.
Trong các hàm va chạm của Script, có thể thực hiện nhiều loại xử lý va chạm
khác nhau, ví dụ:
- Kiểm tra tên hoặc tag của đối tƣợng va chạm để xác định xử lý cụ thể.
- Thay đổi thuộc tính của đối tƣợng sau khi va chạm nhƣ thay đổi màu sắc, tạo
hiệu ứng vụn vỡ,…
- Áp dụng lực lên các đối tƣợng để tạo hiệu ứng va chạm.

84
3.3. Đối tƣợng dựng sẵn
Đối tƣợng dựng sẵn (Prefab) là một mẫu hoặc bản sao của một đối tƣợng mà
ngƣời dùng có thể tái sử dụng và tạo nhiều thể hiện từ đó, cho phép tạo ra các bản sao
nhanh của một đối tƣợng mà không cần thiết lập lại các giá trị khởi tạo của một đối
tƣợng nào đó ngoài trừ các giá trị transform (vị trí, tỷ lệ, quay).
Tạo Prefab:
Để tạo một Prefab cho một đối tƣợng nào đó, chỉ cần kéo thả đối tƣợng đó ở
cửa sổ Hierarchy xuống thƣ mục Prefab (nếu chƣa có thì tạo mới) trong cửa sổ
Project. Prefab sẽ chứa tất cả các thành phần, vị trí, hƣớng, và các thông số khác của
đối tƣợng gốc.

Hình 3.15. Prefab cho đối tƣợng Ground


Sử dụng Prefab:
Sau khi tạo Prefab có thể sử dụng để tạo nhiều thể hiện (instances) của đối
tƣợng đó trong Scene. Trong quá trình thực hiện muốn sử dụng lại cần kéo các Prefab
này trở lại cửa sổ Scene. Khi thay đổi Prefab gốc, tất cả các thể hiện trong Scene cũng
sẽ đƣợc cập nhật theo.

Hình 3.16. Prefab cho đối tƣợng Rock

85
Hình 3.17. Kết quả tạo Prefab cho đối tƣợng Ground
3.4. Di chuyển đối tƣợng
3.4.1. Di chuyển bằng bàn phím
Trong Unity, để di chuyển đối tƣợng có thể sử dụng một số hàm và phƣơng
thức khác nhau tùy thuộc vào loại di chuyển và kiểu vận tốc cần áp dụng.
Translate (Dịch chuyển):
Phƣơng thức Transform.Translate(Vector3 translation) cho phép dịch chuyển
đối tƣợng theo một vectơ xác định. Ví dụ:
transform.Translate(Vector3.forward * speed * Time.deltaTime);
Ví dụ 3.4. Minh họa di chuyển đối tƣợng theo phím mũi tên.
1. using UnityEngine;
2. public class PlayerMovement : MonoBehaviour
3. {
4. public float moveSpeed = 5f;
5. // Tốc độ di chuyển
6. void Update()
7. {
8. // Lấy input từ bàn phím
9. float horizontalInput = Input.GetAxis("Horizontal");
10. float verticalInput = Input.GetAxis("Vertical");
11. // Tính vectơ di chuyển
12. Vector3 movement = new Vector3(horizontalInput, 0f,
verticalInput) * moveSpeed * Time.deltaTime;
13. // Cập nhật vị trí của đối tượng
14. transform.Translate(movement);
15. }
16. }

86
Ví dụ 3.5. Minh họa di chuyển một đối tƣợng bằng phƣơng thức
Transform.Translate.
Bƣớc 1. Tạo một đối tƣợng (ví dụ Sphere hoặc Cube) trong Scene.
Tạo một game object bằng cách nhấp chuột phải vào Hierarchy, chọn 3D
Object và chọn Sphere.
Bƣớc 2. Tạo một Script để di chuyển đối tƣợng.
Tạo một Script mới bằng cách nhấp chuột phải vào thƣ mục Assets, chọn
Create, C# Script và đặt tên là MoveObjectWithKeyboard.
Bƣớc 3. Gắn Script vào Sphere.
Kéo và thả Script MoveObjectWithKeyboard lên đối tƣợng Sphere trong
Hierarchy.
Bƣớc 4. Mở Script MoveObjectWithKeyboard và viết mã nguồn.
1. using UnityEngine;
2. public class MoveObjectWithKeyboard : MonoBehaviour
3. {
4. public float moveSpeed = 5f; // Tốc độ di chuyển
5. void Update()
6. {
7. // Lấy input từ bàn phím
8. float horizontalInput = Input.GetAxis("Horizontal");
9. float verticalInput = Input.GetAxis("Vertical");
10. // Tạo vector di chuyển từ input và tốc độ
11. Vector3 movement = new Vector3(horizontalInput, 0f,
verticalInput) * moveSpeed * Time.deltaTime;
12. // Sử dụng phương thức Translate để di chuyển đối tượng
13. transform.Translate(movement);
14. }
15. }
Trong ví dụ trên, đối tƣợng sẽ di chuyển khi ngƣời chơi nhấn các phím mũi tên
hoặc phím W, A, S, D trên bàn phím. Phím mũi tên trái/phải sẽ làm cho đối tƣợng di
chuyển sang trái/phải, và phím mũi tên lên/xuống hoặc phím W/S sẽ làm cho đối
tƣợng di chuyển lên/xuống. Trong ví dụ này, mỗi khung hình sẽ di chuyển một khoảng
nhỏ theo hƣớng trƣớc, tạo ra hiệu ứng di chuyển liên tục.
MovePosition (Di chuyển vị trí):
Phƣơng thức Rigidbody.MovePosition(Vector3 position) đƣợc sử dụng để di
chuyển vị trí của một đối tƣợng có Rigidbody.
Ví dụ:
rigidbody.MovePosition(transform.position + direction * speed *
Time.deltaTime);
Ví dụ 3.6. Minh họa di chuyển đối tƣợng bằng MovePosition.
1. using UnityEngine;
2. public class MoveWithRigidbody : MonoBehaviour
3. {
4. public float moveSpeed = 5f; // Tốc độ di chuyển

87
5. private Rigidbody rb; // Tham chiếu đến Rigidbody của đối tượng
6. private void Start()
7. {
8. rb = GetComponent<Rigidbody>(); // Lấy tham chiếu đến Rigidbody
9. }
10. void Update()
11. {
12. // Lấy input từ bàn phím
13. float horizontalInput = Input.GetAxis("Horizontal");
14. float verticalInput = Input.GetAxis("Vertical");
15. // Tạo vectơ di chuyển từ input và tốc độ
16. Vector3 movement = new Vector3(horizontalInput, 0f,
verticalInput) * moveSpeed * Time.deltaTime;
17. // Tính toán vị trí mới
18. Vector3 newPosition = rb.position + movement;
19. // Di chuyển vị trí của đối tượng bằng MovePosition
20. rb.MovePosition(newPosition);
21. }
22. }
Trong ví dụ này, đối tƣợng sẽ di chuyển khi nhấn các phím mũi tên hoặc phím
WASD. Phƣơng thức Rigidbody.MovePosition() đƣợc sử dụng để di chuyển vị trí của
đối tƣợng với độ chính xác và ổn định hơn so với việc sử dụng Transform.Translate.
AddForce (Thêm lực):
Phƣơng thức Rigidbody.AddForce(Vector3 force) cho phép thêm lực vào đối
tƣợng, gây ra di chuyển dựa trên khối lƣợng và gia tốc. Ví dụ:
rigidbody.AddForce(Vector3.forward * forceAmount);
Ví dụ 3.7. Minh họa di chuyển đối tƣợng bằng AddForce.
1. using UnityEngine;
2. public class MoveWithForce : MonoBehaviour
3. {
4. public float moveSpeed = 5f; // Tốc độ di chuyển
5. public float forceAmount = 10f; // Lượng lực thêm vào
6. private Rigidbody rb; // Tham chiếu đến Rigidbody của đối tượng
7. private void Start()
8. {
9. rb = GetComponent<Rigidbody>(); // Lấy tham chiếu đến Rigidbody
10. }
11. void Update()
12. {
13. // Lấy input từ bàn phím
14. float horizontalInput = Input.GetAxis("Horizontal");
15. float verticalInput = Input.GetAxis("Vertical");
16. // Tạo vectơ di chuyển từ input và tốc độ
17. Vector3 movement = new Vector3(horizontalInput, 0f,
verticalInput) * moveSpeed * Time.deltaTime;
18. // Tính lực từ vectơ di chuyển và lượng lực thêm vào
19. Vector3 force = movement * forceAmount;

88
20. // Thêm lực vào đối tượng
21. rb.AddForce(force);
22. }
23. }
Velocity (Vận tốc):
Có thể thay đổi vận tốc của đối tƣợng bằng cách gán giá trị mới cho thuộc tính
Rigidbody.velocity hoặc Rigidbody2D.velocity để di chuyển đối tƣợng.
Ví dụ 3.8. Minh họa di chuyển đối tƣợng bằng velocity.
1. using UnityEngine;
2. public class MoveWithVelocity : MonoBehaviour
3. {
4. public float moveSpeed = 5f; // Tốc độ di chuyển
5. private Rigidbody rb; // Tham chiếu đến Rigidbody của đối tượng
6. private void Start()
7. {
8. rb = GetComponent<Rigidbody>(); // Lấy tham chiếu đến Rigidbody
9. }
10. void Update()
11. {
12. // Lấy input từ bàn phím
13. float horizontalInput = Input.GetAxis("Horizontal");
14. float verticalInput = Input.GetAxis("Vertical");
15. // Tạo vectơ di chuyển từ input và tốc độ
16. Vector3 movement = new Vector3(horizontalInput, 0f,
verticalInput) * moveSpeed * Time.deltaTime;
17. // Đặt vận tốc mới cho đối tượng
18. rb.velocity = movement;
19. }
20. }
CharacterController:
Nếu sử dụng Character Controller có thể sử dụng các phƣơng thức nhƣ
CharacterController.Move() để di chuyển đối tƣợng. Điều này cho phép thực hiện di
chuyển với kiểu xử lý và vận tốc riêng biệt của CharacterController.
Ví dụ thêm một Character Controller vào Sphere: Chọn Sphere trong
Hierarchy. Trong Inspector, nhấn nút Add Component và tìm kiếm và thêm thành
phần Character Controller.

Hình 3.18. Thêm thành phần Character Controller

89
Hình 3.19. Thành phần Character Controller đƣợc thêm vào Sphere
Ví dụ 3.9. Minh họa di chuyển đối tƣợng bằng Move của CharacterController.
1. using UnityEngine;
2. public class MoveWithCharacterController : MonoBehaviour
3. {
4. public float moveSpeed = 5f; // Tốc độ di chuyển
5. private CharacterController controller; private void Start()
6. {
7. controller = GetComponent<CharacterController>();
8. }
9. void Update()
10. {
11. float horizontalInput = Input.GetAxis("Horizontal");
12. float verticalInput = Input.GetAxis("Vertical");
13. // Tạo vectơ di chuyển từ input và tốc độ
14. Vector3 movement = new Vector3(horizontalInput, 0f,
verticalInput) * moveSpeed * Time.deltaTime;
15. // Di chuyển đối tượng bằng Character Controller
16. controller.Move(movement);
17. }
18. }
Ví dụ 3.10. Minh họa di chuyển đối tƣợng trong game tránh đá.
1. private void PlayerWalk()
2. {
3. float forceX = 0f;
4. float vel = Mathf.Abs(rb.velocity.x);
5. float h = Input.GetAxis("Horizontal");
6. if (h > 0)
7. {
8. if (vel < maxVelocity)
9. {
10. forceX = moveForce;
11. }
12. else

90
13. {
14. forceX = moveForce * 1.1f;
15. }
16. Vector3 scale = transform.localScale;
17. scale.x = 0.5f;
18. transform.localScale = scale;
19. anim.SetBool("run", true);
20. }
21. else if (h < 0)
22. {
23. if (vel < maxVelocity)
24. {
25. forceX = -moveForce;
26. }
27. else
28. {
29. forceX = -moveForce * 1.1f;
30. }
31. Vector3 scale = transform.localScale;
32. scale.x = -0.5f;
33. transform.localScale = scale;
34. anim.SetBool("run", true);
35. }
36. else if (h == 0)
37. {
38. anim.SetBool("run", false);
39. }
40. rb.velocity = new Vector2(forceX, 0.0f);
41. Pause();
42. if (Input.GetKeyDown(KeyCode.Escape))
43. {
44. Application.Quit();
45. }
46. }
Trong đó cần khai báo đối tƣợng rb là Rigidbody2D:
1. private Rigidbody2D rb;
Tại hàm Awake() thực hiện tìm và trả về thành phần Rigidbody2D trên
GameObject:
1. void Awake()
2. {
3. rb = GetComponent<Rigidbody2D>();
4. }
Đây là phƣơng thức của lớp GameObject. GetComponent<Rigidbody2D>()
đƣợc dùng để tìm và trả về thành phần Rigidbody2D nếu có trên GameObject hiện tại
và gán cho biến rb. Sau khi gán thì có thể sử dụng biến rb để thực hiện các thao tác
liên quan đến vật lý và chuyển động của GameObject. Trong hàm PlayerWalk(), biến
h lấy đầu vào từ bàn phím (phím mũi tên hoặc A/D). Nếu h > 0, đối tƣợng sẽ di

91
chuyển sang phải. Nếu vận tốc hiện tại (vel) nhỏ hơn maxVelocity, forceX sẽ đƣợc gán
bằng moveForce. Ngƣợc lại, forceX sẽ đƣợc gán bằng moveForce * 1.1f để tạo ra hiệu
ứng tăng vận tốc khi đạt vận tốc tối đa. Tƣơng tự, nếu h < 0, đối tƣợng sẽ di chuyển
sang trái. Nếu h == 0, đối tƣợng dừng chạy và animation của đối tƣợng sẽ chuyển
sang trạng thái dừng. Cuối cùng, rb.velocity đƣợc cập nhật để thực hiện di chuyển dựa
trên giá trị forceX.
3.4.2. Di chuyển bằng chuột
Di chuyển đối tƣợng bằng chuột gồm bắt sự kiện nhấp chuột và di chuyển. Để
phát hiện và xử lý khi ngƣời dùng nhấp chuột dùng phƣơng thức GetMouseButton()
của lớp Input, cụ thể:
Input.GetMouseButton:
Hàm Input.GetMouseButton() đƣợc sử dụng để kiểm tra xem một nút chuột cụ
thể đang đƣợc giữ trong frame. Hàm trả về true nếu nút chuột đƣợc kiểm tra đang
đƣợc giữ và false nếu ngƣợc lại. Hàm này thƣờng đƣợc sử dụng để kiểm tra các hành
động đang đƣợc thực hiện bởi ngƣời chơi trong khi giữ nút chuột, ví dụ nhƣ kéo thả.
Input.GetMouseButtonDown:
Hàm Input.GetMouseButtonDown() đƣợc sử dụng để kiểm tra xem một nút
chuột cụ thể đã đƣợc nhấn trong frame hiện tại. Hàm trả về true chỉ trong frame đầu
tiên sau khi nút chuột đã đƣợc nhấn và trả về false trong các frame tiếp theo. Hàm này
thƣờng đƣợc sử dụng để xác định sự kiện xảy ra khi ngƣời chơi nhấn nút chuột một
lần, ví dụ nhƣ bắn đạn hoặc thực hiện hành động cụ thể.
Cả hai hàm Input.GetMouseButton() và Input.GetMouseButtonDown() đều chứa
một tham số chính để chỉ định dạng chuột cần kiểm tra. Tham số này là một số nguyên
đại diện cho mã số của chuột. Cụ thể, trong hàm Input.GetMouseButton() và
Input.GetMouseButtonDown(), tham số là:
0: Đại diện cho nút chuột trái.
1: Đại diện cho nút chuột phải.
2: Đại diện cho nút chuột giữa.
Ví dụ 3.11. Minh họa di chuyển bằng chuột.
1. using UnityEngine;
2. public class MoveSphereWithMouse : MonoBehaviour
3. {
4. public float moveSpeed = 15f;
5. void Update()
6. {
7. // Kiểm tra nút chuột trái đã được nhấn
8. if (Input.GetMouseButtonDown(0))
9. {
10. // Di chuyển Sphere lên khi nút chuột trái được nhấn
11. MoveUp();
12. }
13. }

92
14. void MoveUp()
15. {
16. // Tạo vectơ di chuyển lên
17. Vector3 movement = Vector3.up * moveSpeed * Time.deltaTime;
18. // Di chuyển đối tượng theo vector di chuyển
19. transform.Translate(movement);
20. }
21. }
Trong ví dụ này kiểm tra xem nút chuột trái đã đƣợc nhấn bằng cách sử dụng
Input.GetMouseButtonDown(0). Nếu nút chuột trái đã đƣợc nhấn, gọi hàm MoveUp()
để di chuyển Sphere lên. Hàm MoveUp() tạo một vectơ di chuyển lên bằng cách sử
dụng Vector3.up, sau đó sử dụng transform.Translate để di chuyển đối tƣợng Sphere
theo vectơ di chuyển.
3.4.3. Di chuyển trên điện thoại
Trên điện thoại di động, di chuyển thƣờng đƣợc thực hiện bằng cách sử dụng
màn hình cảm ứng và các cách tƣơng tác khác nhƣ cảm biến gia tốc, cảm biến vị trí, và
các thiết bị đặc biệt nhƣ joystick ảo. Một số phƣơng pháp phổ biến để di chuyển trên
điện thoại là:
Màn hình cảm ứng: Trên điện thoại di động có thể di chuyển bằng cách chạm
và kéo ngón tay trên màn hình. Ví dụ trong trò chơi ngƣời chơi thƣờng sẽ vuốt ngón
tay trên màn hình theo hƣớng muốn nhân vật di chuyển.
Cảm biến gia tốc và cảm biến vị trí: Điện thoại di động thƣờng có cảm biến gia
tốc (accelerometer) và cảm biến vị trí (gyroscope) để phát hiện chuyển động và hƣớng
của điện thoại. Trong một số game, ngƣời dùng có thể di chuyển nhân vật bằng cách
nghiêng hoặc xoay điện thoại.
Joystick ảo và các nút điều khiển: Joystick ảo là một hình ảnh trên màn hình
cảm ứng dùng để chạm và kéo để điều khiển nhân vật. Nhiều game di động cung cấp
joystick ảo và các nút điều khiển trên màn hình để ngƣời dùng có thể di chuyển nhân
vật hoặc thao tác trong game.
Kéo và thả: Trong một số game, ngƣời dùng có thể di chuyển nhân vật bằng
cách chạm vào điểm mục tiêu trên màn hình và sau đó kéo ngón tay để di chuyển nhân
vật đến điểm đó.
Các phím cảm ứng: Trong một số game, ngƣời dùng có thể sử dụng các phím
cảm ứng trên màn hình để điều khiển nhân vật, ch ng hạn nhƣ các phím mũi tên ảo.
Ví dụ 3.12. Minh họa di chuyển đối tƣợng trên điện thoại.
1. using UnityEngine;
2. public class SphereMovement : MonoBehaviour
3. {
4. public float speed = 5.0f;
5. void Update()
6. {
7. // Di chuyển khi chạm vào các phím ảo trên màn hình cảm ứng
8. if (Input.touchCount > 0)

93
9. {
10. Touch touch = Input.GetTouch(0);
// Lấy thông tin về lần chạm đầu tiên
11. Vector3 direction = Vector3.zero;
// Khởi tạo hướng di chuyển ban đầu
12. // Kiểm tra xem người dùng đã chạm vào nút bên trái
13. if (touch.position.x < Screen.width / 2)
14. {
15. direction = Vector3.left;
16. }
17. // Kiểm tra xem người dùng đã chạm vào nút bên phải
18. else if (touch.position.x > Screen.width / 2)
19. {
20. direction = Vector3.right;
21. }
22. // Kiểm tra xem người dùng đã chạm vào nút lên
23. else if (touch.position.y > Screen.height / 2)
24. {
25. direction = Vector3.up;
26. }
27. // Di chuyển Sphere theo hướng đã xác định
28. transform.Translate(direction * speed * Time.deltaTime);
29. }
30. }
31. }
3.5. Tạo các diễn hoạt
Diễn hoạt (Animation) đƣợc sử dụng để tạo ra các hiệu ứng chuyển động cho
các đối tƣợng trong game nhƣ một chiếc xe đang chạy, một nhân vật đang đi,... Một
Animation trong Unity có thể bao gồm nhiều hành động, một hành động nhƣ vậy gọi là
một Clip. Ví dụ một nhân vật có thể có các hành động đi, đứng, nhảy, xoay, thay đổi
kích thƣớc và thậm chí áp dụng các hiệu ứng hình ảnh đặc biệt để làm tăng tính tƣơng
tác và hấp dẫn.
Để tạo diễn hoạt, đầu tiên cần chuẩn bị tài nguyên là các file hình ảnh gồm
nhiều frame hình để hỗ trợ cho việc tạo Animation. Trong cửa sổ Inspector hiện ra các
tùy chọn thao tác với file hình ảnh cần đảm bảo ảnh ở chế độ Sprite Mode là Multiple,
chọn Sprite Editor để mở ra cửa sổ Sprite Editor và cắt ảnh nhƣ đã trình bày ở mục
2.1. Bên cạnh file hình ảnh có mũi tên, khi nhấp vào dấu mũi tên này sẽ hiện ra các
frame đã cắt bằng Sprite Editor.

94
Hình 3.20. Kết quả cắt hình ảnh
Để tạo Animation cho đối tƣợng (ví dụ đứng im là player_0) từ Assets và trong
cửa sổ Hierarchy hoặc cửa sổ Scene. Mở Animation bằng cách vào Window, chọn
Animation, Animation hoặc Ctrl+6:

Hình 3.21. Mở hộp thoại Animation


Tiếp theo chọn đối tƣợng player_0 vừa tạo, tại view Animation chọn create. Cửa
sổ Create New Animation hiện ra, đặt lại tên cho Animation để sử dụng sau này và
chọn đƣờng dẫn để lƣu trữ file Animation. Chú ý, nên tạo một thƣ mục Animation đặt
trong thƣ mục Assets sẵn có để tiện lƣu trữ và quản lý.

95
Hình 3.22. Giao diện Create New Animation
Nếu ngƣời dùng chọn nhiều frame hành động của đối tƣợng chuyển động (ví dụ
player_1 đến player_8) thì chỉ cần kéo thả các frame này vào trong cửa sổ Hierarchy
hoặc Scene.

Hình 3.23. Lƣu trữ Animation

Hình 3.24. Thuộc tính Animation của đối tƣợng


Khi đó, chạy thử chƣơng trình sẽ thấy nhân vật đang di chuyển.

96
Hình 3.25. Kết quả thực thi Animation
Chú ý: Chỉ cần tạo một Animation (playerIdel1), để tạo Animation tiếp theo, tại
cửa sổ Animation, chọn Create new Clip… và đặt tên cho Animation (ví dụ
playerrun1).

Hình 3.26. Cửa sổ Create New Clip

Hình 3.27. Lƣu trữ Animation mới

97
Sau đó, tiếp tục kéo player_1 đến player_8 vào Animation:

Hình 3.28. Cửa sổ Preview Animation


Tại Inspector thì trong player có một thuộc tính là Animator đƣợc tạo ra khi
thêm Animation cho đối tƣợng ở trên. Animator dùng để điều khiển các Animation của
đối tƣợng. Vào Window, chọn Animation, Animator để mở Animator:

Hình 3.29. Cửa sổ Animator


Việc tiếp theo là kết nối các Animation này với nhau bằng cách tạo ra Transition
giữa chúng, ví dụ tạo Transition từ playerIdel1 tới playerIRun1 ta chuột phải vào
playerIdel1 và chọn Make Transition rồi giữ chuột kéo từ playerIdel1 tới playerIRun1:

Hình 3.30. Cửa sổ Make Transition


Tại cửa sổ Animator, Entry trỏ tới playerIdel1 mặc định nhân vật đứng im. Để
mặc định là nhân vật chuyển động, nhấp chuột phải vào playerIRun1 và chọn Set as
Layer Default State.
Chú ý: Khi tạo ra các Animation cần thiết lập để chạy một lần hoặc lặp lại, ví dụ
di chuyển thì lặp lại thì chạy lặp lại còn chết thì một lần. Chọn playerIRun1 và tại
Insspector chọn Loop Time có nghĩa là chạy lặp lại còn không chọn là chạy một lần.

98
Hình 3.31. Kết quả Make Transition
Tiếp theo, tạo các tham số để thay đổi trạng thái giữa các Animation, vào
Windows, Animation, Animation Parameter:

Hình 3.32. Cửa sổ Animator Parameter


Nhấn vào nút dấu cộng trong tab Parameter và tạo tham số kiểu bool là run để
xử lý sự kiện nhân vật giữa di chuyển và đứng im:

Hình 3.33. Cửa sổ thiết lập tham số Parameter


Để điều khiển chuyển đổi giữa các Animation, nhấp chuột vào một mũi tên ví dụ
mũi tên từ playerIdel sang playerRun và nhìn sang cột Inspector ở mục conditions ta
chọn giá trị run là false. Điều này có nghĩa là nếu giá trị của run = false thì sẽ chuyển
trạng thái từ di chuyển sang đứng im.

99
Hình 3.34. Cửa sổ Inspector chuyển trạng thái đứng im sang di chuyển
Khi nhấn phím nhân vật chuyển động và khi bỏ phím thì nhân vật đứng im, cần
tạo Transision cho trạng thái đứng im và di chuyển. Khi nhân vật đang đứng im, nhấn
phím thì nhân vật chuyển động do đó trạng thái từ playerIdel sang playerRun nhƣ sau:

Hình 3.35. Cửa sổ Inspector chuyển trạng thái di chuyển sang đứng im
Ví dụ 3.13. Minh họa điều khiển nhân vật khi nhấn các phím mũi tên sang trái,
trang phải thì nhân vật di chuyển theo hƣớng phím nhấn.
Đầu tiên, khai báo đối tƣợng anim của lớp Animator và lấy giá trị trong hàm
Awake():
1. private Animator anim;
2. void Awake()
3. {
4. rb = GetComponent<Rigidbody2D>();
5. anim = GetComponent<Animator>();
6. }
Trong hàm PlayerWalk() thực hiện thiết lập trạng thái của nhân vật thông qua
hàm SetBool:
1. private void PlayerWalk() {
2. anim.SetBool("run", true); }

1. else if (h == 0)
2. {
3. anim.SetBool("run", false);
4. }

100
3.6. Xử lý Particle [5]
Các hiệu ứng thƣờng gặp trong game nhƣ mƣa, tuyết rơi, khói, lửa, hiệu ứng
phép,… sẽ làm cho game sinh động và ấn tƣợng hơn. Các hiệu ứng này đƣợc gọi
chung là hệ thống hạt hay hiệu ứng Particle. Một số khái niệm cơ bản liên quan đến hệ
thống Particle là:
- Particle System: Là thành phần cốt lõi của hệ thống Particle, chứa các thông
số quyết định cách tạo ra và hoạt động của Particle.
- Particle: Đại diện cho những đối tƣợng nhỏ mà hệ thống Particle tạo ra. Điều
này có thể là những điểm nhỏ, những hình dạng đơn giản nhƣ hình cầu hoặc hình thoi.
- Emitter: Là nguồn phát ra của Particle, có thể tạo ra các loại hạt khác nhau và
kiểm soát tốc độ phát ra, hƣớng di chuyển, mật độ,...
- Modules: Các module là các thành phần của hệ thống Particle giúp kiểm soát
và thay đổi các khía cạnh khác nhau của hạt nhƣ hình dạng, màu sắc, sự tƣơng tác,...
Các module phổ biến bao gồm Renderer, Shape, Color over Lifetime, Size over
Lifetime, và Collision.
- Renderer: Kiểm soát cách các Particle đƣợc hiển thị trên màn hình. Ngƣời
dùng có thể chọn giữa các loại Renderer nhƣ Billboard (đối tƣợng luôn hƣớng về
ngƣời chơi), Mesh Renderer (sử dụng mesh tùy chỉnh cho Particle) và Trail Renderer
(hiển thị dấu vết theo sau Particle).
- Lifetime: Đây là thời gian tồn tại của mỗi Particle từ khi đƣợc tạo ra cho đến
khi biến mất khỏi hệ thống.
- Velocity: Tốc độ ban đầu của Particle khi đƣợc tạo ra. Điều này ảnh hƣởng
đến hƣớng và tốc độ di chuyển của Particle.
- Color over Lifetime: Cho phép thay đổi màu sắc của Particle theo thời gian.
- Size over Lifetime: Cho phép Particle thay đổi kích thƣớc của Particle theo
thời gian.
- Collision: Cho phép các Particle tƣơng tác với các đối tƣợng khác trong môi
trƣờng, ch ng hạn nhƣ va chạm với mặt đất hoặc vật thể khác.
Thêm Particle System:
Có một số cách để thêm Particle System vào dự án:
Cách 1. Từ menu GameObject, Effects, Particle System.
Cách 2. Để thêm Particle System vào game object hiện có, chọn game object đó
và trong cửa sổ Inspector, chọn nút Add Component và nhập Particle System vào
trƣờng tìm kiếm. Khi Particle System đã đƣợc thêm vào sẽ có thuộc tính Particle
System trong tab Inspector:

101
Hình 3.36. Thuộc tính Particle System
Thành phần Particle System:
Particle System nhƣ một game object trong cửa sổ Hierarchy. Trong cửa sổ
Scene, các hạt Sprites sinh ra một cách ngẫu nhiên theo hƣớng đi lên. Ngƣời dùng có
thể chọn Particle System này bằng cách chọn trong Scene hoặc sử dụng Hierarchy.

Hình 3.37. Cửa sổ Particle Effect


Particle System có một số cài đặt cơ bản mà ngƣời dùng có thể cấu hình. Ngoài
ra, trong Inspector Particle System gọi là các module (Hình 3.38).

Hình 3.38. Module của Particle System

102
Các module này chứa các cài đặt bổ sung cho Particle System. Các thuộc tính
chính đƣợc mở rộng theo mặc định đƣợc gọi là modules chính. Chọn Emission và
Shape để mở rộng và hiển thị cài đặt (hình 3.39).

Hình 3.39. Cửa sổ Emission


- Duration: Nếu vòng lặp không đƣợc chọn, điều này xác định hạt sẽ phát trong
bao lâu.
- Looping: Xác định xem hạt lặp lại hay chỉ chơi một lần.
- Prewarm: Chỉ đƣợc sử dụng khi bật vòng lặp.
- Start Delay: Độ trễ tính bằng giây trƣớc khi hệ thống hạt bắt đầu phát ra.
- Start Lifetime: Thời gian tồn tại ban đầu tính bằng giây cho các hạt. Hạt bị phá
hủy sau thời gian trôi qua này.
- Start Speed: Tốc độ ban đầu của các hạt. Vận tốc của các phân tử càng lớn thì
hạt sẽ trải rộng hơn.
- 3D Start Size: Thay đổi kích thƣớc bắt đầu từ một hằng số đối với một vectơ.
- Start Size: Kích thƣớc ban đầu của các hạt.
- 3D Start Rotation: Thay đổi Bắt đầu xoay từ một hằng số đến một vectơ.
- Start Rotation: Góc quay ban đầu của vật rất nhỏ.
- Flip Rotation: Đặt giá trị thành 1 sẽ lật góc quay dọc theo trục nằm ngang.
- Start Color: Màu ban đầu của các hạt.
- Gravity Modifier: Chia tỷ lệ tập hợp giá trị Gravity trong cửa sổ Physics
Manager của Unity. Nếu đặt thành 0, trọng lực sẽ bị tắt.
- Simulation Space: Di chuyển các hạt trong Local.
- Simulation Speed: Ảnh hƣởng đến tốc độ mô phỏng của các hạt.
Ngoài ra có thể tác động đến Particle System thông qua mã nguồn. Khi Particle
System đƣợc thêm vào game object, ngƣời dùng có thể truy xuất Particle System:
1. ParticleSystem part = GetComponent<ParticleSystem>();
2. var part = GetComponent<ParticleSystem>();

103
3. part.Play() ;
4. Destroy(gameObject, part.main.duration);
Ví dụ: Tạo hiệu ứng hạt bắn ra khỏi đạn của súng.
Tạo một game object (ví dụ Gun) đại diện cho khẩu súng. Trong Inspector của
Gun, thêm một Particle System bằng cách chọn Component, Effects, Particle System.
Trong Inspector của Particle System, thiết lập các thuộc tính sau:
Main module:
- Lifetime: Thiết lập thời gian sống của hạt, ví dụ 2 giây.
- Start Speed: Thiết lập tốc độ ban đầu của hạt, ví dụ giá trị 10.
Emission Module: Rate over Time là thiết lập tốc độ phát ra hạt, ví dụ 100
hạt/giây.
Shape Module: Shape để chọn hình dạng phát ra hạt, ví dụ Sphere.
Renderer Module: Material để chọn vật liệu cho hạt, ví dụ một vật liệu dùng
cho hạt bắn.
3.7. Sound [6]
Unity sử dụng đối tƣợng Audio để tạo ra âm thanh trong game. Unity cung cấp
nhiều công cụ và tính năng để quản lý và điều khiển âm thanh:
- Audio Source: Là thành phần thêm vào các đối tƣợng trong game để phát lại
âm thanh. Mỗi Audio Source có thể chứa một âm thanh cụ thể.
- Audio Clip: Là tệp âm thanh thực tế muốn phát lại. Unity import các tệp âm
thanh dƣới dạng Audio Clip. Để sử dụng Audio Clip trong Scene, trƣớc tiên phải đính
kèm vào game object. Một game object có âm thanh kèm theo là Audio Source. Âm
thanh của Audio Source đƣợc thiết lập bởi Audio Listener (micro), thƣờng đƣợc gắn
vào camera chính và phát qua loa của máy tính. Âm thanh có thể đƣợc sửa đổi bằng
Audio Effects (hình 3.40).

Hình 3.40. Minh họa Audio

104
Unity hỗ trợ các định dạng tệp AIFF, WAV, MP3, Ogg, âm thanh đơn âm, âm
thanh nổi và đa kênh với tối đa 8 kênh truyền hình. Âm thanh cũng có thể đƣợc ghi
trực tiếp trong quá trình chơi game bằng micro kết nối với ngƣời dùng máy tính.
- Audio Listener: Là thành phần đƣợc đặt trên một đối tƣợng trong cảnh để
nghe âm thanh từ các nguồn khác nhau và chuyển chúng thành âm thanh mà ngƣời
chơi có thể nghe thấy.
Trong Unity, tệp âm thanh có thể đƣợc lƣu ở các tần số khác nhau. Mức thấp
nhất là 11025Hz và 22050Hz hoặc 48000 là tần số lý tƣởng để sử dụng. Tần số cao
hơn sẽ dẫn đến các tệp lớn hơn.
Một số định dạng âm thanh đƣợc hỗ trợ đƣợc thể hiện trong bảng sau:
Bảng 3.3. Định dạng âm thanh đƣợc hỗ trợ trong Audio
Format Phần mở rộng

MPEG Layer 3 .mp3

OGG Vorbis .ogg

Microsoft Wave .wav

Audio Interchange File Format .aiff / .aif

Ultimate Soundtracker Module .mod

Impulse Tracker Modele .it

Scream Tracker Module .s3m

Fast Tracker 2 Module .xm


Các bƣớc sử dụng Audio trong Unity nhƣ sau:
Bƣớc 1. Chuẩn bị file âm thanh.
Bƣớc 2. Import tệp âm thanh vào Project Unity.
Tập tin âm thanh có thể đƣợc import vào một dự án giống nhƣ các tệp khác
hoặc kéo tệp âm thanh vào bảng Project hoặc đặt tệp âm thanh tập tin trong thƣ mục
Assets của thƣ mục Project Unity. Việc import tệp âm thanh sẽ tạo một vùng chứa cho
dữ liệu âm thanh gọi là Audio Clip.
Trong bảng Project, chọn nội dung âm thanh đã import. Trong bảng Inspector,
thay đổi các cài đặt nếu cần thiết. Ví dụ, nếu tệp âm thanh có hai bản nhạc cho âm
thanh nổi thì có thể chọn Force to Mono nếu tách âm thanh nổi cần thiết. Ngoài ra còn
có các cài đặt dành riêng cho nền tảng cho từng kiểu xây dựng.

105
Hình 3.41. Thuộc tính của Audio trong Inspector
Ở cuối bảng Inspector là dạng sóng xem trƣớc tệp âm thanh đã import. Ngƣời
dùng có thể xem trƣớc âm thanh bằng cách nhấn play (hình 3.42).

Hình 3.42. Cửa sổ xem trƣớc âm thanh


Bƣớc 3. Thêm thành phần Audio Source (nguồn âm thanh).
Thành phần nguồn âm thanh phát lại một Audio Clip trong một Scene. Để chơi,
thành phần này cần chỉ định Audio Clip. Audio Clip là tập tin âm thanh thực tế đƣợc
phát lại. Nguồn là bộ điều khiển cho bắt đầu và dừng phát lại tệp âm thanh và sửa đổi
các thuộc tính âm thanh khác (hình 3.43).

106
Hình 3.43. Cửa sổ Audio Source
Để thêm thành phần Audio Source: Chọn menu GameObject, Audio, Audio
Source. Khi đó sẽ tạo game object trong Scene với Audio Source chứa thành phần đính
kèm.
Các tính năng chính gồm [7]:
- AudioClip: File âm thanh sẽ đƣợc gán vào AudioClip để xử lý và phát âm
thanh ra các thiết bị output.
- Play On Awake: Tùy chọn cho phép âm thanh đƣợc phát ngay khi game
object đƣợc kích hoạt.
- Loop: Tùy chọn lặp lại liên tục âm thanh sau khi đã phát hết.
- Mute: Tùy chọn ngắt kết nối với các thiết bị output âm thanh. Âm thanh sẽ
vẫn chạy bình thƣờng nhƣng sẽ không đƣợc phát ra.
Gán file âm thanh đã import trƣớc đó vào thuộc tính Audio Clip của thành phần
Audio Source trong Inspector. Ngoài ra, ngƣời dùng có thể thực hiện việc này bằng
cách kéo Audio Clip từ Project vào thuộc tính Audio Clip hoặc nhấp vào nút radio (nút
bên cạnh trƣờng thuộc tính) và chọn Audio Clip từ cửa sổ Assets (hình 3.44).

Hình 3.44. Các thuộc tính của Audio Source

107
Điều chỉnh cài đặt Audio Clip cho game object:
Điều chỉnh các thông số Audio Source để thay đổi cách thức và khi âm thanh
đƣợc phát. Tùy chọn Play On Awake đang bật mặc định và sẽ phát Audio Clip ngay
khi Scene bắt đầu. Nếu Audio Clip cần phát trong một khoảng hành động cụ thể, ví dụ
bƣớc chân của nhân vật đang bƣớc đi, nhấp vào Play On Away. Tùy chọn lặp sẽ phát
âm thanh lặp đi lặp lại cho đến khi hành động dừng lại.
Thêm thành phần Audio Listener:
Trong thế giới thực, âm thanh đƣợc truyền đi nhờ sự rung động của một vật thể
và đƣợc ngƣời nghe tiếp nhận. Một nguồn âm thanh chuyển động nhanh (nhƣ quả bom
rơi) sẽ thay đổi cao độ khi di chuyển. Để thêm thành phần Audio Listener cần thực
hiện nhƣ sau: Tạo game object, ví dụ Camera hoặc nhân vật và nhấp vào Add
Component trong Inspector. Chọn Audio, Audio Listener. Khi đó sẽ thêm một Audio
Listener của game object (hình 3.45).

Hình 3.45. Thêm Audio Listener


Thành phần game object của Audio Listener không có thuộc tính (hình 3.46) mà
mặc định là Main Camera. Audio Listener cũng có thể đƣợc gắn vào game object đại
diện cho nhân vật để âm thanh sẽ phát dựa trên vị trí của nhân vật chứ không phải của
Camera. Tuy nhiên, một Scene chỉ có thể có một thành phần Audio Listener gán với
một đối tƣợng.

Hình 3.46. Kết quả thêm Audio Listener

108
Hình 3.47. Cửa sổ thiết lập Audio Listener
Xử lý và phát âm thanh với Script:
Đầu tiên, để thao tác đƣợc với Audio Source cần lấy một thành phần
AudioSource từ đối tƣợng hiện tại:
m_audio = GetComponent<AudioSource>();
Trong đó, m_audio là một biến đƣợc khai báo trƣớc đó trong mã nguồn để lƣu
trữ tham chiếu đến thành phần AudioSource.
Thay đổi file âm thanh:
File âm thanh sẽ đƣợc lƣu trữ dƣới biến clip. Do đó để thay đổi file âm thanh
khác, gán file đó vào biến này. Thao tác nhƣ sau:
m_audio.clip = yourAudioFile;
Chú ý, yourAudioFile phải đƣợc load lên bộ nhớ trƣớc khi thực hiện thay đổi.
Điều khiển bật hoặc tắt nhạc:
Để điều khiển bật hoặc tắt nhạc theo nhu cầu của ngƣời dùng cần sử dụng các
hàm: Play(), PlayOneShot(), Pause(), Stop(). Các này sẽ thay đổi giá trị của
biến isPlaying để thể hiện đƣợc trạng thái của AudioSource.
- AudioSource.Play() và AudioSource.PlayOneShot() đều là phƣơng thức trong
Unity để phát âm thanh từ một AudioSource, nhƣng chúng có mục đích và cách sử
dụng khác nhau.
- AudioSource.Play(): Phát âm thanh từ đầu đến cuối, kiểm soát chính xác việc
phát và quản lý tình trạng phát. Hàm này thích hợp khi cần kiểm soát chính xác việc
phát âm thanh, nhƣ thay đổi âm lƣợng theo thời gian, dừng phát, lặp lại và theo dõi
tình trạng phát âm thanh.
- PlayOneShot: Phát âm thanh một lần duy nhất mà không cần kiểm soát chính
xác tình trạng phát. Phù hợp cho việc phát các âm thanh ngắn, nhƣ âm thanh hiệu ứng
và âm thanh giao diện ngƣời dùng. Đơn giản và tiện lợi khi chỉ cần phát âm thanh một
lần và không cần quản lý tình trạng phát sau đó.
- Pause(): Dừng chơi nhạc và lƣu trữ trạng thái hiện tại để có thể resume.
- Stop(): Dừng hoàn toàn và di chuyển về vị trí đầu của file âm thanh.

109
- isPlaying: Biến này có tác dụng nhƣ một cờ đánh dấu để AudioSource có thể
kiểm tra và bật hoặc tắt âm thanh. Để kiểm tra giá trị của biến sử dụng
hàm Debug.Log() nhƣ sau:
Debug.Log(m_audio.isPlaying);
- mute: Dùng để bật hoặc tắt âm thanh phát ra các thiết bị output.
Ví dụ 3.14. Minh họa tạo Audio cho ảnh.

Hình 3.48. Minh họa phát âm thanh thông qua đối tƣợng là hình ảnh
Khi nhấn Space để phát âm thanh và nhấn ESC để dừng phát âm thanh. Khi đó,
Script gắn với STDIO_Logo nhƣ sau:
1. using UnityEngine;
2. using System.Collections;
3. public class PlaySoundLogo : MonoBehaviour
4. {
5. public AudioClip m_soundLogo;
6. private AudioSource m_audio;
7. void Start ()
8. {
9. m_audio = GetComponent<AudioSource>();
10. m_audio.clip = m_soundLogo;
11. }
12. void Update ()
13. {
14. if(Input.GetKeyDown(KeyCode.Space) && !m_audio.isPlaying)
15. m_audio.Play();
16. if (Input.GetKeyDown(KeyCode.Escape))
17. m_audio.Pause();
18. }
19. }
Trong đó:
public AudioClip m_soundLogo: Biến public kiểu AudioClip đƣợc sử dụng để
lƣu trữ âm thanh muốn phát.
private AudioSource m_audio: Biến private kiểu AudioSource lƣu trữ thành
phần AudioSource đƣợc gắn vào cùng đối tƣợng chứa Script.

110
m_audio = GetComponent<AudioSource>(): Tìm và gán thành phần
AudioSource từ đối tƣợng chứa Script vào biến m_audio.
m_audio.clip = m_soundLogo: Gán âm thanh m_soundLogo cho thành phần
AudioSource, sẵn sàng để phát.
m_audio.Play(): Phát âm thanh đƣợc lƣu trữ trong m_soundLogo.
m_audio.Pause(): Tạm dừng phát âm thanh.
Ví dụ 3.15. Minh họa tạo Audio cho nền và va chạm.

Hình 3.49. Minh họa tạo âm thanh nền và âm thanh va chạm


1. public AudioClip run;
2. public AudioClip Over;
3. private AudioSource audioSource;
4. public AudioClip soundOver;
Trong hàm Start():
1. audioSource = GetComponent<AudioSource>();
Trong hàm va chạm:
1. audioSource.PlayOneShot(Over, 1f);
2. audioSource.PlayOneShot(soundOver, 1f);
Tại đối tƣợng player_0 đã tạo diễn hoạt ở phần trƣớc đƣợc gắn thành phần
Audio nhƣ sau:

Hình 3.50. Kết quả gắn Audio cho player_0


Trong đó: Over, soundOver là tham chiếu đến tệp âm thanh muốn phát. Biến
Over đại diện cho tệp âm thanh cụ thể. Chú ý: Tệp âm thanh này đã đƣợc gán cho
AudioSource hoặc là một biến tham chiếu đến tệp âm thanh đó. Giá trị 1f là tham số
dùng để chỉ định âm lƣợng khi phát âm thanh nghĩa là phát âm thanh với âm lƣợng tối
đa (100%).

111
3.8. Design Pattern trong game
Design Pattern là các kiểu thiết kế đƣợc sử dụng để cấu trúc và tổ chức mã
nguồn một cách có tổ chức hơn, dễ quản lý và dễ mở rộng. Mặc định Unity đã triển
khai một số Design Pattern sau:
- Game Loop: Cốt lõi của mọi game là một vòng lặp vô hạn hoạt động độc lập
với clock speed (tốc độ xung nhịp của CPU), vì phần cứng cung cấp power cho game
thay đổi rất nhiều. Để tính toán cho các thiết bị có tốc độ khác nhau các nhà phát triển
thƣờng cần sử dụng fixed timestep (với số frame cố định mỗi giây) và một biến
timestep có thể thay đổi để đo xem thời gian đã trôi qua kể từ frame trƣớc đó
(deltaTime). Unity đã tính toán sẵn điều này, ngƣời dùng chỉ cần quản lý gameplay
bằng cách sử dụng các event function trong MonoBehaviour.
- Update(): Cập nhật hành vi của từng đối tƣợng một frame tại một thời điểm
thông qua các phƣơng thức Update() có trong MonoBehaviour.
- Prototype: Giải quyết vấn đề sao chép và nhân bản thành các đối tƣợng khác
giống với đối tƣợng hiện tại. Bằng cách này tránh đƣợc việc tạo ra class riêng biệt để
sinh ra mọi loại object trong game. Unity Prefab system là một triển khai của
prototype cho game object.
- Component: Thay vì tạo các class lớn đảm nhiệm nhiều trách nhiệm,
Component pattern xây dựng các class nhỏ hơn với mỗi Component đảm nhiệm một
nhiệm vụ riêng.
Sau đây là một số Design Pattern thƣờng đƣợc sử dụng trong Unity:
- Singleton Pattern: Đảm bảo rằng một lớp chỉ có một instance (thể hiện) duy
nhất và cung cấp cách tiếp cận toàn cục. Thƣờng đƣợc sử dụng cho các tài nguyên
chung nhƣ trình quản lý trạng thái, trình quản lý âm thanh,…

Hình 3.51. Minh họa Singleton Pattern


Pattern này thực sự hữu ích khi cần có chính xác một đối tƣợng quản lý, điều
phối trên toàn bộ Scene. Ví dụ Gamemanager quản lý toàn bộ game loop trong game,
DataManager quản lý lƣu trữ cũng nhƣ đọc dữ liệu.
Ví dụ 3.16. Minh họa trình quản lý âm thanh để điều khiển âm thanh trong
game dùng Singleton Pattern.
Bƣớc 1. Tạo lớp Singleton AudioManager.

112
1. using UnityEngine;
2. public class AudioManager : MonoBehaviour
3. {
4. private static AudioManager _instance;
5. public static AudioManager Instance
6. {
7. get
8. {
9. if (_instance == null)
10. {
11. // Tạo thể hiện nếu chưa có
12. _instance = FindObjectOfType<AudioManager>();
13. // Nếu không tìm thấy, tạo một GameObject mới chứa AudioManager
14. if (_instance == null)
15. {
16. GameObject singleton = new GameObject(
"AudioManagerSingleton");
17. _instance = singleton.AddComponent<AudioManager>();
18. }
19. }
20. return _instance;
21. }
22. }
23. private void Awake()
24. {
25. // Đảm bảo rằng chỉ có một thể hiện duy nhất
26. if (_instance != null && _instance != this)
27. {
28. Destroy(gameObject);
29. }
30. else
31. {
32. _instance = this;
33. DontDestroyOnLoad(gameObject); // Giữ thể hiện khi chuyển
34. }
35. }
36. public void PlaySound(string soundName)
37. {
38. // Thực hiện logic phát âm thanh
39. }
40. public void StopSound(string soundName)
41. {
42. // Thực hiện logic dừng âm thanh
43. }
44. }
Bƣớc 2. Sử dụng AudioManager.
Khi đã triển khai lớp Singleton AudioManager, ngƣời dùng có thể truy cập từ
bất kỳ đâu trong mã nguồn.

113
Ví dụ phát âm thanh gọi AudioManager.Instance.PlaySound("soundName");
Dừng âm thanh gọi AudioManager.Instance.StopSound("soundName");
- Observer Pattern: Cho phép các đối tƣợng theo dõi và tự động cập nhật khi
trạng thái của một đối tƣợng khác thay đổi. Thƣờng đƣợc sử dụng trong các trƣờng
hợp nhƣ sự kiện trong game. Khi một đối tƣợng thay đổi trạng thái tất cả các phụ
thuộc (dependency) sẽ nhận đƣợc thông báo tự động.

Hình 3.52. Minh họa Observer Pattern


Mô hình này tách biệt subject và observer, subject không quan tâm đến
observer nhận đƣợc thông báo (signal). Trong khi observer phụ thuộc vào subject, các
observer độc lập với nhau.
Ví dụ 3.17. Minh họa quản lý sự kiện khi nhân vật thay đổi trạng thái và các
đối tƣợng khác muốn đƣợc thông báo về những thay đổi này dùng Observer Pattern.
Bƣớc 1. Tạo lớp Observer và Subject.
1. using System;
2. using System.Collections.Generic;
3. using UnityEngine;
4. public interface IObserver
5. {
6. void OnNotify(string eventName, object eventData);
7. }
8. // Lớp Subject (đối tượng được theo dõi)
9. public class Subject
10. {
11. private List<IObserver> observers = new List<IObserver>();
12. public void Attach(IObserver observer)
13. {
14. observers.Add(observer);
15. }
16. public void Detach(IObserver observer)
17. {
18. observers.Remove(observer);
19. }
20. public void Notify(string eventName, object eventData)
21. {
22. foreach (var observer in observers)
23. {

114
24. observer.OnNotify(eventName, eventData);
25. }
26. }
27. }
Bƣớc 2. Sử dụng Observer Pattern.
Trong ví dụ này giả định có một nhân vật trong game và muốn các đối tƣợng
khác theo dõi trạng thái của nhân vật. Lớp để theo dõi trạng thái của nhân vật nhƣ sau:
1. public class CharacterObserver : MonoBehaviour, IObserver
2. {
3. private void Start()
4. {
5. // Gắn bản thân vào danh sách người quan sát của nhân vật
6. Character.Instance.Attach(this);
7. }
8. public void OnNotify(string eventName, object eventData)
9. {
10. if (eventName == "CharacterStateChanged")
11. {
12. // Xử lý sự kiện khi trạng thái của nhân vật thay đổi
13. Debug.Log("Character state: " + eventData.ToString());
14. }
15. }
16. }
Lớp đại diện cho nhân vật nhƣ sau:
1. public class Character : Subject
2. {
3. private static Character _instance;
4. public static Character Instance
5. {
6. get
7. {
8. if (_instance == null) {
9. _instance = new Character();
10. }
11. return _instance;
12. }
13. }
14. private string _currentState;
15. public string CurrentState {
16. get { return _currentState; }
17. set
18. {
19. _currentState = value;
20. // Khi trạng thái thay đổi, thông báo cho các người quan sát
21. Notify("CharacterStateChanged", _currentState);
22. }
23. }
24. }

115
Trong ví dụ trên, lớp Character là đối tƣợng đƣợc theo dõi, và lớp
CharacterObserver là ngƣời quan sát. Khi trạng thái của nhân vật thay đổi, thông báo
sẽ đƣợc gửi tới các ngƣời quan sát để xử lý. Việc này giúp tạo một cơ chế linh hoạt để
các thành phần khác trong game có thể tự động phản ánh sự thay đổi trong trạng thái
của nhân vật mà không cần phải kiểm tra liên tục.
- Factory Pattern: Tạo ra các đối tƣợng mà không cần biết chi tiết cụ thể về
cách chúng đƣợc tạo. Có thể sử dụng để tạo ra các đối tƣợng game hoặc các thành
phần trong game.
Ví dụ 3.18. Minh họa tạo ra các đối tƣợng vũ khí trong game khác nhau mà
không cần biết cụ thể về cách chúng đƣợc tạo dùng Factory Pattern.
Bƣớc 1. Tạo lớp Factory và các lớp con của vũ khí.
1. using UnityEngine;
2. public abstract class Weapon
3. {
4. public abstract void Attack();
5. }
6. public class Sword : Weapon
7. {
8. public override void Attack()
9. {
10. Debug.Log("Swinging a sword!");
11. }
12. }
13. public class Bow : Weapon
14. {
15. public override void Attack()
16. {
17. Debug.Log("Firing an arrow from a bow!");
18. }
19. }
20. public class Axe : Weapon
21. {
22. public override void Attack()
23. {
24. Debug.Log("Chopping with an axe!");
25. }
26. }
27. public static class WeaponFactory
28. {
29. public static Weapon CreateWeapon(string weaponType)
30. {
31. switch (weaponType)
32. {
33. case "Sword":
34. return new Sword();
35. case "Bow":

116
36. return new Bow();
37. case "Axe":
38. return new Axe();
39. default:
40. Debug.LogError("Unknown weapon type: " + weaponType);
41. return null;
42. }
43. }
44. }
Bƣớc 2. Sử dụng Factory để tạo đối tƣợng vũ khí.
Sau khi triển khai Factory và các lớp vũ khí, tạo ra đối tƣợng vũ khí nhƣ sau:
1. public class GameManager : MonoBehaviour
2. {
3. private void Start()
4. {
5. Weapon sword = WeaponFactory.CreateWeapon("Sword");
6. Weapon bow = WeaponFactory.CreateWeapon("Bow");
7. Weapon axe = WeaponFactory.CreateWeapon("Axe");
8. sword.Attack();
9. bow.Attack();
10. axe.Attack();
11. }
12. }
Trong ví dụ này, Factory Pattern cho phép tạo ra các đối tƣợng vũ khí mà
không cần biết cụ thể về cách chúng đƣợc tạo. Thay vì gọi trực tiếp các constructor
của lớp vũ khí, gọi phƣơng thức CreateWeapon của Factory để tạo ra các đối tƣợng
tƣơng ứng.
Ví dụ 3.19. Minh họa quản lý hệ thống hoạt động của một nhân vật trong game,
bao gồm khả năng hoàn tác và làm lại các hành động dùng Command Pattern.
Bƣớc 1. Tạo lớp Command và các hành động.
1. using UnityEngine;
2. // Lớp cơ sở cho tất cả các lệnh
3. public interface ICommand
4. {
5. void Execute();
6. void Undo();
7. }
8. // Các lớp đại diện cho các hành động cụ thể
9. public class JumpCommand : ICommand
10. {
11. private Character character;
12. public JumpCommand(Character character)
13. {
14. this.character = character;
15. }

117
16. public void Execute()
17. {
18. character.Jump();
19. }
20. public void Undo()
21. {
22. character.CancelJump();
23. }
24. }
25. public class AttackCommand : ICommand
26. {
27. private Character character;
28.
29. public AttackCommand(Character character)
30. {
31. this.character = character;
32. }
33. public void Execute()
34. {
35. character.Attack();
36. }
37. public void Undo()
38. {
39. character.CancelAttack();
40. }
41. }
Bƣớc 2. Sử dụng Command Pattern.
Trong ví dụ này giả sử có một nhân vật và muốn biến các hành động nhƣ nhảy
và tấn công của ngƣời dùng thành các đối tƣợng lệnh để quản lý và thực thi.
1. public class Character : MonoBehaviour {
2. private ICommand currentCommand;
3. public void ExecuteCommand(ICommand command)
4. {
5. currentCommand = command;
6. command.Execute();
7. }
8. public void UndoCommand()
9. {
10. if (currentCommand != null)
11. {
12. currentCommand.Undo();
13. currentCommand = null;
14. }
15. }
16. public void Jump()
17. {
18. Debug.Log("Jumping");
19. }

118
20. public void CancelJump()
21. {
22. Debug.Log("Jump cancelled");
23. }
24. public void Attack()
25. {
26. Debug.Log("Attacking");
27. }
28. public void CancelAttack()
29. {
30. Debug.Log("Attack cancelled");
31. }
32. }
33. public class GameManager : MonoBehaviour
34. {
35. private void Start()
36. {
37. Character character = new Character();
38. ICommand jumpCommand = new JumpCommand(character);
39. ICommand attackCommand = new AttackCommand(character);
40. character.ExecuteCommand(jumpCommand);
41. character.UndoCommand();
42. character.ExecuteCommand(attackCommand);
43. character.UndoCommand();
44. }
45. }
- Component Pattern: Cho phép tạo các đối tƣợng phức tạp bằng cách kết hợp
các thành phần nhỏ hơn lại với nhau ví dụ MonoBehaviour trong Unity. Ví dụ tạo một
đối tƣợng nhân vật trong Unity bằng cách kết hợp các thành phần nhƣ Transform,
Renderer và CharacterController.
Bƣớc 1. Tạo game object rỗng.
Đầu tiên, tạo một game object rỗng trong Unity và đặt tên ví dụ là Player.
Bƣớc 2. Thêm các thành phần nhƣ Component cho đối tƣợng Player vừa tạo.
Thêm các thành phần nhƣ Transform, Renderer và CharacterController. Mỗi
thành phần này sẽ đại diện cho một khía cạnh cụ thể của nhân vật.
Bƣớc 3. Kịch bản sử dụng các Component.
Ví dụ 3.20. Minh họa kết hợp các thành phần dùng Component Pattern.
1. using UnityEngine;
2. public class PlayerController : MonoBehaviour
3. {
4. private Transform playerTransform;
5. private Renderer playerRenderer;
6. private CharacterController characterController;
7. private void Start()
8. {
9. playerTransform = GetComponent<Transform>();
10. playerRenderer = GetComponent<Renderer>();

119
11. characterController = GetComponent<CharacterController>();
12. MovePlayer();
13. RotatePlayer();
14. RenderPlayer();
15. }
16. private void MovePlayer()
17. {
18. Vector3 moveDirection = new Vector3(0f, 0f, 1f);
19. characterController.Move(moveDirection * Time.deltaTime);
20. }
21. private void RotatePlayer()
22. {
23. playerTransform.Rotate(Vector3.up, 90f * Time.deltaTime);
24. }
25. private void RenderPlayer()
26. {
27. playerRenderer.material.color = Color.red;
28. }
29. }
- State Pattern: Cho phép một đối tƣợng thay đổi hành vi khi trạng thái nội bộ
thay đổi. Thƣờng đƣợc sử dụng trong việc quản lý trạng thái của nhân vật hoặc đối
tƣợng trong game.
Ví dụ 3.21. Minh họa quản lý trạng thái của một nhân vật trong game ở trong
trạng thái bình thường, tấn công và nhảy.
Bƣớc 1. Tạo lớp State và các trạng thái con.
1. using UnityEngine;
2. public abstract class CharacterState
3. {
4. protected Character character;
5. public CharacterState(Character character)
6. {
7. this.character = character;
8. }
9. public abstract void Enter();
10. public abstract void Update();
11. public abstract void Exit();
12. }
13. public class NormalState : CharacterState
14. {
15. public NormalState(Character character) : base(character) { }
16. public override void Enter()
17. {
18. Debug.Log("Entering Normal State");
19. }
20. public override void Update()
21. {
22. // Logic xử lý khi ở trạng thái bình thường
23. }

120
24. public override void Exit()
25. {
26. Debug.Log("Exiting Normal State");
27. }
28. }
29. public class AttackState : CharacterState
30. {
31. public AttackState(Character character) : base(character) { }
32. public override void Enter()
33. {
34. Debug.Log("Entering Attack State");
35. }
36. public override void Update()
37. {
38. // Logic xử lý khi ở trạng thái tấn công
39. }
40. public override void Exit()
41. {
42. Debug.Log("Exiting Attack State");
43. }
44. }
45. public class JumpState : CharacterState
46. {
47. public JumpState(Character character) : base(character) { }
48. public override void Enter()
49. {
50. Debug.Log("Entering Jump State");
51. }
52. public override void Update()
53. {
54. // Logic xử lý khi ở trạng thái nhảy
55. }
56. public override void Exit()
57. {
58. Debug.Log("Exiting Jump State");
59. }
60. }
Bƣớc 2. Sử dụng State Pattern.
Trong ví dụ này giả sử có một lớp Character để quản lý trạng thái của nhân vật.
1. public class Character : MonoBehaviour {
2. private CharacterState currentState;
3. private void Start()
4. {
5. currentState = new NormalState(this);
6. currentState.Enter();
7. }
8. private void Update()
9. {
10. currentState.Update();

121
11. if (/* Điều kiện để chuyển trạng thái tấn công */)
12. {
13. ChangeState(new AttackState(this));
14. }
15. else if (/* Điều kiện để chuyển trạng thái nhảy */)
16. {
17. ChangeState(new JumpState(this));
18. }
19. }
20. private void ChangeState(CharacterState newState)
21. {
22. currentState.Exit();
23. currentState = newState;
24. currentState.Enter();
25. }
26. }
Trong ví dụ này sử dụng State Pattern để quản lý trạng thái của một nhân vật
trong game. Các lớp trạng thái đại diện cho các hành vi cụ thể của nhân vật và cho phép
nhân vật thay đổi hành vi của mình khi trạng thái nội bộ thay đổi. Mỗi lần Update(),
kiểm tra điều kiện để xem liệu có cần chuyển trạng thái hay không và thực hiện việc
chuyển trạng thái thông qua phƣơng thức ChangeState().
- Object Pool Pattern: Trong Unity, để sinh ra một phiên bản copy của một
game object (thƣờng là prefab) sử dụng hàm Instantiate(), nếu không sử dụng bản copy
đó nữa sử dụng hàm Destroy() hay DestroyImmediate().
Hàm Instantiate() sẽ phân bổ, cấp phát tài nguyên bộ nhớ cho game object đƣợc
sao chép đó, hàm Destroy() xóa bỏ các tài nguyên đã cấp phát và nếu việc này xảy ra
liên tục, Garbage Collector (GC) sẽ hoạt động liên tục làm phân mảnh bộ nhớ đồng
thời trong frame đó phải chờ GC xử lý xong.
Giả sử 10 khẩu súng có hàng trăm viên đạn nhƣ bắn liên tục, các viên đạn sẽ
đƣợc sinh ra và xóa đi trong bộ nhớ liên tục nhƣ vậy sẽ ảnh hƣởng lớn tới quá trình
thực thi và công việc Garbage Collector trở nên phức tạp. Object Pool Pattern là một
Design Pattern đƣợc sử dụng để tối ƣu hóa việc tạo và quản lý các đối tƣợng. Mục tiêu
chính của Object Pool Pattern là giảm tải về hiệu năng khi cần tạo và hủy các đối
tƣợng liên tục. Khi cần sử dụng nhiều đối tƣợng có cùng cấu trúc và chức năng, việc
tạo mới chúng mỗi khi cần sử dụng và hủy khi không cần có thể gây ra lãng phí tài
nguyên, đặc biệt là trong các tình huống đòi hỏi hiệu suất cao nhƣ game và ứng dụng
thời gian thực. Object Pool Pattern giúp tiết kiệm tài nguyên bằng cách tái sử dụng
các đối tƣợng đã đƣợc tạo trƣớc đó thay vì tạo mới.
Nguyên tắc hoạt động của Object Pool Pattern nhƣ sau: Ban đầu, một số cố
định các đối tƣợng đƣợc tạo và đƣợc đặt trong một Pool. Khi cần sử dụng một đối
tƣợng, lấy đối tƣợng từ Pool (nếu có sẵn) thay vì tạo mới. Khi không còn sử dụng đối
tƣợng nữa, thay vì hủy nó thì đƣa nó trở lại Pool để có thể tái sử dụng. Object Pool
Pattern giúp giảm độ trễ do việc tạo và hủy đối tƣợng, cải thiện hiệu suất và giúp tiết

122
kiệm tài nguyên hệ thống. Pattern này thƣờng đƣợc sử dụng trong các tình huống mà
việc tạo và hủy đối tƣợng có giá trị thời gian và tài nguyên cao, ví dụ nhƣ quản lý đạn
trong game, các kết nối đến cơ sở dữ liệu,… Nhƣ vậy, thay vì Instantiate() các objects,
ngƣời dùng chỉ việc lấy chúng từ Object Pool ra rồi bật active cho object đó và thay vì
Destroy() thì deactive object đó và gửi về lại Pool.
Ví dụ 3.22. Minh họa quản lý đối tƣợng đạn trong một game bắn súng đơn giản
dùng Object Pool Pattern.
1. using System.Collections.Generic;
2. using UnityEngine;
3. public class Bullet : MonoBehaviour
4. {
5. public float speed = 10f;
6. private void Update()
7. {
8. transform.Translate(Vector3.forward * speed * Time.deltaTime);
9. }
10. }
11. public class BulletPool : MonoBehaviour
12. {
13. public GameObject bulletPrefab;
14. public int initialPoolSize = 10;
15. private List<GameObject> bulletPool;
16. private void Start()
17. {
18. bulletPool = new List<GameObject>();
19. for (int i = 0; i < initialPoolSize; i++)
20. {
21. GameObject bullet = Instantiate(bulletPrefab);
22. bullet.SetActive(false);
23. bulletPool.Add(bullet);
24. }
25. }
26. public GameObject GetBullet() {
27. foreach (var bullet in bulletPool) {
28. if (!bullet.activeInHierarchy)
29. {
30. bullet.SetActive(true);
31. return bullet;
32. }
33. }
34. GameObject newBullet = Instantiate(bulletPrefab);
35. bulletPool.Add(newBullet);
36. return newBullet;
37. }
38. public void ReturnBullet(GameObject bullet) {
39. bullet.SetActive(false);
40. }
41. }

123
42. public class PlayerController : MonoBehaviour
43. {
44. public Transform firePoint;
45. public BulletPool bulletPool;
46. private void Update()
47. {
48. if (Input.GetKeyDown(KeyCode.Space))
49. {
50. GameObject bullet = bulletPool.GetBullet();
51. bullet.transform.position = firePoint.position;
52. }
53. }
54. }
Trong ví dụ này Object Pool Pattern cho phép tái sử dụng các đối tƣợng đạn
thay vì tạo mới mỗi khi bắn. Lớp Bullet đại diện cho đối tƣợng đạn và có một thuộc
tính speed để xác định tốc độ di chuyển của đạn. Lớp BulletPool là lớp quản lý Pool
đối tƣợng đạn. Nó tạo ra một số lƣợng ban đầu các đạn và duyệt qua chúng để trả về
hoặc lấy các đạn từ Pool. Lớp PlayerController đại diện cho ngƣời chơi và có một
firePoint để xác định vị trí bắn đạn. Khi ngƣời chơi nhấn phím Space, đối tƣợng đạn
đƣợc lấy từ Pool và đƣợc đặt tại vị trí firePoint.
3.9. Truyền giá trị giữa các Script
Trong Unity, có nhiều cách để truyền giá trị giữa các Script của các đối tƣợng
khác nhau hoặc trong cùng một đối tƣợng:
- Public Variables Khai báo biến public trong Script của một đối tƣợng, sau đó
kéo và thả đối tƣợng đó vào các trƣờng của Script khác trong Inspector.
Ví dụ 3.23. Minh họa truyền giá trị giữa các Script bằng biến public.
1. public class ScriptA : MonoBehaviour {
2. public int valueToPass = 42;
3. }
4. public class ScriptB : MonoBehaviour
5. {
6. public ScriptA ScriptA;
7. private void Start()
8. {
9. Debug.Log(ScriptA.valueToPass);
10. }
11. }
- Singleton Pattern: Sử dụng Singleton Pattern để tạo một thể hiện duy nhất của
một lớp, sau đó có thể truy cập các biến và phƣơng thức của lớp đó từ bất kỳ đâu (xem
mục 3.8).
- Find Objects: Sử dụng GameObject.Find hoặc GetComponent để tìm các đối
tƣợng khác trong Scene và truyền thông tin qua lại.
Ví dụ 3.24. Minh họa hai đối tƣợng Player truyền thông điệp cho Enemy.
Khi Player va chạm với Enemy, ngƣời dùng muốn truyền thông điệp cho
Enemy để xác định rằng Enemy đã bị va chạm.

124
Bƣớc 1. Tạo các đối tƣợng Player và Enemy.
Bƣớc 2. Viết Script cho Player và Enemy.
1. using UnityEngine;
2. public class Player : MonoBehaviour
3. {
4. private void OnTriggerEnter(Collider other)
5. {
6. if (other.CompareTag("Enemy"))
7. {
8. Enemy enemy = other.GetComponent<Enemy>();
9. if (enemy != null)
10. {
11. enemy.HandleCollision();
12. }
13. }
14. }
15. }
16. public class Enemy : MonoBehaviour
17. {
18. public void HandleCollision()
19. {
20. Debug.Log("Enemy collided with the player!");
21. // Thực hiện các xử lý khác khi va chạm xảy ra
22. }
23. }
Trong ví dụ này Player có một OnTriggerEnter method đƣợc gọi khi Player va
chạm với một Collider khác. Nếu Collider đó có tag Enemy, nó sẽ tìm và lấy
Component Enemy từ đối tƣợng va chạm sử dụng GetComponent. Sau đó, gọi hàm
HandleCollision() của Enemy để truyền thông tin về va chạm. Enemy có một hàm
HandleCollision() để xử lý thông tin va chạm. Trong ví dụ này, nó chỉ in ra một thông
báo đơn giản.
3.10. Slider, Timer
Slider là một thành phần UI cho phép ngƣời chơi chọn giá trị trong một phạm vi
nhất định bằng cách kéo thanh trƣợt (slider) từ một giá trị tối thiểu đến giá trị tối đa.
Slider thƣờng đƣợc sử dụng để điều chỉnh các thiết lập, giá trị trong game, âm lƣợng,
độ sáng và các thông số tƣơng tự.
Tạo Slider trong Scene
Mở Scene trong chế độ Scene (không phải Prefab hoặc Play Mode). Tạo một
game object mới bằng cách nhấn chuột phải vào Hierarchy, chọn UI, chọn Slider.
Tùy chỉnh Slider:
Chọn game object Slider trong Hierarchy. Trong Inspector điều chỉnh các thuộc
tính của Slider nhƣ giá trị tối thiểu (Min Value), giá trị tối đa (Max Value), giá trị ban
đầu (Value), bƣớc nhảy (Step Size), màu sắc và vị trí trƣợt.

125
Xử lý sự kiện khi giá trị Slider thay đổi:
Ví dụ 3.25. Minh họa Slider điều khiển giá trị thay đổi.
1. using UnityEngine;
2. using UnityEngine.UI;
3. public class SliderController : MonoBehaviour
4. {
5. public Slider slider;
6. public void OnSliderValueChanged()
7. {
8. float value = slider.value;
9. Debug.Log("Slider value changed to: " + value);
10. }
11. }
Tƣơng tác với Script khác:
Nếu muốn truyền giá trị của Slider vào một Script khác gọi phƣơng thức
slider.value để lấy giá trị hiện tại của Slider.
Timer:
Trong Unity có thể sử dụng cả Coroutine và Invoke để tạo và quản lý các chức
năng đếm thời gian (timer):
Ví dụ 3.26. Minh họa sử dụng Coroutine để tạo Timer.
1. using UnityEngine;
2. public class TimerExample : MonoBehaviour
3. {
4. private bool isTimerRunning = false;
5. private float timerDuration = 5f;
6. private void Update()
7. {
8. if (Input.GetKeyDown(KeyCode.Space) && !isTimerRunning)
9. {
10. StartCoroutine(StartTimer());
11. }
12. }
13. private IEnumerator StartTimer()
14. {
15. isTimerRunning = true;
16. float elapsedTime = 0f;
17. while (elapsedTime < timerDuration)
18. {
19. elapsedTime += Time.deltaTime;
20. yield return null;
21. }
22. Debug.Log("Timer completed!");
23. isTimerRunning = false;
24. }
25. }

126
Sử dụng Invoke để tạo Timer:
Ví dụ 3.27. Minh họa Invoke để tạo Timer.
1. using UnityEngine;
2. public class TimerExample : MonoBehaviour
3. {
4. private float timerDuration = 5f;
5. private void Update()
6. {
7. if (Input.GetKeyDown(KeyCode.Space))
8. {
9. StartTimer();
10. }
11. }
12. private void StartTimer()
13. {
14. Invoke("TimerCompleted", timerDuration);
15. }
16. private void TimerCompleted()
17. {
18. Debug.Log("Timer completed!");
19. }
20. }
Cả hai ví dụ trên đều sử dụng để tạo một Timer đơn giản để đếm khoảng thời
gian cố định và sau đó thực hiện một hành động khi thời gian đếm kết thúc. Chú ý,
việc sử dụng Invoke thƣờng đơn giản hơn trong một số trƣờng hợp, nhƣng Coroutine
cho phép thực hiện các hành động liên tục trong khoảng thời gian đếm, có khả năng
tạm dừng, ngắt và tiếp tục thực thi.
3.11. Dừng, thoát game
- Dừng game: Để dừng game có thể sử dụng hàm Time.timeScale() để điều
chỉnh tốc độ thời gian trong game. Đặt Time.timeScale = 0 để tạm dừng game và
Time.timeScale = 1 để tiếp tục.
// Dừng game
1. Time.timeScale = 0;
// Tiếp tục game
1. Time.timeScale = 1;
- Thoát game: Để thoát game có thể sử dụng hàm Application.Quit() trong
Script. Chú ý rằng hàm này chỉ hoạt động trên các nền tảng Desktop hoặc Mobile.
// Thoát ứng dụng
1. Application.Quit();
- Xử lý thoát game trong Android: Đối với nền tảng Android cần xử lý sự kiện
thoát game dựa trên sự kiện nút back trên điện thoại. Unity cho phép ghi đè hàm
OnApplicationQuit() trong Script để xử lý việc thoát game.
1. private void Update()
2. {
3. // Kiểm tra nút back trên Android

127
4. if (Application.platform == RuntimePlatform.Android && Input.GetKeyDown
(KeyCode.Escape))
5. {
6. QuitGame();
7. }
8. }
9. // Xử lý thoát game
10. public void QuitGame()
11. {
12. Application.Quit();
13. }
14.

128
CÂU HỎI ÔN TẬP CHƢƠNG 3
Câu 3.1. Xây dựng game tránh đá trong đó ngƣời chơi sẽ điều khiển một đối
tƣợng và cố gắng tránh các đá rơi từ ngẫu nhiên trên xuống (Câu 2.1).
- Xử lý va chạm giữa ngƣời chơi và đá. Nếu ngƣời chơi va chạm với đá với số
lần quy ƣớc, game sẽ kết thúc.
- Đếm điểm cho ngƣời chơi mỗi lần tránh đƣợc đá và hiển thị điểm số của
ngƣời chơi trên màn hình.
- Khi game kết thúc (sau một khoảng thời gian hoặc khi ngƣời chơi va chạm với
đá), hiển thị một thông báo kết thúc game và điểm số cao nhất.
- Sử dụng các phím mũi tên hoặc cảm ứng để điều khiển ngƣời chơi.
- Sử dụng các Button để ngƣời chơi có thể bắt đầu lại game sau khi kết thúc,
xem hƣớng dẫn hoặc menu chọn chế độ chơi.
- Thêm các hiệu ứng âm thanh khi nhện va chạm hoặc ghi điểm.
Câu 3.2. Xây dựng game hứng trứng trong đó ngƣời chơi sẽ điều khiển một cái
rổ để hứng trứng rơi từ trên xuống (Câu 2.2).
- Xử lý va chạm giữa rổ và trứng. Nếu trứng đƣợc hứng, ngƣời chơi ghi điểm.
- Hiển thị điểm số của ngƣời chơi trên màn hình.
- Hiển thị điểm số của ngƣời chơi trên màn hình.
- Khi game kết thúc (sau một khoảng thời gian hoặc khi ngƣời chơi không hứng
đƣợc đủ trứng), hiển thị thông báo kết thúc game và điểm số cao nhất.
- Sử dụng các phím mũi tên hoặc cảm ứng để điều khiển cái rổ.
- Sử dụng các Button để ngƣời chơi có thể bắt đầu lại game sau khi kết thúc,
xem hƣớng dẫn hoặc menu chọn chế độ chơi.
- Thêm các hiệu ứng âm thanh khi va chạm hoặc ghi điểm.
Câu 3.3. Xây dựng game SpiderCave trong đó ngƣời chơi sẽ điều khiển một
nhân vật con nhện đang bò qua một hang động, tránh các chƣớng ngại vật (Câu 2.3).
- Xử lý va chạm giữa con nhện và chƣớng ngại vật. Nếu con nhện va chạm,
game kết thúc.
- Ghi điểm cho ngƣời chơi dựa trên khoảng cách mà con nhện di chuyển hoặc
số lƣợng chƣớng ngại vật đã vƣợt qua.
- Hiển thị điểm số của ngƣời chơi trên màn hình.
- Khi game kết thúc, hiển thị thông báo kết thúc game và điểm số cao nhất.
- Thiết kế game trở nên khó hơn theo thời gian bằng cách tăng tốc độ di chuyển
của con nhện hoặc thêm nhiều chƣớng ngại vật hơn.
- Thêm các hiệu ứng âm thanh khi nhện va chạm hoặc ghi điểm.
Câu 3.4. Xây dựng SpaceShooter trong đó ngƣời chơi sẽ điều khiển một chiến
đấu cơ vũ trụ để tiêu diệt đối thủ và tránh các vật thể nguy hiểm (Câu 2.4).
- Cho phép ngƣời chơi di chuyển chiến đấu cơ lên, xuống, qua trái và qua phải.
- Tạo ra các đối thủ ngoài hành tinh hoặc tàu vũ trụ để xuất hiện từ trên cùng
của màn hình và tấn công ngƣời chơi.

129
- Xử lý va chạm giữa đạn bắn ra từ chiến đấu cơ và đối thủ.
- Cho phép ngƣời chơi bắn đạn từ chiến đấu cơ để tiêu diệt đối thủ.
- Ghi điểm cho ngƣời chơi mỗi khi tiêu diệt một đối thủ.
- Hiển thị điểm số của ngƣời chơi trên màn hình.
- Khi game kết thúc (khi chiến đấu cơ bị hủy hoại), hiển thị thông báo kết thúc
game và điểm số cao nhất.
- Thêm các vật phẩm đặc biệt (đạn nổ, tăng tốc độ bắn) để ngƣời chơi có thể
nhặt đƣợc.
- Thêm âm thanh cho sự va chạm và khi ngƣời chơi ghi điểm.
- Tối ƣu hóa trải nghiệm chơi bằng cách điều chỉnh tốc độ, độ khó và các tham
số khác của game.
Câu 3.5. Xây dựng FlappyBird trong đó ngƣời chơi sẽ điều khiển một chú chim
vƣợt qua các ống để ghi điểm (Câu 2.5).
- Xử lý va chạm giữa chú chim và ống. Nếu chú chim va chạm, game kết thúc.
- Ghi điểm cho ngƣời chơi mỗi khi chú chim đi qua một cặp ống.
- Hiển thị điểm số của ngƣời chơi trên màn hình.
- Khi game kết thúc (do va chạm), hiển thị thông báo kết thúc game và điểm số
cao nhất.
- Thêm các hiệu ứng âm thanh khi chú chim nhảy, va chạm, và khi ghi điểm.
Câu 3.6. Xây dựng game mê cung trong đó ngƣời chơi sẽ khám phá một mê
cung và cố gắng tìm đƣờng để đến đích (Câu 2.6).
- Tích hợp hệ thống camera sao cho ngƣời chơi có thể di chuyển camera để nhìn
quanh và khám phá mê cung.
- Sử dụng thuật toán tìm đƣờng (thuật toán Dijkstra hoặc A*) để tính toán
đƣờng đi từ vị trí ban đầu đến đối tƣợng cần tìm.
- Ghi điểm cho ngƣời chơi dựa trên thời gian hoặc số bƣớc đi để đến đƣợc đích.
- Hiển thị điểm số của ngƣời chơi trên màn hình.
- Khi ngƣời chơi đến đƣợc đích, hiển thị thông báo kết thúc game và điểm số
cao nhất.
Câu 3.7. Xây dựng ứng dụng pháo hoa bằng hệ thống Particle.
- Sử dụng hệ thống Particle của Unity để tạo hiệu ứng pháo hoa.
- Tùy chỉnh màu sắc, hình dạng và kích thƣớc của các hạt Particle để tạo ra hiệu
ứng pháo hoa đa dạng.
- Tạo chuyển động ngẫu nhiên cho các hạt Particle để mô phỏng chuyển động
tự nhiên của pháo hoa.
- Thử nghiệm và tinh chỉnh các tham số để có nhiều loại hình dạng và kiểu
chuyển động khác nhau.
Câu 3.8. Xây dựng game trắc nghiệm.
- Tạo một cơ sở dữ liệu (hoặc sử dụng một hệ thống lƣu trữ dữ liệu) để lƣu trữ
câu hỏi và đáp án. Mỗi câu hỏi có thể có nhiều đáp án, trong đó một hoặc nhiều đáp án
có thể là đáp án đúng.

130
- Tạo giao diện ngƣời chơi đơn giản với các ô chọn đáp án, nút “Tiếp theo” và
hiển thị điểm số.
- Cho phép ngƣời chơi chọn một hoặc nhiều đáp án cho mỗi câu hỏi.
- Hiển thị câu hỏi ngẫu nhiên từ cơ sở dữ liệu cho ngƣời chơi.
- Kiểm tra đáp án của ngƣời chơi sau khi họ chọn và nhấn nút “Tiếp theo”.
- Ghi điểm cho ngƣời chơi dựa trên số câu trả lời đúng.
- Hiển thị điểm số của ngƣời chơi sau khi hoàn thành tất cả câu hỏi.
- Hiển thị thông báo kết thúc game và điểm số cao nhất (nếu có).
- Thêm câu hỏi với nhiều loại trả lời đúng/sai, chọn một đáp án, chọn nhiều đáp
án, điền vào chỗ trống,…
- Thêm thời gian hạn chế cho mỗi câu hỏi.

131
Chƣơng 4. THIẾT KẾ GAME NHIỀU NGƢỜI CHƠI
Chƣơng 4 tập trung vào thiết kế game để hỗ trợ nhiều ngƣời chơi đồng thời,
gồm các thiết kế giao diện ngƣời dùng, tƣơng tác giữa ngƣời chơi và cách game tƣơng
tác với Server sử dụng thƣ viện Mirror, một thƣ viện phổ biến trong cộng đồng Unity
để hỗ trợ việc phát triển trò chơi nhiều ngƣời chơi. Thƣ viện này giúp giảm công đoạn
phức tạp của việc xây dựng mạng và kết nối giữa các ngƣời chơi.
4.1. Tổng quan
Game nhiều ngƣời chơi (game multiplayer) trong Unity là game mà nhiều
ngƣời có thể tham gia cùng một trận đấu hoặc trải nghiệm game trong thời gian thực
thông qua kết nối mạng Internet hoặc mạng cục bộ, ví dụ nhiều ngƣời chơi tham gia
cùng một game để bắn những kẻ thù xuất hiện ngẫu nhiên. Loại game này đã trở thành
một thể loại phổ biến trong ngành công nghiệp game và có nhiều loại hình khác nhau.
- Multiplayer Online Games (MOG): Là các game chơi trực tuyến thông qua
internet hoặc LAN, cho phép hàng nghìn ngƣời chơi tham gia cùng một trận đấu. Ví
dụ điển hình là World of Warcraft, Fortnite hay League of Legends.
- Local Multiplayer Games: Là các game mà ngƣời chơi cùng nhau chơi trên
cùng một máy tính hoặc thiết bị, thƣờng áp dụng cho các game đối kháng nhƣ Mario
Kart hoặc Super Smash Bros.
- Split-Screen Games: Là các game nhiều ngƣời chơi trên cùng một màn hình,
thƣờng đƣợc sử dụng cho các game thể thao nhƣ FIFA hoặc NBA 2K.
Game nhiều ngƣời chơi có thể xây dựng bằng cách sử dụng Unity Networking
API hoặc thƣ viện bên ngoài nhƣ Photon (có phí), Mirror (miễn phí),…
4.2. Thƣ viện Mirror networking [3]
Mirror Networking là thƣ viện mạng cấp cao dành cho Unity, dễ sử dụng và tin
cậy cho xây dựng game nhiều ngƣời chơi. Thƣ viện này đƣợc thiết kế để đơn giản hóa
quá trình làm việc với kết nối mạng, cho phép các nhà phát triển tập trung vào việc tạo
dự án của họ. Mirror cho phép một trong những ngƣời chơi đồng thời là Client và
Server, do đó không cần phải có quy trình Server chuyên dụng. Mirror Networking
tƣơng thích với nhiều giao thức cấp thấp, cho phép triệu gọi thủ tục từ xa và quản lý
các ngữ cảnh qua mạng, đồng thời cũng hỗ trợ làm việc với môi trƣờng vật lý trong
các ứng dụng mạng. Mirror gồm một số đặc điểm sau:
- Sự đồng nhất: Mirror hỗ trợ sự đồng nhất dữ liệu giữa các Client và Server
trong game nhiều ngƣời chơi. Điều này đảm bảo rằng tất cả ngƣời chơi trải nghiệm
game một cách công bằng.
- Kết nối đa máy khách: Mirror cho phép nhiều Client kết nối đến Server cùng
một lúc, cho phép nhiều ngƣời chơi cùng chơi một game.
- Hỗ trợ websocket và udp: Mirror hỗ trợ cả WebSocket và UDP để truyền dữ
liệu giữa các ngƣời chơi, cho phép tùy chỉnh theo nhu cầu của game.
- Tự động serialization: Thƣ viện này tự động thực hiện việc serialization
(chuyển đổi dữ liệu thành dạng có thể truyền đi qua mạng) và deserialization (chuyển

132
đổi dữ liệu từ dạng nhận đƣợc từ mạng thành dữ liệu có thể sử dụng) cho các đối
tƣợng trong game.
- Hỗ trợ cho server authoritative: Có thể thiết lập Server là đối tƣợng duy nhất
có quyền thay đổi dữ liệu để đảm bảo tính công bằng và bảo mật trong game.
- Hỗ trợ cho realtime networking: Mirror đƣợc thiết kế để hỗ trợ các game yêu
cầu tƣơng tác thời gian thực nhƣ trò chơi bắn súng hoặc trò chơi thể thao.
4.3. Mô hình Client - Server [4]
Mô hình Client - Server là một trong những mô hình mạng nhiều ngƣời chơi.
Mô hình mạng cho game nhiều ngƣời chơi mô tả kiến trúc và thiết kế đƣợc sử dụng để
thiết lập giao tiếp giữa thông tin và nguồn và đích ví dụ giữa Client và Server hoặc
giữa các máy ngang hàng.
- Server: Thực thể quyết định về tình trạng hiện tại của mạng. Server là một
phiên bản của game mà tất cả ngƣời chơi khác kết nối khi họ muốn chơi cùng nhau.
Server thƣờng quản lý nhiều khía cạnh khác nhau của game, ch ng hạn nhƣ lƣu trữ
điểm số và truyền dữ liệu đó trở lại Client.
- Client: Thực thể chia sẻ phiên bản trạng thái game với Server. Client là các
phiên bản của game thƣờng kết nối từ các máy tính khác nhau đến Server. Client có
thể kết nối qua mạng cục bộ hoặc trực tuyến. Client là một phiên bản của game đƣợc
kết nối với Server để ngƣời chơi có thể chơi game với những ngƣời khác hoặc những
ngƣời kết nối trên Client. Tất cả các thiết bị đƣợc kết nối đều có một phiên bản và vấn
đề cần giải quyết với mô hình mạng nhiều ngƣời chơi là đƣa ra trải nghiệm giống nhau
cho tất cả ngƣời chơi đã kết nối.
Mô hình Client - Server: Là mô hình mà Server lƣu trữ logic game và gửi thông
tin đến Client để sử dụng thông tin đó và hành động tƣơng ứng. Client cũng có thể gửi
dữ liệu đến Server, ch ng hạn nhƣ dữ liệu đầu vào và hành động của Client. Trong mô
hình này, bất kỳ nhà cung cấp thông tin hoặc dịch vụ nào đều đƣợc gọi là Server,
ngƣời yêu cầu dịch vụ và ngƣời nhận thông tin đó đƣợc gọi là Client. Server trong
trƣờng hợp này có thể là một Server game chuyên dụng, tập trung hoàn toàn vào việc
mô phỏng trạng thái game và chuyển tiếp thông tin. Hoặc ngƣời chơi cũng có thể đƣợc
chỉ định vai trò Server trong cấu trúc mạng ngang hàng.
Mô hình ngang hàng (peer-to-peer hay P2P): Là mô hình trong đó tất cả ngƣời
chơi đƣợc kết nối trực tiếp với nhau mà không cần Server. Không giống nhƣ mô hình
Client - Server yêu cầu một Server đƣợc chỉ định giữ quyền quyết định cuối cùng về
trạng thái của game, trong mô hình P2P, thiết bị của mỗi ngƣời chơi chịu trách nhiệm
chạy logic game.
Mô hình Server nhiều người chơi: Trong mô hình Server chỉ ra Server trong
mối quan hệ Client - Server. Hai mô hình Server chính là:
- Server được lưu trữ trên Client (Client-hosted hay Host server): Mô hình
trong đó một ngƣời chơi trong game đóng vai trò là Server và thiết bị của ngƣời chơi
này có thẩm quyền đối với trạng thái game. Trong mô hình này, logic game đƣợc lƣu

133
trữ trên một trong các máy của chính ngƣời chơi thay vì một Server chuyên dụng riêng
biệt. Server hoạt động vừa là Server vừa là Client và lƣu trữ tạo một phiên bản duy
nhất của game (hình 4.1).

Hình 4.1. Mô hình Client-hosted


Hình 4.2 thể hiện ba ngƣời chơi trong game nhiều ngƣời chơi. Trong game này,
một Client cũng đóng vai trò là Server, có nghĩa là chính Client đó là Local Client.
Local Client kết nối với Host Server và cả hai đều chạy trên cùng một máy tính. Hai
ngƣời chơi còn lại là Client từ xa (remote client), nghĩa là chúng ở trên các máy tính
khác nhau và đƣợc kết nối với Host Server.

Hình 4.2. Minh họa game đa ngƣời chơi


Local Client giao tiếp với Server thông qua các lời gọi hàm trực tiếp và hàng
đợi tin nhắn vì nó nằm trong cùng một quy trình và chia sẻ Scene với Server. Remote
client giao tiếp với Server qua kết nối mạng thông thƣờng.

134
- Server game chuyên dụng (Dedicated Server): Mô hình game đƣợc chạy trên
một thiết bị riêng biệt. Thiết bị này có quyền quyết định trạng thái game nhƣng không
chơi hoặc hiển thị game (hình 4.3).

Client Client

Hình 4.3. Mô hình Server game


Mô hình Server chuyên dụng là nơi logic game đƣợc lƣu trữ trên một máy riêng
biệt với ngƣời chơi. Các Server chuyên dụng mô phỏng thế giới game mà không hỗ trợ
đầu vào hoặc đầu ra trực tiếp, ngoại trừ những gì cần thiết cho việc quản trị. Vì logic
đƣợc lƣu trữ trên một thiết bị tách biệt với ngƣời chơi (Client) nên mô hình Server
game chuyên dụng sẽ an toàn hơn vì IP của ngƣời chơi kết nối với Server đó sẽ không
bị lộ trƣớc đối thủ nhƣ trong mô hình Client-hosted.
4.4. Quản lý và điều khiển mạng
NetworkManager là một thành phần quan trọng trong thƣ viện Mirror của
Unity, đƣợc sử dụng để quản lý mạng trong game multiplayer. NetworkManager đảm
nhiệm nhiều nhiệm vụ quan trọng nhƣ khởi tạo và điều hƣớng các kết nối mạng, quản
lý Client và Server, đồng bộ hóa dữ liệu giữa chúng. NetworkManager cho phép xác
định vai trò của một instance cụ thể là Server hoặc Client trong một game multiplayer
và chuyển đổi giữa chúng tùy theo nhu cầu. NetworkManager cho phép cấu hình các
tham số quan trọng nhƣ IP và cổng kết nối, tốc độ gửi dữ liệu và các thiết lập khác.
Ngoài ra, NetworkManager cung cấp các phƣơng thức để tạo và tham gia vào các
phòng chơi (rooms) multiplayer tự động. Điều này giúp đơn giản hóa quá trình kết nối
và tham gia game nhiều ngƣời chơi. Có thể đăng ký các đối tƣợng cần đồng bộ hóa
thông qua NetworkManager. Các đối tƣợng này sẽ đƣợc tạo tự động trên Server và
Client và đƣợc đồng bộ hóa qua mạng. NetworkManager duy trì thông tin về tất cả các
kết nối và trạng thái mạng trong trò chơi. Ngƣời dùng có thể truy cập thông tin này để

135
xử lý sự kiện liên quan đến mạng nhƣ kết nối, ngắt kết nối, và thay đổi trạng thái của
ngƣời chơi.
Mirror đi kèm với một NetworkManagerHUD cho phép tạo giao diện ngƣời
dùng (UI) để kết nối vào Server hoặc máy khách thông qua một cửa sổ popup trực
quan. Có thể đăng ký và lắng nghe các sự kiện mạng thông qua NetworkManager, ví
dụ nhƣ khi một ngƣời chơi kết nối thành công hoặc bị ngắt kết nối. NetworkManager
cho phép tùy biến và mở rộng chức năng thông qua việc tạo các lớp con tùy chỉnh
(custom subclasses) và viết mã logic mạng riêng.
NetworkManagerHUD là một thành phần cụ thể của Mirror, thƣ viện đa ngƣời
chơi cho Unity, đƣợc sử dụng để tạo giao diện ngƣời dùng (UI) đơn giản để kết nối
vào Server hoặc máy khách trong game multiplayer. NetworkManagerHUD giúp
ngƣời dùng tƣơng tác với trò chơi để thực hiện các tác vụ nhƣ tạo Server, tham gia
Server, hoặc tham gia game multiplayer. NetworkManagerHUD tạo ra một giao diện
ngƣời dùng (UI) đơn giản với các nút và trƣờng văn bản để ngƣời chơi tƣơng tác.
Thông thƣờng gồm các nút Start Host để bắt đầu Server, Join as Client để tham gia
Server và trƣờng để nhập IP và cổng.
Để sử dụng NetworkManagerHUD, cần kéo và thả vào game object chứa
NetworkManager trong Scene. Sau đó, nó sẽ tự động kết nối với NetworkManager và
xử lý các sự kiện kết nối và điều hƣớng ngƣời dùng. Có thể tùy chỉnh giao diện của
NetworkManagerHUD bằng cách chỉnh sửa các trƣờng trong Inspector, thay đổi văn
bản nút, màu sắc, và hình ảnh nút để phù hợp với thiết kế game.
NetworkManagerHUD hỗ trợ các chức năng cơ bản nhƣ tạo Server, Client, và ngắt kết
nối. Khi ngƣời chơi chọn tùy chọn này, NetworkManagerHUD sẽ tƣơng tác với
NetworkManager để thực hiện các hành động tƣơng ứng. Có thể hiển thị trạng thái kết
nối hiện tại, ch ng hạn nhƣ "Chƣa kết nối," "Đang kết nối" hoặc "Đã kết nối." hoặc
tƣơng tác với các sự kiện đƣợc phát ra bởi NetworkManagerHUD để thực hiện các
hành động tùy chỉnh khi ngƣời chơi kết nối hoặc tham gia vào game.
NetworkManagerHUD giúp nhanh chóng thêm một giao diện ngƣời dùng đơn giản để
kiểm soát việc kết nối và tham gia vào game multiplayer bằng cách tƣơng tác với
NetworkManager. Tuy nhiên, nếu muốn tạo một giao diện ngƣời dùng phức tạp hơn
hoặc tùy chỉnh, có thể tạo một giao diện riêng bằng cách sử dụng các công cụ và thƣ
viện GUI của Unity.
Player Prefab: Là đối tƣợng đại diện cho ngƣời chơi trong game multiplayer.
Khi một ngƣời chơi kết nối thành công vào Server hoặc Client, NetworkManager sẽ
tạo các đối tƣợng ngƣời chơi dựa trên Prefab này.
Spawnable Prefabs: Là danh sách các đối tƣợng khác có thể đƣợc đồng bộ hóa
qua mạng. Đối với mỗi đối tƣợng trong danh sách này, NetworkManager sẽ quản lý
việc tạo và đồng bộ hóa nó giữa Server và máy khách.
Network Address: Là địa chỉ IP hoặc URL của Server để kết nối vào game, có
thể đặt địa chỉ này thông qua giao diện ngƣời dùng hoặc thông qua mã nguồn.

136
Network Port: Là cổng kết nối mạng sẽ đƣợc sử dụng để gửi và nhận dữ liệu
qua mạng giữa Server và máy khách.
Auto Create Player: Đặt trạng thái này cho phép NetworkManager tự động tạo
đối tƣợng ngƣời chơi cho các ngƣời chơi kết nối thành công.
Max Players: Là số lƣợng tối đa ngƣời chơi đƣợc phép tham gia vào game
multiplayer.
Spawn Positions: Là các vị trí trong Scene mà NetworkManager sử dụng để đặt
các đối tƣợng ngƣời chơi khi họ tham gia vào game.
Player Spawn Method: Xác định cách NetworkManager xác định vị trí xuất
hiện cho ngƣời chơi, ch ng hạn nhƣ theo thứ tự hoặc ngẫu nhiên.
Custom Messages: Các thông điệp tùy chỉnh cho việc kết nối và quản lý game.
Custom Add Player Methods: Phƣơng thức tùy chỉnh để xử lý việc thêm ngƣời
chơi vào game.
NetworkManagerState: Trạng thái hiện tại của NetworkManager, bao gồm
"Stopped" (Dừng), "Starting" (Đang bắt đầu), "Hosting" (Đang chạy Server), và
"Client" (Đang kết nối vào Server).
4.5. Đồng bộ hóa dữ liệu
NetworkIdentity là một thành phần quan trọng trong thƣ viện Mirror của Unity,
đƣợc sử dụng để đánh dấu các đối tƣợng trong game có thể đồng bộ hóa qua mạng.
Điều này cho phép theo dõi và quản lý các đối tƣợng trên Server và Client, đảm bảo
tính nhất quán trong game. NetworkIdentity đánh dấu đối tƣợng nào trong Scene có
thể đồng bộ hóa qua mạng. Điều này cho phép NetworkManager biết đƣợc đối tƣợng
nào cần đƣợc theo dõi và đồng bộ hóa. NetworkIdentity thƣờng đƣợc gắn kèm với các
đối tƣợng trong game, ch ng hạn nhƣ ngƣời chơi, quái vật, hoặc các đối tƣợng tƣơng
tác. Mỗi đối tƣợng có thể có một NetworkIdentity để xác định nó là một phần của
mạng multiplayer. NetworkIdentity cung cấp các cơ chế để đồng bộ hóa dữ liệu giữa
Server và Client. Các biến dữ liệu trong NetworkIdentity đƣợc đánh dấu bằng SyncVar
để cho phép việc đồng bộ hóa tự động. Mỗi NetworkIdentity có một ID mạng duy nhất
đƣợc sử dụng để xác định nó trong mạng. ID này đƣợc tạo và quản lý bởi
NetworkManager. NetworkIdentity đƣợc sử dụng để quản lý việc tạo và hủy bỏ các
đối tƣợng trên Server và Client. Khi ngƣời chơi tham gia vào game hoặc Server tạo đối
tƣợng mới, NetworkManager sẽ tạo các đối tƣợng NetworkIdentity tƣơng ứng.
NetworkIdentity có hàm là OnStartServer() đƣợc gọi khi đối tƣợng đƣợc tạo trên
Server để thực hiện các tác vụ khởi tạo đối tƣợng mạng. NetworkIdentity cho phép xác
định Prefab đại diện cho đối tƣợng trên mạng. Khi ngƣời chơi tham gia vào game,
NetworkManager sẽ sử dụng Prefab để tạo các đối tƣợng NetworkIdentity tƣơng ứng.
Có thể điều khiển việc đồng bộ hóa của NetworkIdentity bằng cách sử dụng các
phƣơng thức nhƣ Cmd (cho lệnh gửi từ Client đến Server) và Rpc (cho lệnh gửi từ
Server đến Client).
Để xử lý việc đồng bộ hóa dữ liệu của các đối tƣợng trong game nhiều ngƣời
chơi bằng thƣ viện Mirror của Unity sử dụng các hàm và phƣơng thức sau đây:
137
SyncVar: SyncVar là một decorator (đánh dấu) cho biến trong các Script. Biến
đƣợc đánh dấu bằng SyncVar sẽ tự động đồng bộ hóa giữa Server và Client khi giá trị
của nó thay đổi. Điều này giúp đảm bảo rằng các dữ liệu quan trọng nhƣ điểm số, máu,
hoặc trạng thái của đối tƣợng đƣợc đồng bộ hóa đúng cách.
Ví dụ:
[SyncVar]
private int playerScore;
[Command] và [ClientRpc]: Để gửi lệnh từ Client đến Server hoặc từ Server
đến Client và đồng bộ hóa các hành động hoặc thay đổi trạng thái, có thể sử dụng các
decorator [Command] và [ClientRpc]. Decorator [Command] đƣợc sử dụng cho các
phƣơng thức trên Client để gửi lệnh đến Server, trong khi [ClientRpc] đƣợc sử dụng để
gửi lệnh từ Server đến Client (mục 4.6).
Ví dụ:
[Command]
void CmdUpdatePlayerPosition(Vector3 newPosition){
// Thực hiện việc cập nhật vị trí trên Server
}
[ClientRpc]
void RpcUpdatePlayerPosition(Vector3 newPosition){
// Thực hiện việc cập nhật vị trí trên máy khách
}
[SyncVarHook]: Nếu muốn thực hiện một số xử lý sau khi biến SyncVar đƣợc
đồng bộ hóa, có thể sử dụng decorator [SyncVarHook]. Decorator này cho phép gọi
một phƣơng thức cụ thể trên Server hoặc Client khi giá trị của biến SyncVar thay đổi.
Ví dụ:
[SyncVar(hook = nameof(OnPlayerScoreChanged))]
private int playerScore;
void OnPlayerScoreChanged(int newScore)
{
// Thực hiện xử lý khi giá trị playerScore thay đổi
}
Sync Lists: Mirror hỗ trợ danh sách đồng bộ hóa, có thể sử dụng SyncList để
đồng bộ hóa danh sách các phần tử, ch ng hạn nhƣ danh sách ngƣời chơi hoặc danh
sách vật phẩm. Ví dụ: public SyncList<int> playerScores = new SyncList<int>();
4.6. Gọi phƣơng thức từ xa
RPC (Remote Procedure Call) là một cách để gọi các hàm hoặc phƣơng thức ở
một máy tính từ xa (remote machine) trong mạng máy tính. Trong context của game
nhiều ngƣời chơi và thƣ viện Mirror của Unity, RPC thƣờng đề cập đến các phƣơng
thức đồng bộ hóa đƣợc gọi từ một máy tính (thƣờng là Server) và thực hiện trên các
máy tính khác (thƣờng là Client) để đồng bộ hóa dữ liệu hoặc thực hiện hành động

138
trên các đối tƣợng trong game. Các điểm chính về RPC trong Mirror và các thƣ viện
tƣơng tự gồm:
[ClientRpc]: Là một decorator (đánh dấu) đƣợc sử dụng trong Mirror để đánh
dấu các phƣơng thức trên Server muốn gọi từ Client để đồng bộ hóa dữ liệu hoặc thực
hiện các hành động trên Client. Các phƣơng thức này thƣờng đƣợc gọi từ Client và
thực hiện trên tất cả các Client khác. Ví dụ:
[ClientRpc]
void RpcPlayerKilled(string playerName)
{
// Thực hiện hành động khi một người chơi bị giết trên tất cả các Client
}
[TargetRpc]: Decorator này đƣợc sử dụng để gọi RPC chỉ đến Client cụ thể
hoặc một Client duy nhất. Điều này cho phép thực hiện hành động chỉ trên Client nào
đó thay vì tất cả. Ví dụ:
[TargetRpc]
void TargetPlayerHit(NetworkConnection target, int damage)
{
// Thực hiện hành động khi một người chơi bị trúng đạn trên Client cụ thể
}
Đồng bộ hóa dữ liệu: RPC thƣờng đƣợc sử dụng để đồng bộ hóa dữ liệu quan
trọng nhƣ vị trí, trạng thái, điểm số, hoặc bất kỳ dữ liệu game nào quan trọng trên các
Server và Client. Ch ng hạn, khi một ngƣời chơi thay đổi vị trí của mình, Client có thể
gửi một RPC đến Server để thông báo thay đổi này, sau đó Server gửi lại RPC đến tất
cả các Client khác để cập nhật vị trí của ngƣời chơi đó trên mọi Client.
Điều khiển mạng: RPC cho phép Client gọi các hành động trên Server hoặc
Client khác, cho phép điều khiển việc xử lý mạng từ xa.
4.7. Cơ chế ủy quyền
Cơ chế ủy quyền (Authority) trong game nhiều ngƣời chơi đóng vai trò quan
trọng trong việc xác định máy tính nào có quyền kiểm soát và quyết định về một đối
tƣợng hoặc hành vi cụ thể trong game nhiều ngƣời chơi. Cơ chế này giúp đảm bảo tính
nhất quán và kiểm soát trong môi trƣờng đa ngƣời chơi. Một số khái niệm liên quan
đến cơ chế ủy quyền nhƣ sau:
Server Authority: Server thƣờng có quyền kiểm soát tối cao trong một game
nhiều ngƣời chơi. Nó quyết định về trạng thái của game và các hành động quan trọng
nhƣ việc kiểm tra va chạm, tính toán va chạm, và thực hiện hành động ảnh hƣởng đến
toàn bộ game.
Client Authority: Các Client có thể đƣợc ủy quyền kiểm soát đối tƣợng hoặc
hành động cụ thể trong game dựa trên cơ chế xác định trƣớc. Điều này cho phép Client
thực hiện các hành động mà không cần chờ Server phê duyệt. Tuy nhiên, việc sử dụng
quyền ủy quyền trên Client cần đƣợc kiểm soát cẩn thận để tránh gian lận và xâm
phạm tính nhất quán của game.
139
Authority Transfer: Trong một số tình huống, quyền kiểm soát có thể đƣợc
chuyển từ Client sang Server hoặc ngƣợc lại để đảm bảo tính nhất quán. Ví dụ, khi
một Client thực hiện một hành động có thể ảnh hƣởng đến trạng thái chung của game,
thì Client có thể yêu cầu Server xác nhận và chuyển quyền kiểm soát.
Client-Side Prediction: Một cách tiếp cận phổ biến trong việc quản lý quyền ủy
quyền trên Client là sử dụng Client-side prediction. Điều này cho phép Client thực
hiện các hành động cục bộ và dự đoán kết quả, sau đó gửi lệnh đến Server để xác
nhận. Nếu kết quả dự đoán trùng khớp với trạng thái thực tế trên Server, Server chấp
nhận hành động và đồng bộ hóa trạng thái với tất cả các Client khác.
Giả sử đang phát triển một game bắn súng đa ngƣời chơi. Trong game này, mỗi
ngƣời chơi có một nhân vật và có thể bắn ra đạn để tấn công ngƣời chơi khác. Trong
tình huống này:
Server Authority: Server có quyền kiểm soát tất cả các hành động quan trọng,
ch ng hạn nhƣ việc kiểm tra va chạm đạn và xác định xem một ngƣời chơi có bị trúng
đạn hay không.
Client Authority: Các Client có quyền kiểm soát nhân vật của họ và thực hiện
hành động nhƣ nhảy, di chuyển, và bắn đạn cục bộ. Khi một ngƣời chơi bắn đạn,
Client sẽ đánh dấu đạn đó với quyền kiểm soát của Client (client authority).
Authority Transfer: Khi một viên đạn bắn ra từ Client và di chuyển đến một vị
trí gần ngƣời chơi khác, nó có thể gặp va chạm với ngƣời chơi đó. Khi va chạm xảy ra,
Client sẽ gửi thông tin va chạm đến Server. Server kiểm tra xem việc va chạm có xảy
ra hợp lệ không và xác định nếu ngƣời chơi khác bị trúng đạn. Nếu có, Server chuyển
quyền kiểm soát của viên đạn từ Client bắn ra sang Server để đảm bảo tính nhất quán
và đánh giá trúng đạn đúng cách.
Client-Side Prediction: Trong trƣờng hợp này, Client thực hiện Client-side
prediction để giữ trạng thái của nhân vật mƣợt mà và đáng tin cậy trên Client của
mình. Sau đó, thông tin về hành động của nhân vật (nhƣ việc bắn đạn) đƣợc gửi đến
Server để xác nhận và đồng bộ hóa trạng thái với tất cả các Client khác.
4.8. Thành phần Network Transform
NetworkTransform là một thành phần trong thƣ viện Mirror của Unity đƣợc sử
dụng để đồng bộ hóa vị trí và hoạt động di chuyển của đối tƣợng giữa Server và Client
trong một game nhiều ngƣời chơi, giúp đảm bảo các đối tƣợng trong game di chuyển
một cách đồng nhất trên tất cả các máy tính tham gia vào game.
NetworkTransform cho phép đồng bộ hóa vị trí và quay (position và rotation)
của đối tƣợng giữa Server và Client. Khi một đối tƣợng thay đổi vị trí hoặc quay,
thông tin này đƣợc gửi qua mạng để đảm bảo tính nhất quán. Có thể cấu hình
NetworkTransform để hoạt động tự động (auto-sync) hoặc bắt buộc (force-sync).
Trong chế độ tự động sẽ tự động đồng bộ hóa vị trí và quay của đối tƣợng khi có sự
thay đổi. Trong chế độ bắt buộc sẽ đồng bộ hóa vị trí và quay định kỳ, không cần phải
chờ đối tƣợng thay đổi.

140
NetworkTransform cung cấp các thuộc tính tùy chỉnh cho việc đồng bộ hóa,
bao gồm tốc độ đồng bộ hóa (synchronization speed), khoảng cách tối thiểu thay đổi
trƣớc khi gửi dữ liệu (threshold), và tùy chọn cho phép đồng bộ hóa quay (synchronize
rotation). Có thể chỉ định một số dữ liệu cụ thể để đồng bộ hóa, ch ng hạn nhƣ vị trí,
quay, hoặc cả hai. Điều này cho phép kiểm soát các khía cạnh cụ thể của đối tƣợng
muốn đồng bộ hóa. Có thể điều chỉnh cách NetworkTransform hoạt động trên Client
bằng cách sử dụng cài đặt Client Authoritative hoặc Server Authoritative. Client
Authoritative cho phép Client đƣa ra quyết định về vị trí, trong khi Server
Authoritative đƣa ra quyết định trên Server. Ngoài ra, có thể sử dụng sự kiện
OnSerialize để tự động gửi dữ liệu tùy chỉnh qua mạng khi đối tƣợng thay đổi.
Ví dụ 4.1. Minh họa game gồm một Scene triển khai bắn phi thuyền nhiều
ngƣời chơi sử dụng Mirror, nhiều ngƣời chơi sẽ có thể tham gia cùng một game để bắn
những kẻ thù xuất hiện ngẫu nhiên.
Bƣớc 1. Tạo dự án Unity và import tất cả các sprite.
Tạo thƣ mục Sprites và sao chép tất cả các sprite vào thƣ mục này. Tuy nhiên,
một số sprite đó nằm trong các spritesheets, ch ng hạn nhƣ spritesheets của kẻ thù và
cần đƣợc cắt nhỏ bằng cách đặt chế độ Sprite mode là Multiple và mở Sprite Editor.

Hình 4.4. Import Assets và xử lý hình ảnh


Trong Sprite Editor cần mở menu Slice và nhấp vào Slice, với loại Slice là tự
động. Sau đó nhấn Apply.

141
Hình 4.5. Minh họa cắt ảnh
Bƣớc 2. Import Mirror.
Vào Asset Store (chọn Window, Asset Store) và download Mirror.

Hình 4.6. Minh họa download Mirror

Hình 4.7. Minh họa add Mirror bƣớc 1

142
Hình 4.8. Minh họa add Mirror bƣớc 2

Hình 4.9. Minh họa add Mirror bƣớc 3


Bƣớc 3. Tạo Background.
Tạo một khung nền để hiển thị hình nền bằng cách tạo hình ảnh mới trong
Hierarchy và đặt tên là BackgroundCanvas. Trong BackgroundCanvas, đặt chế độ
Render Mode là Screen Space - Camera và gắn Main Camera vào. Sau đó, đặt UI
Scale Mode thành Scale With Screen Size để Canvas xuất hiện ở chế độ nền, không ở
phía trƣớc các đối tƣợng khác.

143
Hình 4.10. Tạo Background
Trong BackgroundImage, cần đặt Source Image là space.

Hình 4.11. Tạo BackgroundCanvas


Bƣớc 4. Kết nối Server.
Để có game nhiều ngƣời chơi, cần một game object với các thành phần
NetworkManager và NetworkManagerHUD.

144
Hình 4.12. Kết nối Server
Đối tƣợng này sẽ chịu trách nhiệm quản lý việc kết nối các Client khác nhau
trong game và đồng bộ hóa các đối tƣợng game giữa tất cả các Client. HUD của
NetworkManager hiển thị một HUD đơn giản để ngƣời chơi kết nối với game.

Hình 4.13. Kết quả kết nối Server


Trong ví dụ này sử dụng các tùy chọn LAN Host và LAN Client. Game hoạt
động theo cách sau: Đầu tiên, ngƣời chơi bắt đầu game với tƣ cách là Server (bằng
cách chọn LAN Host). Server hoạt động đồng thời là một Client và Server. Sau đó,
những ngƣời chơi khác có thể kết nối với Server này với tƣ cách là Client (bằng cách
chọn LAN Client). Client giao tiếp với Server nhƣng không thực thi mã nguồn
Server. Vì vậy, để thực thi game cần mở hai phiên bản của game, một phiên bản là
Host và một phiên bản khác là Client.

145
Tuy nhiên, không thể mở hai phiên bản của game trong Unity Editor. Để làm
điều này cần kết xuất game và chạy phiên bản thứ nhất từ tệp thực thi đƣợc tạo khi kết
xuất. Phiên bản thứ hai có thể đƣợc chạy từ Unity Editor (trong chế độ Play Mode).
Bƣớc 5. Di chuyển đối tƣợng.
Khi đã có NetworkManager có thể bắt đầu tạo các đối tƣợng game sẽ đƣợc
NetworkManager quản lý. Hiện tại, phi thuyền sẽ chỉ di chuyển theo chiều ngang trong
màn hình với vị trí đƣợc cập nhật bởi NetworkManager. Trƣớc hết, tạo một game
object mới là Ship và đặt là đối tƣợng prefab.

Hình 4.14. Di chuyển đối tƣợng


Để NetworkManager quản lý một đối tƣợng game cần thêm thành phần
NetworkIdentity. Ngoài ra, vì phi thuyền sẽ do ngƣời chơi điều khiển nên sẽ chọn trạng
thái Local Player Authority. Thành phần NetworkTransform chịu trách nhiệm cập nhật
vị trí Ship trên toàn Server và tất cả các Client. Mặt khác, nếu di chuyển phi thuyền
trong một màn hình, vị trí của nó sẽ không đƣợc cập nhật ở các màn hình
khác. NetworkIdentity và NetworkTransform là hai thành phần cần thiết cho tính năng
này. Chú ý, kích hoạt quyền của Client Enable Client Authority trong thành phần
NetworkTransform.

Hình 4.15. Enable Client Authority

146
Sau đó, để xử lý chuyển động và va chạm, cần thêm RigidBody2D và
BoxCollider2D. Ngoài ra, BoxCollider2D sẽ là một trigger (trigger là true), vì ngăn
chặn các va chạm ảnh hƣởng đến vật lý của phi thuyền.
Cuối cùng, chúng ta sẽ thêm Script MoveShip, Script này sẽ có tham số
speed. Trong phƣơng thức FixUpdate() sẽ lấy chuyển động từ trục ngang Horizontal
Axis và thiết lập vận tốc tàu tƣơng ứng. Tuy nhiên, có hai chú ý quan trọng liên quan
đến mạng.
Đầu tiên, thông thƣờng tất cả các Script trong game Unity đều kế thừa
MonoBehaviour để sử dụng API. Tuy nhiên, để sử dụng API mạng, Script phải kế thừa
NetworkBehaviour thay vì MonoBehaviour. Để sử dụng NetworkBehaviour cần khai
báo (using UnityEngine.Networking) trong Script.
Ngoài ra, trong game nhiều ngƣời chơi, cùng một mã đƣợc thực thi trong tất cả
các phiên bản của game (Server và Client). Để cho phép ngƣời chơi chỉ điều khiển tàu
của họ chứ không phải tất cả tàu trong game, cần thêm điều kiện if vào đầu phƣơng
thức FixUpdate() để kiểm tra xem đây có phải là ngƣời chơi local hay không.
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4. using Mirror;
5. public class MoveShip : NetworkBehaviour
6. {
7. [SerializeField]
8. private float speed;
9. void FixedUpdate ()
10. {
11. if(this.isLocalPlayer)
12. {
13. float movement = Input.GetAxis("Horizontal");
14. GetComponent<Rigidbody2D>().velocity = new Vector2(movement
* speed, 0.0f);
15. }
16. }
17. }

Trƣớc khi chơi game cần thông báo cho NetworkManager rằng prefab Ship là
Prefab của ngƣời chơi bằng cách chọn trong thuộc tính Player Prefab trong thành
phần NetworkManager. Khi đó, mỗi khi ngƣời chơi bắt đầu phiên bản mới của game,
NetworkManager sẽ khởi tạo Ship.

147
Hình 4.16. NetworkManager sẽ khởi tạo Ship
Bƣớc 6. Sinh đối tƣợng.
Tạo một game object mới làm vị trí sinh phi thuyền và đặt vào vị trí sinh mong
muốn. Sau đó, thêm thành phần NetworkStartPosition, ở đây tạo hai vị trí sinh ở tọa độ
(-4, -4) và (4, -4).

Hình 4.17. Sinh đối tƣợng bƣớc 1

Hình 4.18. Sinh đối tƣợng bƣớc 2

148
Để NetworkManager sử dụng các vị trí đó cần cấu hình thuộc tính Player
Spawn Method. Có hai lựa chọn: Random (ngẫu nhiên) và Round Robin (vòng
tròn). Ngẫu nhiên nghĩa là đối với mỗi phiên bản game, trình quản lý sẽ chọn vị trí bắt
đầu của ngƣời chơi một cách ngẫu nhiên trong số các vị trí xuất hiện. Round Robin
nghĩa là đi tuần tự qua tất cả các vị trí sinh cho đến khi tất cả chúng đều đƣợc sử dụng
(ví dụ SpawnPosition1 đầu tiên sau đó là SpawnPosition2). Sau đó, bắt đầu lại từ đầu
danh sách. Trong ví dụ này chọn Round Robin.

Hình 4.19. Sinh đối tƣợng bƣớc 3


Bƣớc 7. Bắn đạn.
Để phi thuyền có khả năng bắn đạn, viên đạn đó phải đƣợc đồng bộ hóa giữa tất
cả các phiên bản của game. Trƣớc hết, tạo prefab Bullet bằng cách tạo một game
object mới có tên Bullet và đặt là đối tƣợng prefab. Để quản lý đối tƣợng viên đạn
trong mạng cũng cần các thành phần NetworkIdentiy và NetworkTransform nhƣ trong
prefab của phi thuyền. Tuy nhiên, sau khi một viên đạn đƣợc tạo ra, game không cần
phải truyền vị trí của nó qua mạng vì vị trí đã đƣợc cập nhật bởi công cụ vật lý. Vì
vậy, thay đổi tốc độ gửi mạng Network Send Rate trong Network Transform thành 0 để
tránh mạng bị quá tải. Ngoài ra, đạn sẽ có tốc độ và sẽ va chạm với kẻ thù sau này. Vì
vậy, thêm RigidBody2D và CircleCollider2D vào prefab. Chú ý, CircleCollider2D là
một trigger.

149
Hình 4.20. Tạo prefab bắn đạn bƣớc 1

Hình 4.21. Tạo prefab bắn đạn bƣớc 2

Hình 4.22. Tạo prefab bắn đạn bƣớc 3

150
Khi đã có prefab đạn, thêm Script ShootBullets vào Ship. Script này sẽ có các
tham số là Bullet Prefab và Bullet Speed.

Hình 4.23. Các tham số của Bullet


Trong phƣơng thức Update() kiểm tra xem ngƣời chơi local đã nhấn phím
Space hay chƣa và nếu có, sẽ gọi một phƣơng thức để bắn đạn. Phƣơng thức này sẽ
khởi tạo một viên đạn mới, đặt vận tốc và hủy đi sau một giây (khi viên đạn đã rời
khỏi màn hình). Chú ý, thẻ [Command] phía trên phƣơng thức CmdShoot(). Thẻ này
và “Cmd” ở đầu tên phƣơng thức là phƣơng thức đặc biệt đƣợc gọi là
Command. Command là một phƣơng thức đƣợc thực thi trên Server, mặc dù nó đƣợc
gọi trong Client. Trong trƣờng hợp này, khi ngƣời yêu cầu Client bắn một viên đạn,
thay vì gọi phƣơng thức trong Client, game sẽ gửi yêu cầu lệnh đến Server và Server
sẽ thực thi phƣơng thức đó. Ngoài ra, còn có lệnh gọi tới NetworkServer.Spawn trong
phƣơng thức CmdShoot(). Phƣơng thức Spawn() chịu trách nhiệm tạo viên đạn trong
mọi phiên bản của game. Vì vậy, CmdShoot thực hiện tạo một viên đạn trong Server
và sau đó Server sẽ sao chép viên đạn này cho tất cả các Client.
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4. using Mirror;
5. public class ShootBullets : NetworkBehaviour
6. {
7. [SerializeField]
8. private GameObject bulletPrefab;
9.
10. [SerializeField]
11. private float bulletSpeed;
12. void Update ()
13. {
14. if(this.isLocalPlayer && Input.GetKeyDown(KeyCode.Space))

151
15. {
16. this.CmdShoot();
17. }
18. }
19. [Command]
20. void CmdShoot ()
21. {
22. GameObject bullet = Instantiate(bulletPrefab, this.transform.
position, Quaternion.identity);
23. bullet.GetComponent<Rigidbody2D>().velocity = Vector2.up *
bulletSpeed;
24. NetworkServer.Spawn(bullet);
25. Destroy(bullet, 1.0f);
26. }
27. }
Cuối cùng, cần thông báo cho NetworkManager biết có thể tạo ra đạn bằng
cách thêm prefab bullet vào danh sách Registered Spawnable Prefabs.

Hình 4.24. Minh họa Registered Spawnable Prefabs


Bƣớc 8. Sinh kẻ địch Enemy.
Đầu tiên, một prefab của Enemy. Vì vậy, tạo một game object mới có tên
Enemy và tạo đối tƣợng prefab. Giống nhƣ Ship, kẻ địch sẽ có Rigidbody2D và
BoxCollider2D để xử lý các chuyển động và va chạm. Ngoài ra, cần NetworkIdentity
và NetworkTransform để đƣợc NetworkManager xử lý.

Hình 4.25. Minh họa sinh kẻ địch Enemy bƣớc 1

152
Sau đó, tạo một game object có tên EnemySpawner và chọn trƣờng Server Only
trong Component để việc sinh đối tƣợng chỉ tồn tại trong Server vì không tạo ra kẻ
địch trong mỗi Client. Ngoài ra, Script SpawnEnemies sẽ sinh ra kẻ địch theo một
khoảng thời gian (các tham số là prefab của kẻ địch, khoảng thời gian sinh và tốc độ
của kẻ địch).

Hình 4.26. Minh họa sinh kẻ địch Enemy bƣớc 2


Script SpawnEnemies sử dụng phƣơng thức OnStartServer(). Phƣơng pháp này
giống với OnStart(), điểm khác biệt là chỉ đƣợc gọi cho Server. Khi điều này xảy ra,
gọi InovkeRepeating để gọi phƣơng thức SpawnEnemy() sau mỗi giây (theo
spawnInterval). Phƣơng thức SpawnEnemy() sẽ khởi tạo một kẻ địch mới ở một vị trí
ngẫu nhiên và sử dụng NetworkServer.Spawn để sao chép trong tất cả các phiên bản
của game. Cuối cùng, kẻ địch sẽ bị hủy sau 10 giây.
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4. using Mirror;
5. public class SpawnEnemies : NetworkBehaviour
6. {
7. [SerializeField]
8. private GameObject enemyPrefab;
9. [SerializeField]
10. private float spawnInterval = 1.0f;
11. [SerializeField]
12. private float enemySpeed = 1.0f;
13. public override void OnStartServer ()
14. {
15. InvokeRepeating("SpawnEnemy", this.spawnInterval, this.
spawnInterval);
16. }

153
17. void SpawnEnemy ()
18. {
19. Vector2 spawnPosition = new Vector2(Random.Range(
-4.0f, 4.0f), this.transform.position.y);
20. GameObject enemy = Instantiate(enemyPrefab, spawnPosition,
Quaternion.identity) as GameObject;
21. enemy.GetComponent<Rigidbody2D>().velocity = new Vector2(0.0f,
-this.enemySpeed);
22. NetworkServer.Spawn(enemy);
23. Destroy(enemy, 10);
24. }
25. }
Chú ý, thêm prefab của kẻ địch vào danh sách Registered Spawnable Prefabs.

Hình 4.27. Minh họa sinh kẻ địch Enemy bƣớc 3


Bƣớc 9: Xử lý va chạm.
Va chạm đƣợc xử lý bằng Script AcceptDamage, gồm các tham số maxHealth,
enemyTag và destroyOnDeath. maxHealth đƣợc sử dụng để xác định tình trạng ban
đầu của đối tƣợng, enemyTag đƣợc sử dụng để phát hiện va chạm. Ví dụ enemyTag
cho phi thuyền sẽ là “Enemy”, trong khi enemyTag cho kẻ thù sẽ là “Bullet”. Bằng
cách này có thể làm cho phi thuyền chỉ va chạm với kẻ thù và kẻ thù chỉ va chạm bằng
đạn. Tham số cuối cùng (destroyOnDeath) đƣợc sử dụng để xác định xem một đối
tƣợng sẽ đƣợc hồi sinh hay bị tiêu diệt sau khi chết.
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4. using Mirror;
5. public class ReceiveDamage : NetworkBehaviour
6. {
7. [SerializeField]
8. private int maxHealth = 10;

154
9. [SyncVar]
10. private int currentHealth;
11. [SerializeField]
12. private string enemyTag;
13. [SerializeField]
14. private bool destroyOnDeath;
15. private Vector2 initialPosition;
16. // Use this for initialization
17. void Start ()
18. {
19. this.currentHealth = this.maxHealth;
20. this.initialPosition = this.transform.position;
21. }
22. void OnTriggerEnter2D (Collider2D collider)
23. {
24. if(collider.tag == this.enemyTag)
25. {
26. this.TakeDamage(1);
27. Destroy(collider.gameObject);
28. }
29. }
30. void TakeDamage (int amount)
31. {
32. if(this.isServer)
33. {
34. this.currentHealth -= amount;
35. if(this.currentHealth <= 0)
36. {
37. if(this.destroyOnDeath)
38. {
39. Destroy(this.gameObject);
40. }
41. else
42. {
43. this.currentHealth = this.maxHealth;
44. RpcRespawn();
45. }
46. }
47. }
48. }
49. [ClientRpc]
50. void RpcRespawn ()
51. {
52. this.transform.position = this.initialPosition;
53. }
54. }
Trong Script này, phƣơng thức Start() đặt currentHealth ở mức tối đa và lƣu vị
trí ban đầu (vị trí ban đầu sẽ đƣợc sử dụng để hồi sinh phi thuyền sau này). Ngoài ra,

155
chú ý có thẻ [SyncVar] phía trên định nghĩa thuộc tính currentHealth nghĩa là giá trị
thuộc tính này phải đƣợc đồng bộ hóa giữa các phiên bản game. Phƣơng thức
OnTriggerEnter2D() xử lý va chạm. Đầu tiên, kiểm tra xem đối tƣợng va chạm có
phải là enemyTag không, nếu đúng sẽ gọi phƣơng thức TakeDamage(). Phƣơng thức
TakeDamage() sẽ chỉ đƣợc gọi trong Server. Điều này là do currentHealth đã là
SyncVar nên chỉ cần cập nhật trên Server và đƣợc đồng bộ giữa các Client. Ngoài ra,
phƣơng thức TakeDamage() làm giảm currentHealth và kiểm tra nếu nhỏ hơn hoặc
bằng 0 thì đối tƣợng sẽ bị hủy, nếu destroyOnDeath là đúng, hoặc currentHealth sẽ
đƣợc đặt lại và đối tƣợng sẽ đƣợc hồi sinh. Trong thực tế, kẻ địch bị tiêu diệt khi bắn
và phi thuyền đƣợc hồi sinh.
Phƣơng thức hồi sinh sử dụng ClientRpc (thẻ [ClientRpc] định nghĩa trên
phƣơng thức). Điều này trái ngƣợc với thẻ [Command]. Trong khi các lệnh đƣợc gửi từ
Client đến Server, ClientRpc sẽ đƣợc thực thi trong Client, thậm chí khi phƣơng thức
này đƣợc gọi từ Server. Vì vậy, khi một đối tƣợng cần đƣợc hồi sinh, Server sẽ gửi yêu
cầu đến Client để thực thi phƣơng thức RpcRespawn() (chú ý tên phƣơng thức phải bắt
đầu bằng Rpc), thao tác này sẽ chỉ đặt lại vị trí về vị trí ban đầu. Phƣơng thức này phải
đƣợc thực thi trong Client vì đƣợc gọi cho phi thuyền và phi thuyền chỉ đƣợc điều
khiển bởi ngƣời chơi (chú ý đặt thuộc tính Local Player Authority là true trong thành
phần NetworkIdentity).
Chú ý, đối với phi thuyền cần xác định Enemy Tag là “Enemy”, trong khi đối
với Enemy, giá trị thuộc tính này là “Bullet”. Ngoài ra, trong prefab của kẻ địch, cần
kiểm tra thuộc tính Destroy On Death.

Hình 4.28. Xử lý va chạm

156
Hình 4.29. Các thuộc tính của phi thuyền

Hình 4.30. Kết quả game

Hình 4.31. Kết quả LAN host

157
Hình 4.32. Kết quả LAN host và LAN Client
4.9. Kết xuất ra môi trƣờng
Unity cho phép kết xuất dự án cho nhiều nền tảng khác nhau. Để xây dựng một
dự án Unity ra các nền tảng khác nhau, thực hiện các bƣớc sau:
Bƣớc 1. Mở dự án Unity cần kết xuất cho các nền tảng khác nhau.

Hình 4.33. Dự án cần kết xuất


Bƣớc 2. Chọn nền tảng Build.
Vào File, Build Settings hoặc Ctrl + Shift + B để mở cửa sổ Build Settings.

Hình 4.34. Cửa sổ Build Settings


158
Bƣớc 3. Chọn nền tảng cần kết xuất.
Trong cửa sổ Build Settings, chọn nền tảng muốn kết xuất. Unity hỗ trợ nhiều
nền tảng, bao gồm Windows, macOS, iOS, Android, WebGL,...
Ví dụ kết xuất dự án ra Windows, trong cửa sổ Build Settings, chọn nền tảng
PC, Mac & Linux Standalone.

Hình 4.35. Minh họa kết xuất ra Windows


Bƣớc 4. Xác định cài đặt cho nền tảng.
Khi chọn một nền tảng cần cấu hình các cài đặt cụ thể cho nền tảng đó. Các cài
đặt này bao gồm đƣờng dẫn đầu ra, quyền truy cập mạng, chế độ xây dựng và các cài
đặt nền tảng khác. Ví dụ cài đặt mục Player Setting:

Hình 4.36. Cài cho nền tảng


Bƣớc 5. Kết xuất dự án.
Chọn Build hoặc Build and Run để xây dựng dự án Unity cho nền tảng đã chọn.
Khi đó sẽ tạo ra một tệp thực thi hoặc dự án cho nền tảng đó.

159
Hình 4.37. Chọn đƣờng dẫn chứa dự án đã kết xuất

Hình 4.38. File kết xuất môi trƣờng Windows

160
CÂU HỎI ÔN TẬP CHƢƠNG 4
Câu 4.1. Thiết kế game bắn súng nhiều ngƣời chơi trên mạng Lan.
Câu 4.2. Thiết kế game bắn súng nhiều ngƣời chơi trên mạng Internet.
Câu 4.3. Thiết kế game đua xe nhiều ngƣời chơi trên mạng Lan.
Câu 4.4. Thiết kế game đua xe nhiều ngƣời chơi trên mạng Internet.

161
PHẦN 2. THỰC HÀNH
Bài 1. Cài đặt môi trƣờng lập trình Unity, tạo game object và địa hình
1.1. Cài đặt môi trường lập trình Unity
Bước 1. Tải Unity Hub.
- Truy cập trang web chính thức của Unity tại https://unity.com/.
- Tải về và cài đặt ứng dụng quản lý dự án và phiên bản Unity: Unity Hub.
- Khởi chạy tệp .exe nhận đƣợc, nhấp vào I agree:

Hình 1. Cài đặt Unity Hub


- Chọn thƣ mục để cài Unity và nhấp vào Install.
- Chờ quá trình cài đặt hoàn tất và chọn vào Finish để kết thúc.
Bước 2. Đăng ký tài khoản Unity (nếu cần).
Nếu đã có tài khoản Unity, ngƣời dùng có thể đăng nhập. Ngƣợc lại, ngƣời
dùng đăng ký một tài khoản miễn phí trên trang web Unity tại giao diện Unity Hub để
sử dụng các tài nguyên của Unity.
Bước 3. Cài đặt Unity Editor.
- Mở Unity Hub sau khi cài đặt.
- Đăng nhập vào tài khoản Unity (nếu đã đăng ký).
- Chọn tab Installs trong Unity Hub.
- Nhấn vào nút Add để thêm một phiên bản Unity mới.
- Chọn phiên bản Unity muốn cài đặt. Unity Hub sẽ tự động tải xuống và cài đặt
phiên bản này.

Hình 2. Chọn phiên bản và cài đặt Unity Editor

162
Bước 4. Xác định nền tảng mục tiêu.
Sau khi cài đặt Unity Editor, cần xác định nền tảng mục tiêu cho dự án nhƣ PC,
Mac, Android, iOS,…
1.2. Tạo game object và địa hình trong Unity
Bước 1. Khởi tạo dự án.
Tạo một dự án mới trong Unity và đặt tên (mục 1.4.3).
Bước 2. Tạo cảnh (Scene).
Tạo một cảnh mới: Chọn File, New Scene và đặt tên.
Bước 3. Tạo game object.
- Thêm một game object bất kỳ (ví dụ Cube, Sphere, hoặc Cylinder) vào cảnh.
- Nhấp chuột phải vào bất kỳ đối tƣợng nào trong Hierarchy và chọn Create
Empty hoặc nhấp chọn game object ở thanh công cụ (Toolbar) trên cùng màn hình để
tạo một game object rỗng (không có hình dạng).
- Nhấp chuột vào nút Game Object ở thanh công cụ (Toolbar) trên cùng màn
hình và chọn đối tƣợng 3D Object để thêm một game object có hình dạng sẵn.

Hình 3. Tạo đối tƣợng trong Hierarchy

Hình 4. Tạo đối tƣợng trong game object ở thanh công cụ Toolbar
- Đặt vị trí, quay, và tỷ lệ của game object để đối tƣợng nằm ở vị trí mong muốn
trong cảnh.

163
Đặt tên cho game object:
- Click vào tên mặc định (ví dụ GameObject) trong Hierarchy, sau đó nhập tên
mới cho game object.
Chỉnh sửa thuộc tính cho game object:
- Chỉnh sửa các thuộc tính nhƣ vị trí (Position), quay (Rotation), tỷ lệ (Scale),
và nhiều thuộc tính khác để đặt game object ở vị trí và hình dạng mong muốn.
Thêm hình ảnh:
- Để thêm hình ảnh hoặc chất liệu vào game object, kéo và thả tệp hình ảnh vào
cửa sổ Project (nằm bên trái màn hình) để tạo một tài nguyên hình ảnh.
- Sau đó, kéo và thả tài nguyên hình ảnh vào phần Material trong Inspector của
game object.
Bước 6. Xem trước và kiểm tra dự án.
Bấm vào nút Play trong Unity Editor để xem trƣớc cảnh và kiểm tra kết quả.
Bước 7. Tạo địa hình (Terrain).
- Thêm một địa hình: Chọn Game Object, 3D Object, Terrain.
- Tùy chỉnh địa hình bằng cách chỉnh sửa các thuộc tính nhƣ chiều cao, kích
thƣớc, chất liệu, và hình dạng.
1.3. Tạo đối tượng game chặn bóng
Bước 1. Tạo mặt phẳng chơi với Plane và khung bao quanh (cube).

Hình 5. Các đối tƣợng khung


Bước 2. Tạo nhân vật bóng (Sphere) và vật ăn là hình vuông, cài đặt cho quay
nghiêng 45 độ mỗi góc.

Hình 6. Tạo vật ăn là hình vuông

164
Đầu tiên tạo 1 hình hộp, cài đặt các thuộc tính góc (góc Rotation: x = y= z = 45o)
cho đối tƣợng.

Hình 7. Tạo vật ăn là hình cầu

Hình 8. Giao diện game chặn bóng


Bài 2. Lập trình Game sử dụng lớp Input, Debug, Vectơ và các hàm xử lý Script
nâng cao
2.1. Tạo một Game và điều khiển một nhân vật bằng bàn phím
Bước 1. Khởi tạo dự án và tạo nhân vật.
- Tạo dự án mới trong Unity.
- Tạo một cảnh mới và đặt tên cảnh là GameScene.
- Thêm một GameObject làm nhân vật vào cảnh.
- Đặt tên cho GameObject là Player.
Bước 2. Tạo Script.
- Tạo một kịch bản (C# Script) mới và đặt tên là PlayerController.
- Gắn kịch bản này vào GameObject Player.
- Mở Unity Editor và chọn GameObject Player trong cảnh.
- Trong Inspector, tìm phần Add Component và thêm một Component Script
mới. Đặt tên Script là PlayerController.

165
Bước 3. Lập trình PlayerController.
- Trong kịch bản PlayerController, lập trình để ngƣời chơi có thể điều khiển
nhân vật bằng bàn phím. Sử dụng lớp Input để xử lý sự kiện nhập.
- Sử dụng lớp Vectơ để thay đổi vị trí của nhân vật dựa trên các phím mũi tên
hoặc WASD.
- Sử dụng lớp Debug để hiển thị thông tin debug, ví dụ vị trí của nhân vật.
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4. public class PlayerController : MonoBehaviour
5. {
6. public float moveSpeed = 5.0f; // Tốc độ di chuyển của nhân vật
7. private void Update()
8. {
9. // Xử lý sự kiện nhập từ bàn phím
10. float horizontalInput = Input.GetAxis("Horizontal");
// Lấy giá trị từ các phím mũi tên hoặc A/D
11. float verticalInput = Input.GetAxis("Vertical");
// Lấy giá trị từ các phím mũi tên hoặc W/S
12. // Tạo vectơ di chuyển dựa trên sự kiện nhập
13. Vector3 movement = new Vector3(horizontalInput, 0, verticalInput
);
14. // Thay đổi vị trí dựa trên vectơ di chuyển và tốc độ
15. transform.Translate(movement * moveSpeed * Time.deltaTime);
16. // Hiển thị thông tin debug
17. Debug.Log("Vị trí của nhân vật: " + transform.position);
18. }
19. }
2.2. Tạo một Game là quả địa cầu xoay
- Hình cầu di chuyển với tốc độ chậm dần theo thời gian.
- Khi ngƣời chơi tƣơng tác với hình cầu thì hình cầu thay đổi màu sắc và
kích thƣớc.
Bài 3. Lập trình Game sử dụng hình ảnh và giao diện ngƣời dùng
3.1. Tạo Game chặn bóng với hình ảnh và Button “Bắt đầu” và “Thoát”
Bước 1. Chuẩn bị ảnh và tạo Scene mới.
- Chuẩn bị một hình ảnh nền cho menu (ví dụ background.jpg).
- Tạo một Scene mới trong Unity và đặt tên là MainMenu.
Bước 2. Thêm hình ảnh nền vào Canvas.
- Tạo một GameObject là Canvas.
- Chọn GameObject Canvas, nhấn UI, Image để thêm hình ảnh nền vào Canvas.
- Chọn hình ảnh nền vừa tạo trong Hierarchy.
- Trong Inspector, chọn mục Image, Source Image và kéo hình ảnh nền vào.
Bước 3. Thêm Button “Bắt đầu” và “Thoát”.
- Chọn GameObject Canvas và nhấn UI, Button để thêm một Button.

166
- Đổi tên của Button thành “StartButton”.
- Lặp lại bƣớc 1-2 để thêm một Button khác và đặt tên là “ExitButton”.
Bước 4. Đặt văn bản cho Button.
- Chọn GameObject StartButton và thay đổi văn bản thành “Bắt” đầu.
- Chọn GameObject ExitButton và thay đổi văn bản thành “Thoát”.
Bước 5. Tạo kịch bản để xử lý sự kiện.
- Tạo một Script mới bằng cách nhấp chuột phải vào thƣ mục Assets, Create,
C# Script. Đặt tên là MainMenuManager.
1. using UnityEngine;
2. using UnityEngine.SceneManagement;
3. public class MainMenuManager : MonoBehaviour {
4. public void StartGame() {
5. SceneManager.LoadScene("GameScene");
6. // Thay "GameScene" bằng tên Scene chứa game
7. }
8. public void ExitGame() {
9. Application.Quit();
10. }
11. }
- Gán Script “MainMenuManager” cho game object “Canvas”.
Bước 6. Liên kết kịch bản với Button.
- Chọn game object “StartButton”.
- Trong Inspector, tìm phần Button (Script) và thêm Component
MainMenuManager (kéo từ hộp Inspector hoặc bấm nút Add Component).
- Trong Component MainMenuManager, chọn dropdown No Function và chọn
MainMenuManager, StartGame.
- Tƣơng tự, liên kết Script MainMenuManager với Button ExitButton để xử lý
sự kiện thoát.
3.2. Tạo đối tượng hình ảnh, hiệu ứng, đồ họa cho game tháp đôi

Hình 9. Giao diện game

167
Hình 10. Màn hình kết thúc
Bảng 1. Đối tƣợng trong game tháp đôi
Đối tƣợng Mô tả
Ngƣời chơi Có thể di chuyển và tấn công bằng nút và phím.
Quái vật (chƣớng ngại vật) Sinh ngẫu nhiên.
Xu (phần thƣởng, thử thách) Vật phẩm.
Model Mô hình trong game.
Đối tƣợng xu:

Hình 11. Tạo xu

Hình 12. Sao chép đối tƣợng xu

168
Xu là hình tròn, cài đặt cho đứng th ng vuông góc với nền. Tuy nhiên, để
không phải lặp lại code nhiều lần vì trong một màn chơi có rất nhiều chƣớng ngại vật
có thuộc tính giống nhau, thực hiện nhƣ sau:
- Đầu tiên chỉ tạo 1 xu, viết câu lệnh xoay quanh trục:
transform.Rotate(0, 0, rotationSpeed* Time.deltaTime);
- Tạo file Script của vật đó để viết code cho vật xoay quanh trục liên tục.

Hình 13. Kết quả xoay


- Đổi màu đối tƣợng, nhấn đúp chuột phải chọn Create, Material:

Hình 14. Kết quả đổi màu


- Kéo Material với màu vừa đổi vào Coin thì tất cả các xu sẽ đổi màu thành
màu giống của Material.
Đối tƣợng ngƣời chơi:
- Đầu tiên cần tạo nhân vật: Chọn Create, 3D Object, Capsule (Player).
- Lấy hoạt ảnh nhân vật cho Player trong trang Mixamo.
Đối tƣợng quái vật:
- Tƣơng tự nhƣ tạo nhân vật Player.
- Tạo hành vi của quái vật truy đuổi và tấn công Player.

169
Hình 15. Tạo diễn hoạt cho nhân vật
3.3. Tạo đối tượng hình ảnh, hiệu ứng, đồ họa cho game hứng trứng

Hình 16. Kết quả giao diện game hứng trứng

Hình 17. Kết quả giao diện game hứng trứng


Bảng 2. Đối tƣợng trong game hứng trứng
Đối tƣợng Mô tả
Ngƣời chơi Có thể di chuyển chiều ngang bằng nút và phím.
Là đối tƣợng mục tiêu của game. Trứng sinh ngẫu nhiên, rơi từ trên
Trứng
xuống theo tốc độ nhanh dần.
Đối tƣợng mà ngƣời chơi sử dụng để hứng trứng. Ngƣời chơi di
Rổ
chuyển rổ để đặt chúng dƣới trứng rơi để hứng chúng.
Địa hình Mô hình trong game.

170
3.4. Tạo đối tượng hình ảnh, hiệu ứng, đồ họa cho game đánh boss
- Game gồm 3 level nhƣng độ khó sẽ tăng lên theo màn.
- Ngƣời chơi phải điều khiển nhân vật tiêu diệt các Enemy và không đƣợc để
chúng chạm phải hoặc bắn trúng.
Cách di chuyển: Trái (a, ), phải (d, ), nhảy (Space)
- Màn 1: Nút bắn: F, mục tiêu: Thu thập đủ 10 vàng để qua màn.
- Màn 2: Nút bắn: F, tìm kiếm hộp để sử dụng đƣợc chế độ bắn, leo thang: Lên
(w), xuống (s). Mục tiêu: Thu thập đủ 20 vàng khi đánh quái và 1 chìa khóa để qua
màn (Hiện chìa khóa: R, ẩn chìa khóa: T).
- Màn 3: Nút bắn: Chuột trái. Mục tiêu: Tiêu diệt boss để chiến thắng.

Hình 18. Menu game đánh boss

Hình 19. Giao diện game đánh boss màn 1

171
Hình 20. Giao diện game đánh boss màn 2

Hình 21. Giao diện game đánh boss màn 3

Hình 22. Giao diện game đánh boss màn chiến thắng

172
Bảng 3. Đối tƣợng trong game đánh boss
Đối tƣợng Mô tả
Ngƣời chơi Ngƣời chơi điều khiển nhân vật tiêu diệt các Enemy.
Enemy Kẻ thù.
Vàng, chìa khóa Vật phẩm.
3.5. Tạo đối tượng hình ảnh, hiệu ứng, đồ họa cho game mê cung

Hình 23. Giao diện game mê cung


Bảng 4. Đối tƣợng trong game mê cung
Đối tƣợng Mô tả
Ngƣời chơi là model 3D Có thể di chuyển và tấn công bằng nút và phím.
Quỷ Sinh ngẫu nhiên.
Nền là model 3D lâu đài Quang cảnh trong lâu đài từ Asset Store.
Dụng cụ trong lâu đài Các vật dụng khác trong lâu đài.
3.6. Tạo đối tượng hình ảnh, hiệu ứng, đồ họa cho game Zombie

Hình 24. Giao diện game Zombie

173
Hình 25. Giao diện nhân vật di chuyển

Hình 26. Giao diện nhân vật đấm

Hình 27. Giao diện Zombie đuổi theo

174
Hình 28. Giao diện bắn Zombie
Bảng 5. Đối tƣợng trong game Zombie
Đối tƣợng Mô tả
Ngƣời chơi là model 3D Có thể di chuyển và tấn công bằng nút và phím.
Zombie Sinh ngẫu nhiên các Zombie.
Nền là model 3D Quang cảnh trong rừng từ Asset Store.
Súng Dụng cụ để nhân vật bắn Zombie.
Đạn Dụng cụ dùng cho súng để bắn Zombie.
3.7. Tạo đối tượng hình ảnh, hiệu ứng, đồ họa cho game vượt chướng ngại vật

Hình 29. Giao diện game vƣợt chƣớng ngại vật

175
Bảng 6. Đối tƣợng trong game vƣợt chƣớng ngại vật
Đối tƣợng Mô tả
Ngƣời chơi Có thể di chuyển và tấn công bằng các nút.
Gai gây sát thƣơng khi nhân vật chạm vào các kẻ thù. Đá để
Chƣớng ngại vật
chặn nhân vật.
Nền Chỗ đứng cho nhân vật.
Xu Nhân vật chiếm xu để tăng điểm.
Cửa Giúp chuyển qua màn chơi.
Máu Giúp nhân vật hồi máu.

Button Chơi lại, tiếp tục, quay lại màn đầu tiên.
Text Hiển thị máu, điểm hiện tại, điểm cao.
3.8. Tạo đối tượng hình ảnh, hiệu ứng, đồ họa cho game tránh đá

Hình 30. Giao diện menu game tránh đá

Hình 31. Giao diện game tránh đá màn 1

176
Hình 32. Giao diện game tránh đá màn over
Bài 4. Lập trình game sử dụng các sự kiện chuyển động, va chạm và diễn hoạt
4.1. Lập trình chuyển động, va chạm và diễn hoạt game tháp đôi
- Tạo các đối tƣợng game gồm nhân vật ngƣời, quái vật, xu (bài 3.2).
- Tạo các sự kiện di chuyển, hiệu ứng diễn hoạt của nhân vật.
- Nhân vật di chuyển: Ngƣời chơi phải điều khiển nhân vật để tránh quái vật. Sử
dụng các phím hoặc nút bấm để di chuyển nhân vật.
- Xử lý va chạm nhân vật với quái vật, quái vật va chạm lại nhân vật, nhân vật
va chạm với xu.
4.2. Lập trình chuyển động, va chạm và diễn hoạt game hứng trứng
- Tạo các đối tƣợng game gồm nhân vật ngƣời, rổ, trứng (bài 3.3).
- Tạo các sự kiện di chuyển, hiệu ứng diễn hoạt của nhân vật.
- Nhân vật di chuyển: Ngƣời chơi phải điều khiển rổ để hứng các quả trứng rơi
từ trên xuống. Sử dụng các phím hoặc nút bấm để di chuyển nhân vật.
- Xử lý va chạm rổ với trứng, nếu hứng đƣợc thì tăng điểm số. Ngƣợc lại trứng
vỡ và trừ mạng.
4.3. Lập trình chuyển động, va chạm và diễn hoạt game đánh boss
- Tạo các đối tƣợng game gồm nhân vật ngƣời chơi, Enemy, chìa khóa, vàng
(bài 3.4).
- Tạo các sự kiện di chuyển, hiệu ứng diễn hoạt của nhân vật.
- Nhân vật di chuyển: Ngƣời chơi phải điều khiển nhân vật để đánh kẻ thù. Sử
dụng các phím hoặc nút bấm để di chuyển nhân vật.
- Xử lý va chạm nhân vật với Enemy.
4.4. Lập trình chuyển động, va chạm và diễn hoạt game mê cung
- Tạo các đối tƣợng game 3D gồm nhân vật ngƣời và quỷ (bài 3.5).
- Tạo các sự kiện di chuyển, hiệu ứng diễn hoạt của nhân vật.
- Nhân vật di chuyển: Ngƣời chơi phải điều khiển nhân vật để tránh quỷ trong
quá trình thám hiểm đƣờng đi trong lâu đài và di chuyển đến đích để kết thúc màn
chơi. Sử dụng các phím hoặc nút bấm để di chuyển nhân vật.

177
- Xử lý va chạm nhân vật với quỷ, quỷ va chạm lại nhân vật, nhân vật va chạm
với các đối tƣợng khác trong phòng.
- Nhân vật va chạm với quỷ khi gặp, quỷ va chạm lại nhân vật, nhân vật va
chạm với các đối tƣợng khác trong phòng.
4.5. Lập trình chuyển động, va chạm và diễn hoạt game 3D Zombie
- Tạo các đối tƣợng game (bài 3.6).
- Tạo các sự kiện di chuyển, hiệu ứng diễn hoạt của nhân vật.
- Nhân vật di chuyển: Ngƣời chơi phải điều khiển nhân vật để giết Zombie trên
đƣờng đi và di chuyển đến đích để kết thúc màn chơi. Sử dụng các phím hoặc nút bấm
để di chuyển nhân vật.
- Va chạm trong game: Nhân vật va chạm với Zombie khi đấm nhau, đạn súng
va chạm với Zombie khi bắn, Zombie va chạm với nhân vật khi cào.
4.6. Lập trình chuyển động, va chạm và diễn hoạt game vượt chướng ngại vật
- Tạo các đối tƣợng game (bài 3.7).
- Tạo các sự kiện di chuyển, hiệu ứng nhảy.
- Nhân vật di chuyển: Ngƣời chơi phải điều khiển nhân vật vƣợt qua các
chƣớng ngại vật, chém các quái vật trên đƣờng đi và di chuyển đến đích để kết thúc
màn chơi. Sử dụng các phím  để di chuyển bên trái, phím  để di chuyển bên phải.
- Nhân vật nhảy: Sử dụng phím Space để nhân vật nhảy lên.
- Va chạm: Mỗi lần ngƣời chơi ấn phím W nhân vật sẽ chuyển sang animation
tấn công, tính điểm, máu.
Bài 5. Lập trình game sử dụng các Particle, Sound, Slider, Timer
5.1. Tạo game gồm Slider để điều khiển thời gian đếm ngược và một Timer để đếm
ngược. Khi thời gian đếm ngược đạt 0, game sẽ kết thúc
Bƣớc 1. Tạo giao diện.
Tạo giao diện ngƣời dùng (UI) với một Slider để điều khiển thời gian đếm
ngƣợc và hiển thị thời gian còn lại trên màn hình.
Bƣớc 2. Sử dụng Timer, Slider.
- Tạo Timer để thực hiện việc đếm ngƣợc thời gian còn lại. Cập nhật thời gian
trên giao diện ngƣời dùng sau mỗi giây.
- Khi thời gian đếm ngƣợc đạt 0, kết thúc game hoặc thực hiện một hành động
tùy chọn.
- Cho phép ngƣời chơi tùy chỉnh thời gian đếm ngƣợc thông qua Slider. Đảm
bảo rằng thời gian đếm ngƣợc không thể âm hoặc quá lớn.
- Tạo một UI Slider và Text trong Unity và gắn kịch bản GameTimer lên đối
tƣợng chứa chúng.
- Gắn Slider và Text vào các trƣờng timeSlider và timeText trong kịch bản
GameTimer.
- Sử dụng StartCountdown(), StopCountdown() và SetCountdownTime() để điều
khiển thời gian đếm ngƣợc.
1. using UnityEngine;

178
2. using UnityEngine.UI;
3. public class GameTimer : MonoBehaviour {
4. public Slider timeSlider;
5. public Text timeText;
6. private float countdownTime = 60.0f; // Thời gian đếm ngược mặc định
7. private float currentTime;
8. private bool isCountingDown = false;
9. void Start() {
10. currentTime = countdownTime;
11. UpdateUI();
12. }
13. void Update()
14. {
15. if (isCountingDown)
16. {
17. currentTime -= Time.deltaTime;
18. if (currentTime <= 0)
19. {
20. EndGame();
21. }
22. UpdateUI();
23. }
24. }
25. public void StartCountdown()
26. {
27. isCountingDown = true;
28. }
29. public void StopCountdown()
30. {
31. isCountingDown = false;
32. }
33. public void SetCountdownTime(float time)
34. {
35. countdownTime = Mathf.Clamp(time, 0, float.MaxValue);
36. currentTime = countdownTime;
37. UpdateUI();
38. }
39. void UpdateUI()
40. {
41. timeSlider.value = currentTime / countdownTime;
42. timeText.text = currentTime.ToString("F1");
43. }
44. void EndGame()
45. {
46. // Thực hiện các hành động kết thúc game
47. }
48. }

179
5.2. Lập trình bắn pháo hoa sử dụng Particle

Hình 33. Giao diện game pháo hoa


5.3. Lập trình thêm Sound cho va chạm và nền của các game bài 4.1 đến 4.5
5.4. Lập trình thêm Timer, Slider cho các game bài 4.1 đến 4.5 để thực hiện đếm
giờ của game
Bài 6. Lập trình game nhiều ngƣời chơi giữa các máy tính trên mạng
6.1. Lập trình game đua xe nhiều người chơi trên mạng
Bƣớc 1. Tạo đối tƣợng.
Tạo mô hình 3D cho các xe đua và đối tƣợng môi trƣờng (đƣờng đua, cảnh núi,
cây cỏ hoặc các chƣớng ngại vật).
Bƣớc 2. Import Mirror và kết nối Server.
Bƣớc 3: Điều khiển xe.
- Điều khiển xe bằng cách sử dụng các phím.
- Xử lý chuyển động, tốc độ và vật lý của xe.
6.2. Lập trình game mô phỏng thi robot

Hình 34. Giao diện mô phỏng sân robot

180
Bảng 1. Đối tƣợng trong mô phỏng robot
Đối tƣợng Mô tả
Là các robot sẽ tham gia hành trình di chuyển theo vị trí đƣợc
Robot
hƣớng dẫn.
Hộp quà hình lập phƣơng có màu xanh hoặc đỏ tƣơng ứng với
Hộp quà
khu vực xuất phát của các đội trên sân thi đấu.
Khu vực xuất phát Là vùng mà robot xuất phát khi bắt đầu thi đấu.
Khu vực di chuyển Là khu vực có đƣờng quanh co, đƣờng cao tốc và cầu vƣợt.
Khu vực lấy và đặt quà Là vùng mà robot phải di chuyển để lấy và đặt các hộp quà.
Là vùng mà robot sau khi hoàn thành nhiệm vụ về vị trí để kết
Khu vực đích
thúc phần thi của mình.
Thời gian thi đấu 8 phút/trận.
Mỗi phần sân thi đấu bao gồm: Khu vực xuất phát, khu vực di chuyển, khu vực
lấy và đặt quà, khu vực đích.
- Khu vực di chuyển gồm phần vƣợt đƣờng quanh co, robot phải di chuyển qua 2
trụ bằng kẽm theo đƣờng zic zắc theo tín hiệu chỉ đƣờng của Ban Tổ chức. Sau đó robot
di chuyển vào phần đƣờng cao tốc và đi qua cầu vƣợt để vào khu vực lấy quà.
- Tại khu vực lấy và đặt quà, robot thực hiện lấy các hộp quà đã đƣợc chuẩn bị
theo màu tƣơng ứng sân thi đấu để đặt vào 12 ô.
- Sau khi hoàn thành nhiệm vụ đặt quà vào 12 vị trí tƣơng ứng, robot đƣợc phép
di chuyển về khu vực đích để báo danh hoàn thành phần thi của mình. Đội thi nào
hoàn thành tất cả các nhiệm vụ và về đích là đội chiến thắng tuyệt đối. Nếu không có
đội nào giành đƣợc chiến thắng tuyệt đối và cả hai đội đã hết thời gian thi đấu thì trận
đấu sẽ đƣợc dừng lại. Đội giành chiến thắng là đội có điểm số cao hơn sau khi kết thúc
trận đấu.
- Tổng điểm cho đội giành chiến thắng tuyệt đối là 100 điểm. Điểm số cho các
nhiệm vụ robot thực hiện chi tiết nhƣ sau:
+ Khu vực di chuyển: 30 điểm. Trong đó, vƣợt qua khu vực đƣờng quanh co:
10 điểm (qua mỗi trụ theo đúng tuyến đƣờng là 5 điểm); Vƣợt qua đƣờng cao tốc: 10
điểm; Vƣợt qua cầu: 10 điểm.
+ Khu vực lấy và đặt quà: 60 điểm. Mỗi hộp quà đƣợc lấy và đặt vào đúng vị trí
đạt 5 điểm.
+ Khu vực đích: 10 điểm. Đội thi hoàn thành tất cả các nhiệm vụ mới đủ điều
kiện về đích, đội nào về đích và có báo hiệu trƣớc sẽ đạt 10 điểm, giành điểm tuyệt đối
của trận đấu.

181
Hình 35. Giao diện thi đấu giữa hai đội

Hình 36. Giao diện chiến thắng tuyệt đối

182
TÀI LIỆU THAM KHẢO
[1] - Đại học FPT dịch (2015), Lập trình game với Unity, Nhà xuất bản Bách
khoa Hà Nội.
[2] - Alex Okita (2017), Learning C# Programming with Unity 3D, ebook.
[3] https://docs.unity.com/ugs/manual/relay/manual/mirror. Truy cập: 08/2023.
[4] - https://unity.com/how-to/intro-to-network-server-models.
Truy cập: 08/2023.
[5] - https://learn.unity.com/tutorial/introduction-to-particle-systems#6025fdd
9edbc2a112d4f0137. Truy cập: 08/2023.
[6] - https://learn.unity.com/tutorial/working-with-audio-components-2019-3.
Truy cập: 08/2023.
[7] - https://www.iostream.vn/article/am-thanh-trong-unity-tf3UK1.
Truy cập: 08/2023.

183

You might also like