TRƯỜNG ĐẠI HỌC QUẢNG NAM
KHOA CÔNG NGHỆ THÔNG TIN
BÀI GIẢNG Học phần: LẬP TRÌNH GAME Mã học phần: 2103114 Ngành đào tạo: ĐH CNTT
1. Giảng viên biên soạn: ThS. Nguyễn Văn Khương
2. Số tín chỉ/đơn vị học trình: 02
3. Tổ/Khoa quản lý học phần: Khoa CNTT
Quảng Nam, 9/2017
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- CHƯƠNG 1: CÀI ĐẶT MÔI TRƯỜNG LẬP TRÌNH GAME
1.1. CÀI ĐẶT UNITY TRÊN MÔI TRƯỜNG WINDOWS
Unity3D là một Game Engine phổ biến đối với các nhà phát triển game. Nhờ tính đa nền tảng và miễn phí, Unity3D là sự lựa chọn số một đối với các nhà phát triển game vừa và nhỏ. Bài viết này sẽ hướng dẫn các bạn cách cài đặt Unity3D trên môi trường Windows.
Phiên bản Unity mới nhất hiện tại là 4.6.1, có thể được tìm thấy tại trang chính thức (máy tính của tôi sử dụng môi trường Windows 8.1 Update 1 64-bit).
http://unity3d.com/get-unity/download
Ngoài ra, một số yêu cầu tối thiểu về phần cứng để có thể sử dụng bộ engine được công bố tại:
http://unity3d.com/unity/system-requirements
Sau khi tải xuống hoàn tất, các bạn double click vào icon của Unity để tiến hành cài đặt. Việc cài đặt Unity diễn ra bình thường và tương tự như cài đặt các chương trình khác.
Điều khoản và dịch vụ.
1
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Một số tuỳ chọn kèm theo. Nếu đã có kinh nghiệm, các bạn có thể bỏ chọn các tuỳ chọn này. Các tuỳ chọn này cung cấp một số tính năng bổ sung như phát triển game trên nền web, một số project mẫu, …
2
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Từ phiên bản Unity 5.3.4f, các gói Build của mỗi nền tảng được tách thành các components riêng để tối ưu không gian lưu trữ. Người dùng có thể tuỳ chọn cài đặt các thành phần cần thiết.
Quá trình cài đặt diễn ra bình thường. Sau khi cài đặt xong, chọn Finish để thoát. Đến đây các bạn đã cài đặt thành công Unity Engine vào máy tính.
Đăng ký Unity Account:
Unity hiện tại cung cấp phiên bản miễn phí lẫn trả phí cho mọi người. Đăng ký một account miễn phí, chúng ta có thể truy cập hàng loạt dịch vụ của Unity như Community, Assets Store, Online Store, …
Việc đăng ký account khá đơn giản. Sau khi quá trình cài đặt kết thúc, các bạn khởi động Unity3D. Cửa sổ đăng ký sẽ xuất hiện.
Các bạn chọn create one nếu chưa có tài khoản.
Sau khi chọn Create Account, các bạn điền đầy đủ thông tin để khởi tạo account. Lưu ý: password phải có ít nhất 8 ký tự, bao gồm đầy đủ các ký tự in hoa, ký tự in thường và ký tự đặc biệt.
3
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Một email kích hoạt tài khoản sẽ được gửi đến tài khoản email của bạn. Sau khi được kích hoạt, account vừa đăng ký đã có thể bắt đầu sử dụng các dịch vụ của Unity.
4
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 1.2. CÁC THÀNH PHẦN CƠ BẢN TRONG UNITY
1.2.1. Assets
Assets là tài nguyên xây dựng nên một dự án trên Unity. Những tài nguyên có thể là hình ảnh, âm thanh, mô hình 3D, chất liệu (material), texture, … hoặc cả một project hoàn chỉnh.
Các asset do chính những nhà phát triển game tạo ra và có thể được download miễn phí hoặc trả phí trên Unity Asset Store. Đây là một trong những tính năng rất hay của Unity. Các asset này sẽ giúp giảm thiểu rất nhiều thời gian cho việc thiết kế và lập trình game.
Các asset được đăng tải trên trang chính thức: https://www.assetstore.unity3d.com/en
1.2.2. Scenes
Trong Unity, một cảnh chơi (hoặc một phân đoạn) là những màn chơi riêng biệt, một khu vực trong game hoặc thành phần có trong nội dung của trò chơi (các menu). Các thành phần này được gọi là Scene. Bằng cách tạo ra nhiều Scenes, chúng ta có thể phân phối thời gian và tối ưu tài nguyên, kiểm tra các phân đoạn trong game một cách độc lập.
5
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
1.2.3. Game Objects
Khi Asset được sử dụng trong các Scene, Unity định nghĩa đó là Game Object. Đây là một thuật ngữ thông dụng, đặc biệt trong mảng lập trình. Tất cả các Game Object đều chứa ít nhất một thành phần cơ bản là Transform, lưu trữ thông tin về vị trí, góc xoay và tỉ lệ của Game Object. Thành phần Transform có thể được tuỳ biến và chỉnh sửa trong quá trình lập trình.
Bài viết này chỉ nêu khái quát và sơ lược ý nghĩa của các thành phần. Để tìm hiểu thêm về GameObject, bạn đọc có thể xem tại GameObject - Thao Tác Với C# Script.
1.2.4. Components
Components là các thành phần trong game, bổ sung tính năng cho các Game Object. Mỗi Component có chức năng riêng biệt. Đa phần các Component phụ thuộc vào Transform, vì nó lưu trữ các thông số cơ bản của Game Object.
Bản chất của Game Object là không có gì cả, các đặc tính và khả năng của Game Object nằm hoàn toàn trong các Component. Do đó chúng ta có thể xây dựng nên bất kỳ Game Object nào trong game mà chúng ta có thể tưởng tượng được.
6
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
1.2.5. Scripts
Scripts được Unity xem như một Component. Đây là thành phần thiết yếu trong quá trình phát triển game. Bất kỳ một game nào, dù đơn giản nhất đều cần đến Scripts để tương tác với các thao tác của người chơi, hoặc quản lý các sự kiện để thay đổi chiều hướng của game tương ứng với kịch bản game.
Unity cung cấp cho lập trình viên khả năng viết Script bằng các ngôn ngữ: JavaScript, C#. Unity không đòi hỏi lập trình viên phải học cách lập trình trong Unity, nhưng trong nhiều tình huống, chúng ta cần sử dụng Script trong mỗi phần của kịch bản game.
Để viết Script, chúng ta có thể làm việc với một trình biên tập Script độc lập của Unity, hoặc làm việc trên Mono Developer được tích hợp vào Unity trong những phiên bản gần đây. Mono Developer là một IDE khá tốt, cung cấp nhiều chức năng tương tự Visual Studio. Mã nguồn viết trên Mono Developer sẽ đươc cập nhật và lưu trữ trong dự án trên Unity.
7
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 1.2.6. Prefabs
Prefabs thực chất là Game Object được lưu trữ lại để tái sử dụng. Các Game Object được nhân bản từ một prefab sẽ giống nhau hoàn toàn, ngoại trừ thành phần Transform để phân biệt và quản lý được tốt hơn.
Để tạo ra một prefab, ta đơn giản chỉ cần kéo một Game Object vào cửa sổ Project.
1.3. LÀM QUEN VỚI UNITY IDE
Unity là một trong những Game Engine phổ biến nhất hiện nay. Unity cung cấp cho người dùng IDE rất mạnh mẽ và không kém phần tiện dụng cho các nhà phát triển game. Bài viết này sẽ hướng dẫn các bạn một số thao tác cơ bản để làm quen với Unity IDE.
1.3.1. Khởi tạo Project
Việc khởi tạo một project trong Unity tương đối đơn giản và không gây khó khăn cho người dùng. Ta có thể tạo project và thêm vào một số Asset Packages cần thiết. Chi tiết về các packages này sẽ được giới thiệu trong một bài viết khác.
1.3.2. Giao diện chính
Sau khi khởi tạo project, màn hình làm việc của Unity có dạng như sau:
8
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
1.3.3. Thanh công cụ
Thao tác với các Game Object trong cửa sổ Scene. Bao gồm di chuyển toàn bộ 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, …
Thay đổi khung nhìn Scene bằng cách biến đổi chốt Gizmo (sẽ đề cập
bên dưới).
Ba nút công cụ giúp chúng ta demo game trước khi build sang các nền
tảng khác.
Tuỳ chỉnh các đối tượng xuất hiện trong khung Scene.
Bố trí lại toàn bộ màn hình làm việc. Unity cung cấp sẵn cho chúng ta một số cách bố trí màn hình khá khoa học. Ngoài ra các bạn có thể tự bố trí lại màn hình làm việc theo cách của mình bằng thao tác kéo thả các cửa sổ.
Thanh công cụ của Unity bao gồm 5 thành phần, mỗi phần có ý nghĩa và mục đích khác nhau:
9
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 1.3.4. Scene
Khung nhìn Scene là nơi bố trí các Game Object như cây cối, cảnh quan, enemy, player, camera, … trong game. Sự bố trí hoạt cảnh là một trong những chức năng quan trọng nhất của Unity.
1.3.5. Gizmo
Nếu bạn 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 toạ độ Oxyz). Đây là nơi hiển thị góc nhìn hiện tại của khung Scene, giúp ta 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 toạ độ khác nhau, các bạn có thể click chuột vào các trục để thay đổi góc nhìn tương ứng với trục đó.
1.3.6. Định hướng Game Object
Khi xây dựng game, việc đặt các Game Object vào thế giới game là vô cùng quan trọng và diễn ra thường xuyên. Unity cung cấp các công cụ chuyển đổi trên thanh công cụ như dịch chuyển (translate), xoay (rotate), phóng to/thu nhỏ (scale) cùng với các phím tắt của các công cụ này.
1.3.7. Thanh công cụ điều khiển khung 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 như Textured, Wireframe, RGB, Overdraw, … Ngoài ra nó còn cung cấp tuỳ chỉnh về ánh sáng, âm thanh, hiệu ứng, …
10
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 1.3.8. Game
Đây là màn hình hiển thị demo game, là góc nhìn từ các camera trong game.
Thanh công cụ trong cửa sổ game cung cấp các tuỳ chỉnh về độ phân giải màn hình, thông số (stats), gizmos, tuỳ chọn bật/tắt các component, …
1.3.9. Project
Đây là cửa sổ explorer của Unity, hiển thị thông tin của toàn bộ các tài nguyên (assets) trong game của bạn.
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. Ta có thể tạo ra các thư mục mới bằng cách Right click - > 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. Các tài nguyên trong game cũng có thể được tạo ra bằng cách này.
Phía trên cây thư mục là mục Favorites, giúp chúng ta truy cập nhanh vào những tài nguyên thường sử dụng. Chúng ta có thể đưa các tài nguyên vào Favorites bằng thao tác kéo thả.
Đường dẫn của thư mục tài nguyên hiện tại. Chúng ta có thể dễ dàng tiếp cận các thư mục con hoặc thư mục gốc bằng cách click chuột vào mũi tên hoặc tên thư mục.
11
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
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 asset cần tìm.
1.3.10. Hierarchy
Tab Hierarchy là nơi hiển thị các Game Object trong Scene hiện hành. Khi các đối tượng được thêm hoặc xoá trong Scene, tương ứng sẽ xuất hiện đối tượng đó trong tab Hierarchy.
Tương tự như trong tab Project, Hierarchy cũng có một thanh tìm kiếm giúp quản lý và thao tác với các Game Object một cách hiệu quả hơn, đặc biệt đối với những dự án lớn.
1.3.11. Inspector
Cửa sổ Inspector hiển thị chi tiết các thông tin về Game Object đang làm việc, kể cả những component được đính kèm và thuộc tính của nó. Bạn có thể điều chỉnh, thiết lập mọi thông số và chức năng của Game Object thông qua cửa sổ Inspector.
Mọi thuộc tính thể hiện trong Inspector đều có thể dễ dàng tuỳ chỉnh trực tiếp mà không cần thông qua một kịch bản định trước. Tuy nhiên Scripting API cung cấp một số lượng nhiều và đầy đủ hơn do giao diện Inspector là có giới hạn.
Các thiết lập của từng component được đặt trong menu. Các bạn có thể click chuột phải, hoặc chọn icon hình bánh răng nhỏ để xuất hiện menu này.
12
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- CHƯƠNG 2: CÁC KỸ THUẬT CƠ BẢN
2.1. C# SCRIPT - LỚP INPUT
2.1.1. Giới thiệu
Input là một thành phần quan trọng trong bất kì ứng dụng nào. Khi bước đầu học C/C++ ta đã từng làm quen với của sổ dòng lệnh, yêu cầu nhập các số nguyên, số thực, ... Đó là một dạng input cơ bản nhất mà ai cũng đã từng dùng tới. Khi học Lập trình giao diện, ta làm quen thêm một số Event thường dùng để lấy thông tin từ keyboard, mouse như KeyDown, KeyUp, MouseDoubleClick, ... Với Unity, Input cũng quan trọng và là thành phần không thể thiếu. Bài viết sau sẽ giúp bạn có kiến thức cần thiết cho việc input trong Unity.
2.1.2. OnMouse event
Đây là một sự kiện cơ bản thường được sử dụng trong Unity cho phép lập trình viên 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 ta có một GameObject, ví dụ như một quả boom. Chúng ta mong muốn khi người chơi click chuột vào nó thì quả boom đó sẽ phát nổ. Để làm được điều này, ta 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 công việc tới đây khá đơn giản, ta chỉ cần hiện thực một hàm thể hiện một vụ nổ và trong hàm OnMouseDown, ta sẽ có một lời gọi đến hàm này.
Dưới đây là một ví dụ đơn giản minh họa. Khi người dùng click vào GameObject là bức hình thì tại cửa sổ Console sẽ in ra dòng chữ “You clicked me”
13
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Hướng dẫn làm ví dụ:
Bước 1: Tạo một dự án Game 2D mới đặt tên là Vidu2_1.
Bước 2: Chọn Assets, tạo thư mục Resources trong Assets bằng cách Click chuột phải vào Assets chọn Create / Folder, đặt tên thư mục là Resources.
14
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
- Tương tự tạo thêm 2 thư mục Scripts, Scenes trong thư mục Assets.
Bước 3: Click đôi mở thư mục Resources ra, kéo đối tượng hình ảnh bất kỳ vào thư mục Resources.
Bước 4: Mở thư mục Scripts ra, tạo một đoạn chương trình bằng cách Click chuột phải vào thư mục Scripts, chọn Create, chọn C# Scripts.
15
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
- Đặt tên chương trình là ClickEvent:
Bước 5: Click đôi vào ClickEvent để viết lệnh như sau:
16
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
void OnMouseDown() {
Debug.Log("You click me!");
}
- Viết thêm phương thức OnMouseDown(), viết xong nhớ lưu lại, chọn File / Save.
- Lưu ý: Cửa sổ viết code có thể là MonoDevelop hoặc Visual C#. Để thay đổi 2 cửa sổ này chọn trong Unity thẻ Edit / Preferences
Bước 6: Kéo tập tin hình ảnh ở thư mục Resources sang cửa sổ Hierachy.
17
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Bước 7: Chọn hình ảnh, chọn Add componet, chọn Physics 2D, chọn Circle Collider 2D.
18
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Bước 8: Chọn thư mục Scripts, kéo File ClickEvent sang cửa sổ Inspector của đối tượng hình
ảnh.
Bước 9: Lưu Scene lại với tên là ClickEvent như sau: Chọn File / Save Scenes as, lưu vào thư mục Assets / Scenes.
Bước 10: Chạy chương trình và cảm nhận:
Ngoài OnMouseDown ta còn có thêm một số hàm liên quan:
19
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
• 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 đi vào phạm vi Collider
• OnMouseExit: xảy ra khi người dùng di chuyển chuột ra khỏi phạm vi Collider
của GameObject
của GameObject.
2.1.3. GetMouseButton
Các sự kiện OnMouse làm việc khá hiệu quả và thường được sử dụng khi bạn cần phát hiện và xử lý khi người dùng click chuột. Tuy nhiên nó có một số điểm còn hạn chế
Trở về ví dụ quả boom ở trên. Giả sử bây giờ ta muốn dùng chuột để di chuyển quả boom đi một vị trí khác và sau đó mới kích nổ nó. Rõ ràng ở đây ta gặp một vấn đề! Hàm OnMouseDown sẽ được gọi khi Mouse click xuống mà không cần biết đó là Mouse left hay Mouse right. Quả boom sẽ phát nổ ngay khi vừa click vào mà chưa kịp di chuyển.
Vậy bây giờ ta muốn sử dụng chuột trái để di chuyển và chuột phải để kích hoạt thì sao?
• Hàm Input.GetMouseButton(0) đại diện cho Mouse Left. • Hàm Input.GetMouseButton(1) đại diện cho Mouse Center. • Hàm Input.GetMouseButton(2) đại diện cho Mouse Right.
Điều đó có thể thực hiện dễ dàng bằng cách sử dụng các hàm Input.GetMouseButton(x). Cụ thể.
using UnityEngine; using System.Collections;
public class ExampleClass : MonoBehaviour { void Update() { if (Input.GetMouseButtonDown(0)) Debug.Log("Mouse left click."); if (Input.GetMouseButtonDown(1)) Debug.Log("Mouse center click."); if (Input.GetMouseButtonDown(2)) Debug.Log("Mouse right click."); } }
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.
Lưu ý: 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.
2.1.4. Nhập dữ liệu từ bàn phím
20
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Đây là một dạng input bạn sẽ thường xuyên sử dụng khi lập trình các game trên Windows, là nơi 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 ta có 2 cách: GetKey(hay GetButton) và GetAxis
input từ bàn phím trên keyboard. Để lấy
2.1.5. GetKey và GetButton
Trong Unity, GetKey hay GetButton là cách mà ta sẽ nhận input từ bàn phím thông qua class Input. Điểm khác biệt giữa hai cái này 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. Danh sách các KeyCode bạn đọc có thể tham khảo ở link cuối bài viết.
GetButton sẽ sử dụng tên do chính lập trình viên tự 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. Về cơ bản lúc này “DauCach” tương đương với Key.Space
void Update () { // Lấy Input theo GetKey bool down = Input.GetKeyDown(KeyCode.Space); bool held = Input.GetKey(KeyCode.Space); bool up = Input.GetKeyUp(KeyCode.Space);
// Lấy Input theo GetButton bool down = Input.GetButtonDown("Jump"); bool held = Input.GetButton("Jump");
1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
Để thêm một tên đại diện cho một phím bất kì ta có thể vào Edit → Project Settings → Input menu. Với Name là tên thay thế cho phím và PostiveButton chính là phím cần đại diện.
21
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- bool up = Input.GetButtonUp("Jump");
// Do something. }
11. 12. 13. 14.
2.1.6. GetAxis
Điểm khác biệt giữa GetAxis so với các phương thức GetKey hay GetButton là nó 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 nó sẽ giảm giá trị trở về 0. Tương tự như vậy với Negative Button với khoảng [-1, 0].
Ứng dụng của GetAxis
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 Gravaity thông qua Input Manager. Ta 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.
22
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 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/chậm theo ý muốn dễ dàng.
2.2. KỸ THUẬT DEBUG TRONG UNITY
2.2.1. Giới thiệu
Khi lập trình ra một sản phẩm nào đó, hầu hết sẽ đi kèm với những bug – lỗi lập trình. Lỗi có thể do nhiều nguyên nhân khác nhau, nhưng mục tiêu cuối cùng của sản phẩm là loại bỏ hết tất cả các lỗi để có thể đưa sản phẩm hoàn thiện ra thị trường.
Để 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 lập trình viên 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, chúng ta hoàn toàn 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. Bạn đọc có thể đọc thêm tại Các Vấn Đề Về Debug Cơ Bản. Bài viết này sẽ hướng dẫn bạn các thao tác cần thiết và kĩ năng debug trong Unity.
2.2.2. Kiểm tra tính thực thi
Trước khi sửa lỗi chương trình, bạn phải khoanh vùng được đoạn code bị lỗi, phạm vi càng thu hẹp càng tốt. Việc khoanh vùng và xác định đoạn code lỗi thuộc về phần kinh nghiệm của mỗi người nên tôi không đề cập chi tiết trong bài viết.
Sau khi đã khoanh vùng phần code lỗi, ta 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 đó. Ở đây tôi sử dụng hàm Log thuộc lớp Debug trong Unity. Chi tiết về hàm được đề cập bên dưới.
Nếu thông điệp của bạn không được hiển thị, một trong những trường hợp sau đã xảy ra:
• • Hàm chứa đoạn code thông điệp không được gọi.
Script chưa được gắn vào một Game Object nào trong chương trình.
23
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- • 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, ...).
Để xử lý, ta cần xem xét cẩn thận và dựa vào logic của game để chỉnh sửa lại cho phù hợp.
2.2.3. 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, ta 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. Vẫn dùng hàm Log thuộc lớp Debug, bạn 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.
Việc khắc phục lỗi về logic game thuộc về khả năng đọc code và phân tích bài toán. Do đó các công cụ Debug chỉ giúp bạn có thể sửa lỗi được dễ dàng hơn, việc sửa lỗi sẽ phụ thuộc nhiều vào bản thân lập trình viên.
Unity cung cấp một loạt các hàm hỗ trợ debug khá mạnh và tiện dụng thuộc lớp Debug. Dưới đây tôi sẽ giới
thiệu một số thành phần cơ bản và quan trọng nhất của lớp Debug.
2.2.4. Các hàm quan trọng và chức năng
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, tuỳ theo cách hiển thị của kiểu dữ liệu. Các kiểu dữ liệu thường được lập trình viên 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.
Ngoài hàm Log nói trên, Unity còn cung cấp một số hàm Log nâng cao nhưng ít phổ biến đối với lập trình viên chưa có nhiều kinh nghiệm.
Draw
public static void DrawLine(Vector3 start, Vector3 end,
1.
Color color = Color.white, float duration = 0.0f, bool depthTes t = true);
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ể, ta sử dụng hàm DrawLine để vẽ. Hàm có nguyên mẫu như sau:
24
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 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õ ràng.
public static void DrawRay(Vector3 start, Vector3 dir, C
1.
olor color = Color.white, float duration = 0.0f, bool depthTest = true);
Trong trường hợp cần quan sát hướng của đối tượng, ta sử dụng hàm DrawRay. Cách sử dụng hàm tương tự như với hàm DrawLine:
Lưu ý: 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, bạn hãy mở tuỳ chọn hiển thị Gizmo ở cửa sổ Game.
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 nhưng Unity Editor sẽ chỉ minh hoạ một phần của tia.
Break
Trong một số trường hợp, ta 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 chúng ta thực hiện công việc này.
Hàm Break thực sự trở nên hữu ích khi ta cần dừng chương trình tại một thời điểm cụ thể nào đó. Ta cũng có thể sử dụng công cụ Debugger có sẵn trong Visual Studio hay Mono Develop, nhưng ta sẽ không xem xét được 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, hiển nhiên ta không cần đến các đoạn code Debug nữa vì đã khắc phục triệt để các vấn đề liên quan. Do đó ta 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 chí hàng trăm file script với nhiều dòng code, việc xoá từng dòng Debug thủ công là điều gây lãng phí thời gian và công sức, đồng thời thể hiện sự thiếu chuyên nghiệp trong công việc. Vậy tại sao chúng ta không sử dụng một điều kiện trước khi gõ dòng code Debug thông thường để kiểm soát việc này?
Trong lớp Debug, Unity cung cấp cho chúng ta một thuộc tính để kiểm tra trạng thái của sản phẩm - thuộc tính isDebugBuild. Thuộc tính này sẽ 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), check box "Development Build". Ngoài ra tại cửa sổ Unity Editor, thuộc tính này luôn mang giá trị là true.
2.3. CƠ BẢN VỀ CAMERA TRONG UNITY
2.3.1. Giới thiệu
25
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Camera là một khái niệm quen thuộc đối với mọi nền tảng phát triển game. Với các thao tác chỉnh sửa và chuyển đổi camera, chúng ta có thể tạo ra vô số hiệu ứng độc đáo và ứng dụng vào game một cách sinh động và hấp dẫn, thu hút nhiều người chơi. Bài viết này sẽ giúp các bạn hiểu được các thông số của một camera trong Unity, cùng với một số thao tác thường sử dụng với camera.
2.3.2. Các khái niệm cơ bản
Projection
• •
Góc chiếu của camera. Đối với các loại camera trong game thì có hai loại phép chiếu:
Phép chiếu song song – Orthographic. Phép chiếu xa gần – Perspective.
Orthographic được sử dụng trong các game 2D, các đối tượng sẽ được vẽ đúng tỉ lệ dù ở bất kỳ khoảng cách nào của chiều sâu.
Perspective được ứng dụng trong game 3D, mô phỏng lại thế giới thực của con người. Các vật thể càng ở xa thì tỉ lệ hình ảnh sẽ càng nhỏ và ngược lại.
Field of view (fov)
Là góc mở của camera Perspective. Đối với Orthographic camera, tham số này sẽ được bỏ qua. Fov được mô tả thông qua hình minh hoạ sau:
Với cùng một đối tượng và một vị trí, nếu fov càng lớn, đối tượng sẽ càng nhỏ và ngược lại. Đây là một đặc điểm thú vị và được ứng dụng trong việc tạo hiệu ứng zoom trong môi trường 3D.
Near – Far
Near và Far là hai mặt phẳng cắt, quy định khoảng nhìn thấy của camera tính theo chiều sâu. Một vật muốn được hiển thị phải nằm trong phạm vi nhìn thấy của camera, bao gồm cả khoảng cách từ camera đến vật đó. Nếu quá gần hoặc quá xa, camera sẽ không thu được hình ảnh để hiển thị lên màn hình.
Tuỳ chỉnh tại Inspector
26
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
• Clear Flag: Quy định cách thức xoá các đối tượng. Trong 3D game tuỳ chọn
Tại Inspector, chúng ta có thể chỉnh sửa các thuộc tính minh hoạ ở trong hình. Chi tiết một số thuộc tính thường dùng như sau:
• •
Skybox thường được sử dụng và tuỳ chọn Solid color đối với game 2D. • Background: Màu nền sau khi các đối tượng đã được render. • Culling Mask: Quy định các layer sẽ được vẽ hoặc ẩn đi. Bạn đọc có thể xem thêm về cách tạo layer qua bài viết GameObject - Thao Tác Với C# Script.
Projection: Chọn phép chiếu cho camera. Size: Kích thước của camera. Tuỳ chọn này chỉ có khi phép chiếu được chọn là Orthographic.
• Viewport Rect: Kích thước của camera và vị trí của nó trên màn hình. Các giá trị
Field of view: Góc đo tính bằng độ, là góc đo chiều rộng góc nhìn của Camera. • • Clipping Planes: Bao gồm hai thuộc tinh là Near và Far, là khung nhìn thấy của camera.
này sử dụng hệ toạ độ Screen Coordinate (giới thiệu ở phần dưới).
2.3.3. Một số thao tác trong C# Script
Thông thường, các thuộc tính quy định tính chất của camera sẽ ít khi được chỉnh sửa trong script. Tuy nhiên, ta hoàn toàn có thể thay đổi chúng trong Runtime để tạo ra các hiệu ứng như Camera Zoom, thay đổi màu sắc Background qua từng màn chơi, … Các hiệu ứng này là đặc thù của từng game nên tôi sẽ chỉ liệt kê một số thuộc tính chính của Camera để bạn tham khảo. Chi tiết các bạn có thể xem thêm trong đường dẫn ở cuối bài viết.
Thuộc tính
Chức năng
main
Trả về đối tượng camera đầu tiên được gắn tag "MainCamera".
27
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Thuộc tính
Chức năng
allCameras
Trả về tất cả các đối tượng camera đang được kích hoạt.
allCamerasCount
Số lượng camera trong scene hiện tại.
aspect
Tỉ lệ màn hình, tính bằng công thức aspect = width / height.
backgroundColor
Màu nền.
clearFlags
Cờ quy định việc xoá camera sau mỗi frame.
fieldOfView
Góc nhìn của camera, sử dụng cho phép chiếu Perspective.
orthographicSize
Kích thước camera, sử dụng với phép chiếu Orthographic.
cameraToWorldMatrix
Ma trận 4x4 biến đổi toạ độ trong không gian camera sang toạ độ thế giới game.
worldToCameraMatrix
Ma trận 4x4 biến đổi toạ độ thế giới game sang toạ độ trong không gian camera.
2.3.4. Các hệ toạ độ trong Unity
World Coordinate
• 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.
Là hệ toạ độ của thế giới thực trong game. Hệ toạ độ này tuân theo quy tắc bàn tay trái:
Toạ độ của đối tượng được thể hiện trong thành phần Transform chính là toạ độ trong World coordinate. Các đối tượng con còn có một toạ độ 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ệ toạ độ của camera. Trong Unity, các trục toạ độ 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ó toạ độ (0, 0), góc trên bên phải có toạ độ (1, 1).
Các giá trị này có thể được chỉnh sửa thông qua Viewport Rect trong Inspector của đối tượng Camera.
Screen Coordinate
28
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Screen Coordinate là hệ toạ độ 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ó toạ độ (0, 0) trong khi góc trên bên phải là (screenWidth – 1, screenHeight – 1).
UI Coordinate
Là hệ toạ độ của các đối tượng UI, giá trị của các trục toạ độ sẽ nằm trong khoảng (0, 1). Góc trên bên trái có toạ độ (0, 0) và góc dưới bên phải là (1, 1).
Chuyển đổi giữa các hệ toạ độ trong Unity
Trong khi thao tác với các đối tượng thông qua script, nhiều trường hợp ta nhận được input từ các sự kiện click chuột, touch, … và dùng nó để thao tác với các đối tượng. Thông thường các sự kiện này sẽ trả về toạ độ của Screen space. Vậy làm sao chúng ta có thể chuyển đổi sang World space để thao tác được với các đối tượng một cách chính xác?
• Camera.WorldToScreenPoint • Camera.WorldToViewportPoint • Camera.ScreenToViewportPoint • Camera.ScreenToWorldPoint • Camera.ViewportToScreenPoint • Camera.ViewportToWorldPoint
Unity hỗ trợ sẵn cho chúng ta 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 1 camera):
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ệ toạ độ cần chuyển.
• GUIUtility.ScreenToGUIPoint • GUIUtility.GUIToScreenPoint
Đối với UI Coordinate, Unity hỗ trợ chuyển từ UI sang Screen Coordinate và ngược lại. Do đó chúng ta có thể sẽ cần thực hiện hai thao tác liên tiếp để thực hiện được điều cần thiết. Các hàm đó như sau:
2.4. GAMEOBJECT - THAO TÁC VỚI C# SCRIPT
2.4.1. Giới thiệu
GameObject là một khái niệm cơ bản trong Unity. Mọi đối tượng trong game, từ Camera đến các đối tượng UI, hay các đối tượng khác trong game được hiển thị trong cửa sổ Hierarchy đều là các GameObject. Các loại đối tượng khác nhau sẽ có những thao tác đặc thù riêng, nhưng là GameObject thì sẽ đều có những phương thức và thuộc tính chung của GameObject. Và đó chính là mục đích của bài viết – giới thiệu các thành phần chung nhất của một GameObject và các thao tác với C# Script.
29
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 2.4.2. Thuộc tính và hàm cơ bản
tag – name
Unity sử dụng hai cơ chế để lưu trữ và quản lý danh sách các GameObject, được hiển thị trong cửa sổ Hierarchy. Thuộc tính name được kế thừa lại từ lớp Object, lưu trữ tên của đối tượng được hiển thị ở Hierarchy.
Thuộc tính tag được sử dụng để phân loại và định danh các GameObject, phải được định nghĩa sẵn trước khi sử dụng. Các đối tượng chưa được phân loại sẽ được gắn mặc định tag “Untagged”. Để định nghĩa tag, ta có thể sử dụng Tag Manager tại cửa sổ Inspector của bất kỳ đối tượng nào.
Chọn nút Add Tag, bấm chọn nút dấu + để thêm một tag mới và đặt tên cho nó:
30
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Lưu ý: Name không bắt buộc khác nhau giữa các đối tượng, nhưng tôi khuyến khích các bạn nên đặt tên cho mỗi đối tượng khác nhau để dễ phân biệt và xử lý trong Script. Cách đặt tên phổ biến là 1 tên chung và 1 index cho từng loại đối tượng.
Name và Tag được sử dụng nhiều trong Script để chọn được đối tượng cần thao tác.
Layer
Tại Tag Manager, chúng ta cũng có thể tạo ra các Layer và Sorting Layer để thuận tiện trong việc quản lý các đối tượng. Sorting Layer được sử dụng để phân lớp hiển thị các đối tượng và sử dụng với thành phần SpriteRenderer.
Layer được sử dụng để phân lớp đối tượng và tuỳ chọn hiển thị trong Camera. Một số lớp được Unity tạo sẵn cơ bản là đủ với các nhu cầu thông thường của lập trình viên.
Active
Hai thuộc tính activeInhierarchy và activeSelf được sử dụng để kiểm tra trạng thái hoạt động của đối tượng. Đối với việc thiết lập trạng thái, ta sử dụng hàm SetActive với giá trị truyền vào là true/false, là trạng thái cần thiết để thiết lập cho đối tượng đó.
Nếu đối tượng cha có trạng thái active là false thì dù bạn có thay đổi trạng thái của đối tượng con, chúng vẫn sẽ trả về giá trị false.
isStatic
Một số đối tượng trong game, ví dụ như bản đồ hay các chướng ngại vật tĩnh đều không có khả năng di chuyển. Do đó chúng ta nên thiết lập trạng thái Static cho các đối tượng đó để tiết kiệm tài nguyên. Các đối tượng Static sẽ được tiền xử lý và gom chung thành một đối tượng gọi là Batching object, nhờ đó sẽ tiết kiệm được một số lượng Draw call (Draw call là số lần render một đối tượng, mỗi lần render là một draw call).
31
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Tương tự như trạng thái Active của đối tượng, trong Unity Editor có 1 checkbox dùng cho việc thiết lập trạng thái static của đối tượng. Thuộc tính isStatic dùng để kiểm tra trạng thái tĩnh của đối tượng, từ đó bỏ qua các thao tác xử lý với đối tượng này.
2.4.3. Thao tác với Component
transform
Thành phần Transform là một thành phần bắt buộc, không thể thêm hoặc xoá đối với bất kỳ GameObject nào. Transform lưu trữ các thông tin về vị trí, phép quay và tỉ lệ của đối tượng. Ngoài ra Transform còn có một số thuộc tính và phương thức hay để thao tác với GameObject. Chi tiết về các tính năng này sẽ được hướng dẫn ở một bài viết khác.
GetComponent và GetComponents
Hàm GetComponent sẽ trả về component đầu tiên cần tìm của đối tượng, trong khi GetComponents sẽ tra về một danh sách toàn bộ các component cần tìm. Nếu không tìm được sẽ trả về null hoặc mảng không có phần tử tương ứng.
public Component GetComponent(Type type); public Component GetComponent(string type);
1. 2.
Nguyên mẫu của hàm như sau:
public Type GetComponent
1.
Hàm có thể nhận vào giá trị kiểu Type hoặc kiểu string đều được chấp nhận. Tuy nhiên, để tối ưu ta nên sử dụng kiểu Type trong hầu hết mọi trường hợp. Một cú pháp khác để lấy một thành phần với Type như sau:
Cú pháp này sử dụng template để lấy ra các component. Đây là cách thông dụng nhất được lập trình viên sử dụng trong C# Script.
comp = gameObject.GetComponent("Comp") as Comp; comp.xyz = 1.0f;
1. 2.
Một trường hợp bắt buộc phải sử dụng string để tìm kiếm là khi cần truy xuất các lớp được định nghĩa trong C# Script bằng Javascript hoặc ngược lại. Do tính đa ngôn ngữ, Unity cho phép lập trình viên sử dụng C# hoặc Javascript để lập trình và các ngôn ngữ này không thể được liên kết trực tiếp bằng Type. Lúc này ta sẽ sử dụng string và thao tác tương tự như sau:
Tìm kiếm Component trong các đối tượng cha hoặc con
32
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Unity cũng cung cấp sẵn một số phương thức giúp ta có thể thao tác được với component thuộc đối tượng cha/con của nó. Cách thức hoạt động tương tự như GetComponent và GetComponents.
• GetComponentInChildren. • GetComponentsInChildren. • GetComponentInParent. • GetComponentsInParent.
Các hàm như sau:
Các hàm này chỉ có thể được sử dụng với Type của component, kiểu string không được chấp nhận.
2.4.4. Tìm kiếm GameObject
Tìm kiếm với name
public static GameObject Find(string name);
1.
Hàm Find được Unity thiết kế để tìm kiếm một đối tượng trong cửa sổ Hierarchy. Cú pháp của hàm như sau:
Hàm sẽ trả về giá trị GameObject là chính đối tượng được tìm thấy, nếu không có đối tượng có tên cần tìm, hàm sẽ trả về null.
Lưu ý: Hàm sẽ chỉ trả về đối tượng được active trong cửa sổ Hierarchy. Mọi đối tượng
khác cho dù có tên đúng yêu cầu nhưng chưa được active cũng sẽ bị bỏ qua.
Chuỗi name truyền vào chính là tên của đối tượng cần tìm. Nếu chuỗi kí tự có chứa kí hiệu "/", Unity sẽ xem đó là đường dẫn của đối tượng ở trong cửa sổ Hierarchy.
Tìm kiếm với tag
Để tìm kiếm tag, với sử ta
GameObject stdioObject = GameObject.FindGameObjectWithTa
1.
g("StdioObject");
dụng hàm FindGameObjectWithTag hoặc FindGameObjectsWithTag tương ứng để tìm một hay toàn bộ các đối tượng có tag cần tìm. Cách sử dụng tương tự như hàm Find đề cập ở trên, sử dụng tag để tìm kiếm đối tượng.
33
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Lưu ý: Tìm kiếm với name và tag đều có thể cho ra kết quả giống nhau, nhưng về chi phí cần sử dụng thì tìm kiếm với tag sẽ tối ưu hơn. Do đó trong mọi trường hợp, bạn hãy sử dụng tag cho việc tìm kiếm nếu khả thi.
2.4.5. Khởi tạo và giải phóng GameObject
Khởi tạo
CreatePrimitive
Khởi tạo một đối tượng được định nghĩa sẵn bởi Unity. Đối tượng được tạo ra sẽ có sẵn Mesh Renderer và Collider tương ứng với hình dạng của đối tượng.
GameObject plane = GameObject.CreatePrimitive(PrimitiveT
1.
ype.Plane);
GameObject cube = GameObject.CreatePrimitive(PrimitiveTy
2. 3.
pe.Cube);
cube.transform.position = new Vector3(0, 0.5f, 0);
GameObject sphere = GameObject.CreatePrimitive(Primitive
4. 5. 6.
Type.Sphere);
sphere.transform.position = new Vector3(0, 1.5f, 0);
7.
Cách sử dụng hàm như sau:
Danh sách các PrimitiveType được hỗ trợ:
Sphere
Đối tượng hình cầu, bán kính bằng 1.
Capsule
Đối tượng hình capsule.
Cylinder
Đối tượng hình trụ.
Cube
Tạo ra một khối lập phương có độ dài cạnh bằng 1.
Plane
Tạo ra một mặt phẳng, được sử dụng trong việc xây dựng map.
Quad
Đối tượng quad (4 điểm trong không gian).
Instantiate
Instantiate được sử dụng để clone một đối tượng được lập trình viên xây dựng sẵn. Nguyên mẫu hàm như sau:
34
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
public static Object Instantiate(Object original, Vector
1.
3 position, Quaternion rotation);
public static Object Instantiate(Object original);
2.
Nếu không truyền vào position và rotation, hàm sẽ lấy các giá trị của đối tượng cơ sở để gán cho đối tượng mới, hoặc trả về Vector3.zero và Quaternion.identity nếu không khả dụng. Tìm hiểu thêm về Vector3 và Quaternion tại Vector Trong Unity và C# Script - Lớp Quaternion.
Instantiate cũng sao chép được các đối tượng "inactive". Tính chất này sẽ được giữ
lại và bản sao được tạo ra cũng sẽ có trạng thái "inactive".
Khi sử dụng Instantiate, toàn bộ các đối tượng con và cấu trúc sẽ được sao chép với đúng tính chất của chúng. Tuy nhiên, đối tượng cha (nếu có) sẽ được đặt là null. Do đó đối tượng được clone có thể sẽ không nằm chung vị trí với đối tượng ban đầu.
Giải phóng
Destroy
Hàm Destroy được sử dụng để giải phóng một GameObject, component hay tài nguyên được sử dụng trong game. Hàm nhận vào giá trị là đối tượng cần giải phóng, cùng với thời gian delay (mặc định là 0). Việc huỷ đối tượng sẽ được delay cho đến khi kết thúc hàm Update, nhưng đối tượng sẽ được huỷ trước quá trình render.
// Destroy current game object Destroy (gameObject);
// Removes this script instance from the game object Destroy (this);
// Removes the rigidbody from the game object
Destroy (GetComponent
// Destroy the game object in 5 seconds after loading th
Destroy (gameObject, 5);
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. e object 11.
DontDestroyOnLoad
Dưới đây là ví dụ để bạn hiểu về chức năng và cách sử dụng hàm Destroy:
Khi thực hiện chuyển scene, các đối tượng ở scene cũ sẽ được giải phóng hoàn toàn. Hàm này giúp chỉ định một đối tượng nào đó sẽ không bị giải phóng khi chuyển scene. Cách sử dụng hàm như sau:
35
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
DontDestroyOnLoad(targetObject);
1.
Lưu ý: Đối với các GameObject của scene, hàm chỉ có tác dụng nếu GameObject đó không là con của bất kì đối tượng nào. Toàn bộ cấu trúc và các đối tượng con của nó sẽ được giữ lại qua mọi thao tác chuyển scene.
targetObject chính là đối tượng sẽ được giữ lại khi chuyển scene. Đối tượng này vẫn có thể được giải phóng bằng hàm Destroy.
2.5. VECTOR TRONG UNITY
2.5.1. Giới thiệu
Trong môi trường game 3D, một vị trí hay vector được biểu diễn bằng 3 con 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 vector 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. Bài viết này sẽ giới thiệu và hướng dẫn các đối tượng cơ bản thuộc lớp Vector3 trong Unity.
2.5.2. Các vector đơn vị
Trong một số trường hợp, chúng ta 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, ta 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 lập trình viên, Unity đã hỗ trợ sẵn các vector đơn vị với tên gọi dễ nhớ để thuận tiện trong các thao tác. Danh sách các vector đơn vị như sau:
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).
36
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
// Set default position transform.position = Vector3.zero;
// Translate object through z-axis transform.Translate(Vector3.forward * Time.deltaTime);
1. 2. 3. 4. 5.
Dưới đây là một vài ví dụ thực tế khi sử dụng các vector đơn vị:
2.5.3. Các thuộc tính cơ bản
Vector3 stdioLogoPosition = Vector3.zero;
stdioLogoPosition.x += 10; stdioLogoPosition[2] += 5; // z-axis
1. 2. 3. 4.
Đối với Vector3, việc thay đổi từng thành phần bên trong là hoàn toàn được. Bạn có thể 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 đều được chấp nhận. Đố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.
2.5.4. Tính toán độ dài
Lưu ý: đối với vector có độ dài quá nhỏ, Vector3.zero sẽ được trả về thay vì vector cần
tìm.
Thuộc tính normalize hỗ trợ chuyển đổi vector thành vector đơn vị với độ dài là 1, tức là chỉ quan tâm đến chiều của vector. Vector ban đầu sẽ được giữ lại giá trị cũ và thuộc tính sẽ trả về một vector mới. Để chuẩn hoá vector hiện tại, vui lòng xem phần hướng dẫn bên dưới.
. Một thuộc tính magnitude sẽ trả về độ dài của vector, tính bằng công thức khác là sqrMagnitude, trả về giá trị bình phương của magnitude.
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, hoặc tính toán để hai đối tượng không thể tách rời nhau, ... Tuỳ vào tình huống cụ thể mà ta sử dụng cho phù hợp. Khi chỉ cần so sánh giữa hai vector, bạn nên sử dụng sqrMagnitude vì chi phí tính toán căn bậc hai là tương đối cao.
// Make gameObject to default position when near if ((transform.position - position).magnitude <= 0.1f) transform.position = position;
// Destroy gameObject when it not moving anymore
1. 2. 3. 4. 5.
Dưới đây là một số ví dụ thực tế mà tôi đã sử dụng trong các dự án cá nhân của tôi:
37
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game
-----------------------------------------------------------------------------------------------------------------
if(gameObject.GetComponent
6.
itude < 0.1f)
Destroy(gameObject);
7.
2.5.5. Danh sách các hàm hỗ trợ
Max - Min
public static Vector3 Max(Vector3 lhs, Vector3 rhs); public static Vector3 Min(Vector3 lhs, Vector3 rhs);
1. 2.
Nguyên mẫu của hàm như sau:
public Vector3 a = new Vector3(1, 2, 3); public Vector3 b = new Vector3(4, 3, 2); public Vector3 c = new Vector3(0, 5, 1);
void StdioTutorial() {
Debug.Log(Vector3.Max(a, b)); Debug.Log(Vector3.Min(a, c)); Debug.Log(Vector3.Min(a, Vector3.Max(b, c))); //M
1. 2. 3. 4. 5. 6. 7. 8. 9.
in(a, Vector3(4, 5, 2)
}
10.
Hàm Max sẽ so sánh từng thành phần của hai vector và trả về một vector mới với các thành phần là lớn nhất giữa hai vector đó. Hàm Min cũng có chức năng tương tự hàm Max. Bạn đọc xem ví dụ sau đây để hiểu về tính năng của hai hàm trên:
Giá trị trả về tương ứng tại cửa sổ Console như sau:
38
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Tìm hiểu về kĩ thuật Debug cũng như hiển thị giá trị Debug ra màn hình Console của Unity
tại Debug Trong Unity.
MoveTowards - RotateTowards
public static Vector3 MoveTowards(Vector3 current, Vecto
1.
r3 target, float maxDistanceDelta);
public static Vector3 RotateTowards(Vector3 current, Vec
2.
tor3 target, float maxRadiansDelta, float maxMagnitudeDelta);
Lưu ý: Nếu như maxDistanceDelta và maxRadianDelta nhận giá trị âm, vector ban đầu sẽ dần dần di chuyển xa khỏi target. Khi hai vector hoàn toàn ngược nhau cả về độ lớn, thì hàm sẽ dừng lại.
Hai hàm có chức năng di chuyển một vector đế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 vector trong không gian đến vị trí target với góc quay tối đa là maxRadianDelta.
Lerp - Slerp
public static Vector3 Lerp(Vector3 from, Vector3 to, flo
public static Vector3 Slerp(Vector3 from, Vector3 to, fl
1. at t); 2. oat t);
Hai hàm có chức năng tương tự nhau, đều thực hiện nội suy giữa hai vector và trả về một vector mới tương ứng với thành phần t trong hàm.
Với t = 0, hàm sẽ trả về vector from. Với t = 1, hàm sẽ trả về vector 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.
Sự khác nhau về kết quả thu được giữa hai hàm là không nhiều, do đó bạn đọc không cần tìm hiểu sâu về sự khác nhau đó mà có thể sử dụng hai hàm tương đương. Nếu bạn đọc có nhu cầu, vui lòng gửi phản hồi cho tôi để được giải đáp chi tiết hơn.
Dot - Cross
Hàm Dot trả về tích vô hướng giữa hai vector và hàm Cross tra về tích có hướng. Chi tiết về công thức tính Dot và Cross product giữa hai vector nằm ngoài phạm vi bài viết, bạn đọc vui lòng tham khảo link ở cuối bài viết nếu có nhu cầu.
Các hàm khác
39
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Hàm
Chức năng
Angle
Trả về góc đo giữa hai vector tính bằng độ.
Distance
Trả về khoảng cách giữa hai vector.
Chuẩn hoá vector và gán lại giá trị. Vector sẽ mang giá trị mới sau khi sử dụng hàm
Normalize
Normalize.
Thực hiện chiếu một vector lên một vector khác. Tham số onNormal của hàm là hướng của
Project
vector mới.
Thực hiện phép phản xạ một vector lên một mặt phẳng chiếu. Chi tiết về hàm vui lòng xem
Reflect
ở link tham khảo.
Thu phóng một vector với một vector khác. Từng thành phần của hai vector sẽ được nhân
Scale
với nhau tạo thành vector mới.
Operator
Ngoài các hàm và thuộc tính nói 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 vector được dễ dàng hơn. Danh sách các toán tử được hỗ trợ như sau:
Toán tử
Chức năng
operator+
Cộng hai vector.
operator-
Phép trừ giữa hai vector.
operator*
Nhân một vector với một số.
operator/
Chia một vector cho một số.
operator!=
So sánh và trả về true nếu hai vector khác nhau.
operator==
So sánh và trả về true nếu hai vector giống nhau.
40
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- CHƯƠNG 3: CÁC HÀM XỬ LÝ SCRIPT NÂNG CAO
3.1. XỬ LÝ TOÁN HỌC TRONG UNITY
3.1.1. Giới thiệu
Toán học rất quan trọng trong việc phát triển game, cho dù là ở bất cứ nền tảng nào. Đối với Unity, lập trình viên được cung cấp khá nhiều các hàm toán học hỗ trợ lập trình được thuận tiện hơn. Bài viết này sẽ trình bày một số hàm toán học cơ bản được Unity hỗ trợ.
3.1.2. Lớp Mathf
Mathf là một trong số các bộ chứa các biến và hàm toán học tiện ích do Unity cung cấp. Trong bài viết, tôi sẽ hướng dẫn một số hàm phổ dụng với lập trình viên.
Abs
public static float Abs(float value); public static int Abs(int value);
1. 2.
Là hàm trả về giá trị tuyệt đối của tham số. Hai prototype của hàm cho kiểu dữ liệu float và int như sau:
Asin - Acos - Atan - Sin- Cos - Tan
public static float Sin(float f);
1.
Một loạt 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:
Lưu ý: Giá trị f truyền vào là góc lượng giác tính bẳng radian. Do đó chúng ta 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.
Các hàm lượng giác khác cũng được sử dụng tương tự.
Min - Max
Hàm Min sẽ trả về giá trị nhỏ nhất trong danh sách tham số, trong khi hàm Max sẽ trả về giá trị lớn 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.
Đối với việc so sánh hai giá trị, ta có các hàm sau:
41
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
public static float Min( float a, float b); public static float Max( float a, float b);
1. 2.
public static float Min(params float[] values); public static float Max(params float[] values);
1. 2.
Nếu có nhiều hơn hai giá trị cần so sánh, ta sử dụng mảng để chứa các giá trị đó và sử dụng hàm được override như sau:
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. Tuỳ vào nhu cầu, bạn có thể sử dụng hàm Clamp để giới hạn bất cứ khả năng nào của một đối tượng.
• Giới hạn toạ độ 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.
Một số ví dụ về ứng dụng của hàm Clamp:
public static float Clamp(float value, float min, float
1. max);
Prototype của hàm như sau:
Trong đó, min và max là hai giá trị mút. 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 của nó. Nó làm ta liên tưởng đến trò chơi bóng bàn, khi quả bóng được đánh qua lại giữa hai người chơi.
public static float PingPong(float t, float length);
1.
Hàm PingPong trong Mathf có nguyên mẫu của hàm như sau:
Trong đó, length chính 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].
3.1.3. Một số hàm khác và chức năng
42
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Ngoài các hàm trên, lớp Mathf còn một số hàm khác khá hay được đề cập trong bảng sau:
PI
Hằng số PI (3.1415926535...), không được chỉnh sửa.
Deg2Rag
Hằng số chuyển đổi từ độ thông thường sang radians.
Rag2Deg
Hằng số chuyển đổi từ radians sang độ thông thường.
Approximately
So sánh giá trị của hai số thực nếu chúng tương đương nhau.
IsPowerOfTwo
Trả về true nếu giá trị là luỹ thừa của 2.
ClosetPowerOfTwo
Trả về giá trị luỹ thừa của 2 gần nhất.
NextPowerOfTwo
Trả về giá trị luỹ thừa của 2 lớn hơn gần nhất.
Log
Tính giá trị logarit của một số với cơ số tuỳ ý.
Log10
Tính 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
Tính căn bậc hai của một số.
Pow
Tính luỹ thừa của một số với số mũ bất kỳ.
3.2. C# SCRIPT - LỚP TIME
3.2.1. Giới thiệu
Thời gian là một khái niệm quen thuộc trong mọi lĩnh vực của đời sống. Đối với lâp trình cũng vậy, việc nắm bắt và xử lý thời gian rất quan trọng đối với mọi chương trình. Chúng ta xử lý thời gian để giới hạn tốc độ của game (FPS), xử lý lực bắn, ứng dụng để tính toán và di chuyển đối tượng một cách mượt mà nhất, … Trong Unity, các thao tác này trở nên đơn giản hơn bao giờ hết bởi Unity đã hỗ trợ sẵn hầu hết các thuộc tính cơ bản có liên quan đến việc xử lý thời gian. Bài viết sau sẽ giới thiệu về lớp Time trong Unity và một số ứng dụng của nó.
3.2.2. 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 trong Unity. Lớp Time không bao gồm bất kỳ phương thức nào, nhưng có một vài thuộc tính có thể chỉnh sửa để tương thích và phù hợp với chương trình.
3.2.3. Một số thuộc tính quan trọng
43
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
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.
Lưu ý: Time.time sẽ trả về giá trị của Time.fixedTime nếu được gọi trong hàm
FixedUpdate.
Thuộc tính này sẽ trả về giá trị như nhau cho dù được gọi ở các thời điểm khác nhau trong cùng một vòng lặp.
deltaTime
Khi được gọi trong FixedUpdate, thuộc tính sẽ trả về giá trị của fixedDeltaTime thuộc
lớp Time.
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 đó. 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.
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, ta 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.
Lưu ý: Hàm FixedUpdate (nếu có) sẽ không được gọi khi timeScale nhận giá trị 0.
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.
maximumDeltaTime
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 – GarbageCollector và các thao tác xử lý vật lý tốn kém 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, lag khi chơi game.
Tôi chưa có điều kiện để sử dụng nhiều thuộc tính này, nhưng giá trị hợp lý để thiết lập cho thuộc tính vào khoảng 1/10 – 1/3 giây. Các thông tin có thể được tìm thấy trên trang chủ chính thức của Unity.
3.2.4. Các thuộc tính khác và chức năng
44
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình 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 đỡ hao tốn tài nguyên hơn.
smoothDeltaTime
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 như thế.
Ví dụ nhỏ dưới đây sẽ giúp các bạn hiểu rõ hơn về chức năng của thuộc tính smoothDeltaTime:
deltaTime
smoothDeltaTime
...
...
0.2
0.2
0.2
0.2
0.2
0.2
0.2
0.4833
0.9
0.4833
0.8
0.4833
0.4
0.4833
0.4
0.4833
0.2
0.4833
0.2
0.2
...
...
Ở bảng trên, ta thấy mức độ thay đổi của smoothDeltaTime là nhỏ hơn tại hầu hết thời điểm so với deltaTime.
45
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
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, bạn 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 (scene) được khởi chạy cho đến thời gian bắt đầu của frame hiện tại. Để tìm hiểu về scene và các thao tác chuyển scene, vui lòng tham khảo bài viết Chuyển Đổi Scene Trong Unity của tác giả Lê Thắm.
3.3. C# SCRIPT - LỚP RANDOM
3.3.1. Giới thiệu
Các yếu tố ngẫu nhiên là điều cơ bản và là nền tảng để xây dựng nên trí thông minh nhân tạo (AI) trong game. Lẽ dĩ nhiên, AI càng hoàn hảo càng tốt. Nhưng game thủ chẳng thể nào chịu được nếu như một AI quá thông minh, không có một kẽ hở nào để khai thác và xây dựng chiến thuật.
Ngoài ra, yếu tố ngẫu nhiên còn được sử dụng để tạo nên điều bất ngờ trong một vài trường hợp. Do đó có thể nói Random là một phần không thể thiếu đối với lập trình game.
Riêng đối với Unity, lớp Random được cung cấp sẵn để tạo các yếu tố ngẫu nhiên đối với nhiều trường hợp.
3.3.2. Random một số
Hàm Range
public static float Range(float min, float max); public static int Range(int min, int max);
1. 2.
Nguyên mẫu hàm như sau:
46
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 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 bao giờ trả về giá trị max.
Random.Range được ứng dụng rất nhiều trong Unity. Tuỳ theo nhu cầu và logic game mà ta có thể tuỳ biến sử dụng một cách phù hợp.
value
Thuộc tính này sẽ 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].
3.3.3. Random một vector
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 toạ độ 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, ta cho đối tượn đó 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 toạ độ đến điểm trả về luôn có độ dài bằng 1.
• Giả sử thế giới game của bạn 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 đó, ta có thể sử dụng onUnitSphere để xác định ngẫu nhiên một toạ độ 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, ta
Bản thân tôi chưa có dịp sử dụng đến thuộc tính này trong các dự án, nhưng một vài ứng dụng mà tôi có thể nghĩ ra được:
public float m_speed;
void RandomDirection()
{
gameObject.GetComponent
1. 2. 3. 4.
UnitSphere * m_speed;
}
5.
thao tác tương tự như sau:
47
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 3.3.4. Random một góc
Hai thuộc tính Random.rotation và Random.rotationUniform đều trả đề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ư bạn sẽ 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ị.
Với nhu cầu thông thường, ta không cần thiết phải quan tâm đến sự khác nhau của chúng mà có thể sử dụng thay thế cho nhau.
3.3.5. Thiết lập máy sinh số ngẫu nhiên
Nếu tìm hiểu sâu hơn về cách thức xây dựng lớp Random và các giá trị trả về ngẫu nhiên, bạn sẽ thấy các giá trị không hoàn toàn 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 mà cá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ả (pseudo-random). Các giá trị được trả về sẽ 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, chương trình sẽ tự quy định giá trị seed này 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 ta sẽ 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 cũng khá hữu ích.
Random.seed = value;
1.
Một trường hợp khá thực tế mà tôi có thể ứng dụng việc quản lý này 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 bạn chơi đi chơi lại nhiều lần, do đó, ta có thể đảm bảo các giá trị ngẫu nhiên sẽ giống nhau bằng cách thiết đặt giá trị seed khi load tài nguyên của level. Trong Unity, giá trị seed được thiết đặt tương tự như sau:
Trong đó, value có kiểu dữ liệu là số nguyên.
Lúc này, việc quản lý level sẽ bao gồm luôn việc xử lý biến seed này, đả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.
48
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 3.4. C# SCRIPT - LỚP QUATERNION
3.4.1. Giới thiệu
Quaternion là một thuật ngữ có lẽ khá xa lạ đối với nhiều lập trình viên. Thuật ngữ này được khởi nguồn từ lý thuyết toán học, được ứng dụng trong các phép quay không gian. Trong Unity, Quaternion được sử dụng để biểu diễn phép quay của mọi đối tượng. Bài viết này sẽ hướng dẫn các bạn một số chức năng mà Unity cung cấp thông qua lớp Quaternion.
3.4.2. Tổng quan
Q = w + xi + yj + zk
1.
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:
Với i, j, k là các thành phần ảo, được xem như ba vector đơn vị của các trục toạ độ tương ứng. Để hiểu sâu sắc về Quaternion, bạn đọc cần có một lượng kiến thức toán học về số phức, lượng giác, … Do đó bài viết sẽ không đề cập chi tiết đến Quaternion về mặt toán học.
Trong Unity, 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 kể trên. Các thành phần này không nên được chỉnh sửa độc lập đối với lập trình viên thông thường.
void Example() {
public Quaternion p;
p[3] = 0.5f;
p.x = 0.75f;
}
1. 2. 3. 4. 5. 6. 7.
Để truy xuất các thành phần đó, bạn có thể sử dụng qua 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. Ví dụ như sau:
Ngoài ra còn những cách khác để 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. Đây là cách thức biểu diễn thông thường và dễ hiểu hơn của phép quay.
49
THS. NGUYỄN VĂN KHƯƠNG
3.4.3. Các hàm quan trọng và chức năng -----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
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 toạ độ trùng với hệ trục toạ độ thế giới (World axes) hoặc đối tượng chứa nó (Parent axes).
LookRotation
public static Quaternion LookRotation(Vector3 forward, V
1.
ector3 upwards = Vector3.up);
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. Nguyên mẫu hàm như sau:
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 của đối tượng sẽ hướng lên trên so với forward. Với nhu cầu sử dụng thông thường, bạn nên giữ nguyên giá trị mặc định của upwards để có được hiệu ứng đẹp nhất.
Angle
Trả về một góc (tính bằng độ - degree) là góc giữa hai Quaternion. Công thức tính góc này khá phức tạp và dựa trên cơ sở toán học, do đó bài viết sẽ không đề cập sâu về vấn đề này. Bạn đọc có thể tham khảo đường dẫn ở cuối bài viết để tìm hiểu thêm về công thức.
public static float Angle(Quaternion a, Quaternion b);
1.
Nguyên mẫu của hàm:
Euler
public static Quaternion Euler(float x, float y, float z
1.
);
Thao tác với Quaternion qua 4 kênh x, y, z, w thật sự là một điều gì đó khá mơ hồ và khó hiểu. Do đó, Unity cung cấp một hàm giúp ta thao tác và điều chỉnh góc quay của đối tượng thông qua chính ba trục toạ độ của đối tượng đó. Cú pháp của hàm như sau:
Trong đó, x, y, z là ba góc quay tương ứng với ba trục toạ độ của đối tượng. Các góc quay sử dụng đơn vị là độ.
Lerp - Slerp
50
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 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
public static Quaternion Slerp(Quaternion from, Quaterni
1.
on to, float t);
public static Quaternion Lerp(Quaternion from, Quaternio
2.
n to, float t);
hình theo thời gian. Cách sử dụng và chức năng cả hai hàm là tương đối giống nhau:
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. Nhưng bù lại, 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.
51
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- CHƯƠNG 4: XỬ LÝ HIỆU ỨNG
4.1. TẠO SCROLLING BACKGROUND VỚI UNITY
4.1.1. Giới thiệu
Có nhiều cách để xây dựng một phông nền (background) cho game. Đó có thể là một bức ảnh tĩnh, một phần của bức ảnh lớn hay một bức ảnh nhỏ được lặp đi lặp lại (Scrolling background). Scrolling background sẽ tạo cảm giác chuyển động xa vô tận của nhân vật, rất phù hợp với các game thuộc thể loại endless-running. Bài viết này sẽ hướng dẫn các bạn một cách hiện thực đơn giản scrolling background trên Unity Game Engine.
Trong bài viết có sử dụng ảnh không gian vũ trụ, các bạn có thể download tại STDIO_BACKGROUND hoặc
liên hệ khuongdx@gmail.com để copy.
4.1.2. Tài nguyên sử dụng
4.1.3. Hiện thực
Khởi tạo project và setup môi trường làm việc
Đầu tiên các bạn khởi tạo một project Unity 2D
Sau khi khởi tạo project, chúng ta sẽ tạo một số folder như sau để tiện quản lý game:
52
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Các tài nguyên của game chúng ta đặt trong folder Resources.
Lưu ý: Background tôi sử dụng trong bài phù hợp với các thiết bị có kích thước màn hình có tỉ lệ 4 : 3. Do đó các bạn cần tuỳ chỉnh lại màn hình game cho phù hợp.
53
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Tạo background
Bước 1: Kéo background từ trong folder Resources vào cửa sổ Hierarchy.
54
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Bước 2: Tạo Prefab cho background. Các bạn chỉ cần kéo thả ngược background từ cửa sổ Hierarchy vào cửa sổ Project. Để thuận tiện trong việc quản lý thì khuyến khích các bạn lưu trữ Prefab này trong folder tương ứng đã tạo ra ở trên.
Dễ nhận thấy sau khi tạo Prefab, Background trong Hierarchy đã chuyển sang màu xanh. Đây là ưu điểm của Prefab khi tạo được liên kết với các Game Object trong Hierarchy. Bằng cách thêm/xoá/chỉnh sửa các component trong Prefab, các Game Object tương ứng cũng sẽ thay đổi theo.
Bước 3: Tạo background thứ hai từ Prefabs.
55
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Bước 4: Điều chỉnh background. Sau khi thực hiện xong các thao tác trên, hai background của chúng ta sẽ nằm chồng lên nhau. Các bạn có thể tự điều chỉnh cho hai background này nằm liền kề nhau bằng thao tác chuột, hoặc tham khảo các thông số tôi đã điều chỉnh sẵn:
Bước 5: Điều chỉnh camera. Các bạn cần điều chỉnh lại thông số Size của Main Camera cho phù hợp với màn hình game. Vùng nằm phía trong của camera sẽ là những gì mà người dùng nhìn thấy và sẽ hiển thị trong cửa sổ Game. Thông số tôi đã thiết lập sẵn như sau:
Tới đây các bạn đã tạo thành công hai background liên tiếp. Nếu chạy demo lúc này sẽ không có gì xảy ra do chúng ta chưa viết đoạn script cập nhật lại toạ độ của background.
56
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Scrolling background
Để tạo scrolling background, chúng ta sử dụng script để tạo điều kiện cho background di chuyển phù hợp sau khi ra khỏi vùng nhìn thấy của camera. Tôi sẽ sử dụng ngôn ngữ C# để minh hoạ.
Các bạn tạo một file C# Script (Create -> C# Script), đặt trong folder Scripts đã tạo ở trên.
Double click vào file Script vừa tạo, mặc định sẽ mở ra MonoDevelop – được tích hợp với Unity Engine. Các bạn thay thế hàm Update() mặc định bằng đoạn code sau:
using UnityEngine; using System.Collections;
public class Background : MonoBehaviour {
//Initialize variables public float speed; public int background_count; public float background_height;
// Use this for initialization void Start () {
}
// Update is called once per frame void Update () { //Move down transform.Translate (Vector3.down * speed * Time.delta Time);
Vector3 pos = transform.position; if (pos.y < -background_height) { pos.y += background_height * background_count; transform.position = pos; }
} }
Sau khi tạo xong đoạn script, các bạn kéo đoạn script này vào background trong thư mục Prefabs (kéo thả vào cửa sổ Inspector). Lúc này cả hai background trong cửa sổ Hierarchy đều được tích hợp đoạn script này.
57
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Các bạn điều chỉnh các thông số của đoạn script trong Prefabs > Background như sau:
Ngay lập tức, hai background trong Hierarchy cũng sẽ được cập nhật tương ứng.
Đến đây các bạn đã hiện thực xong scrolling background với Unity. Các bạn có thể chạy thử demo để xem thành quả đạt được.
58
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
59
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 4.2. TẠO ANIMATION VỚI UNITY
4.2.1. Giới thiệu
Animation là một yếu tố không thể thiếu được trong bất kỳ game nào. Animation giúp lập trình viên diễn tả hành động của các nhân vật một cách chân thực và sống động hơn. Bài viết sẽ hướng dẫn các bạn cách tạo ra một animation đơn giản trong Unity.
4.2.2. Tài nguyên sử dụng
Tôi sẽ hiện thực animation trên nền scrolling background mà tôi đã hướng dẫn trong bài viết Tạo Scrolling Background Với Unity. Do đó khuyến khích các bạn hoàn thành phần scrolling background trước khi thực hiện các hướng dẫn này.
Trong bài viết có sử dụng hình ảnh phi thuyền, các bạn có thể copy về: spaceship.png.
4.2.3. Hiện thực
Setup môi trường làm việc
Sau khi download về, các bạn mở project bằng Unity. Các bạn chọn Scene STDIO_StarWar và
chọn Open.
60
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Các bạn import file spaceship.png đã download ở trên vào thư mục Resources.
Sử dụng Sprite Editor
File hình ảnh sử dụng trong bài viết bao gồm nhiều frame hình, hỗ trợ cho việc tạo animation.
Các bạn hoàn toàn có thể tạo animation một cách thủ công. Tuy nhiên Unity đã hỗ trợ công cụ tạo Animation khá hiệu quả và dễ sử dụng. Do đó trong bài viết tôi sẽ hướng dẫn các bạn cách sử dụng công cụ Sprite Editor.
Các bạn chọn file spaceship.png. Trong cửa sổ Inspector hiện ra các tuỳ chọn thao tác với file. Phần Sprite Mode chúng ta sẽ chọn Multiple, sau đó nhấn vào nút Sprite Editor để mở ra cửa sổ Sprite Editor.
61
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Giao diện của Sprite Editor khá đơn giản và dễ thao tác. Các bạn chọn button Slice, ở mục Type chọn Grid để tạo các frame có kích thước bằng nhau. Các bạn điều chỉnh theo thông số tôi đã thiết lập sẵn rồi nhấn vào button Slice phía dưới.
62
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Mỗi frame sẽ được bao phủ bởi một hình chữ nhật. Sau khi thực hiện các bước trên, click
chọn Apply để hoàn tất.
Ngoài tuỳ chọn Grid, Unity còn hỗ trợ cắt Sprite tự động bằng tuỳ chọn Automatic, hoặc bạn có thể tự chia sprite bằng cách quét chọn vùng cần lấy một cách trực quan.
Nếu để ý các bạn sẽ thấy bên cạnh file resource có một dấu mũi tên. Click vào dấu mũi tên này sẽ hiện ra các frame chúng ta vừa cắt xong bằng Sprite Editor.
63
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Animation
Sau khi thực hiện xong, chúng ta sẽ bắt tay vào tạo animation cho phi thuyền.
Nếu sử dụng phiên bản Unity từ 4.6 trở về trước, các bạn nhấn giữ phím Ctrl và chọn 4 frame: spaceship_0, spaceship_1, spaceship_2 và spaceship_3, sau đó kéo thả 4 frame này vào trong cửa sổ Hierarchy. Cửa sổ Create New Animation hiện ra. Các bạn chọn đường dẫn để lưu trữ file animation để sử dụng sau này, đặt lại tên và nhấn chọn Save.
Trong bài viết, tôi tạo thêm một sub folder Animation đặt trong folder Assets sẵn có để tiện lưu trữ và quản lý. File animation tôi đặt tên là idleShip.anim để thể hiện trạng thái “chờ” của phi thuyền.
Tới đây về cơ bản chúng ta đã tạo xong một animation đơn giản cho phi thuyền. Việc còn lại là tuỳ
chỉnh lại toạ độ của phi thuyền cho phù hợp. Thông số tôi thiết lập sẵn như sau:
Đối với phiên bản Unity 5, Unity tự động tạo animation khi bạn kéo thả các frame vào Hierarchy. Animation được tạo ra sẽ nằm cùng thư mục với spritesheet sử dụng.
64
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Cuối cùng các bạn nhấn Play để xem thành quả đạt được.
65
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 4.3. ÂM THANH TRONG UNITY
4.3.1. Giới thiệu
Đối với bất kỳ game nào, âm thanh là một trong những yếu tố không thể thiếu. Âm thanh như một chất xúc tác nhằm kích thích các giác quan của người chơi giúp cho game trở nên thu hút và hấp dẫn hơn.
Audio Source Component
game. Để Source, trong bạn các sử
Audio Source là một built-in component được Unity xây dựng sẵn để hỗ trợ phát các file âm thanh chọn Add dụng Audio Component → Audio → Audio Source. Các thuộc tính của Audio Source được hiển thị trong hình sau:
4.3.2. Các tính năng chính
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: Tuỳ chọn cho phép âm thanh được phát ngay khi Game Object được kích hoạt.
Loop: Tuỳ chọn lặp lại liên tục âm thanh sau khi đã phát hết.
Mute: Tuỳ 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.
66
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Ngoài ra còn một số chức năng và tuỳ chọn không được hiển thị. Chúng ta sẽ xử lý các tính
năng cần thiết này với C# Script.
4.3.4. Các định dạng hỗ trợ
Theo thông tin từ trang chủ chính thức của Unity thì các định dạng âm thanh được hỗ trợ được thể hiện trong bảng sau:
FORMAT
EXTENSION
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
4.3.5. Xử lý và phát âm thanh với Script
m_audio = GetComponent
1.
Đầu tiên, để thao tác được với Audio Source, ta sử dụng dòng code sau:
Với C# Script, ta có thể thực hiện được mọi thao tác với AudioSource, nhưng tôi sẽ chỉ hướng dẫn các thao tác đơn giản và phổ biến.
Thay đổi file âm thanh
m_audio.clip = yourAudioFile;
1.
File âm thanh sẽ được lưu trữ dưới biến clip. Do đó để thay đổi file âm thanh khác, ta gán file đó vào biến này. Thao tác như sau:
yourAudioFile phải được load lên bộ nhớ trước khi thực hiện dòng code trên.
67
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Play - Pause - Stop
m_audio.Play(); m_audio.Pause(); m_audio.Stop();
1. 2. 3.
Ba hàm này dùng để điều khiển bật / tắt nhạc theo nhu cầu của lập trình viên. Các hàm này sẽ thay đổi giá trị của biến isPlaying để thể hiện được trạng thái của AudioSouce.
Hàm Pause sẽ dừng chơi nhạc và lưu trữ trạng thái hiện tại để có thể resume, trong khi hàm Stop sẽ dừng hoàn toàn và di chuyển về vị trí đầu của file âm thanh trong bộ nhớ.
isPlaying
Debug.Log(m_audio.isPlaying);
1.
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 / tắt âm thanh theo ý lập trình viên. Để kiểm tra giá trị của biến, ta có thể sử dụng hàm Debug.Log như sau:
mute
Dùng để bật / tắt âm thanh phát ra các thiết bị output. Thao tác tương tự như hướng dẫn ở trên.
4.4. HIỆU ỨNG CAMERA ZOOM TRONG UNITY
4.4.1. Giới thiệu
Hiệu ứng Camera Zoom là một hiệu ứng được sử dụng nhiều trong các game hiện nay. Hiệu ứng này được ứng dụng trong những đoạn cắt cảnh game, những đoạn tập trung vào một nhân vật trong game hay khi cần phóng to/thu nhỏ bản đồ game để có thể điều khiển game theo ý người chơi. Bài viết này sẽ hướng dẫn cho các bạn cách hiện thực hiệu ứng này trong Unity.
4.4.2. OrthographicSize
Mỗi camera đều có một biến để lưu trữ kích thước của camera. Đối với game 2D trong Unity, ta có biến orthographicSize. Giá trị của biến càng lớn thì kích thước camera càng lớn, đồng nghĩa với các đối tượng trong game sẽ được thu nhỏ lại.
Camera.main.orthographicSize = value;
1.
Để chỉnh sửa giá trị của biến này, ta sử dụng dòng code sau:
68
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Để tạo cảm giác phóng to camera, ta thay đổi giá trị orthographicSize nhỏ dần theo thời gian.
Tương tự đối với việc phóng to.
4.4.3. Tạo button phóng to, thu nhỏ camera
Để minh hoạ cho việc phóng to, thu nhỏ camera, tôi tạo ra hai button có chức năng tương ứng. Thao tác tạo button bạn đọc có thể xem thêm tại bài viết Thiết Kế Giao Diện Người Dùng Trên Unity.
Bước 1: Tạo một dự án mới, đặt tên CameraZoom.
Bước 2: Tạo 2 thư mục Resources, Scripts ở Assets. Copy 1 tập tin hình ảnh bất kì và hình 2 nút tăng (+), giảm (-) vào thư mục Resources (liên hệ thầy để lấy hình ảnh).
69
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Bước 3: Click chuột phải vào cửa sổ Hierarchy, chọn UI, chọn canvas. Thiết lập lại thuộc tính của canvas như hình vẽ:
Bước 4: Click chuột phải vào canvas chọn UI, chọn button tăng zoom, thiết lập thuộc tính cho button (tên, hình ảnh):
70
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Bước 5: Click chuột phải vào canvas chọn UI, chọn button giảm Zoom, thiết lập thuộc tính cho button (tên, hình ảnh):
using UnityEngine; using System.Collections;
public class CameraZoom : MonoBehaviour { private float m_cameraMinSize; private float m_cameraMaxSize; private float m_cameraSaveSize;
void Start () {
Bước 7: Tạo một Script trong thư mục Scripts, đặt tên là CameraZoom và viết code cho nó như sau và lưu lại:
71
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
m_cameraMinSize = 2f; m_cameraMaxSize = 6f; m_cameraSaveSize = Camera.main.orthographicSize; }
public void ZoomIn() { m_cameraSaveSize = Mathf.Clamp(m_cameraSaveSize - 0.5f, m_camer aMinSize, m_cameraMaxSize); StartCoroutine(Zoom(-0.025f)); }
public void ZoomOut() { m_cameraSaveSize = Mathf.Clamp(m_cameraSaveSize + 0.5f, m_camer aMinSize, m_cameraMaxSize); StartCoroutine(Zoom(0.025f)); }
IEnumerator Zoom(float _step) { while(Mathf.Abs(Camera.main.orthographicSize - m_cameraSaveSize ) >= 0.05f) { yield return new WaitForSeconds(0.005f); Camera.main.orthographicSize += _step; }
Camera.main.orthographicSize = m_cameraSaveSize; } }
Bước 8: Chọn Main Camera, kéo file Script vừa tạo sang, thiết lập thuộc tính như sau:
Bước 9: Chọn vào Button Magnifier thiết lập thuộc tính nhận sự kiện nút nhấn cho nó như sau:
72
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Bước 10: Chọn vào Button Minifier thiết lập thuộc tính nhận sự kiện nút nhấn cho nó như sau:
Chạy chương trình và cảm nhận.
Giải thích vài dòng lệnh:
73
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Dòng 12 – 14: Khởi tạo các giá trị ban đầu. Biến orthographicSize sẽ dao động trong khoảng
min và max này.
Dòng 17 – 27: Đây là hai hàm sẽ được button gọi khi có sự kiện nhấn chuột.
Dòng 29 – 38: Hàm Zoom được gọi liên tục khi hàm ZoomIn/Out được gọi. Hàm này có nhiệm vụ thay đổi kích thước của camera cho phù hợp khi nhấn button tương ứng.
Trong script, tôi có sử dụng kỹ thuật cập nhật đối tượng thông qua hàm StartCoroutine. Các hàm có dạng này sẽ được cập nhật mà không cần sự can thiệp của hàm Update. Chi tiết về Coroutine bạn đọc xem tại Ứng Dụng Của StartCoroutine Trong Unity.
4.5. HIỆU ỨNG CAMERA SHAKE TRONG UNITY
4.5.1. Giới thiệu
Đối với các game hành động nhập vai, việc xây dựng các hiệu ứng âm thanh hấp dẫn, hiệu ứng về ánh sáng, sự kịch tính, yếu tố bất ngờ, ... đóng vai trò không nhỏ trong sự thành công của game. Bài viết sẽ hướng dẫn xây dựng hiệu ứng Camera Shake, còn gọi là hiệu ứng rung màn hình, giúp các bạn phát triển các dự án cá nhân.
4.5.2. Khái niệm ShakeIntensity và ShakeDecay
Intensity – cường độ là thuật ngữ diễn tả sức mạnh và mức độ ảnh hưởng lên một đối tượng. ShakeIntensity cũng được diễn giải tương tự. Giá trị của nó càng lớn, cường độ rung càng mạnh, mức độ dịch chuyển mà người xem cảm nhận được sẽ càng cao.
shakeIntensity -= shakeDecay;
1.
Mặt khác, không một hiện tượng nào là không có điểm dừng. Do đó, để đưa cường độ rung về 0, chúng ta sử dụng một khái niệm khác là ShakeDecay – độ phân rã của sự rung chuyển. Sau mỗi khoảng thời gian, chúng ta sẽ giảm dần giá trị của ShakeIntensity đến khi nó trở về 0, hiện tượng rung sẽ dừng lại.
ShakeIntensity và ShakeDecay không có sẵn trong Unity. Chúng ta sẽ xây dựng hai thuộc tính này và gắn với camera chính trong game. Để tạo hiệu ứng Camera Shake, chúng ta thay đổi vị trí, góc quay của camera liên tục trong một thời gian ngắn dựa trên hai thuộc tính trên. Sau thời gian này, camera được trả lại trạng thái ban đầu.
Ngẫu nhiên vị trí camera
Để thay đổi ngẫu nhiên vị trí, ta sử dụng một thuộc tính khá hay thuộc lớp Random là insideUnitSphere. Biến
này sẽ trả về một vị trí ngẫu nhiên (Vector3) trong một hình cầu có bán kính là 1.
4.5.3. Sử dụng các yếu tố ngẫu nhiên để tạo hiệu ứng
74
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Để tạo cảm giác camera rung chuyển với cường độ giảm dần, ta sử dụng thêm biến thể hiện cường độ rung (shakeIntensity), đồng thời sử dụng một biến lưu vị trí ban đầu của camera. Mã giả như sau:
Vector3 originPosition; Camera.position = originPosition + Random.insideUnitSphe
1. 2.
re * shakeIntensity;
Camera.position = originPosition;
1.
Ngẫu nhiên góc quay của camera
Hiện thực theo cách này, camera sẽ có vị trí ngẫu nhiên trong một phạm vi nhất định và giảm dần theo thời gian. Sau khi biến shakeIntensity giảm về 0, ta đưa vị trí camera về ban đầu:
Hiện thực ngẫu nhiên vị trí của camera là ta đã có một hiệu ứng Camera Shake khá đẹp mắt. Tuy nhiên, tôi khuyến khích các bạn thêm một yếu tố ngẫu nhiên nữa là góc quay của camera.
Camera.rotation = new Quaternion(
1. 2.
originRotation.x + Random.Range(-shakeIntensity, shakeIntensit y) * 0.1f,
3.
originRotation.y + Random.Range(-shakeIntensity, shakeIntensit y) * 0.1f,
4.
originRotation.z + Random.Range(-shakeIntensity, shakeIntensit y) * 0.1f,
5.
originRotation.w + Random.Range(-shakeIntensity, shakeIntensit y) * 0.1f
);
6.
Tương tự đối với việc ngẫu nhiên vị trí của camera, chúng ta cũng sử dụng Góc quay ban đầu (originRotation), một yếu tố ngẫu nhiên và shakeIntensity:
Góc quay được biểu diễn dưới dạng Quaternion.
Camera.rotation = Vector3(0, 0, 0);
1.
Sau khi thực hiện xong, ta trả góc quay của camera về giá trị sao lưu ban đầu. Mã giả như sau:
75
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Hướng dẫn tạo Demo:
Bước 1: Tạo một dự án mới, đặt tên Vidu4_5.
Bước 2: Tạo 2 thư mục Resources, Scripts. Copy 2 tập tin vào thư mục Resources:
Bước 3: Click chuột phải vào cửa sổ Hierarchy, chọn UI, chọn canvas. Thiết lập lại thuộc tính của canvas như hình vẽ:
76
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Bước 4: Click chuột phải vào canvas, chọn UI, chọn slide, thiết lập thuộc tính cho slide:
Bước 4: Click chuột phải vào canvas chọn UI, chọn button, thiết lập thuộc tính cho button (tên, hình ảnh):
77
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
using UnityEngine; using System.Collections; using UnityEngine.UI;
public class CameraShake : MonoBehaviour { public GameObject m_shakeAmountSlider;
private float m_shakeIntensity; private float m_shakeDecay;
private Vector3 m_originPosition; private Quaternion m_originRotation;
void Start() { m_originPosition = transform.position; m_originRotation = transform.rotation; }
void Update() { if (m_shakeIntensity > 0) { transform.position = m_originPosition + Random.insideUnitSphere * m_s hakeIntensity; transform.rotation = new Quaternion( -----------------------------------------------------------------------------------------------------------------
78
THS. NGUYỄN VĂN KHƯƠNG
Bước 5: Tạo một Script đặt tên là CameraShake và viết code cho nó như sau:
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- m_originRotation.x + Random.Range(- m_shakeIntensity, m_shakeIntensity) * 0.1f, m_originRotation.y + Random.Range(- m_shakeIntensity, m_shakeIntensity) * 0.1f, m_originRotation.z + Random.Range(- m_shakeIntensity, m_shakeIntensity) * 0.1f, m_originRotation.w + Random.Range(- m_shakeIntensity, m_shakeIntensity) * 0.1f ); m_shakeIntensity -= m_shakeDecay; } else { transform.position = Vector3.back * 10; transform.rotation = Quaternion.Euler(Vector3.zero); } }
public void Shake()
{
if (m_shakeIntensity <= 0)
{
m_shakeIntensity = m_shakeAmountSlider.GetComponent
Bước 6: Chọn Main Camera, kéo file Script vừa tạo sang, thiết lập thuộc tính như sau:
Bước 7: Chọn vào ShakeBtn thiết lập thuộc tính nhận sự kiện nút nhấn cho nó như sau:
79
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Bước 8: Chạy ứng dụng và cảm nhận:
4.6. CHUYỂN ĐỔI SCENE TRONG UNITY
4.6.1. Giới thiệu
Đối với đa số game hiện nay, 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.
4.6.2. Tạo Scene
Để chuyển đổi giữa các scene trong game, bạn tạo hai hoặc nhiều scene và sao lưu lại. Trong bài viết, tôi hiện thực đơn giản các scene main, backbutton và highscore. Chi tiết các bạn có thể tham khảo trong phần demo cuối bài viết.
Main scene
80
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Backbutton scene
Highscore scene
81
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Trong bài viết, tôi sử dụng các UI Button để minh hoạ cho việc chuyển scene. Bạn có thể dùng nhiều cách khác nhau, tạo điều kiện để chuyển scene, chuyển scene khi nhấn phím, ... Tất cả tuỳ thuộc vào logic game của bạn.
Hướng dẫn làm ví dụ:
Bước 1: Tạo một dự án mới, đặt tên Vidu4_6.
Bước 2: Tạo 2 thư mục Resources, Scripts, Scences.
- Copy các tập tin hình ảnh vào thư mục Resources. - Tạo một Canvas và tạo 2 Button trong Canvas như hình vẽ. - Lưu Scene lại với tên main vào thư mục Scences (Chọn File / Save Sence)
Bước 3: Bây giờ tạo Scence thứ 2 (chứa back button) bằng cách chọn File/ New Scene. Sẽ xuất hiện một Main Camera mới. Thiết kế cho Scene này vào lưu lại với tên backbutton trong thư mục Scences
82
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Bước 4: Tương tự tạo thêm Scene thứ 3 đặt tên là highscore trong thư mục Scences.
using UnityEngine; using System.Collections;
public class SceneControl : MonoBehaviour { public void LoadBackScene() { Application.LoadLevel("backbutton"); }
public void LoadHighScoreScene() { Application.LoadLevel("highscore"); }
public void LoadMenuScene() { Application.LoadLevel("main"); } }
Bước 5: Tạo một Script tên SceneControl lưu vào thư mục Scripts và viết lệnh như sau:
83
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Bước 6: Click đôi vào Scene main ở thư mục Scences, Click chuột vào Button Next và thiết lập sự kiện cho nó bằng cách:
- Chọn Button cần thiết kế sự kiện. - Kéo Script SceneControl sang cửa sổ thuộc tính của Button. - Chọn thiết lập cho phương thức On Click() cho phù hợp với sự kiện của Button.
- Tương tự cho Button tiếp theo, thiết lập On Click ( ) cho phù hợp như bên dưới:
84
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Bước 7: Click đôi vào Scene backbutton để mở Scene này ra, thiết lập sự kiện cho Button
quay lại như hình vẽ:
Bước 8: Tương tự, Click đôi vào Scene highscore để mở Scene này ra, thiết lập sự kiện cho Button quay lại như sau:
Bước 9: Biên dịch chương trình, Click đôi vào Scene main để mở Scence này chạy đầu tiên, chọn File / Build Setting…
85
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
- Kéo 3 Scene vào cửa sổ Scenes In Build.
Bước 10: Đóng cửa sổ Build lại, chạy chương trình và cảm nhận:
86
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
4.6.3. Chuyển Scene với C# Script
public static void LoadLevel(int index); public static void LoadLevel(string name);
1. 2.
Việc chuyển đổi scene khá đơn giản do Unity đã hỗ trợ việc load một màn chơi trong game (level) thông qua hàm Application.LoadLevel. Nguyên mẫu của hàm như sau:
Đơn giản nhất, ta dùng trực tiếp tên của scene để chuyển đổi sang scene đó. Tuy nhiên một số trường hợp game có nhiều màn tương tự nhau, việc chuyển đổi bằng tên scene có đôi chút phức tạp hơn. Do đó ta có thể sử dụng index của scene, là ID của scene trong cửa sổ Build Setting (đề cập ở dưới).
Ngoài ra ta có thể truy xuất tên và index của scene hiện tại thông qua các thành phần loadedLevel và loadedLevelName nằm trong lớp Application.
Cấu hình Build Setting
Sau khi thực hiện các thao tác trên, nếu bạn test 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.
• Vào menu File → Build Setting. • Nhấn tổ hợp phím Ctrl + Shift + B.
Để mở menu Build Setting, bạn thực hiện một trong những cách sau:
Giao diện Build Setting hiện ra, bạn 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 đó bạn cần cẩn thận để tránh lãng phí tài nguyên khi build lại những scene trùng lặp.
87
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Đến đây thì các scene đã sẵn sàng để có thể chuyển đổi qua lại theo logic game.
88
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- CHƯƠNG 5: GIAO DIỆN NGƯỜI DÙNG
5.1. THIẾT KẾ GIAO DIỆN NGƯỜI DÙNG TRÊN UNITY
5.1.1. Giới thiệu
Giao diện người dùng (User Interface – UI) là thành phần không thể thiếu đối với bất kỳ game nào. UI cung cấp các thông tin trực quan cần thiết cho người chơi, giúp người chơi có cái nhìn toàn diện về các khả năng của mình (thời gian, điểm, “máu”, ...) và có chiến thuật thích hợp để vượt qua được các thử thách trong game.
thấy tìm Việc thiết kế giao diện người dùng đơn giản là sử dụng các assets có sẵn (hình ảnh, font chữ, các hiệu ứng, ...) và sắp xếp chúng theo một bố cục được Designer thiết kế. Các assets này có trên các website, Assets Store của Unity, hoặc do chính thể được các Designer, Artist trong dự án thiết kế (thường gặp ở những dự án lớn hoặc vừa).
Các thành phần cơ bản trong thiết kế UI bao gồm Canvas, Text, Image, Button, …
5.1.2. 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, ta chọn Create → UI → Canvas. Các đối tượng UI khác cũng được khởi tạo tương tự.
89
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 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.
Các chức năng quan trọng của Canvas
Render mode
•
Có 3 tuỳ 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.
• World Space. Với tuỳ chọn này, đối tượng canvas sẽ được xem như một game object thông thường. Tuỳ 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, toạ độ z, ... của các đối tượng UI.
Screen Space – Camera. Đối với mode này, ta cần chỉ định một camera cho canvas, nó sẽ được render theo camera. Nếu như không có camera được chỉ định thì canvas và các thành phần bên trong sẽ không được render.
Rect Transform
Đố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ờ.
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.
Graphic Raycast
Đối với các tuỳ chọn render mode Screen Space – Overlay và Screen Space – Camera, thành phần Rect Transform sẽ được khoá lại và không thể tuỳ 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ỗ trợ bắt sự kiện. Khi nhận được tín hiệu (mouse click, touch, ...), một tia nhìn tại vị trí tương tác sẽ được tạo ra. Bằng cách này, chúng ta 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.
90
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 5.1.3. Text
UI Text được sử dụng để hiển thị các thông tin trên màn hình như Score, Lives, Times, ... Một số game sử dụng các texture riêng để hiển thị thông tin thay cho text, tuy nhiên text vẫn là lựa chọn phổ biến hơn vì tính đơn giản và dễ thao tác.
• Rect Transform: quản lý vị trí, kích thước, góc quay, ... của Text. • Text: lưu trữ chuỗi ký tự cần hiển thị ra màn hình. Unity hỗ trợ một số chức năng để tuỳ biến chuỗi như font, kiểu chữ (thường, in đậm, in nghiêng, ...), cỡ chữ, màu chữ, ... Tuỳ 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.
Sau khi khởi tạo một đối tượng Text, cửa sổ Inspector có dạng như sau:
5.1.4. Image
UI Image được sử dụng rất phổ biến, như thiết kế background, các button, title, ...
91
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Cách sử dụng rất đơn giản, chúng ta chọn hình ảnh và kéo thả vào khung Source Image.
Ngoài ra còn có một số tuỳ chọn thay đổi màu sắc, chất liệu, ...
5.1.5. 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, ...
92
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Unity cung cấp khá nhiều tính năng giúp hiển thị và thao tác với button được dễ dàng và đẹp hơn.
Về hiển thị, có 4 tùy chọn hiển thị trong phần Transition:
93
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- • None: sử dụng hình ảnh button được truyền vào thành phần Image. Với tùy chọn
• 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 có 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 mà ta lựa chọn tùy chọn phù hợp.
này, hình hiển thị button sẽ không thay đổi mỗi khi người chơi thao tác.
Về phần tương tác, Unity đã cung cấp sẵn event On Click. Khi click vào button, tất cả các hành động được thiết lập sẵn trong event On Click sẽ được thực hiện, do đó ta có thể thực hiện đồng thời nhiều hành động tùy theo nhu cầu.
Lưu ý: nếu cần gọi một hàm trong script, script đó phải được gắn vào một game object
trong cửa sổ Hierarchy và hàm này cần có phạm vi truy cập là public.
Các đối tượng được thêm vào có thể là một Game Object trong cửa sổ Hierarchy hoặc chính button đó. Khi đó event sẽ tự động nhận được các thành phần của game object và hiển thị trong menu như trong hình dưới.
Ngoài ra, còn hàng loạt event khác cho button được cung cấp trong thành phần Event Trigger (Add Component -> Event -> Event Trigger). Cách sử dụng tương tự như với event On Click.
5.1.6. Thao tác với các đối tượng UI trong Script
Hầu hết thao tác với các đối tượng UI trên cửa sổ Inspector đều có thể được thực hiện trong Script. Tôi sử dụng ngôn ngữ C# để minh họa cho các thao tác dưới đây.
Các đối tượng UI đã được trừu tượng hóa thành các class và được đặt trong một namespace có tên là UnityEngine.UI.
using UnityEngine; using UnityEngine.UI;
public class UIController : MonoBehaviour { Canvas m_canvas;
1. 2. 3. 4. 5. 6.
Ví dụ sau sẽ minh họa một vài thao tác với các đối tượng UI cơ bản:
94
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Button m_button; Image m_image; Text m_text;
void Start() { SetupExampleUI(); }
void SetupExampleUI() { m_canvas.enabled = true; m_canvas.renderMode = RenderMode.WorldSpace; m_canvas.pixelPerfect = true; m_canvas.scaleFactor = 1.0f;
m_image.enabled = true; Image.FillMethod fillMethod = m_image.fillMethod;
m_text.text = "Stdio Training"; m_text.fontSize = 13; m_text.lineSpacing = 0.5f;
m_button.enabled = true; m_button.name = "Stdio"; } }
7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33.
Ngoài ra, khi xử lý các đối tượng UI đối với game hỗ trợ đa màn hình, ta cần gắn Anchor Point cho tất cả các
đối tượng.
5.2. XÂY DỰNG GAME ĐA MÀN HÌNH - GẮN ANCHOR POINT CHO CÁC ĐỐI TƯỢNG UI
5.2.1. Giới thiệu
Đối với phát triển game trên nhiều nền tảng, nhiều kích thước màn hình khác nhau, vị trí của các đối tượng UI có thể sẽ không giống nhau trên từng kích thước màn hình. Điều này có khả năng gây ảnh hưởng đến trải nghiệm người dùng. Do đó, chúng ta cần có một giải pháp khắc phục vấn đề này. Bài viết này sẽ hướng dẫn các bạn một cách đơn giản thực hiện được việc đó bằng cách gắn Anchor Point cho các đối tượng UI.
5.2.2. Anchor point là gì
• Anchor với cạnh trên hoặc dưới màn hình. • Anchor với cạnh trái hoặc phải màn hình.
Anchor point (điểm neo) là vị trí tương đối của đối tượng UI với màn hình game. Anchor point được chia làm hai thành phần:
95
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Khi gắn anchor vào một cạnh màn hình, đối tượng UI sẽ được ghim vào cạnh đó một khoảng
cách cố định dù cho kích thước màn hình thay đổi.
Anchor point thường được ứng dụng để gắn các thông tin của game như Score, button Pause, thanh HP, … vào các cạnh màn hình để xử lý vấn đề đa màn hình. Ví dụ như cùng một chiếc Smartphone, bạn muốn việc xoay chiếc điện thoại ngang hoặc dọc cũng không ảnh hưởng nhiều đến trải nghiệm người chơi. Khi đó, các đối tượng lưu trữ thông tin người chơi phải được ghim vào các cạnh màn hình để dành phần không gian cho gameplay.
5.2.3. Pivot
Pivot được định nghĩa là tâm của một hình chữ nhật. Theo mặc định, tâm của hình chữ nhật sẽ nằm ở chính giữa của hình, nhưng ta có thể thay đổi điểm này đến vị trí tuỳ chọn trong hoặc ngoài hình. Các thao tác như Scale, Rotate, … đối tượng sẽ được thực hiện qua Pivot.
Toạ độ của Pivot trong hình tương tự như Viewport Coordinate, góc dưới bên trái có toạ độ (0, 0) và góc trên bên phải có toạ độ (1, 1). Xem thêm tại bài viết Cơ Bản Về Camera Trong Unity.
5.2.4. Cách sử dụng Anchor point
Để gắn Anchor point cho một đối tượng UI, ta thực hiện như sau:
Bước 1: Chọn đối tượng cần gắn Anchor point.
Bước 2: Tại component Rect Transform, bấm chọn nút Anchor Presets để mở ra các tuỳ chọn.
96
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Bước 3: Chọn vị trí muốn gắn đối tượng UI. Minh hoạ trong hình, tôi ghim đối tượng RyeButton vào góc dưới bên phải màn hình. Khi thay đổi kích thước màn hình, đối tượng sẽ luôn giữ khoảng cách là PosX và PosY đối với hai cạnh tương ứng của màn hình. Các tuỳ chọn khác như middle, top, left, … cũng có chức năng tương tự.
Tip: Bạn có thể nhấn giữ phím Shift hoặc phím Alt để thay đổi Pivot point và vị trí của
đối tượng đồng thời.
Kết quả
5.3. SỬ DỤNG AUTO LAYOUT SẮP XẾP CÁC ĐỐI TƯỢNG UI
5.3.1. Giới thiệu
Trong một số trường hợp, chúng ta cần tự động hoá việc sắp xếp các đối tượng trong chương trình do số lượng đối tượng quá nhiều, gây hao tốn thời gian nếu quản lý thủ công. Ví dụ như việc xây dựng hệ thống level trong menu Select Level (Bạn đọc tham khảo bài viết Hiện Thực Menu Select Level Với Scroll Rect). Đáp ứng nhu cầu đó, Unity đã hiện thực sẵn một số component giúp lập trình viên quản lý vị trí các đối tượng được tốt hơn. Bài viết này sẽ giới thiệu với các bạn về Auto Layout trong Unity.
5.3.2. Auto Layout là gì
Hệ thống Auto Layout cung cấp giải pháp để quản lý vị trí và kích thước của các đối tượng UI. Khi sử dụng một hệ thống Auto Layout, đối tượng Rect Transform của đối tượng sẽ bị khoá lại và quản lý bởi Auto Layout.
97
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Auto Layout được xây dựng dựa trên mô hình Controller - Elements, một đối tượng quản lý các đối tượng con. Đối tượng con không trực tiếp quản lý vị trí và kích thước của nó mà đối tượng Controller sẽ nắm giữ các thông tin này và quản lý tự động. Khi thêm hoặc loại bỏ một đối tượng, vị trí của các đối tượng còn lại sẽ được tính toán và thay đổi phù hợp.
5.3.3. Các loại Auto Layout
Unity hỗ trợ một số cách quản lý các element, tôi sẽ tập trung giới thiệu về các component Layout Group – quản lý vị trí của các element thành nhóm.
Layout Group không quản lý chính đối tượng chứa nó mà chỉ quản lý tất cả các đối tượng con. Thành phần Rect Transform của nó được quản lý thủ công hoặc tự động dựa trên một Auto Layout khác, có thể lồng nhau. Với bất kì kích thước nào của đối tượng chứa Layout Group (gọi tắt là Đối tượng Controller), các đối tượng con đều được tính toán vị trí theo các thông số cho trước. Unity hỗ trợ ba loại Layout Group với các hiệu quả và mục đích khác nhau.
Horizontal Layout Group
Component Horizontal Layout Group sắp xếp các đối tượng con liền kề nhau thành một hàng ngang. Kích thước của element được tính toán dựa trên Padding và Spacing của đối tượng Controller.
•
Các thuộc tính của Horizontal Layout Group như sau:
•
Padding: Bốn thuộc tính Left, Right, Top và Bottom lần lượt tương ứng với các cạnh của Layout Group. Với padding càng lớn, khu vực hiển thị đối tượng sẽ càng bị thu hẹp vào trong Layout Group.
• Child Alignment: Vị trí bắt đầu sắp xếp đối tượng đầu tiên. Các đối tượng tiếp
Spacing: Khoảng cách giữa các đối tượng con. Khoảng cách này bạn nên xem xét và thiết lập vừa phải, tránh quá nhỏ hoặc quá lớn. Với Spacing càng lớn, kích thước các đối tượng sẽ càng giảm cho đến khi không còn hiển thị được nữa.
• Child Force Expand: Tuỳ chọn này sẽ override kích thước của các đối tượng. Các đối tượng sẽ được thay đổi kích thước để phủ toàn bộ phần không gian của đối tượng Controller.
Ứng dụng
theo sẽ dựa vào đối tượng đầu tiên này để tính toán vị trí phù hợp.
98
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Horizontal Layout Group thường được sử dụng kết hợp với Scroll Rect để tạo ra một menu các tuỳ chọn theo hàng ngang, trong trường hợp danh sách đối tượng con khá nhiều, hay số lượng có thể tuỳ biến thêm hoặc bớt trong Runtime.
Vertical Layout Group
Nếu Horizontal Layout Group quản lý và sắp xếp các đối tượng theo chiều ngang thì Vertical Layout Group lại quản lý theo chiều dọc. Tất cả các đối tượng sẽ nằm thẳng hàng với nhau trên một cột và lần lượt từ trên xuống dưới hoặc ngược lại, tuỳ vào tuỳ chọn Child Alignment được thiết lập ra sao. Chiều cao của các đối tượng sẽ được tính toán tự động.
Về cơ bản thì Vertical Layout Group khá giống Horizontal Layout Group về các thuộc tính và cách sử dụng. Tôi chưa có dịp sử dụng thực tế Vertical Layout Group trong các dự án nên không thể nói nhiều về phần ứng dụng của nó. Tuy nhiên nếu cảm thấy cần thiết thì bạn có thể tự do sử dụng.
Grid Layout Group
Loại Layout Group được ứng dụng nhiều nhất trong Unity là Grid Layout Group. Grid Layout Group sắp xếp các đối tượng con thành một mạng lưới. Các thông số quy định cách sắp xếp của Grid Layout Group như sau:
99
THS. NGUYỄN VĂN KHƯƠNG
-----------------------------------------------------------------------------------------------------------------
•
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Padding và Spacing: Tương tự như các loại Layout Group nói trên, Padding và Spacing là các thuộc tính quy định khoảng cách giữa các đối tượng con với nhau và với cạnh của đối tượng Controller. Do sắp xếp theo dạng mạng lưới nên Spacing có tới hai giá trị tương ứng với khoảng cách về chiều dài và rộng.
• Cell Size: Kích thước sẽ được override lại cho tất cả các đối tượng con. Khác với hai loại Layout Group nói trên, kích thước của các đối tượng con sẽ không đổi dù kích thước của Controller tăng hay giảm. Riêng vị trí của các đối tượng thì sẽ được tính toán lại. • •
• Child Alignment: Canh lề cho các đối tượng khi chúng không phủ hết đối tượng
Start Corner: Vị trí của đối tượng đầu tiên. Start Axis: Hướng sắp xếp các đối tượng con. Tuỳ chọn Horizontal sẽ sắp xếp từ trái qua phải, từ trên xuống dưới cho đến hết danh sách đối tượng. Ngược lại tuỳ chọn Vertical sẽ sắp xếp theo chiều dọc trước, thêm một cột mới khi cột hiện tại đã đầy.
• Constraint: Hạn chế số lượng hàng hoặc cột. Tuỳ chọn Flexible thường được sử dụng do không hạn chế số hàng và cột. Các tuỳ chọn Fixed Column Count và Fixed Row Count sẽ giới hạn số hàng hoặc cột tương ứng.
Controller.
THS. NGUYỄN VĂN KHƯƠNG 100
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- CHƯƠNG 6: MỘT SỐ KỸ THUẬT MỞ RỘNG
6.1. LƯU TRỮ THÔNG TIN GAME VỚI PLAYERPREFS
6.1.1. Giới thiệu
Lưu điểm số, thông tin của game hay trạng thái lần trước là một điều quan trọng trong bất kì game nào. Giả sử bạn là người chơi, có lẽ bạn sẽ không muốn mỗi lần mở game ra là phải chơi lại từ đầu giống như lúc vừa cài đặt, chơi lại từng màn đã qua. Khi lập trình game với Unity, chúng ta được hỗ trợ sẵn để có thể lưu lại thông tin và trạng thái trong game thông qua lớp PlayerPrefs. Bài viết sau sẽ hướng dẫn các bạn cách để thao tác với PlayerPrefs trong Unity.
6.1.2. Lưu trạng thái game
Lưu ý: Mỗi khi thực hiện các hàm set thì dữ liệu chỉ thực sự được lưu xuống đĩa khi chúng
ta gọi hàm Save().
PlayerPrefs hỗ trợ lưu trữ ba kiểu dữ liệu cơ bản: Int, Float, và String. Tương ứng với ba phương thức là SetInt, SetFloat, SetString. Cả ba phương thức này đều nhận vào hai tham số trong đó tham số đầu tiên là tên đại điện cho giá trị được lưu trữ và tham số còn lại là nội dung cần lưu trữ. Cần ghi nhớ key này để ta có thể lấy lại được nội dung đã lưu trữ về sau.
PlayerPrefs.SetString("username", "PhamNgocPhuoc"); PlayerPrefs.SetString("password", "stdio1235"); PlayerPrefs.SetInt("level", 10);
1. 2. 3.
Dưới đây là cách dùng PlayerPrefs để lưu thông tin người chơi.
6.1.3. Truy xuất thông tin
Sau khi đã sử dụng PlayerPrefs để lưu trữ thông tin, ta sử các phương thức GetInt, GetFloat, GetString để truy xuất các nội dụng đã lưu trữ bất cứ lúc nào.
Cũng giống như các phương thức Set ở trên, khi dùng các phương thức Get ta phải lựa chọn hàm tương ứng với kiểu dữ liệu đã ghi trước đó. Các hàm Get(X) nhận vào một tham số là tên của key và có kiểu trả về X (với X là Int, Float hoặc String).
String _userName = PlayerPrefs.GetString("username"); String _password = PlayerPrefs.GetString ("password"); Int _level = PlayerPrefs.GetInt("level");
1. 2. 3.
Truy xuất các thông số đã lưu trữ ở trên như sau:
THS. NGUYỄN VĂN KHƯƠNG 101
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Lưu ý: Nếu như bạn truyền vào một Key chưa từng tồn lại, thì ứng mỗi phương thức Get sẽ trả về cho bạn một giá trị mặc định. Với GetString thì giá trị đó là một chuỗi trỗng "", GetFloat là 0.0f, GetInt là 0.
6.2.4. Một số phương thức hỗ trợ
DeleteAll
Xóa tất cả các key và giá trị đã được lưu trữ.
DeleteKey
Xóa một key cụ thể.
GetFloat
Trả về giá trị float tương ứng với key nếu key tồn tại.
GetInt
Trả về giá trị int tương ứng với key nếu key tồn tại.
GetString
Trả về giá trị string tương ứng với key nếu key tồn tại.
HasKey
Trả về true nếu key tồn tại.
Save
Lưu trữ tất cả dữ liệu được chỉnh sữa xuống đĩa.
SetFloat
Lưu giá trị float theo key vào bộ nhớ chính.
SetInt
Lưu giá trị int theo key vào bộ nhớ chính.
SetString
Lưu giá trị string theo key vào bộ nhớ chính.
6.1.5. Đường dẫn tệp tin lưu trữ
• Mac OS: Thư mục ~/Library/Preferences
Nơi lưu trữ dữ liệu của PlayerPrefs ở các nền tảng khác nhau:
• Windows: Lưu trữ ở registry dưới đường dẫn HKCU\Software\[company
trong tập tin unity.[company name].[product name].plist.
• Linux: Lưu trữ ở đường dẫn ~/.config/unity3d/[CompanyName]/[product name]. Store • Windows
name]\[product name].
• WebPlayer: PlayerPrefs được lưu trữ trong tệp tin nhị phân với đường dẫn:
o Mac OS X: ~/Library/Preferences/Unity/WebPlayerPrefs/ o Windows: %APPDATA%\Unity\WebPlayerPrefs/
Apps: %userprofile%\AppData\Local\Packages\[ProductPackageId]>\LocalState\playe rprefs.dat.
6.1.6. Bảo mật
Nếu bạn từng truy cập thử vào trong đường dẫn lưu trữ PlayerPrefs thì ta có thể nhận thấy rằng các thông tin lưu trữ trong đó đều ở dạng nguyên gốc như lúc truyền vào. Điều này làm
THS. NGUYỄN VĂN KHƯƠNG 102
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- cho các thông tin này dễ dàng bị sử đổi bất hợp pháp dẫn đến sai lệch thông tin của game hay đánh cắp thông tin người dùng (username, password, email, ...).
Giá trị High Score trước khi bị chỉnh sửa:
Dưới đây là màn chơi của game AngryBird được viết bằng Unity trong đó giá trị High Score được dùng để đánh dấu lại số điểm cao nhất từng đạt được của màn chơi đó sử dụng PlayerPrefs và thông tin mà PlayerPrefs lưu trữ. Giá trị này có thể dễ dàng được thay đổi bằng tay và dẫn đến game cũng bị ảnh hưởng theo.
Nơi lưu trữ giá trị High Score trên môi trường Windows:
THS. NGUYỄN VĂN KHƯƠNG 103
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Giá trị High Score sau khi đã chỉnh sửa:
Để hạn chế điều này, ta có thể sử dụng các phương thức Encrypt/Decrypt có sẵn trong C# để mã hóa dữ liệu trước khi lưu trữ xuống. Bạn đọc có thể tìm thấy mô tả cũng thư như cách hiện thực các phương thức này thông qua các tài liệu về C#.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks;
namespace EncryptStringSample { public static class StringCipher { // This constant string is used as a "salt" value for the P
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.
asswordDeriveBytes function calls.
// This size of the IV (in bytes) must = (keysize / 8). De
14.
fault keysize is 256, so the IV must be
// 32 bytes long. Using a 16 character string here gives u
15.
s 32 bytes when converted to a byte array.
private static readonly byte[] initVectorBytes = Encoding.A
16.
SCII.GetBytes("tu89geji340t89u2");
// This constant is used to determine the keysize of the en
17. 18.
cryption algorithm.
Trong bài viết, tôi hiện thực đơn giản một lớp dùng cho việc mã hoá và giải mã tập tin như sau:
THS. NGUYỄN VĂN KHƯƠNG 104
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
private const int keysize = 256;
public static string Encrypt(string plainText, string passP
{ byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainTex
19. 20. 21. hrase) 22. 23.
t);
using (PasswordDeriveBytes password = new PasswordDeriv
24.
eBytes(passPhrase, null))
{ byte[] keyBytes = password.GetBytes(keysize / 8); using (RijndaelManaged symmetricKey = new RijndaelM
25. 26. 27.
anaged())
{ symmetricKey.Mode = CipherMode.CBC; using (ICryptoTransform encryptor = symmetricKe
28. 29. 30.
y.CreateEncryptor(keyBytes, initVectorBytes))
{ using (MemoryStream memoryStream = new Memo
31. 32.
ryStream())
{ using (CryptoStream cryptoStream = new
33. 34.
CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{ cryptoStream.Write(plainTextBytes,
35. 36.
0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock(); byte[] cipherTextBytes = memoryStre
37. 38.
am.ToArray();
return Convert.ToBase64String(ciphe
39.
rTextBytes);
} } } } } }
public static string Decrypt(string cipherText, string pass
{ byte[] cipherTextBytes = Convert.FromBase64String(ciphe
using(PasswordDeriveBytes password = new PasswordDerive
40. 41. 42. 43. 44. 45. 46. 47. Phrase) 48. 49. rText); 50.
Bytes(passPhrase, null))
{ byte[] keyBytes = password.GetBytes(keysize / 8); using(RijndaelManaged symmetricKey = new RijndaelMa
{ symmetricKey.Mode = CipherMode.CBC; using(ICryptoTransform decryptor = symmetricKey
51. 52. 53. naged()) 54. 55. 56.
.CreateDecryptor(keyBytes, initVectorBytes))
{ using(MemoryStream memoryStream = new Memor
57. 58.
yStream(cipherTextBytes))
{ using(CryptoStream cryptoStream = new C
59. 60.
ryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
61.
THS. NGUYỄN VĂN KHƯƠNG 105
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- byte[] plainTextBytes = new byte[ci
62.
pherTextBytes.Length];
int decryptedByteCount = cryptoStre
63.
am.Read(plainTextBytes, 0, plainTextBytes.Length);
return Encoding.UTF8.GetString(plai
64.
nTextBytes, 0, decryptedByteCount);
} } } } } } } }
65. 66. 67. 68. 69. 70. 71. 72.
using System; using System.Linq;
namespace EncryptStringSample { class Program { static void Main(string[] args) { Console.WriteLine("Please enter a password to use
string password = Console.ReadLine(); Console.WriteLine("Please enter a string to encry
string plaintext = Console.ReadLine(); Console.WriteLine("");
Console.WriteLine("Your encrypted string is:"); string encryptedstring = StringCipher.Encrypt(pla
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. :"); 11. 12. pt:"); 13. 14. 15. 16. 17.
intext, password);
Console.WriteLine(encryptedstring); Console.WriteLine("");
Console.WriteLine("Your decrypted string is:"); string decryptedstring = StringCipher.Decrypt(enc
18. 19. 20. 21. 22.
ryptedstring, password);
Console.WriteLine(decryptedstring); Console.WriteLine("");
Console.ReadLine(); } } }
23. 24. 25. 26. 27. 28. 29.
Cách sử dụng và thao tác với lớp StringCipher:
Áp dụng mã hóa đối với trường hợp phía trên, lúc này giá trị HighScore sẽ được mã hóa thành một chuỗi kí tự không quy tắc.
THS. NGUYỄN VĂN KHƯƠNG 106
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Chuỗi ký tự này có thể đảm bảo thông tin không bị đánh cắp hoặc chỉnh sửa trái phép, nhưng chỉ ở mức tương đối an toàn. Các hacker vẫn có thể phát hiện ra thuật toán mã hóa bạn đang dùng, mã hóa một chuỗi kết quả khác tương tự và thay thế vào đó. Nhưng dù sao thì đây vẫn là một cách tương đối tốt để tự bảo mật dữ liệu cho game của mình.
6.2. KHỞI TẠO ĐỐI TƯỢNG TRONG RUNTIME
6.2.1. Giới thiệu
Trong một vài trường hợp, việc khởi tạo đối tượng mà không khai báo trước khi chạy chương trình là điều cần thiết. Chẳng hạn như khi cần load một bản đồ mới vào thế giới game khi người chơi đã vượt qua mức độ hiện tại, ta không thể load toàn bộ các bản đồ lên bộ nhớ, vì như thế sẽ vô cùng tốn kém tài nguyên và giảm hiệu suất của game. Giải pháp đặt ra là chỉ load những đối tượng cần thiết, khi không sử dụng sẽ giải phóng vùng nhớ để tiết kiệm tài nguyên. Các đối tượng chưa sử dụng sẽ được khởi tạo sau trong khi đang chạy chương trình.
Trong Unity, công việc này khá đơn giản chỉ với một vài dòng code. Chi tiết về cách thực hiện mời bạn đọc theo dõi ở nội dung của bài viết.
6.2.2. Resources folder
Resources là một folder đặc biệt của Unity, cho phép bạn truy cập các tài nguyên sử dụng trong game thông qua tên và đường dẫn của file, bên cạnh thao tác kéo thả (drag-and-drop) trong Unity Editor.
Folder Resources có thể hiểu là Working Directory trong Unity. Nếu một asset được đặt trong folder Resources, chúng có thể được truy xuất bởi hàm Resources.Load thông qua đường dẫn tương đối đến nó.
THS. NGUYỄN VĂN KHƯƠNG 107
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Ngoài Resources, còn một số folder đặc biệt khác trong Unity. Mức độ phổ biến và ứng dụng của chúng không cao nên tôi không đề cập đến. Các bạn có thể tham khảo trong link ở cuối bài viết.
6.2.3. Load tài nguyên với Resources.Load
Hàm Load thuộc lớp Resources nhận vào giá trị là đường dẫn đến vị trí của asset trong project. Nếu asset tồn tại tại đường dẫn, hàm sẽ trả về asset đó dưới dạng Object.
Ví dụ sau sẽ giúp bạn hiểu rõ về cách sử dụng hàm cũng như tầm quan trọng của folder Resources:
Object object = Resources.Load("SFX/sfx_stdio_logo");
1.
Tôi thực hiện load file “sfx_stdio_logo.mp3” bằng một trong ba cách như sau:
THS. NGUYỄN VĂN KHƯƠNG 108
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
AudioClip stdioLogo = Resources.Load("SFX/sfx_stdio_logo") as
2.
AudioClip;
AudioClip stdioLogo2 = Resource.Load
3.
o_logo");
Ở dòng 1, tôi thực hiện việc load file logo âm thanh của STDIO vào đối tượng object. Khi cần sử dụng để gắn vào một AudioSource nào đó, tôi thực hiện ép kiểu sang AudioClip để sử dụng. Bạn đọc vui lòng đọc thêm bài viết Âm Thanh Trong Unity để biết thêm về AudioClip và AudioSource.
Ở dòng 2, tôi thực hiện ép kiểu tường minh ngay sau khi tài nguyên đã được load bằng từ khoá as Type. Đối tượng sẽ được tự động ép kiểu sang Type và được lưu trữ trong một biến cùng kiểu.
Lưu ý: Tất cả các đường dẫn của asset đều sử dụng dấu “/” để phân chia các thư mục,
đường dẫn sử dụng dấu “\” sẽ không hoạt động và hàm sẽ không tìm được asset.
Ở dòng 3, đây là cú pháp sử dụng template, do đó nó sẽ trả về đúng đối tượng cần tìm với kiểu dữ liệu cần thiết. Đây là cách mà cá nhân tôi rất thích và sử dụng thường xuyên.
6.2.4. Instantiate
Instantiate là hàm được sử dụng để clone một đối tượng được xây dựng sẵn. Đối tượng này có thể là một GameObject, component hay là các tài nguyên được load trực tiếp từ Resources.
6.3. ỨNG DỤNG CỦA STARTCOROUTINE TRONG UNITY
6.3.1. Giới thiệu
Khi tiếp nhận và xử lý các sự kiện thu được, có những lúc chúng ta cần một khoảng thời gian chờ giữa việc nhận được sự kiện và xử lý sự kiện. Một ví dụ đơn giản mà tôi đã từng hiện thực lại trong bài viết Hiệu Ứng Camera Zoom Trong Unity. Khi cần một hiệu ứng zoom camera thật đẹp và mượt mà, tôi sử dụng kĩ thuật điều chỉnh size và nghỉ liên tục thành một vòng lặp. Độ chênh lệch kích thước camera và thời gian nghỉ là vừa đủ cho người dùng có trải nghiệm tốt nhất.
Ngoài ra còn khá nhiều những trường hợp cụ thể khác trong game mà cần delay lại trước khi thực hiện hành động. Tất cả những trường hợp đó, Unity đã hỗ trợ chúng ta hiện thực lại bằng các Coroutine.
6.3.2. Coroutine là gì
Khi bạn thực thi một hàm, hàm đó sẽ được xử lý hoàn toàn trước khi trả về một giá trị. Điều đó có nghĩa là dù hàm có phức tạp đến mấy cũng sẽ được xử lý trong duy nhất một vòng lặp của chương trình. Do đó, các hàm dạng này không thể được sử dụng để xử lý các hiệu ứng, animation hoặc các sự kiện diễn ra trong nhiều thời gian.
THS. NGUYỄN VĂN KHƯƠNG 109
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Coroutine là một thành phần của chương trình máy tính giúp chúng ta tạo ra các tiến trình con độc lập, bằng cách cho phép tại một vị trí có thể có nhiều entry point hoạt động trong cùng thời điểm. Trong Unity, Coroutine được sử dụng để bắt đầu một tiến trình độc lập với hàm Update và được gọi một lần duy nhất. Unity sẽ tự động quản lý tiến trình này cho đến khi nó kết thúc và được giải phóng.
6.3.3. Từ khoá yield
Từ khoá yield được ứng dụng nhiều trong các ngôn ngữ lập trình. yield là một tín hiệu để báo hiệu cho trình biên dịch biết đoạn code hay hàm chứa nó chính là một khối lặp, mặc dù không sử dụng các cú pháp thông thường đã biết. yield sẽ kết hợp với return, cho phép trả về các giá trị của khối lặp đó, đồng thời có thể quay trở lại khối lặp trong những lần lặp tiếp theo.
yield return sẽ không kết thúc phương thức chứa nó mà vẫn tiếp tục chạy cho đến khi thực thi xong lệnh cuối cùng của khối lặp. Muốn kết thúc phương thức, ta sử dụng yield break. Phương thức chứa yield có kiểu trả về là IEnumerator, được sử dụng nhằm tạo ra một Coroutine.
Mục đích của bài viết là giới thiệu và hướng dẫn một số ứng dụng thực tế của Coroutine trong Unity nên tôi không đề cập quá chi tiết về từ khoá yield. Bạn đọc có thể tham khảo link cuối bài viết hoặc liên lạc trực tiếp với tôi để hiểu rõ hơn về từ khoá yield.
6.3.4. WaitForSeconds
WaitForSeconds là một phương thức của Unity có chức năng trì hoãn một Coroutine với một khoảng thời gian tính bằng giây. WaitForSeconds được sử dụng duy nhất với từ khoá yield. Chi tiết về cách sử dụng các bạn xem ở các ví dụ thực tế ở phần dưới đây.
6.3.5. Sử dụng Coroutine trong Unity
public Coroutine StartCoroutine(IEnumerator routine);
1.
Ta dùng phương thức StartCoroutine để bắt đầu một coroutine trong Unity. Nguyên mẫu của hàm như sau:
Tham số nhận vào là một IEnumerator, giá trị trả về của một hàm IEnumerator. Trong Unity, có thể có nhiều Coroutine hoạt động đồng thời trong cùng một thời gian. Ngoài ra, ta cũng có thể truyền trực tiếp tên hàm IEnumerator vào hàm dưới dạng chuỗi.
void Start() { Debug.Log("Starting " + Time.time); StartCoroutine(PrintfAfter(2.0f)); }
1. 2. 3. 4. 5.
Ví dụ đơn giản dưới đây sẽ giúp các bạn hiểu được mục đích của việc sử dụng Coroutine và ứng dụng trong Unity:
THS. NGUYỄN VĂN KHƯƠNG 110
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
IEnumerator PrintfAfter(float seconds) { yield return new WaitForSeconds(seconds); Debug.Log("Done " + Time.time); }
6. 7. 8. 9. 10. 11.
Hàm PrintfAfter có kiểu trả về là IEnumerator. Dòng số 9, cặp từ khoá yield return, kết hợp với WaitForSeconds sẽ dừng hàm và bỏ qua dòng lệnh số 10 cho đến khi khoảng thời gian đã trôi qua hết. Khi đó dòng lệnh số 10 mới được thực thi và hàm sẽ thật sự kết thúc và được giải phóng.
• Disable đối tượng khi không còn sử dụng nữa. Đối tượng sẽ được đưa ra khỏi
Ngoài ứng dụng trong Hiệu Ứng Camera Zoom Trong Unity, tôi có sử dụng Coroutine trong một số trường hợp. Dưới đây là một số ví dụ thực tế mà tôi sử dụng trong các dự án cá nhân:
void Update () { if (m_state == State.DEATH) { StartCoroutine(InactiveAfter(1f)); } }
IEnumerator InactiveAfter(float seconds) { yield return new WaitForSeconds(seconds);
GetComponent
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.
• Giải phóng đối tượng sau một khoảng thời gian:
void Update ()
{
if (!GetComponent
IEnumerator DestroyAfter(float seconds) { yield return new WaitForSeconds(seconds);
Score.m_score += 500; DoScoreEffect();
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.
game và sẵn sàng được kích hoạt lại mà không cần khởi tạo mới:
THS. NGUYỄN VĂN KHƯƠNG 111
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Destroy(gameObject); }
13. 14.
Tôi có sử dụng hiệu ứng và một số thao tác khác trước khi giải phóng đối tượng nên không thể sử dụng hàm Destroy với tham số thời gian delay. Coroutine là cách hiện thực đơn giản nhất trong trường hợp này.
6.4. HIỆN THỰC MENU SELECT LEVEL VỚI SCROLL RECT
6.4.1. Giới thiệu
Đối với các thể loại game giải đố (puzzle), luôn luôn có một hệ thống màn chơi đa dạng với các cấp độ từ dễ đến khó để thử thách người chơi. Do đó cần một nơi để hiển thị các cấp độ chơi này một cách trực quan sinh động và hấp dẫn. Đối với Unity, việc này được hỗ trợ một phần bởi thành phần Scroll Rect. Bài viết sau sẽ hướng dẫn các bạn cách sử dụng Scroll Rect để tạo ra một menu quản lý cấp độ chơi (Level Select) cùng với các hiệu ứng đẹp mắt.
6.4.2. Thành phần Scroll Rect
Scroll Rect trong Unity là một thành phần UI, do đó bạn chỉ có thể thêm Scroll Rect vào một đối tượng UI. Để thêm thành phần Scroll Rect, ta chọn Add Component → UI → Scroll Rect. Giao diện của Scroll Rect trong cửa sổ Inspector như sau:
Content
6.4.3. Tuỳ chỉnh tại Inspector
THS. NGUYỄN VĂN KHƯƠNG 112
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Thuộc tính này sẽ tham chiếu đến thành phần Rect Transform của một đối tượng UI. Đối tượng này sẽ được Scroll Rect quản lý và chuyển động theo các thuộc tính chức năng của Scroll Rect.
Horizontal – Vertical
Scrollbar
Hai checkbox quy định hướng mà đối tượng có thể được scroll. Khi bỏ đánh dấu checkbox nào thì đối tượng sẽ không thể scroll theo hướng tương ứng. Horizontal tương đương với khả năng cuộn theo chiều ngang và Vertical tương đương với khả năng cuộn theo chiều dọc của đối tượng.
Movement Type
Hai tuỳ chọn Scrollbar cho phép Scroll Rect tham chiếu tới các đối tượng UI Scrollbar để hiển thị thanh cuộn. Nếu không cần thiết hiển thị Scrollbar thì bạn có thể bỏ qua hai tuỳ chọn này.
• Unrestricted: Đối tượng Content sẽ không bị giới hạn bởi kích thước Rect Transform của đối tượng quản lý Scroll Rect. Nói cách khác, khi click chuột di chuyển đối tượng, đối tượng đó sẽ có thể di chuyển tự do ra ngoài đối tượng quản lý.
• Elastic: Đối tượng Content bị giới hạn bởi kích thước Rect Transform của đối tượng quản lý. Ở tuỳ chọn này, đối tượng Content có thể di chuyển “một phần” ra khỏi đối tượng quản lý khi ta giữ nguyên sự kiện Input (như click chuột, touch, …) và di chuyển ra ngoài giới hạn của đối tượng quản lý. Tuy nhiên ngay khi sự kiện Input kết thúc, đối tượng Content sẽ di chuyển lại vào giới hạn với hiệu ứng built-in đẹp mắt (bounce). Mức độ bounce của đối tượng được quản lý ở thuộc tính Elasticity.
• Clamped: Tương tự tuỳ chọn Elastic, đối tượng sẽ không thể đi ra khỏi giới hạn. Khi chạm đến giới hạn, đối tượng đơn giản sẽ dừng lại và không thể di chuyển vượt quá giới hạn của đối tượng quản lý.
Đây là các kiểu di chuyển của đối tượng được quản lý bởi Content. Unity hỗ trợ ba kiểu chuyển động cho Scroll Rect:
Inertia
Với các nhu cầu thông thường thì tuỳ chọn Elastic là khá phù hợp và đẹp mắt.
Tuỳ chọn này hỗ trợ tính chất vật lý trong chuyển động của đối tượng. Khi được kích hoạt và sự kiện Input kết thúc, đối tượng vẫn sẽ được scroll với vận tốc giảm dần. Vận tốc ban đầu của đối tượng dựa vào khoảng cách giữa hai lần nhận được input và được tính toán một cách tự động.
THS. NGUYỄN VĂN KHƯƠNG 113
Thuộc tính Deceleration Rate chính là độ giảm vận tốc của đối tượng. Giá trị càng lớn thì đối tượng sẽ dừng lại càng nhanh và ngược lại. Tuỳ chọn này chỉ hiện ra khi Inertia được kích hoạt. -----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Event OnValueChange
Event này sẽ được gọi khi vị trí của Scroll Rect được thay đổi. Vị trí hiện tại sẽ được trả về dưới dạng một Vector2.
Thao tác với C# Script
OnDrag
public void OnDrag(EventSystems.PointerEventData eventData); public void OnBeginDrag(EventSystems.PointerEventData eventDa
public void OnEndDrag(EventSystems.PointerEventData eventData
1. 2. ta); 3.
);
Các hàm được override và tự động gọi mỗi khi nhận được sự kiện trên Scroll Rect. Các hàm nhận sự kiện Drag có nguyên mẫu như sau:
Mỗi hàm đều có một chức năng cụ thể, bạn đọc không cần quan tâm đến giá trị truyền vào hàm. Trong quá trinh làm việc thực tế thì tôi thường bỏ qua hai hàm này và sử dụng sự kiện Input thay thế.
Ngoài ra, các thuộc tính hiển thị ở Inspector cũng có thể được tuỳ chỉnh trong script. Bạn đọc vui lòng tham khảo thêm ở link cuối bài viết.
6.4.4. My Scroll Rect
• Menu Level Select là một đối tượng UI Image với color trong suốt hoàn toàn. Các level được thêm vào thủ công hoăc tự động và được chia thành nhiều trang. Chi tiết cách hiện thực tôi sẽ hướng dẫn trong một bài viết khác, hoặc bạn đọc có thể tham khảo thêm ở phần demo.
• Thành phần Scroll Rect được gắn vào một Canvas với Content là menu Level
Nếu bạn đã từng chơi game Angry Bird của hãng Rovio, bạn sẽ thấy menu Level Select được thể hiện dưới dạng các trang liên tiếp theo chiều ngang. Khi kéo sang trái hoặc phải, menu sẽ dịch chuyển qua một trang level với hiệu ứng rất hợp lý. Tôi đã tham khảo tư tưởng của Angry Bird và hiện thực lại menu Level Select như sau:
Select. Các thông số được tôi thiết lập như hình dưới.
THS. NGUYỄN VĂN KHƯƠNG 114
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
• Ngoài ra, tôi hiện thực một C# Script giúp kiểm tra và cập nhật trạng thái cho menu. Các hiệu ứng và logic chuyển động cũng được tích hợp và xử ls trong script. Nội dung của script như sau:
using UnityEngine; using UnityEngine.UI;
public class ScrollRectController : MonoBehaviour {
private ScrollRect m_scrollPanel;
public int m_pages; private int m_currentPage; private Vector2[] m_currentPagePosition;
private float m_dragTime; private float m_lastTime;
private Vector3 m_savePosition; private Vector3 m_startPosition;
void Start() {
m_scrollPanel = GetComponent
m_currentPage = 1; m_currentPagePosition = new Vector2[m_pages];
for (int i = m_pages - 1; i >= 0; i--)
m_currentPagePosition[m_pages - 1 - i] =
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26.
new Vector2(
Screen.width / 2 * (m_pages - 1)
27.
- Screen.width * (m_pages - i - 1),
m_scrollPanel.content.anchoredPos
28.
ition.y);
THS. NGUYỄN VĂN KHƯƠNG 115
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
m_dragTime = 1f;
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
m_savePosition = m_startPosition = Input
29. 30. 31. 32. 33. 34. 35. 36. 37. 38.
.mousePosition;
m_lastTime = Time.time;
}
if (Input.GetMouseButton(0))
Dragging();
else {
if (Input.GetMouseButtonUp(0)) {
Dropping();
}
Vector2 temp =
Vector2.Lerp(m_scrollPanel.conten
39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52.
t.anchoredPosition,
m_cur
53.
rentPagePosition[m_currentPage - 1],
5f *
54.
Time.deltaTime);
m_scrollPanel.content.anchoredPosition =
}
}
void Dragging() {
if (Time.time - m_lastTime >= m_dragTime) {
m_savePosition = Input.mousePosition; m_lastTime = Time.time;
}
}
void Dropping() {
55. temp; 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70.
if (Input.mousePosition.x - m_savePosition.x >= 50f || Input.mousePosition.x - m_startPosition.x >= Screen.width / 2)
{
if (m_currentPage > 1)
m_currentPage--;
71. 72. 73. 74. 75.
} else if (m_savePosition.x - Input.mousePosition .x >= 50f || m_startPosition.x - Input.mousePosition.x >= Screen.widt h / 2)
THS. NGUYỄN VĂN KHƯƠNG 116
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
{
if (m_currentPage < m_pages)
m_currentPage++;
}
}
}
76. 77. 78. 79. 80. 81.
Dòng 6 – 16 là tất cả những thuộc tính cần thiết để thao tác với Scroll Rect cũng như quản lý vị trí, … Thuộc tính m_pages có phạm vi là public để lập trình viên có thể tự do tạo ra nhiều trang level và tuỳ chỉnh dễ dàng trong Unity.
Tại hàm Start, tôi thiết lập giá trị ban đầu cho các thuộc tính.
Hàm Update chính là luồng sự kiện và logic của Scroll Rect. Khi nhận sự kiện click chuột (hoặc touch trên smartphone), đối tượng Content sẽ di chuyển theo input do thành phần Scroll Rect quản lý. Khi bỏ click chuột (hoặc touch), script sẽ cập nhật lại trang hiện tại và di chuyển menu về trang đó.
Demo
Bạn đọc vui lòng điều chỉnh kích thước cửa sổ Game về đúng kích thước thật (trong demo là 800x480) hoặc chọn tuỳ chọn Maximize on Play trước khi chạy demo để có được kết quả chính xác.
6.5. SỬ DỤNG BLUETOOTH XÂY DỰNG MULTIPLAYER GAME VỚI UNITY
6.5.1. Giới thiệu
Việc hỗ trợ nhiều người chơi là xu hướng tất yếu hiện nay của nhiều game. Thông thường, những người chơi được kết nối với nhau thông qua mạng Internet hay mạng nội bộ (mạng LAN). Tuy nhiên trong một số trường hợp các kết nối mạng không khả dụng, ví dụ như thiết bị đang kết nối với thiết bị khác hay đôi khi ta chỉ cần sự đơn giản trong việc kết nối hai thiết bị với nhau.
Lúc này, Bluetooth là một giải pháp phù hợp để giải quyết tình trạng người chơi không thể giao tiếp với dựa trên các kết nối mạng thông thường.
6.5.2. Đối tượng hướng đến
Đối tượng hướng đến của bài viết này là các bạn quan tâm đến các công nghệ hỗ trợ nhiều người chơi trên Unity - cụ thể là sử dụng Bluetooth để kết nối nhiều người chơi với nhau.
• Thao tác trên Unity và các component.
Bạn đọc bài viết này phải có các khối kiến thức nền tảng về:
THS. NGUYỄN VĂN KHƯƠNG 117
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- • Hiểu được kiến trúc lập trình mạng và cách hiện thực cơ chế Multiplayer của
• Có kiến thức về lập trình và xây dựng ứng dụng trên Android, cấu hình các thông
Unity.
tin như manifest, package.
Trong bài viết này tôi sử dụng Unity 5.3.4f1 và Bluetooth LE for iOS and Android.
6.5.3. Công nghệ Bluetooth
Bluetooth được phát hành vào năm 1999 do công ty Sony Erricson phát triển trong thời kì đầu. Bluetooth có chuẩn là IEEE 802.15.1. Bluetooth có thể đạt tốc độ 1Mb/s và sử dụng giải tần 2,4 GHz.
Bluetooth có thể tạo ra một mạng lưới không dây giữa các thiết bị như laptop, smartphone. Kết nối giữa các thiết bị input, output như mouse, keyboard, printer.
Máy chơi game WII cũng như console Playstation 3 cũng vẫn sử dụng loại kết nối này.
Phiên bản mới nhất hiện nay của Bluetooth là 4.0 gọi là Bluetooth Smart/Bluetooth low energy tuy loại này không có kết nối tốc độ cao như 3.0 nhưng điện năng tiêu thụ rất thấp, thường được sử dụng cho các thiết bị điều khiển như remote TV, máy tính bảng, smartphone.
6.5.4. Bluetooth với Unity
THS. NGUYỄN VĂN KHƯƠNG 118
Các hàm dưới đây được cung cấp trong package Bluetooth LE for iOS and Android v2.3.unitypackage là một package có tính phí của Unity. Nên trong bài viết này tôi sẽ chỉ cung -----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- cấp các phương thức để giao tiếp Bluetooth giữa các thiết bị, chứ không cung cấp project vì tôn trọng bản quyền.
Log
public static void Log (string message)
1.
Chức năng:
Initialize
public static BluetoothDeviceScript Initialize (bool asCentra
1.
l, bool asPeripheral, Action action, Action
Hiện thị một chuỗi(message) lên màn hình console.
Chức năng:
Khởi tạo một hệ thống Bluetooth. Hệ thống có thể hoạt động như một Central (tương tự như server để các thiết bị khác có thể kết nối tới) hay như một Peripheral (Client kết nối tới Server có sẵn) hoặc cả hai dựa vào các tham số asCentral, asPeripheral.
DeInitialize
public static void DeInitialize (Action action)
1.
Khi hoàn thành việc khởi tạo thì action sẽ được thực thi. Nếu có có một error thì errorAction sẽ được gọi và thực thi.
Chức năng:
FinishDeInitialize
public static void FinishDeInitialize ()
1.
Hành động này là hủy khởi tạo và thường được gọi khi các tắt ứng dụng hoặc ngắt kết nối Bluetooth. Khi quá trình hoàn thành thì action sẽ được gọi và thực thi.
Chức năng:
Phương thức sẽ tự động được gọi bởi BluetoothDeviceScript khi mà Bluetooth được hủy.
THS. NGUYỄN VĂN KHƯƠNG 119
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
PauseMessages
public static void PauseMessages (bool isPaused)
1.
Chức năng:
ScanForPeripheralsWithServices
1.
public static void ScanForPeripheralsWithServices (string[] s
erviceUUIDs, Action
Phương thức giúp thông báo cho các hệ thống Bluetooth rằng các ứng dụng nào sẽ được tạm dừng hoặc tiếp tục thực thi dựa vào tham số isPaused.
Chức năng:
StopScan
public static void StopScan ()
1.
Phương thức sẽ đặt thiết bị của bạn vào chế độ tìm tất cả các thiết bị . serviceUUIDs chính là tham số hỗ trợ tìm các Peripheral, nếu giá trị của serviceUUIDs là NULL thì tất cả các Bluetooth LE Peripheral sẽ được tìm thấy. Nếu một thiết bị được tìm thấy thì action sẽ được gọi với ID và tên của thiết bị vừa được tìm thấy.
Chức năng:
RetrieveListOfPeripheralsWithServices
public static void RetrieveListOfPeripheralsWithServices (str
1.
ing[] serviceUUIDs, Action
Phương thức này cho phép bạn dừng chế độ tìm quét các thiết bị bằng phương thức ScanForPeripheralsWithServices.
Chức năng:
Phương thức này sẽ lấy về một danh sách các thiết bị đang được kết nối tới hệ thống Bluetooth của bạn.
THS. NGUYỄN VĂN KHƯƠNG 120
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
ConnectToPeripheral
1.
public static void ConnectToPeripheral (string name, Action connectAction, Acti on characteristicAction, Action
Chức năng:
Phương thức này giúp thiết bị của bạn cố gắng kết nối tới các thiết bị ngoại vi đã được đặt tên. Nếu kết nối thành công thì connectAction sẽ được gọi cho mỗi dịch vụ thiết bị.
DisconnectPeripheral
public static void DisconnectPeripheral (string name, Action<
1.
string> action)
Giá trị mặc định cho tham số disconnectAction là null và có khả năng tương thích ngược. Nếu bạn cung cấp một callback cho tham số này nó sẽ được gọi bất cứ khi nào kết nối ngắt kết nối thiết bị. Chú ý: Nếu bạn cũng cung cấp một callback cho phương thức DisconnectPeripheral thì cả hai callbacks đều sẽ được gọi tới.
Chức năng:
ReadCharacteristic
public static void ReadCharacteristic (string name, string se
1.
rvice, string characteristic, Ac tion
Phương pháp này sẽ ngắt kết nối tới một thiết bi theo tên. Khi ngắt kết nối hoàn tất thì callback function được gọi với dữ liệu đi kèm là ID của thiết bị.
Chức năng:
WriteCharacteristic
Phương pháp này sẽ bắt đầu đọc một đặc tính bằng cách sử dụng tên của các thiết bị ngoại vi má nó kết nối đến. Nếu đọc là thành công callback funtion sẽ được gọi cùng với đó là các dữ liệu có kiểu dữ liệu là byte.
THS. NGUYỄN VĂN KHƯƠNG 121
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
1.
public static void WriteCharacteristic (string name, string s
ervice, string characteristic, by te[] data, int length, bool withRes
ponse, Action
Chức năng:
Phương pháp này sẽ truyền dữ liệu tới các thiết bị ngoại vi đã được kết nối trước đó thông qua name, service. Giá trị để truyền là một bộ đệm byte với chiều dài chỉ định trong các thông số dữ liệu và thời gian.
SubscribeCharacteristic
1.
public static void SubscribeCharacteristic (string name, stri
ng service, string characteristi c, Action
Các tham số withResponse là các action được trả về sau khi truyền dữ liệu thành công hoặc không thành công.
Chức năng:
Phương pháp này sẽ đăng ký với thiết bị ngoại vi theo tên, các dịch vụ và đặc điểm. Phương thức notificationAction được gọi bất cứ khi nào có sự thay cập nhật những data từ các thiết bị có kết nối bluetooth.
SubscribeCharacteristicWithDeviceAddress
1.
public static void SubscribeCharacteristicWithDeviceAddress (
string name, string service , string characteristic, Action
Tham số đầu tiên là UUID. Thứ hai là các byte dữ liệu thô đã được cập nhật. Phương pháp này là tương thích ngược.
Phương pháp này sẽ đăng ký với một đặc trưng theo tên của thiết bị ngoại vi và các dịch vụ và đặc trưng. Sau notificationAction thì hàm callback được gọi khi thông báo xảy ra là các data được cập nhật bởi các thiết bị ngoại vi.
UnSubscribeCharacteristic
Tham số đầu tiên là địa chỉ thiết bị. Tham số thứ hai là UUID. Thứ ba là các byte dữ liệu thô đã được cập nhật.
THS. NGUYỄN VĂN KHƯƠNG 122
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
public static void UnSubscribeCharacteristic (string name, st
1.
ring service, string characteristic, Action
Phương pháp này là hủy đăng ký các đặc tính của bluetooth theo tên, dịch vụ và đặc điểm. Khi hoàn thành thì các callback function được thực thi.
THS. NGUYỄN VĂN KHƯƠNG 123
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- CHƯƠNG 7: XÂY DỰNG GAME ZERO VỚI UNITY
7.1. NHẬN SỰ KIỆN BUTTON
Gameplay của Game khá đơn giản: Mỗi lượt chơi là một phép tính được biểu thị bằng hình ảnh ngẫu nhiên 4 chất của bộ bài Tây (chúng tôi – những nhà phát triển Zero, gọi là suit). Người chơi sẽ phải tính số lượng suit có ở hai bên, kết hợp với các toán tử (>, <, =) và quyết định nhanh chóng.
Sau một khoảng thời gian ngắn, nếu trả lời đúng sẽ được chuyển sang mức độ tiếp theo với độ khó tăng dần, trả lời sai sẽ thua. Người chơi có thể cạnh tranh bằng cách chia sẻ điểm số đạt được với nhau. Với yếu tố về thời gian và các hiệu ứng, Zero là một trong những game thú vị thuộc thể loại endless.
Khởi tạo project và setup môi trường làm việc
Không gian game của chúng ta là 2D nên tôi sẽ tạo một project Unity 2D đặt tên là Vidu7_1. Trong hướng dẫn, tôi có import package Visual Studio 2015 Tools For Unity để thao tác với script bằng Visual Studio 2015. Bạn đọc có thể sử dụng MonoDevelop thay thế hoặc tải package tại link cuối bài viết. Bộ resources sử dụng trong bài viết thuộc sở hữu của STDIO. Do đó chương trình tạo ra chỉ mang tính học tập và nghiên cứu mà không có tính thương mại. Bạn đọc có thể tải về bộ resource tại STDIO_ZeroResources (hoặc liên hệ thầy Khương để Copy)
Sau khi tải về, bạn đọc giải nén và copy tất cả vào đường dẫn Vidu7_1/Assets/Resources. Nếu folder Resources chưa tồn tại thì bạn hãy tự tạo ra nó.
THS. NGUYỄN VĂN KHƯƠNG 124
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Ngoài ra, toàn bộ sprite đều đươc tích hợp vào spritesheet với định dạng riêng của Unity. Bạn đọc cần import thêm package TexturePacker Importer để làm việc với bộ resources. Package yêu cầu phiên bản Unity 4.5.0 hoặc cao hơn. Bạn có thể tải về từ internets ở đường dẫn bên dưới (có thể tải về sau cũng được vì bài này chưa cần dùng đến nó)
https://www.assetstore.unity3d.com/en/#!/content/16641 - 17/04/2016.
Game Zero hướng dẫn trong bài viết là phiên bản mobile, do đó bạn cần điều chỉnh lại trong Build Setting và trong cửa sổ Game với kích thước màn hình dọc. Tôi chọn nền tảng Android và độ phân giải màn hình WVGA Portrait (480x800) như sau:
Bấm Ctrl + Shift + B hoặc chọn menu File → Build Setting.
Chọn nền tảng Android → Switch Platform.
THS. NGUYỄN VĂN KHƯƠNG 125
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Nếu không thấy Player Settings, bạn cần phải kết nối Internet để tải nền tảng IOS hoặc Android về (hoặc liên hệ thầy Khương để copy) và cài đặt để biên dịch sau này. Nhưng phần này đang thiết kế Game nên không cần thiết, bạn có thể điều chỉnh độ phân giải màn hình bằng cách:
Tại cửa sổ Game, chọn độ phân giải màn hình như trong hình dưới. Nếu không có các bạn chọn dấu (+) và thêm độ phân giải: 480x800.
Tạo background
Đến đây cơ bản chúng ta đã chuẩn bị xong môi trường làm việc với game Zero.
Mã màu background của game Zero là 1DE9B6FF. Thay đổi màu background tại Main Camera như sau:
THS. NGUYỄN VĂN KHƯƠNG 126
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Tôi tạo thêm một đối tượng UI Image là Canvas để gắn BoardGame như sau:
- Click chuột phải vào cửa sổ Hierachy chọn UI, chọn Canvas. Tôi thiết lập Canvas để render theo camera. Nhờ đó mà các đối tượng UI sẽ luôn nằm trên màn hình hiển thị. Ngoài ra, tôi thay đổi UI Scale Mode thành Scale With Screen Size với Reference Resolution (độ phân giải gốc để scale khi kích thước màn hình game thay đổi) là 480x800. Khi phóng lớn hay thu nhỏ cửa sổ Game thì các đối tượng sẽ được scale lại tương ứng. Bằng cách, click chuột vào Canvas thiết lập thuộc tính như sau:
THS. NGUYỄN VĂN KHƯƠNG 127
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- - Click chuột phải vào Canvas chọn UI, chọn Image, đổi tên Image thành
BoardGame. Thiết lập thuộc tính như bên dưới:
BoardGame được tạo ra sẽ nằm trong một Canvas.
Tạo Button
Hai button Right và Wrong sử dụng trong gameplay là UI Button. Thao tác với UI Button, bạn đọc có thể tham khảo bài viết Thiết Kế Giao Diện Người Dùng Trên Unity. Tôi tạo ra hai button như sau:
Button Right.
Click chuột phải vào BoardGame, chọn UI, chọn Button và thiết lập thuộc tính Button right như sau:
THS. NGUYỄN VĂN KHƯƠNG 128
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Button Wrong.
Click chuột phải vào BoardGame, chọn UI, chọn Button và thiết lập thuộc tính Button left như sau:
THS. NGUYỄN VĂN KHƯƠNG 129
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Tôi sử dụng Transition là Sprite Swap với resource có sẵn trong Spritesheet. Ngoài ra tôi gắn tag "Button" cho hai button này để tiện quản lý. Thông số về vị trí của hai button bạn có thể tuỳ chỉnh cho vừa mắt hoặc sử dụng thông số tôi thiết lập sẵn.
THS. NGUYỄN VĂN KHƯƠNG 130
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Tạo script nhận sự kiện button click
Chúng ta sẽ viết các đoạn Code Script để xử lý sự kiện nhấn nút vào bài sau.
Lưu Scene Game lại bằng cách chọn File / Save Scene. Lưu Scene lại với tên là GameScene trong thư mục Scenes.
THS. NGUYỄN VĂN KHƯƠNG 131
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Chạy chương trình và cảm nhận.
7.2. HIỆN THỰC GAMEPLAY
Giới thiệu gameplay
Gameplay của Zero khá đơn giản: Mỗi lượt chơi là một phép tính được biểu thị bằng hình ảnh ngẫu nhiên 4 chất của bộ bài Tây (chúng tôi – những nhà phát triển Zero, gọi là suit). Người chơi sẽ phải tính số lượng suit có ở hai bên, kết hợp với các toán tử (>, <, =) và quyết định nhanh chóng.
Sau một khoảng thời gian ngắn, nếu trả lời đúng sẽ được chuyển sang mức độ tiếp theo với độ khó tăng dần, trả lời sai sẽ thua. Người chơi có thể cạnh tranh bằng cách chia sẻ điểm số đạt được với nhau. Với yếu tố về thời gian và các hiệu ứng, Zero là một trong những game thú vị thuộc thể loại endless.
Trong bài viết này, tôi sẽ hướng dẫn bạn hiện thực lại gameplay bao gồm random phép tính và kiểm tra tính đúng sai của phép tính. Các thành phần khác của gameplay tôi sẽ hướng dẫn trong các bài viết sau.
THS. NGUYỄN VĂN KHƯƠNG 132
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
- Mở lại dự án Vidu7_1. - Tạo giao diện và cấu trúc dự án của chương trình lại bằng cách Click chuột phải và cửa sổ Hierachy, chọn Create Empty, đặt tên lại là Calculation. - Click chuột phải vào Calculation, chọn Create Empty, đặt tên lại là Left, thiết lập thuộc tính đối tượng Left như sau:
- Lần lượt tạo 4 Sprite trong Left dùng để chứa 4 quân bài: Cơ, rô, chuồn, bích phía trái bằng cách Click chuột phải vào Left chọn 2D Object, chọn Spite, đặt tên Spite lại là L (1).
- Thiết lập thuộc tính Spite L (1) như sau:
THS. NGUYỄN VĂN KHƯƠNG 133
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
- Lần lượt tạo thêm 3 Spite là: L (2), L (3), L (4). - Click chuột phải vào Calculation, chọn Create Empty, đặt tên lại là Right, thiết lập thuộc tính đối tượng Right như sau:
- Lần lượt tạo 4 Sprite trong Right dùng để chứa 4 quân bài: Cơ, rô, chuồn, bích phía phải bằng cách Click chuột phải vào Right chọn 2D Object, chọn Spite, đặt tên Spite lại là R (1). - Thiết lập thuộc tính R (1) là:
THS. NGUYỄN VĂN KHƯƠNG 134
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
- Lần lượt tạo thêm 3 Spite là: R (2), R (3), R (4). - Click chuột phải vào Calculation, tạo thêm 1 Spite đặt tên là Operator, thiết lập thuộc tính như sau:
Như vậy cấu trúc giao diện như sau:
THS. NGUYỄN VĂN KHƯƠNG 135
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Bây giờ chúng ta bắt đầu viết các đoạn code Script để xử lý chương trình Game bằng 3 Script là: GameManager, ButtonManager, Statics
Kiểm tra tình trạng Game đang chơi bằng Statics.cs
using UnityEngine;
namespace Stdio {
static class Static {
public static void S_Debug(object message) {
if (Debug.isDebugBuild)
Debug.Log(message);
}
}
static class Constant {
public static readonly int s_numSuit = 4;
}
}
Viết code của Script Statics như sau và lưu lại:
THS. NGUYỄN VĂN KHƯƠNG 136
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
GameManager
GameManager là nơi quản lý tập trung các sự kiện xảy ra trong game. Toàn bộ phần Gameplay sẽ được quản lý bởi script GameManager.cs. Script này sẽ được gắn vào một đối tượng được gọi là GameManager sẽ tạo sau.
using UnityEngine; using Stdio;
public class GameManager : MonoBehaviour { public GameObject m_leftContainer; public GameObject m_rightContainer; public GameObject m_operator;
private GameObject[] m_lefts; private GameObject[] m_rights;
public Sprite[] m_sprSuits; public Sprite[] m_sprOperators;
private int[] m_calculation;
void Start() { m_lefts = new GameObject[Constant.s_numSuit]; m_rights = new GameObject[Constant.s_numSuit];
for(int i = 0; i < Constant.s_numSuit; i++) { m_lefts[i] = m_leftContainer.transform.GetChild(i).gameObject; m_rights[i] = m_rightContainer.transform.GetChild(i).gameObject; }
m_calculation = new int[3]; GenerateNextCalculation(); }
void Update() { #if UNITY_EDITOR { if(Input.GetKeyDown(KeyCode.LeftArrow)) { if (IsRightAnswer()) GenerateNextCalculation(); else Static.S_Debug("You're wrong!"); } else if (Input.GetKeyDown(KeyCode.RightArrow)) { if (!IsRightAnswer()) GenerateNextCalculation(); else Static.S_Debug("You're wrong!"); -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 137
Tạo một Script GameManager và lưu vào thư mục Scripts như sau:
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- } } #endif }
public void GenerateNextCalculation() { m_calculation[0] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[1] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[2] = Random.Range(0, 3);
GetSuitsPosition(m_lefts, m_calculation[0]); GetSuitsPosition(m_rights, m_calculation[1]);
m_operator.GetComponent
internal void GetSuitsPosition(GameObject[] _suits, int _count) { switch (_count) { case 1: _suits[0].SetActive(true); _suits[1].SetActive(false); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = Vector3.zero;
_suits[0].GetComponent
break; case 2: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
break; case 3: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, -0.5f, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, -0.5f, 0);
_suits[1].GetComponent
THS. NGUYỄN VĂN KHƯƠNG 138
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- m.Range(0, 4)];
_suits[2].transform.localPosition = new Vector3(0, 0.5f, 0);
_suits[2].GetComponent
break; case 4: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(true);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 1.0f, 0);
_suits[2].GetComponent
_suits[3].transform.localPosition = new Vector3(0, -1.0f, 0);
_suits[3].GetComponent
break; } }
public bool IsRightAnswer() { return (m_calculation[2] == 0 && m_calculation[0] == m_calculation[1]) || (m_calculation[2] == 1 && m_calculation[0] > m_calculation[1]) || (m_calculation[2] == 2 && m_calculation[0] < m_calculation[1]); } }
Giải thích một số đoạn code quan trọng trong Scripts GameManager.cs:
• Left, Right, Operator: Lưu trữ ảnh hiển thị mô phỏng của phép tính hiện tại. • Calculation: Lưu trữ phép tính được ngẫu nhiên sau mỗi lần trả lời đúng.
Các thuộc tính và phương thức cần thiết của script GameManager.cs:
• Hàm GenerateNextCalculation: Tạo phép tính mới. Hàm sẽ được gọi khi người
Calculation là cơ sở để vẽ các đối tượng lên màn hình.
• Hàm IsRightAnswer: Kiểm tra tính đúng sai của phép tính. Hàm này được sử dụng
chơi vượt qua được một câu hỏi.
trong button khi click chuột.
THS. NGUYỄN VĂN KHƯƠNG 139
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Đối với các suit đại diện cho phép tính, tôi sử dụng các container (đối tượng chứa) và thành phần Transform của container để tìm và lưu trữ các đối tượng con. Cách hiện thực như sau (dòng 23-27, GameManager.cs):
for(int i = 0; i < 4; i++) { m_lefts[i] = m_leftContainer.transform.GetChild(i).gameObject; m_rights[i] = m_rightContainer.transform.GetChild(i).gameObjec
1. 2. 3. 4.
t;
}
5.
Random phép tính
public void GenerateNextCalculation() {
m_calculation[0] = Random.Range(1, 5); // Suits left m_calculation[1] = Random.Range(1, 5); // Suits right m_calculation[2] = Random.Range(0, 3); // Operator
GetSuitsPosition(m_lefts, m_calculation[0]); GetSuitsPosition(m_rights, m_calculation[1]);
m_operator.GetComponent
1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
m_calculation[2]];
}
11.
Việc random phép tính phụ thuộc vào số lượng suit mỗi bên và số lượng các toán tử. Đối với Zero, số lượng tối đa ở một phía là 4, đồng thời sử dụng ba toán tử >, < và =. Hiện thực hàm GenerateNextCalcultion như sau (dòng 55-65, GameManager.cs):
void GameManager.GetSuitsPosition(GameObject[] _suits, int _count);
1.
Sau khi thực hiện ngẫu hiên phép tính, tôi cập nhật lại các suit hai bên bằng cách cho ẩn những suit không cần thiết, đồng thời toán tử cũng được cập nhật lại. Việc cập nhật vị trí các suits được hiện thực trong hàm GetSuitsPosition. Nguyên mẫu hàm như sau:
void GetSuitsPosition(GameObject[] _suits, int _count) {
switch (_count) {
case 1:
_suits[0].SetActive(true); _suits[1].SetActive(false); _suits[2].SetActive(false); _suits[3].SetActive(false);
1. 2. 3. 4. 5. 6. 7. 8. 9.
Hàm nhận vào hai giá trị là danh sách đối tượng cùng với số lượng. Tôi thiết kế hàm như thế để sử dụng chung cho cả các suits ở hai bên. Hiện thực hàm khá đơn giản do Zero chỉ có tối đa 4 suit mỗi bên (dòng 67-130, GameManager.cs):
THS. NGUYỄN VĂN KHƯƠNG 140
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
_suits[0].transform.localPosition = Vector3.
_suits[0].GetComponent
10. 11. zero; 12.
ite = m_sprSuits[Random.Range(0, 4)];
break;
case 2:
_suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vect
13. 14. 15. 16. 17. 18. 19. 20. 21.
or3(-0.6f, 0, 0);
_suits[0].GetComponent
22.
ite = m_sprSuits[Random.Range(0, 4)];
_suits[1].transform.localPosition = new Vect
23. 24.
or3(0.6f, 0, 0);
_suits[1].GetComponent
25.
ite = m_sprSuits[Random.Range(0, 4)];
break;
case 3:
_suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vect
26. 27. 28. 29. 30. 31. 32. 33. 34.
or3(-0.6f, -0.5f, 0);
_suits[0].GetComponent
35.
ite = m_sprSuits[Random.Range(0, 4)];
_suits[1].transform.localPosition = new Vect
36. 37.
or3(0.6f, -0.5f, 0);
_suits[1].GetComponent
38.
ite = m_sprSuits[Random.Range(0, 4)];
_suits[2].transform.localPosition = new Vect
39. 40.
or3(0, 0.5f, 0);
_suits[2].GetComponent
41.
ite = m_sprSuits[Random.Range(0, 4)];
break;
case 4:
_suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(true);
_suits[0].transform.localPosition = new Vect
42. 43. 44. 45. 46. 47. 48. 49. 50.
or3(-0.6f, 0, 0);
_suits[0].GetComponent
51.
ite = m_sprSuits[Random.Range(0, 4)];
_suits[1].transform.localPosition = new Vect
52. 53.
or3(0.6f, 0, 0);
THS. NGUYỄN VĂN KHƯƠNG 141
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game
-----------------------------------------------------------------------------------------------------------------
_suits[1].GetComponent
54.
ite = m_sprSuits[Random.Range(0, 4)];
_suits[2].transform.localPosition = new Vect
55. 56.
or3(0, 1.0f, 0);
_suits[2].GetComponent
57.
ite = m_sprSuits[Random.Range(0, 4)];
_suits[3].transform.localPosition = new Vect
58. 59.
or3(0, -1.0f, 0);
_suits[3].GetComponent
60.
ite = m_sprSuits[Random.Range(0, 4)];
break;
}
}
61. 62. 63. 64.
Viết Code kiểm tra tính đúng sai bằng button
using UnityEngine;
using Stdio;
public class ButtonManager : MonoBehaviour
{
public void CheckAnswer()
{
GameManager manager = GameObject.FindGameObjectWithTag("GameManager").GetC
omponent
if(gameObject.name == "BtnRight") { if (manager.IsRightAnswer()) manager.GenerateNextCalculation(); else Static.S_Debug("You're wrong!"); } else { if (!manager.IsRightAnswer()) manager.GenerateNextCalculation(); else Static.S_Debug("You're wrong!"); } } }
Tạo đối tượng GameManager và gắn Script GameManager vào
Hai button được hiện thực trong bài viết trước có nhiệm vụ kiểm tra tính đúng đắn của phép tính. Khi người chơi trả lời sai thì lượt chơi sẽ kết thúc. Script ButtonManager.cs như sau:
- Click chuột phải vào Calculation, chọn Create Empty, đặt tên lại là GameManager.
THS. NGUYỄN VĂN KHƯƠNG 142
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- - Kéo Script GameManager sang cửa sổ thuộc tính của đối tượng GameManager và
Gắn sự kiện vào 2 nút RightBtn, LeftBtn
thiết lập thuộc tính như sau:
- Chọn đối tượng RightBtn, kéo Script ButtonManager sang và thiết lập thuộc tính như sau:
THS. NGUYỄN VĂN KHƯƠNG 143
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Tag GameManager
- Tương tực, chọn đối tượng LeftBtn, kéo Script ButtonManager sang và thiết lập thuộc tính như sau:
- Chọn đối tượng GameManager, vào thẻ Tag chọn Add Tag, gõ vào GameManager.
THS. NGUYỄN VĂN KHƯƠNG 144
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
- Chọn Tag là GameManager.
Lưu chương trình lại, chạy chương trình và cảm nhận.
7.3. ĐIỂM SỐ VÀ PROGRESS TIMER
Giới thiệu
Tiếp tục chuỗi bài viết Hướng Dẫn Hiện Thực Game Zero Với Unity, ở phần trước, tôi đã giới thiệu và hướng dẫn các bạn cách hiện thực gameplay của game Zero. Trong phần 3 này, tôi sẽ giúp các bạn hoàn thiện gameplay với các tính năng thú vị hơn với người chơi là hiển thị điểm số hiện tại và thời gian (Progress Timer).
Lưu trữ điểm số và hiển thị
THS. NGUYỄN VĂN KHƯƠNG 145
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Static.s_score
Điểm số trong Zero được sử dụng duy nhất trong Gameplay, do Zero không hỗ trợ các tính năng như Leaderboard (bảng xếp hạng), hay Statistic (bảng thống kê các số liệu người chơi đạt được), … nhưng bạn hoàn toàn có thể lưu trữ điểm số ở GameManager. Tuy nhiên, tôi khuyến khích các bạn hiện thực theo hướng tổng quát hoá, có thể tái ứng dụng trong các trò chơi khác có quy mô lớn hơn.
public static int s_score;
1.
Để hiện thực tổng quát, tôi mở và sửa lại Script Static, đặt thêm biến lưu trữ điểm số tại lớp Static (dòng 13, Statics.cs):
using Stdio;
1.
Thuộc tính này có thể truy cập ở bất cứ nơi nào trong project. Do lớp Static được tôi đặt trong namespace Stdio nên bạn đọc cần lưu ý dòng code sau (dòng 2, GameManager.cs):
namespace Stdio phải được import trước khi sử dụng bất cứ thành phần nào chứa trong nó. Đây là một kỹ thuật quản lý code của C# nên bạn đọc không cần thắc mắc tại sao lại cần thiết làm điều này.
s_score sẽ được cập nhật lại khi người chơi vượt qua một level. Tôi sẽ dừng lại và cùng các bạn xem xét một chút về cấu trúc của game và có hướng hiện thực hiệu quả nhất.
using UnityEngine;
namespace Stdio { static class Static { public static void S_Debug(object message) { if (Debug.isDebugBuild) Debug.Log(message); }
public static int s_score; }
static class Constant { public static readonly int s_numSuit = 4; } }
Như vậy Script Static sẽ hoàn chỉnh như sau:
THS. NGUYỄN VĂN KHƯƠNG 146
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
public void CheckAnswer() { GameManager manager = GameObject.FindGameObjectWithTag("Game
1. 2. 3.
Manager").GetComponent
if(gameObject.name == "BtnRight") { if (manager.IsRightAnswer()) manager.GenerateNextCalculation(); else Static.S_Debug("You're wrong!"); } else { if (!manager.IsRightAnswer()) manager.GenerateNextCalculation(); else Static.S_Debug("You're wrong!"); } }
4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.
Hàm CheckAnswer thuộc script ButtonManager như sau:
Ta thấy, với mỗi button đều có hai trường hợp xảy ra. Nếu s_score được cập nhật trực tiếp ở mỗi button, bạn cần sao chép đoạn code đó sang vị trí tương ứng ở button còn lại, chưa kể đến hai phím mũi tên được gắn chức năng của hai button mà tôi đã hiện thực ở bài viết trước. Do đó hiện thực theo cách này sẽ khá bất tiện và kém hiệu quả.
Static.s_score++;
1.
Nếu để ý, bạn sẽ thấy với mỗi trường hợp trả lời đúng, hàm GenerateNextCalculation sẽ được gọi để tạo thử thách tiếp theo, bất kể là với phím hay button nào. Do đó, tôi thêm đoạn code xử lý s_score vào hàm GenerateNextCalculation như sau ( trong lớp GameManager.cs):
public void GenerateNextCalculation() { m_calculation[0] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[1] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[2] = Random.Range(0, 3);
GetSuitsPosition(m_lefts, m_calculation[0]); GetSuitsPosition(m_rights, m_calculation[1]);
m_operator.GetComponent
Static.s_score++; m_scoreText.text = Static.s_score.ToString();
Sửa lại hàm GenerateNextCalculation như sau:
THS. NGUYỄN VĂN KHƯƠNG 147
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
m_currentTime = 0f; }
Hiển thị điểm số ra màn hình
Nếu xem xét kĩ hàm Start tại GameManager.cs, bạn sẽ thấy tôi khởi tạo giá trị cho s_score bằng -1. Do ngay khi game bắt đầu, tôi cho tạo một thử thách đầu tiên dẫn đến việc tăng s_score lên khi chưa bắt đầu chơi. Đây chỉ là một mẹo nhỏ nhưng các bạn cần lưu ý để hiện thực game được hiệu quả hơn.
Để đưa s_score hiển thị lên màn hình game, tôi sử dụng UI Text. Click chuột phải vào Canvas, chọn UI, chọn Text.
Thiết lập lại thuộc tính của Text như bên dưới, nhớ đặt tên Text là Score.
THS. NGUYỄN VĂN KHƯƠNG 148
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Mở là sửa lại Script GameManager.cs.
using UnityEngine.UI;
1.
Bạn cần import thư viện như sau (dòng 3, GameManager.cs):
m_scoreText.text = Static.s_score.ToString();
1.
Tôi khởi tạo một thuộc tính có kiểu Text để tiện xử lý (dòng 19, GameManager.cs). Sau khi cập nhật điểm số, thuộc tính này cũng được thay đổi theo như sau (dòng 91, GameManager.cs):
using UnityEngine; using Stdio; using UnityEngine.UI;
public class GameManager : MonoBehaviour { public GameObject m_leftContainer; -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 149
Như vậy Script GameManager.cs sẽ được hoàn chỉnh như sau:
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- public GameObject m_rightContainer; public GameObject m_operator;
private GameObject[] m_lefts; private GameObject[] m_rights;
public Sprite[] m_sprSuits; public Sprite[] m_sprOperators;
private int[] m_calculation;
public Text m_scoreText;
private float m_currentTime; private float m_levelTime; public Image m_progressTimer;
/* Initialize */ void Start() { m_lefts = new GameObject[Constant.s_numSuit]; m_rights = new GameObject[Constant.s_numSuit];
for (int i = 0; i < Constant.s_numSuit; i++) { m_lefts[i] = m_leftContainer.transform.GetChild(i).gameObject; m_rights[i] = m_rightContainer.transform.GetChild(i).gameObject; }
m_calculation = new int[3];
m_levelTime = 1.5f; m_currentTime = 0f;
Static.s_score = -1; GenerateNextCalculation(); }
/* Update */ void Update() { m_currentTime += Time.deltaTime; if (m_currentTime >= m_levelTime) m_currentTime = 0f;
m_progressTimer.fillAmount = m_currentTime / m_levelTime;
#if UNITY_EDITOR if (Input.GetKeyDown(KeyCode.LeftArrow)) { if (IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); } } else if (Input.GetKeyDown(KeyCode.RightArrow))
THS. NGUYỄN VĂN KHƯƠNG 150
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- { if (!IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); } } #endif }
public void GenerateNextCalculation() { m_calculation[0] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[1] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[2] = Random.Range(0, 3);
GetSuitsPosition(m_lefts, m_calculation[0]); GetSuitsPosition(m_rights, m_calculation[1]);
m_operator.GetComponent
Static.s_score++; m_scoreText.text = Static.s_score.ToString();
m_currentTime = 0f; }
internal void GetSuitsPosition(GameObject[] _suits, int _count) { switch (_count) { case 1: _suits[0].SetActive(true); _suits[1].SetActive(false); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = Vector3.zero;
_suits[0].GetComponent
break; case 2: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
THS. NGUYỄN VĂN KHƯƠNG 151
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- break; case 3: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, -0.5f, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, -0.5f, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 0.5f, 0);
_suits[2].GetComponent
break; case 4: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(true);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 1.0f, 0);
_suits[2].GetComponent
_suits[3].transform.localPosition = new Vector3(0, -1.0f, 0);
_suits[3].GetComponent
break; } }
public bool IsRightAnswer() { return (m_calculation[2] == 0 && m_calculation[0] == m_calculation[1]) || (m_calculation[2] == 1 && m_calculation[0] > m_calculation[1]) || (m_calculation[2] == 2 && m_calculation[0] < m_calculation[1]); } }
Đưa điểm số Score vào GameManager bằng cách chọn đối tượng GameManager, thiết lập thuộc tính Score
Text như bên dưới:
THS. NGUYỄN VĂN KHƯƠNG 152
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Đến đây, bạn có thể chạy thử và xem thành quả đạt được.
Hiện thực Progress Timer
Progress Timer trong Zero được biểu diễn dưới dạng Clock (đồng hồ). Khoảng thời gian đã trôi qua của một level được thể hiện bằng tỉ lệ phần được vẽ của Clock.
• Tạo UI Image, đặt tên là ProgressTimer. • Kéo sprite spr_clock từ spritesheet vào thuộc tính Source Image của Progress
Unity hỗ trợ hiện thực đúng như mong muốn của tôi với UI Image. Các bước hiện thực Progress Timer:
• Chọn Image Type là Filled. Các thông số còn lại được thiết lập như sau:
Timer.
THS. NGUYỄN VĂN KHƯƠNG 153
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Đồng bộ Progress Timer với thời gian thực của game
Chú ý thuộc tính Fill Amount. Thuộc tính này quyết định tỉ lệ hình sẽ được hiển thị, 0 tương ứng với hình hoàn toàn không được hiển thị và 1 tương ứng với toàn bộ hình đều được hiển thị.
Progress Timer tạo ra ở trên chỉ là hình ảnh hiển thị của thời gian chơi trong game, chúng ta sẽ quản lý thời gian với Script.
Để quản lý thời gian, tôi sử dụng hai thuộc tính CurrentTime và LevelTime. CurrentTime là thời gian hiện tại, tính từ thời điểm một level mới được bắt đầu, LevelTime chính là thời gian tối đa người chơi có được trong level đó. Ngoài ra, để thay đổi tỉ lệ vẽ của ProgressTimer, tôi sử dụng thêm một thuộc tính UI Text.
private float m_currentTime; private float m_levelTime; public Image m_progressTimer;
1. 2. 3.
Mở lại Script GameManager , khai báo các thuộc tính trên như sau (dòng 21-23, GameManager.cs):
Tương thị điểm số đề cập ở trên, ta phải import thư tự như phần hiển viện UnityEngine.UI trước khi sử dụng Image.
m_levelTime = 1.5f;
1.
Tại hàm Start, tôi thêm phần khai báo giá trị ban đầu cho các thuộc tính trên như sau (dòng 39-40, GameManager.cs):
THS. NGUYỄN VĂN KHƯƠNG 154
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
m_currentTime = 0f;
2.
m_currentTime += Time.deltaTime; if (m_currentTime >= m_levelTime) m_currentTime = 0f;
m_progressTimer.fillAmount = m_currentTime / m_levelTime;
1. 2. 3. 4. 5.
Quản lý thời gian và cập nhật Progress Timer (dòng 49-53, GameManager.cs):
Thuộc tính fillAmount của Progress Timer truyền vào từ Unity Editor sẽ được cập nhật lại tương ứng với currentTime. Lưu ý là fillAmount sẽ thay đổi trong khoảng (0, 1) còn currentTime thay đổi trong khoảng (0, levelTime). Do đó chúng ta sẽ điều chỉnh lại currentTime theo tỉ lệ phù hợp trước khi gán vào fillAmount.
m_currentTime = 0f;
1.
Cuối cùng, để currentTime cập nhật lại từ đầu sau mỗi level, tôi thêm dòng sau vào hàm GenerateNextCalculation (dòng 93, GameManager.cs):
using UnityEngine; using Stdio; using UnityEngine.UI;
public class GameManager : MonoBehaviour { public GameObject m_leftContainer; public GameObject m_rightContainer; public GameObject m_operator;
private GameObject[] m_lefts; private GameObject[] m_rights;
public Sprite[] m_sprSuits; public Sprite[] m_sprOperators;
private int[] m_calculation;
public Text m_scoreText;
private float m_currentTime; private float m_levelTime; public Image m_progressTimer;
/* Initialize */ void Start() {
Như vậy Script GameManager hoàn chỉnh như sau:
THS. NGUYỄN VĂN KHƯƠNG 155
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- m_lefts = new GameObject[Constant.s_numSuit]; m_rights = new GameObject[Constant.s_numSuit];
for (int i = 0; i < Constant.s_numSuit; i++) { m_lefts[i] = m_leftContainer.transform.GetChild(i).gameObject; m_rights[i] = m_rightContainer.transform.GetChild(i).gameObject; }
m_calculation = new int[3];
m_levelTime = 1.5f; m_currentTime = 0f;
Static.s_score = -1; GenerateNextCalculation(); }
/* Update */ void Update() { m_currentTime += Time.deltaTime; if (m_currentTime >= m_levelTime) m_currentTime = 0f;
m_progressTimer.fillAmount = m_currentTime / m_levelTime;
#if UNITY_EDITOR if (Input.GetKeyDown(KeyCode.LeftArrow)) { if (IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); } } else if (Input.GetKeyDown(KeyCode.RightArrow)) { if (!IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); } } #endif }
public void GenerateNextCalculation() { m_calculation[0] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[1] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[2] = Random.Range(0, 3);
THS. NGUYỄN VĂN KHƯƠNG 156
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
GetSuitsPosition(m_lefts, m_calculation[0]); GetSuitsPosition(m_rights, m_calculation[1]);
m_operator.GetComponent
Static.s_score++; m_scoreText.text = Static.s_score.ToString();
m_currentTime = 0f; }
internal void GetSuitsPosition(GameObject[] _suits, int _count) { switch (_count) { case 1: _suits[0].SetActive(true); _suits[1].SetActive(false); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = Vector3.zero;
_suits[0].GetComponent
break; case 2: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
break; case 3: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, -0.5f, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, -0.5f, 0);
THS. NGUYỄN VĂN KHƯƠNG 157
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game
-----------------------------------------------------------------------------------------------------------------
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 0.5f, 0);
_suits[2].GetComponent
break; case 4: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(true);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 1.0f, 0);
_suits[2].GetComponent
_suits[3].transform.localPosition = new Vector3(0, -1.0f, 0);
_suits[3].GetComponent
break; } }
public bool IsRightAnswer() { return (m_calculation[2] == 0 && m_calculation[0] == m_calculation[1]) || (m_calculation[2] == 1 && m_calculation[0] > m_calculation[1]) || (m_calculation[2] == 2 && m_calculation[0] < m_calculation[1]); } }
Đưa đối tượng Progress Timer vào GameManager bằng cách, chọn đối tượng GameManager, thiết lập thuộc tính Progress Timer là ProgresTimer như bên dưới:
THS. NGUYỄN VĂN KHƯƠNG 158
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Đến đây cơ bản gameplay đã hoàn thiện. Bạn chạy Game và cảm nhận:
THS. NGUYỄN VĂN KHƯƠNG 159
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 7.4. SCENE VÀ POPUP
7.4.1. Giới thiệu
Tiếp tục chuỗi bài viết Hướng Dẫn Hiện Thực Game Zero Với Unity, trong bài viết này, tôi sẽ tập trung thiết kế hệ thống scene và popup trong game. Ngoài ra, tôi mở rộng thêm một chút về gameplay đã tương đối hoàn thiện trong các bài viết trước.
7.4.2. Quản lý GameState trong game
Trong Zero, khi người chơi không vượt qua được một level, hoặc CurrentTime vượt quá LevelTime, một popup sẽ xuất hiện và lượt chơi sẽ kết thúc, điểm số cuối cùng sẽ được hiển thị. Để kiểm soát quá trình này, tôi ứng dụng một kĩ thuật quản lý GameState đơn giản, sử dụng một thuộc tính để lưu trữ GameState.
public enum GameState { PLAYING, FAILED }
1. 2. 3. 4. 5.
Trước hết, GameState được định nghĩa là một enum trong Statics.cs. Mở Script Static thêm hàm như sau:
thuộc Khai báo trong GameManager với trữ GameState, tôi đặt tính lưu
public GameState m_gameState;
1.
tuỳ chọn HideInInspector để không hiển thị tại cửa sổ Inspector bằng cách mở Script GameManager thêm dòng lệnh bên dưới (dòng 26, GameManager.cs):
m_gameState = GameState.PLAYING;
1.
Thiết lập giá trị ban đầu cho GameState trong hàm Start, thêm dòng lệnh sau vào GameManager (dòng 46, GameManager.cs):
level. được xem Bạn qua xét kĩ Ngay khi GameScene được nạp, trạng thái của game sẽ được cập nhật là PLAYING, đồng nghĩa game sẽ được bắt đầu ngay lập tức. Ở hai button, các phím mũi tên tương ứng và vị trí kiểm tra m_currentTime, tôi cập nhật lại trạng thái game thành FAILED khi người chơi không vượt các đọc một script ButtonManager.cs và GameManager.cs để nắm rõ hơn.
Để quản lý trạng thái của game, tôi hiện thực việc kiểm tra m_gameState trong hàm Update của GameManager.cs. Tại đây tôi dùng cấu trúc rẽ nhánh switch… case để quản lý tương tự như sau:
THS. NGUYỄN VĂN KHƯƠNG 160
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
switch(m_gameState) { case GameState.PLAYING: // Logic Game break; case GameState.FAILED: // Do something break; }
1. 2. 3. 4. 5. 6. 7. 8. 9.
Mở Script GameManager sửa lại hàm Update như sau:
/* Update */
void Update() { switch (m_gameState) { case GameState.PLAYING: m_currentTime += Time.deltaTime; if (m_currentTime >= m_levelTime) ShowGameOverPopup();
m_progressTimer.fillAmount = m_currentTime / m_levelTime;
#if UNITY_EDITOR if (Input.GetKeyDown(KeyCode.LeftArrow)) { if (IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); ShowGameOverPopup(); } } else if (Input.GetKeyDown(KeyCode.RightArrow)) { if (!IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); ShowGameOverPopup(); } } #endif break; case GameState.FAILED: break; } }
THS. NGUYỄN VĂN KHƯƠNG 161
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Đến đây, cơ bản chúng ta đã quản lý được trạng thái của game. Tuy nhiên, khi người chơi thua, game sẽ dừng lại và không có cách nào đưa game trở lại trạng thái ban đầu. Đó là do tôi chưa hiện thực một thao tác nào để trả giá trị của m_gameState về PLAYING. Phần bên dưới tôi sẽ hiện thực điều đó.
Lưu ý: khi chạy chương trình sẽ báo lỗi vì thiếu đối tượng ShowGameOverPopup
7.4.3. GameOver popup
Đây là đối tượng sẽ xuất hiện khi người chơi trả lời sai. Trên GameOver popup sẽ hiển thị điểm số người chơi đạt được, điểm số cao nhất (Best Score) và một số button. Giao diện của GameOver popup tương tự như sau:
Các dòng chữ "Your Score", "Best Score", số điểm và các button đều là các đối tượng UI.
Click chuột phải vào Main Camera ở cửa sổ Hierachy chọn UI, chọn Canvas.
THS. NGUYỄN VĂN KHƯƠNG 162
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Đặt tên Canvas mới tạo là Canvas1, thiết lập thuộc tính Canvas để cho hiển thị cùng Camera chương trình như sau:
Click chuột phải vào Canvas1 mới tạo chọn UI, chọn Image. Thiết lập thuộc tính Image như bên dưới:
THS. NGUYỄN VĂN KHƯƠNG 163
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Click chuột phải vào PopupBoard, chọn UI, chọn Text, thiết lập thuộc tính như bên dưới:
THS. NGUYỄN VĂN KHƯƠNG 164
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Click chuột phải vào PopupBoard, chọn UI, chọn Text, thiết lập thuộc tính như bên dưới:
Click chuột phải vào YourScore, chọn UI, chọn Text, thiết lập thuộc tính như bên dưới:
Như vậy ta đã thiết lập được các Text hiển thị số điểm người chơi:
THS. NGUYỄN VĂN KHƯƠNG 165
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Tương tự ta tạo các đối tượng Text để hiển thị số điểm cao nhất, theo cấu trúc như bên dưới:
Bây giờ tạo 2 Button (Menu và Reply) bằng cách Click chuột phải vào PopupBoard, chọn UI, chọn Button. Thiết lập thuộc tính Button Menu như sau:
Tương tự tạo Button Reply với thuộc tính như sau:
THS. NGUYỄN VĂN KHƯƠNG 166
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
1. public void ShowGameOverPopup() 2. { 3. m_gameOverPopup.enabled = true; 4. 5. m_gameState = GameState.FAILED; 6. m_popupScore.text = Static.s_score.ToString(); 7. 8. if(PlayerPrefs.GetInt("BestScore") < Static.s_score) 9. PlayerPrefs.SetInt("BestScore", Static.s_score); 10.
11. m_popupBestScore.text = PlayerPrefs.GetInt("BestScore");
}
12.
Mở lại Script, thêm hàm ShowGameOverPopup để hiển thị GameOver popup như sau:
public void ChangeToMenuScene()
{ Application.LoadLevel("MenuScene"); }
public void ChangeToGameScene() { Application.LoadLevel("GameScene"); }
Mở lại Script ButtonManager, thêm 2 hàm như sau:
public Canvas m_gameOverPopup;
public Text m_popupScore; public Text m_popupBestScore;
Tiếp tục thêm các khai báo cho Script này, dòng 28
THS. NGUYỄN VĂN KHƯƠNG 167
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Đưa các đối tượng vừa tạo vào GameManager bằng cách chọn GameManager thiết lập các
thuộc tính như sau:
Đưa các sự kiện vào 2 button MENU và REPLY bằng cách lần lượt chọn từng button và kéo Script ButtonManager sang và thiết lập thuộc tính sự kiện cho từng Button như sau:
THS. NGUYỄN VĂN KHƯƠNG 168
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Thiết lập ẩn Canvas1 bằng cách chọn Canvas1, bỏ chọn Canvas
Đối với việc hiển thị điểm hiện tại, bạn đọc sử dụng biến s_score trong Statics.cs và thực hiện tương tự như hướng dẫn trong bài viết Hiện Thực Game Zero Với Unity - Phần 3 - Điểm Số Và Progress Timer. Đối với BestScore, tôi sử dụng PlayerPrefs để lưu trữ xuống bộ nhớ máy, sử dụng cho các lần chơi sau. Tìm hiểu thêm về PlayerPrefs tại bài Lưu Trữ Thông Tin Game Với PlayerPrefs.
Hàm ShowGameOverPopup sẽ được gọi khi người chơi thua, do đó tôi thay thế các dòng code thay đổi m_gameState sang FAILED bằng việc gọi hàm ShowGameOverPopup.
Thiết lập để cho hiển thị Canvas và ẩn Canvas1 đi bằng cách chọn từng Canvas và thiết lập cho thuộc tính Canvas.
Chạy và kiểm tra chương trình.
Bây giờ ta tiếp tục tạo MenuScene, sẽ hiển thị khi chọn Button MENU.
THS. NGUYỄN VĂN KHƯƠNG 169
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 7.4.4. MenuScene
Info popup
Click chuột vào File chọn New Scene, xuất hiện một Scene mới. Lưu Scense bằng cách chọn
MenuScene của Zero khá đơn giản, bao gồm button Play và một button mở InfoPopup. Button Play sẽ quay lại giao diện để tiếp tục chơi Game, Button InfoPopup hiển thị thông tin về Game.
File, chọn Save Scene as, lưu vào thư mục Scenes, đặt tên là MenuScene như bên dưới:
THS. NGUYỄN VĂN KHƯƠNG 170
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Click chuột phải vào cửa sổ Hierachy, chọn UI, chọn Canvas, thiết lập thuộc tính Canvas như bên dưới.
Click chuột phải vào Canvas, chọn UI, chọn Image, thiết lập thuộc tính Image như bên dưới:
THS. NGUYỄN VĂN KHƯƠNG 171
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Click chuột phải vào Canvas, chọn UI, chọn Image và thiết lập thuộc tính như bên dưới:
Tạo thêm một Button Play bằng cách click chuột phải vào Canvas, chọn UI, chọn Button, thiết lập thuộc tính như bên dưới:
Tạo thêm một Button InfoBtn, click chuột phải vào Canvas, chọn UI, chọn Button, thiết lập thuộc tính như bên dưới:
THS. NGUYỄN VĂN KHƯƠNG 172
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Tạo giao diện thông tin Game bằng cách Click chuột phải vào cửa sổ Hierachy chọn UI, chọn Canvas, thiết lập thuộc tính Canvas như sau:
Ẩn giao diện Canvas trước bằng cách chọn Canvas, bỏ chọn ở mục Canvas:
THS. NGUYỄN VĂN KHƯƠNG 173
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Click chuột phải vào Canvas1, chọn UI, chọn Image, thiết lập thuộc tính như sau:
Click chuột phải vào GameBoard, tạo 1 Image Stdio_Logo và thiết lập thuộc tính như sau:
THS. NGUYỄN VĂN KHƯƠNG 174
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Tạo Text Developer bằng cách Click chuột phải vào GameBoard, chọn UI, chọn Text, thiết lập thuộc tính như sau:
Click chuột phải vào Developer, chọn UI, chọn Text và thiết lập thuộc tính như sau:
Tạo Button Back bằng cách click chuột phải vào GameBoard, chọn UI, chọn Button và thiết lập thuộc tính như sau:
THS. NGUYỄN VĂN KHƯƠNG 175
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Chạy thử và kiểm tra.
Đưa các sự kiện vào 3 Button. Chọn Button BackBtn, thiết lập thuộc tính như sau (Chọn Canvas, chọn bool enabled)
Như vậy Canvas1 đã hoàn thành, ẩn Canvas1 này đi bằng cách chọn Canvas1, bỏ chọn vào thuộc tính Canvas.
Đưa sự kiện vào 2 button ở Canvas, chọn Canvas, thiết lập thuộc tính để hiển thị lại Cavas này:
THS. NGUYỄN VĂN KHƯƠNG 176
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Chọn Button PlayBtn, kéo Script Button sang cửa sổ thuộc tính, thiết lập thuộc tính cho PlayBtn như sau:
Tương tự chọn Button InfoBtn, thiết lập thuộc tính như sau:
THS. NGUYỄN VĂN KHƯƠNG 177
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Thiết lập để cho hiển thị Canvas và ẩn Canvas1 đi bằng cách chọn từng Canvas và thiết lập cho thuộc tính Canvas.
Như vậy chương trình đã hoàn chỉnh. Bây giờ mở lại Scene chính là GameScene bằng cách vào File, chọn Open Scen, chọn Scene là GameScene. Nhớ thiết lập hiển thị Canvas và ẩn Canvas1 ở Scene này.
Biên dịch chương trình đển kết nối 2 Scene (GameScene và MenuScene) lại bằng cách chọn File, chọn Build Setting…, kéo 2 Scene vào như hình vẽ, xong đóng cửa sổ Build Setting… lại.
THS. NGUYỄN VĂN KHƯƠNG 178
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Biên dịch và cảm nhận chương trình.
Nếu muốn biên dịch chương trình để đưa vào điện thoại sử dụng thì các bạn Build trong cửa sổ Build Setting.
Đến đây, game Zero đã khá hoàn thiện về mặt nội dung. Bài học sau tôi sẽ đi sâu về âm thanh và các hiệu ứng của game Zero, các tính năng thu hút người chơi.
THS. NGUYỄN VĂN KHƯƠNG 179
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Mã nguồn hoàn chỉnh các Script:
Script ButtonManager:
using UnityEngine; using Stdio;
public class ButtonManager : MonoBehaviour
{
public void CheckAnswer()
{
GameManager manager = GameObject.FindGameObjectWithTag("GameManager").Get
Component
if(gameObject.name == "BtnRight") { if (manager.IsRightAnswer()) manager.GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); } } else { if (!manager.IsRightAnswer()) manager.GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); } } }
public void ChangeToMenuScene() { Application.LoadLevel("MenuScene"); }
public void ChangeToGameScene() { Application.LoadLevel("GameScene"); } }
Script GameManager:
using UnityEngine; using Stdio; using UnityEngine.UI; -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 180
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
public class GameManager : MonoBehaviour { public GameObject m_leftContainer; public GameObject m_rightContainer; public GameObject m_operator;
private GameObject[] m_lefts; private GameObject[] m_rights;
public Sprite[] m_sprSuits; public Sprite[] m_sprOperators;
private int[] m_calculation;
public Text m_scoreText;
private float m_currentTime; private float m_levelTime; public Image m_progressTimer;
[HideInInspector] public GameState m_gameState;
public Canvas m_gameOverPopup; public Text m_popupScore; public Text m_popupBestScore;
/* Initialize */ void Start() { m_lefts = new GameObject[Constant.s_numSuit]; m_rights = new GameObject[Constant.s_numSuit];
for (int i = 0; i < Constant.s_numSuit; i++) { m_lefts[i] = m_leftContainer.transform.GetChild(i).gameObject; m_rights[i] = m_rightContainer.transform.GetChild(i).gameObject; }
m_calculation = new int[3];
m_levelTime = 1.5f; m_currentTime = 0f;
Static.s_score = -1; GenerateNextCalculation(); m_gameState = GameState.PLAYING;
}
/* Update */ void Update()
THS. NGUYỄN VĂN KHƯƠNG 181
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- { switch (m_gameState) { case GameState.PLAYING: m_currentTime += Time.deltaTime; if (m_currentTime >= m_levelTime) ShowGameOverPopup();
m_progressTimer.fillAmount = m_currentTime / m_levelTime;
#if UNITY_EDITOR if (Input.GetKeyDown(KeyCode.LeftArrow)) { if (IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); ShowGameOverPopup(); } } else if (Input.GetKeyDown(KeyCode.RightArrow)) { if (!IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); ShowGameOverPopup(); } } #endif break; case GameState.FAILED: break; } }
public void GenerateNextCalculation() { m_calculation[0] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[1] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[2] = Random.Range(0, 3);
GetSuitsPosition(m_lefts, m_calculation[0]); GetSuitsPosition(m_rights, m_calculation[1]);
m_operator.GetComponent
Static.s_score++; m_scoreText.text = Static.s_score.ToString();
THS. NGUYỄN VĂN KHƯƠNG 182
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- m_currentTime = 0f; }
internal void GetSuitsPosition(GameObject[] _suits, int _count) { switch (_count) { case 1: _suits[0].SetActive(true); _suits[1].SetActive(false); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = Vector3.zero;
_suits[0].GetComponent
break; case 2: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
break; case 3: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, -0.5f, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, -0.5f, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 0.5f, 0);
_suits[2].GetComponent
break; case 4:
THS. NGUYỄN VĂN KHƯƠNG 183
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(true);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 1.0f, 0);
_suits[2].GetComponent
_suits[3].transform.localPosition = new Vector3(0, -1.0f, 0);
_suits[3].GetComponent
break; } }
public bool IsRightAnswer() { return (m_calculation[2] == 0 && m_calculation[0] == m_calculation[1]) || (m_calculation[2] == 1 && m_calculation[0] > m_calculation[1]) || (m_calculation[2] == 2 && m_calculation[0] < m_calculation[1]); }
public void ShowGameOverPopup() { m_gameOverPopup.enabled = true;
m_gameState = GameState.FAILED; m_popupScore.text = Static.s_score.ToString();
if (PlayerPrefs.GetInt("BestScore") < Static.s_score) PlayerPrefs.SetInt("BestScore", Static.s_score);
m_popupBestScore.text = PlayerPrefs.GetInt("BestScore").ToString(); } }
Script Static:
using UnityEngine;
namespace Stdio -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 184
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- { static class Static { public static void S_Debug(object message) { if (Debug.isDebugBuild) Debug.Log(message); }
public static int s_score; }
static class Constant { public static readonly int s_numSuit = 4; }
public enum GameState { PLAYING, FAILED } }
THS. NGUYỄN VĂN KHƯƠNG 185
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 7.5. ÂM THANH VÀ EFFECT
7.5.1. Giới thiệu
Tiếp tục chuỗi bài viết Hướng Dẫn Hiện Thực Game Zero Với Unity, trong bài viết này, tôi sẽ hướng dẫn bạn đọc thêm các hiệu ứng âm thanh và hiệu ứng của gameplay. Đây là bài viết thứ 5, đồng thời là bài viết kết thúc chuỗi bài hướng dẫn.
7.5.2. Các hiệu ứng âm thanh trong Zero
Trong Zero sử dụng ba hiệu ứng âm thanh chính:
sfx_answer_right.wav: Sử dụng khi người chơi vượt qua một level.
sfx_answer_wrong.wav: Sử dụng khi GameOver.
sfx_button_press.wav: Sử dụng khi người chơi bấm chọn một button (trừ hai button Right và Wrong được xử lý riêng).
Hai button Right và Wrong quan trọng nên tôi sẽ ưu tiên hiện thực trước. Về mặt logic, sfx_answer_right và sfx_answer_wrong không gắn vào hai button tương ứng. Câu trả lời của người chơi ở mỗi level là yếu tố quyết định âm thanh nào sẽ được phát ra. Do đó, mở Script GameManager ra thêm các đoạn code như sau:
m_audioController.clip = m_sfxAnswerRight; m_audioController.Play();
1. 2.
Xử lý âm thanh Right, thêm 2 dòng lệnh cho hàm GenerateNextCalculation ( ):
m_audioController.clip = m_sfxAnswerWrong; m_audioController.Play();
1. 2.
Xử lý âm thanh Wrong, thêm 2 dòng lệnh cho hàm ShowGameOverPopup ():
Trước đó, tôi định nghĩa thêm các thuộc tính cần thiết và khởi tạo chúng trước khi sử dụng. Thao tác như sau:
private AudioClip m_sfxAnswerRight; private AudioClip m_sfxAnswerWrong; private AudioSource m_audioController;
1. 2. 3.
Khai báo (từ dòng lệnh 32, GameManager.cs):
Khởi tạo (từ dòng lệnh 49, GameManager.cs):
THS. NGUYỄN VĂN KHƯƠNG 186
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
m_sfxAnswerRight = Resources.Load
1. 2. 3.
Tôi sử dụng Resources.Load để load trực tiếp các file âm thanh trong thư mục tài nguyên bằng cách chọn đối tượng GameManager. Bên cửa sổ thuộc tính chọn Add Component, chọn Audio, chọn Audio Source như bên dưới:
Đối với các button còn lại, việc phát âm thanh có thể xử lý trong code hoặc cách khác tiện dụng hơn là sử dụng event OnClick của button.
Ẩn Canvas đi, hiển thị Canvas1 lên, chọn đối tượng MenuBtn, chọn Add Component, chọn Audio, chọn Audio Source, thiết lập thuộc tính như sau:
THS. NGUYỄN VĂN KHƯƠNG 187
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
Trong mục On Click(), bấm dấu cộng và thêm âm thanh khi Click như sau:
Tương tự thêm âm thanh khi Click vào Button ReplyBtn với thuộc tính như sau:
THS. NGUYỄN VĂN KHƯƠNG 188
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- Tiếp tục mở MenuScene để thêm âm thanh cho các Button còn lại. Chọn File, chọn Open Scene, chọn MenuScene ở thư mục Scenes. Chọn Button PlayBtn và thiết lập thuộc tính như sau:
Tương tự thêm âm thanh vào các Button: InfoBtn, BackBtn.
Mở lại Scence GameScene, chạy chương trình để kiểm tra âm thanh.
using UnityEngine; using Stdio; using UnityEngine.UI;
public class GameManager : MonoBehaviour { public GameObject m_leftContainer; public GameObject m_rightContainer; public GameObject m_operator;
private GameObject[] m_lefts; private GameObject[] m_rights;
public Sprite[] m_sprSuits; public Sprite[] m_sprOperators;
private int[] m_calculation;
public Text m_scoreText;
private float m_currentTime; private float m_levelTime; public Image m_progressTimer;
[HideInInspector] public GameState m_gameState;
public Canvas m_gameOverPopup;
Mã nguồn của Script GameManager trong phần này như sau:
THS. NGUYỄN VĂN KHƯƠNG 189
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- public Text m_popupScore; public Text m_popupBestScore;
/* Defined for sound and effect */ private AudioClip m_sfxAnswerRight; private AudioClip m_sfxAnswerWrong; private AudioSource m_audioController;
/* Initialize */ void Start() { m_lefts = new GameObject[Constant.s_numSuit]; m_rights = new GameObject[Constant.s_numSuit];
for (int i = 0; i < Constant.s_numSuit; i++) { m_lefts[i] = m_leftContainer.transform.GetChild(i).gameObject; m_rights[i] = m_rightContainer.transform.GetChild(i).gameObject; }
/* Initialize sound and effect */
m_sfxAnswerRight = Resources.Load
m_calculation = new int[3];
m_levelTime = 1.5f; m_currentTime = 0f;
Static.s_score = -1; GenerateNextCalculation(); m_gameState = GameState.PLAYING;
}
/* Update */ void Update() { switch (m_gameState) { case GameState.PLAYING: m_currentTime += Time.deltaTime; if (m_currentTime >= m_levelTime) ShowGameOverPopup();
m_progressTimer.fillAmount = m_currentTime / m_levelTime;
#if UNITY_EDITOR if (Input.GetKeyDown(KeyCode.LeftArrow)) { if (IsRightAnswer()) GenerateNextCalculation();
THS. NGUYỄN VĂN KHƯƠNG 190
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- else { Static.S_Debug("You're wrong!"); ShowGameOverPopup(); } } else if (Input.GetKeyDown(KeyCode.RightArrow)) { if (!IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); ShowGameOverPopup(); } } #endif break; case GameState.FAILED: break; } }
public void GenerateNextCalculation() { m_calculation[0] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[1] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[2] = Random.Range(0, 3);
GetSuitsPosition(m_lefts, m_calculation[0]); GetSuitsPosition(m_rights, m_calculation[1]);
m_operator.GetComponent
Static.s_score++; m_scoreText.text = Static.s_score.ToString();
m_currentTime = 0f;
m_audioController.clip = m_sfxAnswerRight; m_audioController.Play(); }
internal void GetSuitsPosition(GameObject[] _suits, int _count) { switch (_count) { case 1: _suits[0].SetActive(true); _suits[1].SetActive(false); _suits[2].SetActive(false); _suits[3].SetActive(false);
THS. NGUYỄN VĂN KHƯƠNG 191
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
_suits[0].transform.localPosition = Vector3.zero;
_suits[0].GetComponent
break; case 2: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
break; case 3: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, -0.5f, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, -0.5f, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 0.5f, 0);
_suits[2].GetComponent
break; case 4: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(true);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
THS. NGUYỄN VĂN KHƯƠNG 192
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
_suits[2].transform.localPosition = new Vector3(0, 1.0f, 0);
_suits[2].GetComponent
_suits[3].transform.localPosition = new Vector3(0, -1.0f, 0);
_suits[3].GetComponent
break; } }
public bool IsRightAnswer() { return (m_calculation[2] == 0 && m_calculation[0] == m_calculation[1]) || (m_calculation[2] == 1 && m_calculation[0] > m_calculation[1]) || (m_calculation[2] == 2 && m_calculation[0] < m_calculation[1]); }
public void ShowGameOverPopup() { m_audioController.clip = m_sfxAnswerWrong; m_audioController.Play();
m_gameOverPopup.enabled = true;
m_gameState = GameState.FAILED; m_popupScore.text = Static.s_score.ToString();
if (PlayerPrefs.GetInt("BestScore") < Static.s_score) PlayerPrefs.SetInt("BestScore", Static.s_score);
m_popupBestScore.text = PlayerPrefs.GetInt("BestScore").ToString(); } }
7.5.3. Thêm effect cho gameplay
Để tăng tính hấp dẫn cho Game, chúng ta sẽ thêm 2 hiệu ứng Rotate và Flash. Hai hiệu ứng này đã được tôi hiện thực lại trong script EffectController.cs.
using UnityEngine; using System.Collections;
public class EffectsController : MonoBehaviour { private float m_lastTime; -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 193
Các bạn tạo một Script mới đặt tên là EffectController.cs và lưu vào thư mục Script, mã nguồn như sau:
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- private float m_flashTime;
void Start() { m_flashTime = 0.1f; m_lastTime = 0f; }
void Update() { m_lastTime += Time.deltaTime; }
public void DoRotateEffect(GameObject suit) { Vector3 newAngle = suit.transform.eulerAngles; newAngle.z += 5.0f; suit.transform.eulerAngles = newAngle; }
public void DoFlashEffect(GameObject suit) { if (m_lastTime >= m_flashTime) { if (!suit.activeInHierarchy) suit.SetActive (true); else suit.SetActive (false);
m_lastTime = 0; } } }
Chọn đối tượng GameManager và kéo Script này sang như sau:
Nguyên mẫu của hai hàm như sau:
THS. NGUYỄN VĂN KHƯƠNG 194
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
public void EffectsController.DoRotateEffect(GameObject suit); public void EffectsController.DoFlashEffect(GameObject suit);
1. 2.
Tham số truyền vào là suit cần tạo hiệu ứng. Mỗi khi người chơi vượt qua một level, Effect state (trạng thái hiệu ứng) của mỗi suit sẽ được cập nhật lại. Chỉ những suit đang được kích hoạt mới có hiệu ứng.
Mở Script Static và thêm một số đoạn lệnh như sau:
public enum Effect {
EFFECT_FLASH, EFFECT_ROTATE, EFFECT_NONE
}
1. 2. 3. 4. 5. 6.
Hai hằng số về tỉ lệ xuất hiện hiệu ứng (dòng 19-20, Statics.cs):
public static readonly int s_effectFlashRate = 10; public static readonly int s_effectRotateRate = 20;
1. 2.
Trước hết, các hiệu ứng được định nghĩa trong enum Effect (dòng 29-34, Statics.cs):
using UnityEngine;
namespace Stdio { static class Static { public static void S_Debug(object message) { if (Debug.isDebugBuild) Debug.Log(message); }
public static int s_score; }
static class Constant { public static readonly int s_numSuit = 4; public static readonly int s_effectFlashRate = 10; public static readonly int s_effectRotateRate = 20; }
public enum GameState {
Mã nguồn Script Static hoàn chỉnh:
THS. NGUYỄN VĂN KHƯƠNG 195
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- PLAYING, FAILED }
public enum Effect { EFFECT_FLASH, EFFECT_ROTATE, EFFECT_NONE } }
private Effect[] m_suitEffects; m_suitEffects = new Effect[Constant.s_numSuit * 2];
1. 2.
Mỗi suit sẽ có một biến tương ứng quyết định effect của suit trong một level. Tôi sử dụng mảng một chiều để lưu trữ chung trạng thái effect của các suit như sau (dòng 37, 56, GameManager.cs):
/* Do Effect */ for (int i = 0; i < Constant.s_numSuit; i++) {
/* Effect for left side */ if (m_suitEffects[i] == Effect.EFFECT_ROTATE)
GetComponent
else if (m_suitEffects[i] == Effect.EFFECT_FLASH)
GetComponent
1. 2. 3. 4. 5. 6. i]); 7. 8.
]);
/* Effect for right side */ if (m_suitEffects[i + Constant.s_numSuit] == Effect.EFFECT_ROTATE)
GetComponent
else if (m_suitEffects[i + Constant.s_numSuit] == Effect.EFFECT_FL
9. 10. 11. 12. [i]); 13.
ASH)
GetComponent
14.
i]);
}
15.
Tại hàm Update, các suit có effect sẽ được cập nhật liên tục từng frame. Do trong Zero chỉ có hai effect chính là Rotate và Flash nên tôi hiện thực đơn giản như sau (dòng 106-120, GameManager.cs):
/* Reset effect to none */ for(int i = 0; i < Constant.s_numSuit; i++) {
1. 2. 3. 4.
m_suitEffects[i] = Effect.EFFECT_NONE; -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 196
Tại hàm GenerateNextCalculation, tôi loại bỏ effect của tất cả các suit, sau đó tiến hành random effect cho các suit đang được kích hoạt. Các hiệu ứng chỉ tồn tại trong một level duy nhất nên việc cập nhật lại tại GenerateNextCalculation là hợp lý và hiệu quả nhất. Code như sau (dòng 136-165, GameManager.cs):
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
m_suitEffects[i + Constant.s_numSuit] = Effect.EFFECT_NONE;
m_lefts[i].transform.rotation = Quaternion.identity; m_rights[i].transform.rotation = Quaternion.identity;
}
/* Get new effect */ for(int i = 0; i < m_calculation[0]; i++) {
int randValue = Random.Range(0, 100);
if (randValue <= Constant.s_effectFlashRate)
m_suitEffects[i] = Effect.EFFECT_FLASH;
else if (randValue <= Constant.s_effectRotateRate)
m_suitEffects[i] = Effect.EFFECT_ROTATE;
}
for (int i = 0; i < m_calculation[1]; i++) {
int randValue = Random.Range(0, 100);
if (randValue <= Constant.s_effectFlashRate)
m_suitEffects[i + Constant.s_numSuit] = Effect.EFFECT_FLA
5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27.
SH;
else if (randValue <= Constant.s_effectRotateRate)
m_suitEffects[i + Constant.s_numSuit] = Effect.EFFECT_ROT
28. 29.
ATE;
}
30.
Sau khi hiệu ứng kết thúc, cụ thể là hiệu ứng xoay, suit có thể sẽ không còn góc xoay mặc định ban đầu. Do đó tôi gán lại góc xoay Quaternion.identity cho các suit khi hiệu ứng kết thúc (dòng 142-143).
using UnityEngine; using Stdio; using UnityEngine.UI;
public class GameManager : MonoBehaviour { public GameObject m_leftContainer; public GameObject m_rightContainer; public GameObject m_operator;
private GameObject[] m_lefts; private GameObject[] m_rights;
public Sprite[] m_sprSuits; public Sprite[] m_sprOperators;
private int[] m_calculation;
public Text m_scoreText;
private float m_currentTime; private float m_levelTime;
Mã nguồn hoàn chỉnh của Script GameManager như sau:
THS. NGUYỄN VĂN KHƯƠNG 197
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- public Image m_progressTimer;
[HideInInspector] public GameState m_gameState;
public Canvas m_gameOverPopup; public Text m_popupScore; public Text m_popupBestScore;
/* Defined for sound and effect */ private AudioClip m_sfxAnswerRight; private AudioClip m_sfxAnswerWrong; private AudioSource m_audioController;
private Effect[] m_suitEffects;
/* Initialize */ void Start() { m_lefts = new GameObject[Constant.s_numSuit]; m_rights = new GameObject[Constant.s_numSuit];
for (int i = 0; i < Constant.s_numSuit; i++) { m_lefts[i] = m_leftContainer.transform.GetChild(i).gameObject; m_rights[i] = m_rightContainer.transform.GetChild(i).gameObject; }
/* Initialize sound and effect */
m_sfxAnswerRight = Resources.Load
m_suitEffects = new Effect[Constant.s_numSuit * 2]; /////////////////////////////////////////////////////////////
m_calculation = new int[3];
m_gameState = GameState.PLAYING;
m_levelTime = 1.5f; m_currentTime = 0f;
Static.s_score = -1; GenerateNextCalculation(); }
/* Update */ void Update() { switch (m_gameState) {
THS. NGUYỄN VĂN KHƯƠNG 198
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- case GameState.PLAYING: m_currentTime += Time.deltaTime; if (m_currentTime >= m_levelTime) ShowGameOverPopup();
m_progressTimer.fillAmount = m_currentTime / m_levelTime;
#if UNITY_EDITOR if (Input.GetKeyDown(KeyCode.LeftArrow)) { if (IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); ShowGameOverPopup(); } } else if (Input.GetKeyDown(KeyCode.RightArrow)) { if (!IsRightAnswer()) GenerateNextCalculation(); else { Static.S_Debug("You're wrong!"); ShowGameOverPopup(); } } #endif
/* Do Effect */
for (int i = 0; i < Constant.s_numSuit; i++)
{
/* Effect for left side */
if (m_suitEffects[i] == Effect.EFFECT_ROTATE)
GetComponent
/* Effect for right side */
if (m_suitEffects[i + Constant.s_numSuit] == Effect.EFFECT_ROTATE
)
GetComponent
THS. NGUYỄN VĂN KHƯƠNG 199
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
public void GenerateNextCalculation() { m_calculation[0] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[1] = Random.Range(1, Constant.s_numSuit + 1); m_calculation[2] = Random.Range(0, 3);
GetSuitsPosition(m_lefts, m_calculation[0]); GetSuitsPosition(m_rights, m_calculation[1]);
/* Reset effect to none */ for(int i = 0; i < Constant.s_numSuit; i++) { m_suitEffects[i] = Effect.EFFECT_NONE; m_suitEffects[i + Constant.s_numSuit] = Effect.EFFECT_NONE;
m_lefts[i].transform.rotation = Quaternion.identity; m_rights[i].transform.rotation = Quaternion.identity; }
/* Get new effect */ for(int i = 0; i < m_calculation[0]; i++) { int randValue = Random.Range(0, 100);
if (randValue <= Constant.s_effectFlashRate) m_suitEffects[i] = Effect.EFFECT_FLASH; else if (randValue <= Constant.s_effectRotateRate) m_suitEffects[i] = Effect.EFFECT_ROTATE; }
for (int i = 0; i < m_calculation[1]; i++) { int randValue = Random.Range(0, 100);
if (randValue <= Constant.s_effectFlashRate) m_suitEffects[i + Constant.s_numSuit] = Effect.EFFECT_FLASH; else if (randValue <= Constant.s_effectRotateRate) m_suitEffects[i + Constant.s_numSuit] = Effect.EFFECT_ROTATE; }
m_operator.GetComponent
Static.s_score++; m_scoreText.text = Static.s_score.ToString();
m_currentTime = 0f;
m_audioController.clip = m_sfxAnswerRight; m_audioController.Play(); }
THS. NGUYỄN VĂN KHƯƠNG 200
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- internal void GetSuitsPosition(GameObject[] _suits, int _count) { switch (_count) { case 1: _suits[0].SetActive(true); _suits[1].SetActive(false); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = Vector3.zero;
_suits[0].GetComponent
break; case 2: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(false); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
break; case 3: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true); _suits[3].SetActive(false);
_suits[0].transform.localPosition = new Vector3(-0.6f, -0.5f, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, -0.5f, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 0.5f, 0);
_suits[2].GetComponent
break; case 4: _suits[0].SetActive(true); _suits[1].SetActive(true); _suits[2].SetActive(true);
THS. NGUYỄN VĂN KHƯƠNG 201
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- _suits[3].SetActive(true);
_suits[0].transform.localPosition = new Vector3(-0.6f, 0, 0);
_suits[0].GetComponent
_suits[1].transform.localPosition = new Vector3(0.6f, 0, 0);
_suits[1].GetComponent
_suits[2].transform.localPosition = new Vector3(0, 1.0f, 0);
_suits[2].GetComponent
_suits[3].transform.localPosition = new Vector3(0, -1.0f, 0);
_suits[3].GetComponent
break; } }
public bool IsRightAnswer() { return (m_calculation[2] == 0 && m_calculation[0] == m_calculation[1]) || (m_calculation[2] == 1 && m_calculation[0] > m_calculation[1]) || (m_calculation[2] == 2 && m_calculation[0] < m_calculation[1]); }
public void ShowGameOverPopup() { m_audioController.clip = m_sfxAnswerWrong; m_audioController.Play();
m_gameOverPopup.enabled = true;
m_gameState = GameState.FAILED; m_popupScore.text = Static.s_score.ToString();
if (PlayerPrefs.GetInt("BestScore") < Static.s_score) PlayerPrefs.SetInt("BestScore", Static.s_score);
m_popupBestScore.text = PlayerPrefs.GetInt("BestScore").ToString(); } }
Đến đây game đã hoàn thiện, bạn có thể chạy Game và cảm nhận:
THS. NGUYỄN VĂN KHƯƠNG 202
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
---END---
THS. NGUYỄN VĂN KHƯƠNG 203
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- TÀI LIỆU THAM KHẢO
[1]. https://www.stdio.vn/unity-game
[2]. https://www.udemy.com/unity3d-concepts/
[3]. http://online.khoapham.vn/5-Unity
[4]. https://unity.com/
THS. NGUYỄN VĂN KHƯƠNG 204
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game -----------------------------------------------------------------------------------------------------------------
MỤC LỤC
CHƯƠNG 1: CÀI ĐẶT MÔI TRƯỜNG LẬP TRÌNH GAME ....................................................................... 1
1.1. CÀI ĐẶT UNITY TRÊN MÔI TRƯỜNG WINDOWS ............................................................................ 1
1.2. CÁC THÀNH PHẦN CƠ BẢN TRONG UNITY ..................................................................................... 5
1.2.1. Assets .................................................................................................................................................. 5
1.2.2. Scenes.................................................................................................................................................. 5
1.2.3. Game Objects ...................................................................................................................................... 6
1.2.4. Components ........................................................................................................................................ 6
1.2.5. Scripts.................................................................................................................................................. 7
1.2.6. Prefabs ................................................................................................................................................. 8
1.3. LÀM QUEN VỚI UNITY IDE .................................................................................................................. 8
1.3.1. Khởi tạo Project .................................................................................................................................. 8
1.3.2. Giao diện chính ................................................................................................................................... 8
1.3.3. Thanh công cụ ..................................................................................................................................... 9
1.3.4. Scene ................................................................................................................................................. 10
1.3.5. Gizmo ................................................................................................................................................ 10
1.3.6. Định hướng Game Object ................................................................................................................. 10
1.3.7. Thanh công cụ điều khiển khung Scene ............................................................................................ 10
1.3.8. Game ................................................................................................................................................. 11
1.3.9. Project ............................................................................................................................................... 11
1.3.10. Hierarchy ......................................................................................................................................... 12
1.3.11. Inspector .......................................................................................................................................... 12
CHƯƠNG 2: CÁC KỸ THUẬT CƠ BẢN ....................................................................................................... 13
2.1. C# SCRIPT - LỚP INPUT ........................................................................................................................ 13
2.1.1. Giới thiệu .......................................................................................................................................... 13
2.1.2. OnMouse event ................................................................................................................................. 13
2.1.3. GetMouseButton ............................................................................................................................... 20
2.1.4. Nhập dữ liệu từ bàn phím .................................................................................................................. 20
2.1.5. GetKey và GetButton ........................................................................................................................ 21
2.1.6. GetAxis ............................................................................................................................................. 22
2.2. KỸ THUẬT DEBUG TRONG UNITY ................................................................................................... 23
2.2.1. Giới thiệu .......................................................................................................................................... 23
2.2.2. Kiểm tra tính thực thi ........................................................................................................................ 23
2.2.3. Kiểm tra tính logic ............................................................................................................................ 24
2.2.4. Các hàm quan trọng và chức năng .................................................................................................... 24
THS. NGUYỄN VĂN KHƯƠNG 205
-----------------------------------------------------------------------------------------------------------------
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 2.3. CƠ BẢN VỀ CAMERA TRONG UNITY ............................................................................................... 25
2.3.1. Giới thiệu .......................................................................................................................................... 25
2.3.2. Các khái niệm cơ bản ........................................................................................................................ 26
2.3.3. Một số thao tác trong C# Script ........................................................................................................ 27
2.3.4. Các hệ toạ độ trong Unity ................................................................................................................. 28
2.4. GAMEOBJECT - THAO TÁC VỚI C# SCRIPT .................................................................................... 29
2.4.1. Giới thiệu .......................................................................................................................................... 29
2.4.2. Thuộc tính và hàm cơ bản ................................................................................................................. 30
2.4.3. Thao tác với Component ................................................................................................................... 32
2.4.4. Tìm kiếm GameObject ...................................................................................................................... 33
2.4.5. Khởi tạo và giải phóng GameObject ................................................................................................. 34
2.5. VECTOR TRONG UNITY ...................................................................................................................... 36
2.5.1. Giới thiệu .......................................................................................................................................... 36
2.5.2. Các vector đơn vị .............................................................................................................................. 36
2.5.3. Các thuộc tính cơ bản ........................................................................................................................ 37
2.5.4. Tính toán độ dài ................................................................................................................................ 37
2.5.5. Danh sách các hàm hỗ trợ ................................................................................................................. 38
CHƯƠNG 3: CÁC HÀM XỬ LÝ SCRIPT NÂNG CAO ............................................................................... 41
3.1. XỬ LÝ TOÁN HỌC TRONG UNITY .................................................................................................... 41
3.1.1. Giới thiệu .......................................................................................................................................... 41
3.1.2. Lớp Mathf ......................................................................................................................................... 41
3.1.3. Một số hàm khác và chức năng ......................................................................................................... 42
3.2. C# SCRIPT - LỚP TIME .......................................................................................................................... 43
3.2.1. Giới thiệu .......................................................................................................................................... 43
3.2.2. Lớp Time ........................................................................................................................................... 43
3.2.3. Một số thuộc tính quan trọng ............................................................................................................ 43
3.2.4. Các thuộc tính khác và chức năng ..................................................................................................... 44
3.3. C# SCRIPT - LỚP RANDOM .................................................................................................................. 46
3.3.1. Giới thiệu .......................................................................................................................................... 46
3.3.2. Random một số ................................................................................................................................. 46
3.3.3. Random một vector ........................................................................................................................... 47
3.3.4. Random một góc ............................................................................................................................... 48
3.3.5. Thiết lập máy sinh số ngẫu nhiên ...................................................................................................... 48
3.4. C# SCRIPT - LỚP QUATERNION ......................................................................................................... 49
3.4.1. Giới thiệu .......................................................................................................................................... 49
3.4.2. Tổng quan ......................................................................................................................................... 49
3.4.3. Các hàm quan trọng và chức năng .................................................................................................... 49 -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 206
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- CHƯƠNG 4: XỬ LÝ HIỆU ỨNG..................................................................................................................... 52
4.1. TẠO SCROLLING BACKGROUND VỚI UNITY ................................................................................ 52
4.1.1. Giới thiệu .......................................................................................................................................... 52
4.1.2. Tài nguyên sử dụng ........................................................................................................................... 52
4.1.3. Hiện thực ........................................................................................................................................... 52
4.2. TẠO ANIMATION VỚI UNITY .................................................................................................................... 60
4.2.1. Giới thiệu .......................................................................................................................................... 60
4.2.2. Tài nguyên sử dụng ........................................................................................................................... 60
4.2.3. Hiện thực ........................................................................................................................................... 60
4.3. ÂM THANH TRONG UNITY ................................................................................................................. 66
4.3.1. Giới thiệu .......................................................................................................................................... 66
4.3.2. Các tính năng chính ........................................................................................................................... 66
4.3.4. Các định dạng hỗ trợ ......................................................................................................................... 67
4.3.5. Xử lý và phát âm thanh với Script .................................................................................................... 67
4.4. HIỆU ỨNG CAMERA ZOOM TRONG UNITY .................................................................................... 68
4.4.1. Giới thiệu .......................................................................................................................................... 68
4.4.2. OrthographicSize .............................................................................................................................. 68
4.4.3. Tạo button phóng to, thu nhỏ camera ................................................................................................ 69
4.5. HIỆU ỨNG CAMERA SHAKE TRONG UNITY................................................................................... 74
4.5.1. Giới thiệu .......................................................................................................................................... 74
4.5.2. Khái niệm ShakeIntensity và ShakeDecay........................................................................................ 74
4.5.3. Sử dụng các yếu tố ngẫu nhiên để tạo hiệu ứng ................................................................................ 74
4.6. CHUYỂN ĐỔI SCENE TRONG UNITY ................................................................................................ 80
4.6.1. Giới thiệu .......................................................................................................................................... 80
4.6.2. Tạo Scene .......................................................................................................................................... 80
4.6.3. Chuyển Scene với C# Script ............................................................................................................. 87
CHƯƠNG 5: GIAO DIỆN NGƯỜI DÙNG ..................................................................................................... 89
5.1. THIẾT KẾ GIAO DIỆN NGƯỜI DÙNG TRÊN UNITY ........................................................................ 89
5.1.1. Giới thiệu .......................................................................................................................................... 89
5.1.2. Canvas ............................................................................................................................................... 89
5.1.3. Text ................................................................................................................................................... 91
5.1.4. Image ................................................................................................................................................. 91
5.1.5. Button ................................................................................................................................................ 92
5.1.6. Thao tác với các đối tượng UI trong Script ....................................................................................... 94
5.2. XÂY DỰNG GAME ĐA MÀN HÌNH - GẮN ANCHOR POINT CHO CÁC ĐỐI TƯỢNG UI ........... 95
5.2.1. Giới thiệu .......................................................................................................................................... 95
5.2.2. Anchor point là gì ............................................................................................................................. 95 -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 207
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 5.2.3. Pivot .................................................................................................................................................. 96
5.2.4. Cách sử dụng Anchor point ............................................................................................................... 96
5.3. SỬ DỤNG AUTO LAYOUT SẮP XẾP CÁC ĐỐI TƯỢNG UI ............................................................. 97
5.3.1. Giới thiệu .......................................................................................................................................... 97
5.3.2. Auto Layout là gì .............................................................................................................................. 97
5.3.3. Các loại Auto Layout ........................................................................................................................ 98
CHƯƠNG 6: MỘT SỐ KỸ THUẬT MỞ RỘNG .......................................................................................... 101
6.1. LƯU TRỮ THÔNG TIN GAME VỚI PLAYERPREFS ....................................................................... 101
6.1.1. Giới thiệu ........................................................................................................................................ 101
6.1.2. Lưu trạng thái game ........................................................................................................................ 101
6.1.3. Truy xuất thông tin .......................................................................................................................... 101
6.2.4. Một số phương thức hỗ trợ .............................................................................................................. 102
6.1.5. Đường dẫn tệp tin lưu trữ ................................................................................................................ 102
6.1.6. Bảo mật ........................................................................................................................................... 102
6.2. KHỞI TẠO ĐỐI TƯỢNG TRONG RUNTIME .................................................................................... 107
6.2.1. Giới thiệu ........................................................................................................................................ 107
6.2.2. Resources folder .............................................................................................................................. 107
6.2.3. Load tài nguyên với Resources.Load .............................................................................................. 108
6.2.4. Instantiate ........................................................................................................................................ 109
6.3. ỨNG DỤNG CỦA STARTCOROUTINE TRONG UNITY ................................................................. 109
6.3.1. Giới thiệu ........................................................................................................................................ 109
6.3.2. Coroutine là gì ................................................................................................................................. 109
6.3.3. Từ khoá yield .................................................................................................................................. 110
6.3.4. WaitForSeconds .............................................................................................................................. 110
6.3.5. Sử dụng Coroutine trong Unity ....................................................................................................... 110
6.4. HIỆN THỰC MENU SELECT LEVEL VỚI SCROLL RECT ............................................................. 112
6.4.1. Giới thiệu ........................................................................................................................................ 112
6.4.2. Thành phần Scroll Rect ................................................................................................................... 112
6.4.3. Tuỳ chỉnh tại Inspector.................................................................................................................... 112
6.4.4. My Scroll Rect ................................................................................................................................ 114
6.5. SỬ DỤNG BLUETOOTH XÂY DỰNG MULTIPLAYER GAME VỚI UNITY ................................ 117
6.5.1. Giới thiệu ........................................................................................................................................ 117
6.5.2. Đối tượng hướng đến ...................................................................................................................... 117
6.5.3. Công nghệ Bluetooth....................................................................................................................... 118
6.5.4. Bluetooth với Unity ......................................................................................................................... 118
CHƯƠNG 7: XÂY DỰNG GAME ZERO VỚI UNITY ............................................................................... 124
7.1. NHẬN SỰ KIỆN BUTTON ................................................................................................................... 124 -----------------------------------------------------------------------------------------------------------------
THS. NGUYỄN VĂN KHƯƠNG 208
Bài giảng Lập trình Game ----------------------------------------------------------------------------------------------------------------- 7.2. HIỆN THỰC GAMEPLAY .................................................................................................................... 132
7.3. ĐIỂM SỐ VÀ PROGRESS TIMER ...................................................................................................... 145
7.4. SCENE VÀ POPUP ................................................................................................................................ 160
7.4.1. Giới thiệu ........................................................................................................................................ 160
7.4.2. Quản lý GameState trong game ...................................................................................................... 160
7.4.3. GameOver popup ............................................................................................................................ 162
7.4.4. MenuScene ...................................................................................................................................... 170
7.5. ÂM THANH VÀ EFFECT ..................................................................................................................... 186
7.5.1. Giới thiệu ........................................................................................................................................ 186
7.5.2. Các hiệu ứng âm thanh trong Zero .................................................................................................. 186
7.5.3. Thêm effect cho gameplay .............................................................................................................. 193
TÀI LIỆU THAM KHẢO ............................................................................................................................. 204
THS. NGUYỄN VĂN KHƯƠNG 209
-----------------------------------------------------------------------------------------------------------------