Muốn tạo ra những hiệu ứng đồ họa đặc biệt khi sử dụng canvas, bạn không thể chỉ sử dụng cá thuộc tính và phương thức có sẵn của đối tượng context.. Chính vì vậy, bài viết này s[r]
(1)HTML5 Canvas
-Lập Trình Game 2D
v1.0
(2)(3)LỜI TỰA
Flash công nghệ hiệu quả, phổ biến cho phép lập trình viên tạo ứng dụng với đầy đủ hiệu ứng hình ảnh, âm đặc sắc Những công nghệ tương tự Java Applet hay “đứa con” sáng giá Microsoft Silverlight đứng vững cạnh tranh với Flash Nhưng vấn đề nảy sinh khả tương tác công nghệ với thành phần xung quanh (như thẻ HTML) dường Chúng bị cô lập hoạt động độc lập với giới bên
Giải pháp quay trở lại sử dụng HTML, Javascript CSS, lập trình viên tạo ứng dụng với hiệu ứng đặc biệt không bị giới hạn mà công nghệ gặp phải Nhưng trở ngại lớn khơng có đủ API để tạo ứng dụng tương tự Flash Và tốc độ ứng dụng HTML chậm, chấp nhận với game có u cầu cấu hình trung bình
Nhưng với đời HTML5 với thành phần API mới, giới hạn bị phá bỏ bước thay dần công nghệ Flash Với ứng dụng cần hiệu ứng đồ họa chuyển động đặc biệt, lập trình viên sử dụng Canvas với kiểu bitmap SVG với kiểu vector Không áp dụng cho việc thiết kế trang web trực quan, HTML5 áp dụng để tạo thư viện đồ họa giúp tạo ứng dụng đồ thị, game môi trường 2D 3D ứng dụng desktop
Một điều đáng mừng HTML, Javascript CSS khơng cịn bị giới hạn trình duyệt mà triển khai desktop dạng widget, thiết bị di động thiết bị Như vậy, lập trình viên khơng cần sử dụng hay u cầu người dùng cài đặt thư viện để chạy ứng dụng họ Một lợi lớn mà có HTML đạt Tuy nhiên việc xây dựng game trình duyệt trải nghiệm khó khăn phải cân nhắc việc chọn lựa thư viện đại, đầy đủ chức hay làm theo API cấp thấp HTML (thông qua Javascript)
(4)Mục lục
A GIỚI THIỆU
B HTML5 API 10
I Web Storage (DOM Storage) 10
1 Giới thiệu 10
2 Interface Storage 10
3 Local Storage Session Storage 11
4 Sử dụng 12
5 Storage event 14
6 Thêm phương thức vào Storage 15
II Web SQL Database 16
1 Giới thiệu 16
2 Open Database 16
3 Transaction 17
4 Execute SQL 17
III Web Worker 18
1 Giới thiệu 18
2 Ví dụ đơn giản nhất: 19
3 Kết luận 20
IV Tạo chuyển động với WindowAnimationTiming API 20
1 setTimeout setInterval 21
2 WindowAnimationTiming 21
3 Lợi ích hiệu 22
4 Sử dụng 23
C Canvas 2D API 25
I Vẽ ảnh thao tác với pixel 25
1 Nạp vẽ ảnh 25
2 Thao tác với pixel 26
II Vẽ hình chuột 30
1 Xác định tọa độ chuột 30
2 Lưu nội dung Canvas 31
III Chọn di chuyển đối tượng 34
1 Tạo cấu trúc liệu 34
(5)3 Các kiện chuột Canvas 36
IV Sử dụng bàn phím 37
1 Bắt kiện bàn phím 37
2 Kiểm tra trạng thái nhiều phím 38
3 Giới hạn phím bắt 38
V Chuyển động Canvas 39
1 Cơ 39
2 Thêm hiệu ứng bóng di chuyển 41
3 Kiểm tra va chạm 42
D Kĩ thuật lập trình Game – Cơ 44
I Vòng lặp game (Game loop) hoạt động nào? 44
1 Vòng lặp 44
2 Vịng lặp có tính toán thời gian 45
3 Giải pháp cuối 46
4 Ví dụ hồn chỉnh 46
II Kiểm tra va chạm: hình trịn chữ nhật 47
1 Giữa hai hình chữ nhật 47
2 Giữa hai hình trịn 48
3 Giữa hình trịn hình chữ nhật 48
III Kiểm tra điểm nằm đoạn thẳng 50
IV Vector 2D 51
1 Khái niệm 51
2 Vector đơn vị (Unit Vector, Normalized Vector) 51
3 Tích vơ hướng (Dot product, Scalar product) 52
4 Phép chiếu (Projection) 52
5 Hiện thực với javascript 53
V Khoảng cách từ điểm đến đoạn thẳng 54
VI Giao điểm hai đường thẳng 56
1 Tạo phương trình đường thẳng từ đoạn thẳng 56
2 Tính giao điểm hai đường thẳng 57
3 Minh họa với HTML5 Canvas 58
VII Va chạm phản xạ 58
1 Kiểm tra hai đoạn thẳng cắt 58
2 Phương pháp 59
(6)1 Va chạm 59
2 Phản xạ 60
IX Va chạm nhiều đường tròn 62
1 Xử lý va chạm nhiều đường tròn 63
X Kiểm tra va chạm dựa pixel 64
1 Một wrapper Image 65
2 Xác định vùng giao hai hình chữ nhật 66
3 Kiểm tra va chạm 67
E Kỹ thuật lập trình Game – Nâng cao 69
I Cuộn ảnh đồ (Map Scrolling) 69
1 Ảnh nhiều tầng 69
2 Cuộn giả 70
3 … cuộn thật 70
II Tạo Amimated Sprite 71
III Nạp trước hình ảnh tài nguyên 75
IV Phóng to/thu nhỏ game nút cuộn chuột 76
1 Sự kiện Mouse Wheel javascript 78
2 Thay đổi kích thước đồ 78
3 Vẽ vùng đồ 79
4 Áp dụng cho nhân vật đồ 79
V Thay đổi kích thước Canvas theo trình duyệt 80
1 Điều chỉnh canvas thao kích thước trình duyệt 80
VI Sử dụng Full Screen API 82
1 Giới thiệu 82
2 Ví dụ 84
VII Tạo menu chuyển đổi hình Game 86
1 Lớp MenuItem 86
2 Lớp Screen 87
3 Kiểm tra kết 89
F AI game 90
I Giới thiệu 90
II Phân tích để lựa chọn thuật tốn 90
III Thuật toán Breadth First Search 91
IV Các quy tắc game 92
(7)VI Cài đặt thuật toán Breadth First Search 94
VII Di chuyển đối tượng theo đường 96
VIII Vịng lặp game 96
G Một tảng game 2D side-scrolling 98
I Cơ 98
1 Tạo đồ 98
2 Kiểm tra va chạm 98
II Thêm chức nhân vật 101
1 Lớp Character 101
2 Lớp Monster 103
3 Lớp Player 104
4 Lớp Map 105
H Một số ứng dụng minh họa 107
I Game đua xe 107
1 Các thông số xe 107
2 Di chuyển quay xe 107
3 Kiểm tra va chạm (tiếp xúc) với địa hình 108
4 Hạn chế xe di chuyển xoay bị va chạm 109
5 Tạo checkpoint 109
6 Kết 109
II Game bắn đại bác 110
1 Bản đồ địa hình 110
2 Phá hủy phần địa hình 111
3 Trọng lực Gió 111
4 Di chuyển Cannon 111
5 Sát thương đạn 111
6 Hỗ trợ nhiều người chơi 112
7 Kết 112
III Game Mario 113
I Lời kết 114
(8)(9)A GIỚI THIỆU
HTML5 hỗ trợ hầu tất trình duyệt Nó tập hợp tính đặc biệt ta tìm thấy hỗ trợ cho số phần đặc trưng canvas, video định vị địa lý Những đặc điểm kỹ thuật HTML5 xác định làm dấu ngoặc nhọn tương tác với JavaScrip, thông qua tài liệu thông qua tài liệu Object Model (DOM) HTML5 không xác định tag <video>, DOM API tương ứng cho đối tượng video
DOM Bạn sử dụng API để tìm kiếm hỗ trợ cho định dạng video khác nhau, nghe nhạc, tạm dừng đoạn video, mute audio , theo dõi video tải về, thứ khác bạn cần phải xây dựng trải nghiệm người dùng phong phú xung quanh tag <video>
Khơng cần phải vứt bỏ thứ gì:
Ta khơng thể phủ nhận HTML định dạng đánh dấu thành công từ trước đến HTML5 xây dựng dựa thành cơng Bạn khơng cần phải bỏ đánh dấu có Bạn khơng cần phải học lại điều bạn biết Nếu ứng dụng web bạn trước sử dụng HTML 4, hoạt động HTML5
Bây giờ, bạn muốn cải thiện ứng dụng web, bạn đến nơi Ví dụ cụ thể: HTML5 hỗ trợ tất hình thức kiểm sốt từ HTML 4, bao gồm điều khiển đầu vào Một số số hạn bổ sung trượt date pickkers, thành phần khác tinh tế
Ví dụ, loại email input trơng giống textbox, trình duyệt linh động tùy biến bàn phím hình họ để dễ dàng nhập địa email Các trình duyệt cũ khơng hỗ trợ loại email input xem văn thơng thường, hình thức làm việc khơng có thay đổi đánh dấu kịch hack Điều có nghĩa bạn bắt đầu cải thiện hình thức web bạn ngày hơm nay, số khách truy cập vào web bạn
Rất dễ dàng để bắt đầu:
"Nâng cấp" lên HTML5 đơn giản việc thay đổi thẻ DOCTYPE bạn DOCTYPE cần phải nằm dòng tất trang HTML Các phiên trước HTML định nghĩa nhiều loại doctype, lựa chọn doctype rắc rối Trong HTML5 có DOCTYPE:
<!DOCTYPE html>
Nâng cấp lên DOCTYPE HTML5 không phá vỡ cấu trúc tài liệu bạn, thẻ lỗi thời trước định nghĩa HTML vẽ HTML5 Nhưng cho phép bạn sử dụng xác nhận thẻ <article> <section> , <header> ,
(10)B HTML5 API mới
HTML5 bổ sung nhiều API mà lập trình viên sử dụng để hỗ trợ cho ứng dụng game Ví dụ lưu lại liệu với Web Storage, Web Sql, Indexed DB, thực công việc song song với Web Worker, giao tiếp với server thơng qua Web Socket Do thời lượng có lượng, tơi trình bày phần nhỏ số
I Web Storage (DOM Storage)
HTML5 cung cấp tính lưu trữ liệu client với dung lượng giới hạn lớn nhiều so với cookie Tính gọi Web Storage chia thành hai đối tượng localStorage sessionStorage Bài viết giúp bạn nắm kiến thức đầy đủ sử dụng hai đối tượng việc lập trình web
1 Giới thiệu
Hiện nay, cookie cho phép lưu trữ tối đa 4KB vài chục cookie cho domain Vì cookie dùng để lưu trữ thông tin đơn giản ngắn gọn email, username, … giúp người dùng đăng nhập tự động vào trang web Điều khiến cho trang web muốn nâng cao hiệu suất làm việc cách cache liệu client thực
Sự xuất Web Storage điểm nhấn cho việc đời ứng dụng web có khả tương tác nạp liệu tức trình duyệt Một hiệu q uả dung lương truyền tải qua mạng giảm thiểu đáng kể Ví dụ ứng dụng tra cứu sách trực tuyến, sách tra lưu lại máy người dùng Khi cần tra lại, máy người dùng không cần kết nối đến server để tải lại liệu cũ
Với ứng dụng web có sở liệu nhỏ gọn, lập trình viên thực việc cache “âm thầm” sở liệu xuống client sau người dùng thoải mái tra cứu mà khơng cần request đến server
2. Interface Storage interface Storage {
readonly attribute unsigned long length; DOMString? key(unsigned long index); getter DOMString getItem(DOMString key);
setter creator void setItem(DOMString key, DOMString value); deleter void removeItem(DOMString key);
(11)Như bạn thấy, liệu lưu trữ Storage kiểu chuỗi, với loại liệu khác số nguyên, số thực, bool, … bạn cần phải thực chuyển đối kiểu Mỗi đối tượng Storage danh sách cặp key/value, đối tượng bao gồm thuộc tính phương thức:
- length: số lượng cặp key/value có đối tượng - key(n): trả tên key thứ n danh sách - getItem(key): trả value gắn với key
- setItem(key,value): thêm gán cặp key/value vào danh sác h - removeItem(key): xóa cặp key/value khỏi danh sách
- clear: xóa tồn liệu danh sách
Bên cạnh đó, đối tượng Storage cịn truy xuất qua thuộc tính key danh sách
Ví dụ:
localStorage.abc="123"; // equivalent to:
// localStorage.setItem("abc","123");
3. Local Storage Session Storage
Hai đối tượng tạo từ interface Storage, bạn sử dụng hai đối tượng javascript qua hai biến tạo sẵn window.localStorage window.sessionStorage Hai lợi ích mà chúng mang lại là:
- Dễ sử dụng: bạn truy xuất liệu hai đối tượng thơng qua thuộc tính phương thức Dữ liệu lưu trữ theo cặp key/value khơng cần cơng việc khởi tạo hay chuẩn bị
- Dung lượng lưu trữ lớn: Tùy theo trình duyệt, bạn lưu trữ từ 5MB đến 10MB cho domain Theo Wikipedia số là: 5MB Mozilla Firefox, Google Chrome,và Opera, 10MB Internet Explorer
Phạm vi:
(12)- localStorage: truy xuất lẫn cửa sổ trình duyệt Dữ liệu lưu trữ không giới hạn thời gian
Đối với localStorage:
“Each domain and subdomain has its own separate local storage area Domains can access the storage areas of subdomains, and subdomains can access the storage areas of parent domains For example, localStorage['example.com'] is accessible to example.com and any of its subdomains The
subdomainlocalStorage['www.example.com'] is accessible to example.com, but not to other
subdomains, such as mail.example.com.”
http://msdn.microsoft.com/en-us/library/cc197062(VS.85).aspx 4 Sử dụng
Bạn tạo tập tin HTML với nội dung phía để chạy trình duyệt Ở ta sử dụng Chrome cung cấp sẵn cửa sổ xem phần Resources Google Chrome Developer Tools (Ctrl + Shift + I) Nội dung tập tin HTML sau:
<!DOCTYPE html> <html>
<body>
<script type="text/javascript"> sessionStorage.myVariable="Hello ";
localStorage.myVariable="Nice to meet you!"; document.write(sessionStorage.myVariable); document.write(localStorage.myVariable); </script>
</body> </html>
(13)(14)5 Storage event
Đối tượng Window javascript cung cấp event với tên “storage” Event kích hoạt storageArea bị thay đổi
interface StorageEvent : Event {
readonly attribute DOMString key;
readonly attribute DOMString? oldValue;
readonly attribute DOMString? newValue;
readonly attribute DOMString url;
readonly attribute Storage? storageArea;
};
Event khơng hoạt động bạn xem tập tin HTML máy cục kích hoạt cửa sổ/thẻ khác Tức bạn mở nhiều cửa sổ trình duyệt truy xuất đến domain, bạn thay đổi đối tượng Storage bên cửa sổ cửa sổ cịn lại kích hoạt event “storage”, cịn cửa sổ khơng xảy
<!DOCTYPE html> <html>
<body>
<button onclick="changeValue();">Change Value</button> <script type="text/javascript">
localStorage.clear(); console.log(localStorage); if (window.addEventListener)
window.addEventListener('storage', storage_event, false); else if (window.attachEvent) // IE
window.attachEvent('onstorage', storage_event, false); function storage_event(event){
console.log(event); }
function changeValue() {
localStorage.myValue=Math.random(); }
(15)6 Thêm phương thức vào Storage
Các phương thức Storage khơng đủ đáp ứng u cầu bạn, bạn thêm vài phương thức vào để sử dụng cần thiết Ví dụ ta thêm phương thức search() để lọc giá trị chứa từ khóa cần tìm kiếm
Storage.prototype.search = function(keyword) { var array=new Array();
var re = new RegExp(keyword,"gi"); for (var i = 0; i < this.length; i++) {
var value=this.getItem(this.key(i)); if(value.search(re)>-1)
array.push(value); }
return array; }
(16)II Web SQL Database
Web SQL Database công nghệ kết hợp trình duyệt SQLite để hỗ trợ việc tạo quản lý database phía client Các thao tác với database thực javasc ript sử dụng câu lệnh SQL để truy vấn liệu
1 Giới thiệu
Lợi ích SQLite có để tích hợp vào ứng dụng với thư viện để truy xuất database Chính vậy, bạn sử dụng làm sở liệu cho ứng dụng nhỏ khơng cần thiết cài đặt phần mềm driver Hiện Web SQL Database hỗ trợ trình duyệt Google Chrome, Opera Safari
Trong javascript, bạn sử dụng phương thức sau để làm việc với Web SQL Database
openDatabase: mở database có sẵn tạo chưa tồn
transaction / readTransaction: hỗ trợ thực thao tác với database rollback xảy sai sót
executeSql: thực thi câu lệnh SQL
2 Open Database
Phương thức có nhiệm vụ mở database có sẵn tạo chưa tồn Phương thức khai báo sau:
Database openDatabase( in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize,
in optional DatabaseCallback creationCallback
); Tham số:
- name: tên database
- version: phiên Hai database trùng tên khác phiên khác - displayname: mơ tả
- estimatedSize: dung lượng tính theo đơn vị byte
- creationCallback: phương thức callback thực thi sau database mở/tạo Ví dụ tạo database với tên “mydb” dung lượng MB:
(17)3 Transaction
Bạn cần thực thi câu SQL ngữ cảnh transactio n Một transaction cung cấp khả rollback câu lệnh bên thực thi thất bại Nghĩa lệnh SQL thất bại, thao tác thực trước transaction bị hủy bỏ database khơng bị ảnh hưởng
Interface Database hỗ trợ hai phương thức giúp thực điều transaction() readTransaction() Điểm khác biệt chúng transaction() hỗ trợ read/write, readTransaction() read-only Như sử dụng readTransaction() cho hiệu suất cao bạn cần đọc liệu
void transaction(
in SQLTransactionCallback callback,
in optional SQLTransactionErrorCallback errorCallback,
in optional SQLVoidCallback successCallback
); Ví dụ:
var db = openDatabase("mydb", "1.0", "My First DB", * 1024 * 1024); db.transaction(function (tx) {
// Using tx object to execute SQL Statements });
4. Execute SQL
executeSql() phương thức để thực thi câu lệnh SQL Web SQL Nó sử dụng cho mục đích đọc ghi liệu
void executeSql(
in DOMString sqlStatement,
in optional ObjectArray arguments,
in optional SQLStatementCallback callback,
in optional SQLStatementErrorCallback errorCallback
);
Ví dụ 1: tạo bảng Customers thêm hai dòng liệu vào bảng
db.transaction(function (tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS CUSTOMERS(id unique, name)"); tx.executeSql("INSERT INTO CUSTOMERS (id, name) VALUES (1, 'peter')"); tx.executeSql("INSERT INTO CUSTOMERS (id, name) VALUES (2, 'paul')"); });
(18)tx.executeSql("INSERT INTO CUSTOMERS (id, name) VALUES (?,?)", [1,"peter"]); tx.executeSql("INSERT INTO CUSTOMERS (id, name) VALUES (?,?)", [2,"paul"]);
III Web Worker
Với công nghệ phần cứng nay, việc sử dụng đa luồng trở nên phần thiếu phần mềm Tuy nhiên, công nghệ thiết kế web chưa tận dụng sức mạnh Với công việc địi hỏi q trình xử lý lâu, lập trình viên thường phải sử dụng thủ thuật setTimeout(), setInterval(),… để thực phần công việc Hiện nay, để giải vấn đề này, API dành cho javascript xuất với tên gọi Web Worker
1 Giới thiệu
Đối tượng Web Worker tạo thực thi thread độc lập chạy chế độ nên không ảnh hưởng đến giao diện tương tác trang web với người dùng Với đặc điểm này, bạn sử dụng Web Workert cơng việc địi hỏi thời gian xử lý lâu nạp liệu, tạo cache,…
Điểm hạn chế Web Worker truy xuất thành phần DOM, đối tượng window, document hay parent Mã lệnh công việc cần thực thi phải cách ly tập tin script
Việc tạo Web Worker cần thực sau: var worker = new Worker('mytask.js');
(19)2 Ví dụ đơn giản nhất:
Tạo hai tập tin sau thư mục: mytask.js:
this.onmessage = function (event) { var name = event.data;
postMessage("Hello "+name); };
Test.html:
<!DOCTYPE html> <body>
<input type="text" id="username" value="2" /> <br />
<button id="button1">Submit</button> <script>
worker = new Worker("mytask.js"); worker.onmessage = function (event) {
alert(event.data); };
(20)var name = document.getElementById("username").value; worker.postMessage(name);
}; </script> </body> </html>
Bây bạn chạy thử nhấn nút Submit, thông điệp hiển thị với nội dung tương tự “Hello Yin Yang”
3 Kết luận
Với cơng việc đơn giản, lập trình viên gửi liệu kiểu mảng bao gồm tên lệnh liệu cần xử lý Worker phân tích liệu nhận gọi phương thức xử lý tương ứng Tuy nhiên, Worker bạn tạo nên dành riêng để thực cơng việc cụ thể Bởi mục đích việc tạo chúng để làm công việc “nặng nhọc” Cuối cùng, hồn tất cơng việc, bạn giải phóng cho Worker cách dùng phương thức worker.terminate()
Bạn xem demo sau cách sử dụng Web Worker để thực đồng thời việc tính tốn tìm số ngun tố cập nhật lại canvas:
IV. Tạo chuyển động với WindowAnimationTiming AP I
(21)1 setTimeout setInterval
Cách truyền thống mà bạn dùng để tạo hiệu ứng đồ họa chuyển động với javascript gọi liên tục công việc update draw sau khoảng thời gian xác định thông qua phương thức setInterval() setTimeout() Mỗi lần gọi, frame (khung hình) tạo vẽ đè lên hình cũ
Khó khăn phương pháp khó xác định giá trị thời gian thích hợp dựa thiết bị sử dụng (thông thường khoảng 60 fps – frames per second) Ngoài với hiệu ứng phức tạp việc update/draw diễn lâu so với thời gian hai lần gọi Cách tổng quát để giải vấn đề thực tính tốn dựa vào khoảng cách thời gian lần gọi trước tại, sau xác định bỏ qua vài bước draw thay đổi giá trị timeout cho phù hợp
2 WindowAnimationTiming
Một tính đời cho phép bạn đơn giản hóa cơng việc API WindowAnimationTiming
Đây interface bao gồm hai phương thức requestAnimationFrame() cancelAnimationFrame() Việc xác định thời điểm cập nhật frame tự động chọn giá trị thích hợp
interface WindowAnimationTiming {
long requestAnimationFrame(FrameRequestCallback callback); void cancelAnimationFrame(long handle);
};
Window implements WindowAnimationTiming;
callback FrameRequestCallback = void (DOMTimeStamp time);
requestAnimationFrame: gửi request đến trình duyệt thực hành động update/draw frame thông qua tham số callback Các request lưu đối tượng document với cặp <handle, callback> thực Giá trị handle số định danh tạo trả sau gọi phương thức
cancelAnimationFrame: hủy request tạo requestAnimationFrame với tham số handle request
(22)3 Lợi ích hiệu quả
(23)setTimeout requestAnimationFrame
Expected callbacks 40543 40544
Actual callbacks 41584 40544
Callback Efficiency 59.70% 100.00%
Callback Efficiency Medium High
Power Consumption Medium Low
Background Interference High Low
Một hiệu khác animation tự động dừng lại tab chứa khơng hiển thị (khi bạn chuyển sang tab khác trình duyệt) Điều giúp hạn chế tài nguyên sử dụng cách hợp lý
4 Sử dụng
Kiểm tra trình duyệt hỗ trợ:
Tùy theo trình duyệt mà tên phương thức có prefix khác (vendor):
_reqAnimation = window.requestAnimationFrame ||
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ; if(_reqAnimation)
update(); else
alert("Your browser doesn't support requestAnimationFrame.");
Ví dụ hồn chỉnh:
<HTML> <head> <script>
const RADIUS = 20; var cx = 100; var cy = 100; var speedX = 2; var speedY = 2; var _canvas; var _context; var _reqAnimation; var _angle = 0;
(24)if(cx<0 || cx > _canvas.width) speedX = -speedX;
if(cy<0 || cy > _canvas.height) speedY = -speedY;
// draw
_context.clearRect(0, 0, _canvas.width, _canvas.height); _context.beginPath();
_context.arc(cx, cy, RADIUS, 0, Math.PI*2, false); _context.closePath();
_context.fill();
_reqAnimation(update); }
window.onload = function(){
_canvas = document.getElementById("canvas"); _context = _canvas.getContext("2d");
_context.fillStyle = "red"; cx = _canvas.width/2;
cy = _canvas.height/2;
_reqAnimation = window.requestAnimationFrame ||
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame || window.oRequestAnimationFrame ; if(_reqAnimation)
update(); else
alert("Your browser doesn't support requestAnimationFrame."); };
</script> </head> <body>
<canvas id="canvas" width="400px" height="300px" style="border: 1px solid"></canvas>
(25)C Canvas 2D API
HTML5 xác định <canvas> bitmap phụ thuộc vào độ phân giải, sử dung để vẽ đồ thị, đồ họa game hình ảnh trực quan khác Canvas hình chữ nhật trang sử dụng JavaScript để vữ điều bạn muốn HTML5 định nghĩa tập chức ( canvas API) để vẽ hình dạng, xác định đường dẫn, tạo gradient
HTML5 Canvas JavaScript API để mã hóa vẽ Canvas API cho phép định nghĩa đối tượng canvas thử <canvas> trang HTML ,chúng ta vẽ bên <canvas> khối khơng gian vơ hình, mặc định 300x250 pixels (trên tất trình duyệt) Chúng ta vẽ hình 2D 3D (WebGL) 2D có sẵn tất trình duyệt Web đại, IE9, thông qua thư viện excanvas.js phiên IE
Canvas 2D cung cấp API đơn giản mạnh mẽ để vẽ cách nhanh chóng bề mặt bitmap 2D Khơng có định dạng tập tin, bạn vẽ cách sử dụng script Bạn khơng có nút DOM cho hình khối bạn vẽ - bạn vẽ pixels, vectơ điểm ảnh ghi nhớ
Một số tính Canvas: Vẽ hình ảnh
Tơ màu
Tạo hình học kiểu mẫu Văn
Sao chép hình ảnh, video, canvas khác Thao tác điểm ảnh
Xuất nội dung thẻ <canvas> sang tập tin ảnh tĩnh
I Vẽ ảnh thao tác với pixel
Muốn tạo hiệu ứng đồ họa đặc biệt sử dụng canvas, bạn sử dụng cá thuộc tính phương thức có sẵn đối tượng context Chính vậy, viết giới thiệu cho bạn cách vẽ ảnh thao tác với pixel từ đối tượng ImageData
1 Nạp vẽ ảnh
Để vẽ ảnh canvas, ta tạo đối tượng image thực phương thức context.drawImage() kiện load image Như để đảm bảo hình ảnh vẽ sau nạp hoàn tất Ngoài ra, bạn nên đặt kiện load trước gán đường dẫn cho ảnh Nếu không image load xong trước bạn gắn kiện load cho
(26)- Vẽ image vị trí destX, destY:
context.drawImage(image,destX,destY);
- Vẽ image vị trí destX, destY kích thước destWidth, destHeight:
context.drawImage(image,destX,destY,destWidth,destHeight);
- Cắt image vị trí [sourceX, sourceY, sourceWidth, sourceHeight] vẽ [destX, destY, destWidth, destHeight]:
context.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
Ví dụ:
window.onload = function(){
var canvas = document.getElementById("mycanvas"); var context = canvas.getContext("2d");
var img = new Image(); img.onload = function(){
context.drawImage(img, 10, 10,50,50); };
img.src = "foo.png"; };
2 Thao tác với pixel
Một ảnh bao gồm mảng pixel với giá trị red, green, blue alpha (RGBA) Trong alpha giá trị xác định độ mờ đục (opacity) ảnh Giá trị alpha lớn độ màu sắc rõ nét màu sắc trở nên suốt alpha
Trong Canvas 2D API, liệu ảnh lưu đối tượng ImageData với thuộc tính width, height data Trong data mảng chiều chứa pixel Mỗi pixel chứa phần tử tương ứng R,G,B,A
(27)Bạn tham khảo thơng tin API tại: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.HTML#pixel-manipulation:
imagedata = context.createImageData(sw, sh)
Trả đối tượng ImageData với kích thước sw x sh Tất pixel đối tượng có màu đen suốt
imagedata = context.createImageData(imagedata)
Trả đối tượng ImageData với kích thước với đối tượng tham số Tất pixel có màu đen suốt
imagedata = context.getImageData(sx, sy, sw, sh)
Trả đối tượng ImageData chứa liệu ảnh vùng chữ nhật (xác định tham số) canvas
Ném NotSupportedError exception có tham số khơng phải số hợp lệ Ném IndexSizeError exception width height zero
imagedata.width imagedata.height
Trả kích thước thật đối tượng ImageData, tính theo pixel
imagedata.data
(28)Vẽ liệu từ đối tượng ImageData lên canvas vị trí dx, dy Nếu hình chữ nhật (từ tham số dirtyX, dirtyY, dirtWidth, dirtyHeight) xác định, phần liệu ImageData vùng chữ nhật vẽ lên canvas
Các thuộc tính xác định hiệu ứng vẽ context bị bỏ qua phương thức gọi Các pixel từ canvas thay hồn tồn ImageData mà khơng có kết hợp màu sắc, hiệu ứng, … với liệu ảnh sẵn có canvas
Một ví dụ thường gặp đơn giản đảo ngược màu ảnh Điều thực cách lấy giá trị màu tối đa (255) trừ giá trị kênh màu RGB pixel Giá trị alpha để giá trị tối đa để ảnh rõ nét
<HTML> <head> <script>
window.onload = function(){ var img = new Image(); img.onload = function(){
invertColor(this); };
img.src="panda.jpg"; };
function invertColor(img){
var canvas = document.getElementById("mycanvas"); var context = canvas.getContext("2d");
// draw image at top-left corner context.drawImage(img,0,0);
// draw original image right beside the previous image context.drawImage(img,img.width,0);
// get ImageData object from the left image
var imageData = context.getImageData(0, 0, img.width, img.height);
(29)for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // red data[i + 1] = 255 - data[i+1]; // green data[i + 2] = 255 - data[i+2]; // blue data[i + 3] = 255; // alpha
}
context.putImageData(imageData,0,0); }
</script> </head> <body>
<canvas id="mycanvas" width="600" height="250"></canvas> </body>
</HTML>
Kết quả:
Bạn thêm tham số để tạo “dirty rectangle” phương thức putImageData() muốn vẽ ImageData lên vùng chữ nhật xác định canvas
Ví dụ ta chọn vùng chữ nhật ImageData vẽ lên hai vùng chữ nhật khác canvas để kết sau:
context.putImageData(imageData,0,0,0,0,img.width/2,img.height/2);
(30)II Vẽ hình chuột
Trong phần này, ta tìm hiểu cách bắt xử lý thao tác chuột Canvas để thực ứng dụng vẽ hình đơn giản Bên cạnh đó, bạn biết cách để lưu phục hồi lại liệu canvas cần thiết
1 Xác định tọa độ chuột
Để xác định tọa chuột canvas, ta sử dụng tham số kiện mousedown, mousemove, … Tham số kiện chứa tọa độ chuột lưu hai biến pageX pageY Ta dùng tọa độ trừ tọa độ canvas (canvas.offsetLeft canvas.offsetTop) để lấy vị trí xác chuột canvas
Đầu tiên việc mô công cụ Pen cho phép vẽ tự canvas:
<HTML> <head>
<script src="http://code.jquery.com/jquery-latest.js"></script> <script>
var preX; var preY; var paint; var canvas; var context; $(function(){
canvas = document.getElementById("canvas"); context = canvas.getContext("2d");
$('#canvas').mousedown(function(e){
preX = e.pageX - this.offsetLeft; preY = e.pageY - this.offsetTop; paint = true;
});
(31)if(paint){
var x = e.pageX - this.offsetLeft; var y = e.pageY - this.offsetTop; context.moveTo(preX,preY);
context.lineTo(x,y); context.stroke(); preX = x;
preY = y; }
});
$('#canvas').mouseenter(function(e){ if(paint)
{
preX = e.pageX - this.offsetLeft; preY = e.pageY - this.offsetTop; }
});
$("#canvas").mouseup(function(){ paint = false;
});
$('#canvas').mouseleave(function(e){ paint = false;
}); });
</script> </head> <body>
<canvas id="canvas" width="500px" height="500px" style="border: 1px solid gray;"></canvas>
</body> </HTML>
2 Lưu nội dung Canvas
Để lưu lại liệu Canvas nhằm phục hồi cần thiết (chẳng hạn chức Undo, Redo), ta sử dụng phương thức context.getImageData().Ta sử dụng phương pháp để thực chức vẽ đoạn thẳng Canvas Trước k hi bắt đầu vẽ đoạn thằng, canvas cần lưu lại nội dung chuột di chuyển, ta phục hồi lại nội dung lưu đồng thời vẽ đoạn thẳng từ điểm bắt đầu đến vị trí chuột
Ta sửa ví dụ thành jQuery plugin để tiện sử dụng:
<HTML> <head>
<script src="http://code.jquery.com/jquery-latest.js"></script> <script>
(32)var tool; // pen, line var canvas;
var context; var imageData; var paint;
$.fn.makeDrawable = function() { canvas = this[0];
if( !$(canvas).is("canvas") )
throw "The target element must be a canvas"; context = canvas.getContext("2d");
$(canvas).mousedown(function(e){
preX = e.pageX - canvas.offsetLeft; preY = e.pageY - canvas.offsetTop; paint = true;
if(tool=="line") {
imageData = context.getImageData(0, 0, canvas.width, canvas.height); } }); $(canvas).mousemove(function(e){ if(paint) {
var x = e.pageX - canvas.offsetLeft; var y = e.pageY - canvas.offsetTop; if(tool=="pen")
{
context.moveTo(preX,preY); context.lineTo(x,y);
context.stroke(); preX = x;
preY = y; }
else if(tool=="line") {
canvas.width=canvas.width; // clear canvas content context.putImageData(imageData,0,0); context.moveTo(preX,preY); context.lineTo(x,y); context.stroke(); } } }); $(canvas).mouseup(function(e){ if(tool=="line") {
(33)var y = e.pageY - canvas.offsetTop; context.moveTo(preX,preY);
context.lineTo(x,y); context.stroke(); }
paint = false; });
$(canvas).mouseleave(function(e){ paint = false;
});
return $(canvas); };
$.fn.setTool = function(newTool) { tool=newTool;
return $(canvas); }
$.fn.clear = function() {
canvas.width=canvas.width; return $(canvas);
}
})( jQuery ); $(function(){ $("#canvas").makeDrawable(); $("#button1").click(function(){ $("#canvas").clear(); }); $("#pen").change(function(){ if(this.value) $("#canvas").setTool("pen"); }); $("#line").change(function(){ if(this.value) $("#canvas").setTool("line"); }); $("#canvas").setTool("pen"); }); </script> </head> <body> <div> <button id="button1">Clear</button>
<input name="tool" type="radio" id="pen" checked="true">Pen</input> <input name="tool" type="radio" id="line">Line</input>
</div>
<canvas id="canvas" width="500px" height="500px" style="border: 1px solid gray;"></canvas>
</body> </HTML>
(34)III Chọn di chuyển đối tượng
Thay lưu trữ nội dung Canvas dạng ImageData, ta lưu trữ đối tượng đồ họa dạng cấu trúc liệu thực vẽ phần tử lên Canvas Với phương pháp này, ta minh họa ví dụ sử dụng chuột để chọn di chuyển hình vẽ Canvas
1 Tạo cấu trúc liệu
Đầu tiên ta tạo lớp Rect dùng để chứa liệu hình chữ nhật Lớp ngồi biến lưu trữ tọa độ, kích thước trạng thái (selected) cần phương thức dùng để kiểm tra xem tọa độ [x,y] có nằm bên khơng Ta gọi phương thức isContain() có kiểu trả boolean Mã nguồn lớp có dạng:
function Rect() {
this.isSelected = false; this.x = 0;
this.y = 0; this.width = 1; this.height = 1; }
(35)return x > this.x && x < right && y > this.y && y < bottom; }
Tiếp theo, ta tạo lớp ShapeList dùng để chứa đối tượng Rect kiểu liệu mảng Ngồi ra, ShapeList có thêm biến dùng để đối tượng Rect chọn (selectedItem), hai biến dùng để lưu vị trí chuột click lên đối tượng Rect (offsetX, offsetY) Hai biến offset dùng để tính tọa độ xác người dùng sử dụng chuột để di chuyển đối tượng Rect
ShapeList cịn có hai phương thức:
- addItem: thêm đối tượng Rect vào danh sách
- selectAt: tìm chọn đối tượng Rect chứa tọa độ [x,y] Ta duyệt ngược từ cuối mảng để đảm bảo đối tượng nằm sau chọn trước trường hợp nhiều Rect chứa điểm [x,y]
function ShapeList(){ this.items = [];
this.selectedItem = null; this.offsetX = -1;
this.offsetY = -1; }
ShapeList.prototype.addItem = function(x,y,width,height){ var rect = new Rect;
rect.x = x; rect.y = y;
rect.width = width; rect.height = height; this.items.push(rect); }
ShapeList.prototype.selectAt = function(x,y){ if(this.selectedItem)
this.selectedItem.isSelected = false; this.selectedItem = null;
for (var i = 0; i < this.items.length; i++) { var rect = this.items[i];
if(rect.contains(x,y)) {
this.selectedItem = this.items[i]; this.offsetX = x - this.items[i].x; this.offsetY = y - this.items[i].y; this.items[i].isSelected = true; break;
} }
}
2 Các phương thức vẽ context
(36)for (var i = _list.items.length-1;i>=0; i ) { drawRect(_list.items[i]);
} }
function drawRect(rect){
_context.fillRect(rect.x,rect.y,rect.width,rect.height); if(rect.isSelected)
{
_context.save();
_context.strokeStyle = "red";
_context.strokeRect(rect.x,rect.y,rect.width,rect.height); _context.restore();
} }
3 Các kiện chuột Canvas
Trong kiện này, ta dùng cờ _ismoving để xác định xem đối tượng có chọn hay không thực di chuyển đối tượng ShapeList.selectedItem mousemove Giá trị _ismoving xác định kiện mousedown bị hủy mouseup
function canvas_mousedown(e){
var x = e.pageX - _canvas.offsetLeft; var y = e.pageY - _canvas.offsetTop; _list.selectAt(x,y)
if(!_list.selectedItem)
_list.addItem(x-RECT_SIZE,y-RECT_SIZE,RECT_SIZE*2,RECT_SIZE*2);
_ismoving = true; draw();
}
function canvas_mousemove(e){
if(_ismoving && _list.selectedItem){
var x = e.pageX - _canvas.offsetLeft; var y = e.pageY - _canvas.offsetTop; _list.selectedItem.x = x - _list.offsetX; _list.selectedItem.y = y - _list.offsetY; draw();
} }
function canvas_mouseup(e){ _ismoving = false; }
(37)IV. Sử dụng bàn phím
Bàn phím thiết bị khơng thể thiếu phương tiện quan trọng để thực chức ứng dụng tương tác với người dùng Trong viết này, ta hướng dẫn cách bắt kiện bàn phím canvas dùng để điều khiển góc xoay hướng di chuyển đối tượng đồ họa
1 Bắt kiện bàn phím
Để bắt kiện này, bạn đăng kí trực tiếp cho đối tượng window, nhiên điều gây rắc rối trang bạn có nhiều thành phần Cách tốt đăng kí riêng cho canvas hàm xử lý Tuy nhiên, để canvas focus được, bạn cần xác định thuộc tínhTabIndex nó:
<canvas id=”canvas” width=”300″ height=”200″ tabindex=”1″ style=”border: 1px solid;”></canvas>
Sau bạn đăng kí kiện cần thiết cho canvas Khi chạy trình duyệt, bạn cần phải click chuột vào canvas để nhận focus trước thực thao tác bàn phím
_canvas.onkeydown = canvas_keyDown; function canvas_keyDown(e){
alert(e.keyCode); }
(38)trang “Javascript Keycode Enums” liệt kê sẵn keyCode dạng enum đối tượng Ta sửa lại chút để tiện sử dụng hơn:
var Keys = {
BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, PAUSE: 19, CAPS_LOCK: 20, ESCAPE: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40, INSERT: 45, DELETE: 46, KEY_0: 48, KEY_1: 49, KEY_2: 50, KEY_3: 51, KEY_4: 52, // };
2 Kiểm tra trạng thái nhiều phím
Một khó khăn kiện bàn phím javascript xác định phím nhấn thơng qua thuộc tính event.keyCode Để kiểm tra trạng thái nhiều phím nhấn lúc, ta phải lưu trạng thái chúng lại kiện keyDown xảy xóa trạng thái kiện keyUp
var _keypressed = {};
function canvas_keyDown(e){
_keypressed[e.keyCode] = true; }
function canvas_keyUp(e){
_keypressed[e.keyCode] = false; }
3 Giới hạn phím bắt
Sử dụng phương pháp trên, bạn cần lọc phím cần sử dụng để đối tượng _keypressed khơng lưu giá trị không cần thiết Một cách đơn giản sử dụng mảng để lưu keyCode phím kiểm tra kiện bàn phím:
const AVAILABLE_KEYS =
(39)];
function canvas_keyDown(e){
if(AVAILABLE_KEYS.indexOf(e.keyCode)!=-1) {
_keypressed[e.keyCode] = true; }
}
function canvas_keyUp(e){
if(_keypressed[e.keyCode]) {
_keypressed[e.keyCode] = false; }
}
V Chuyển động Canvas
Một ví dụ đơn giản để làm quen với đồ họa chuyển động lập trình viết ví dụ bóng nảy bên vùng cửa sổ (canvas) Một bóng vẽ bên canvas chuyển động theo hướng xác định Khi chạm thành tường nào, bóng đổi hướng chuyển động tùy theo hướng di chuyển
1 Cơ bản
Ta xác định hai giá trị speedX speedY tương ứng với tốc độ di chuyển theo phương ngang dọc trái bóng Hướng di chuyển bóng phụ thuộc vào giá trị âm dương speedX speedY
Trong ví dụ này, thành tường biên canvas có hai phương ngang dọc Nguyên lý hoạt động đơn giản: bóng tiếp xúc với biên dọc canvas, ta đổi chiều speedY với biên ngang ta đổi chiều speedX
Trong lớp Ball sau, ta bổ sung thêm thuộc tính right, bottom để tiện kiểm tra va chạm Hai thuộc tính cx cy vị trí tâm bóng khởi tạo ngẫy nhiên cho ln nằm hồn tồn bên canvas
Ball.js:
function Ball(mapWidth, mapHeight){ this.mapWidth = mapWidth; this.mapHeight = mapHeight; this.radius = 20;
this.speedX = 3; this.speedY = 3;
this.cx = Math.floor(Math.random()*(this.mapWidth-2*this.radius)) + this.radius;
(40)}
Ball.prototype.draw = function(context){ context.beginPath();
context.fillStyle = "red";
context.arc(this.cx,this.cy,this.radius,0,Math.PI*2,true); context.closePath();
context.fill(); }
Ball.prototype.move = function(){ this.cx += this.speedX; this.cy += this.speedY;
this.left = this.cx - this.radius; this.top = this.cy - this.radius; this.right = this.cx + this.radius; this.bottom = this.cy + this.radius; }
Ball.prototype.checkCollision = function(shapes) {
if(this.left <= || this.right >= this.mapWidth) this.speedX = -this.speedX;
if(this.top <= || this.bottom >= this.mapHeight) this.speedY = -this.speedY; } Sample.HTML: <HTML> <head> <script src="ball.js"></script> <script> var _canvas; var _context; var _ball; function draw(){ _context.clearRect(0,0,_canvas.width,_canvas.height); _ball.draw(_context); } function update(){ _ball.move(); _ball.checkCollision(); draw(); }
window.onload = function(){ var interval = 10;
_canvas = document.getElementById("canvas"); _context = _canvas.getContext("2d");
_ball = new Ball(_canvas.width,_canvas.height); setInterval("update()",interval);
}
(41)<body>
<canvas id="canvas" width="400px" height="300px" style="border: 1px solid gray;"></canvas>
</body> </HTML>
2 Thêm hiệu ứng bóng di chuyển
Để ví dụ khơng q đơn điệu, ta thêm ảnh bóng mờ di chuyển phía sau trái bóng Ta lưu vị trí bóng di chuyển qua vào mảng có tối đa 10 phần tử vẽ canvas hàm draw() Phương thức Ball.draw() cần chỉnh sửa chút phép nhận tham số alpha xác định độ mờ đục Tham số alpha có giá trị nằm đoạn 1:
Ball.prototype.draw = function(context,alpha){ if(!alpha)
alpha = 255; context.beginPath();
context.fillStyle = "rgba(255, 100, 100," + alpha + ")"; context.arc(this.cx,this.cy,this.radius,0,Math.PI*2,true); context.closePath();
context.fill(); }
Để trái bóng khơng nằm q sát nhau, ta thực lưu vị trí bóng sau lần bóng di chuyển hay sau lần phương thức update() gọi Phương thức traceBall() sau tạo đối tượng Ball với hai giá trị cx cy lấy từ trái bóng lưu vào mảng _balls Khi chiều dài mảng _balls vượt 10, ta dùng phương thức Array.splice() để cắt phần tử nằm đầu mảng:
function traceBall(ball){ var b = new Ball; b.cx = ball.cx; b.cy = ball.cy; _balls.push(b); if(_balls.length>10)
_balls.splice(0,1); }
(42)3 Kiểm tra va chạm
Phần quan trọng ví dụ nằm phương thức kiểm tra va chạm trái bóng Phương thức duyệt qua tất chướng ngại vật kiểm tra khoảng cách so với chướng ngại vật theo hai phương dọc ngang
Với ví dụ này, ta kiểm tra va chạm mức đơn giản nên việc phản xạ trái bóng chưa hồn tồn xác Ta giới thiệu kĩ thuật kiểm tra va chạm di chuyển viết sau Phương thức kiểm tra va chạm lớp Ball:
Ball.prototype.checkCollision = function(shapes) {
if(this.left <= || this.right >= this.mapWidth) this.speedX = -this.speedX;
if(this.top <= || this.bottom >= this.mapHeight) this.speedY = -this.speedY;
for(var i = 0; i < shapes.items.length ; i++){ var item = shapes.items[i];
var hCollision = false; var vCollision = false;
if( this.right >= item.left && this.left <= item.right && // the ball is ((this.cy < item.top && this.bottom >= item.top) || // on or
(this.cy > item.bottom && this.top <= item.bottom))) // under the rectangle
{
this.speedY = -this.speedY; vCollision = true;
}
if(this.bottom >= item.top && this.top <= item.bottom && // the ball is in the
((this.cx < item.left && this.right >= item.left) || // left or
(this.cx > item.right && this.left <= item.right))) // right side of the rectangle {
this.speedX = -this.speedX; hCollision = true;
(43)if(hCollision || vCollision) break;
} }
(44)D Kĩ thuật lập trình Game – Cơ bản
I Vòng lặp game (Game loop) hoạt động nào?
Phần cốt lõi hầu hết game vòng lặp dùng để cập nhật hiển thị trạng thái game Trong viết này, ta minh họa phương pháp tạo vòng lặp game với ngơn ngữ javascript
1 Vịng lặp bản
Một vòng lặp game bao gồm việc thực theo thứ tự sau:
while(gameRunning) {
processInput(); // keyboard, mouse, updateGame();
draw();
// checkGameOver(); }
(45)Trong javascript, thay dùng vịng lặp, ta thay setInterval() setTimeout() Thông thường bạn cần xác định giá trị interval thích hợp theo tốc độ game
function gameLoop() {
processInput(); updateGame(); draw();
setTimeout(gameLoop,100); // 10 fps }
Hợp lý bạn muốn xác định rõ số khung hình/giây (fps):
const FPS = 60; function gameLoop() {
// … }
window.setInterval(gameLoop,1000/FPS);
2 Vịng lặp có tính tốn thời gian
Tuy nhiên, lúc công việc update draw hoà n thành trước lần gọi thực Như tốc độ game không đồng thiết bị có cấu hình khác
Để giải quyết, ta sử dụng đến thời gian hệ thống để so sánh định thời điểm update/draw
const FPS = 60;
const TICKS = 1000/FPS; var lastUpdateTime; function gameLoop() {
var now = Date.now();
var diffTime = now - lastUpdateTime; if(diffTime >= TICKS)
processInput(); updateGame();
lastUpdateTime = now; }
draw();
var sleepTime = TICKS - diffTime; if(sleepTime<0)
sleepTime = 0;
setTimeout(gameLoop,sleepTime); }
(46)3 Giải pháp cuối cùng
Giải pháp ta thực update với số lần dựa vào tỉ lệ diffTime/TICKS lần lặp game Sẽ hiệu ta bỏ qua việc draw thực update liên tiếp giúp tăng tốc độ game để bù vào khoảng thời gian bị trì hỗn
const FPS = 60;
const TICKS = 1000/FPS; var lastUpdateTime; function gameLoop() {
var diffTime = Date.now() - lastUpdateTime; var numOfUpdate = Math.floor(diffTime/TICKS); for(var i = 0;i < numOfUpdate;i++){
processInput(); updateGame(); }
if(diffTime >= TICKS) draw();
lastUpdateTime += TICKS * numOfUpdate; diffTime -= TICKS * numOfUpdate;
var sleepTime = TICKS - diffTime; setTimeout(gameLoop,sleepTime); }
Nếu bạn sử dụng requestAnimationFrame cho vòng lặp game, bạn khơng cần quan tâm đến việc tính tốn giá trị sleepTime
4 Ví dụ hồn chỉnh
Kiểm tra ví dụ so sánh với phương pháp trước đó, thực vài cơng việc “q tải” trình duyệt bạn thấy khác biệt:
const FPS = 6;
const TICKS = 1000 / FPS; var startTime;
var expectedCounter = 0; var lastUpdateTime; var actualCounter = 0; var output;
function gameLoop() {
var diffTime = Date.now() - lastUpdateTime; var numOfUpdate = Math.floor(diffTime/TICKS); for(var i = 0;i < numOfUpdate;i++){
updateGame(); }
(47)draw();
lastUpdateTime += TICKS * numOfUpdate; diffTime -= TICKS * numOfUpdate;
var sleepTime = TICKS - diffTime; setTimeout(gameLoop,sleepTime); }
function updateGame() { actualCounter++; }
function draw() {
var s = "Actual updates: "+actualCounter;
s += "<br/>Expected updates: "+Math.floor((Date.now()-startTime)/TICKS); output.innerHTML = s;
}
// onLoad
output = document.getElementById("output"); startTime = Date.now();
lastUpdateTime = startTime; gameLoop();
Output:
Actual updates: 1323 Expected updates: 1323
II Kiểm tra va chạm: hình trịn chữ nhật
Thông thường đối tượng game xác định va chạm cách đưa chúng dạng hình học hình chữ nhật, hình trịn Bài viết giúp bạn cách tính tốn để kiểm tra va chạm hai loại hình học
1 Giữa hai hình chữ nhật
Phương pháp: kiểm tra đỉnh hình có nằm bên hình khơng
Rect.prototype.collideWidthRect = function(rect) { return this.contains(rect.left,rect.top) ||
this.contains(rect.right,rect.top) || this.contains(rect.right,rect.bottom) || this.contains(rect.left,rect.bottom); }
Cách cách nhanh nhất, bạn dùng cách sau, đơn giản hiệu hơn:
(48)this.right < rect.left || this.top > rect.bottom || this.bottom < rect.top); }
2 Giữa hai hình trịn
Phương pháp: Bởi điểm nằm đường trịn cách tâm, nên việc kiểm tra va chạm hai hình trịn xác định dựa vào khoảng cách tâm chúng Để xác định khoảng cách hai điểm, ta dựa vào định lý Pythagoras (Pythagorean theorem) Ta coi khoảng cách hai điểm đường chéo tam giác vuông Vậy độ lớn đường chéo là:
c² = a² + b²
=> c = sqrt(a² + b²)
Circle.prototype.collideWithCircle = function(circle){ var dx = this.cx - circle.cx;
var dy = this.cy - circle.cy;
return Math.sqrt(dx*dx + dy*dy) <= this.radius+circle.radius; }
Trong minh họa đây, hai hình trịn có màu mặc định xanh lá, va chạm nhau, chúng chuyển sang màu đỏ
3 Giữa hình trịn hình chữ nhật
(49)Khoảng cách tâm C hình trịn điểm A hình chữ nhật minh họa hình Khi tâm hình trịn nằm bên hình chữ nhật, điểm C A trùng
Gọi rect hình chữ nhật cần xác định va chạm Ta có thuật tốn để xác định điểm A sau: - B1: Gán A với C
- B2: Nếu C.X < rect.Left, đặt A.X = rect.Left Ngược lại C.X > rect.Right, đặt A.X = rect.Right
- B3: Nếu C.Y < rect.Top, đặt A.Y = rect.Top Ngược lại C.Y > rect.Bottom, đặt A.Y = rect.Bottom
Khi có điểm A, ta lại dùng cơng thức Pythagoras để so sánh với bán kính hình trịn
Circle.prototype.collideWithRect = function(rect){ var px = this.cx;
var py = this.cy; if(px < rect.left)
px = rect.left; else if(px > rect.right)
px = rect.right; if(py < rect.top)
py = rect.top; else if(py > rect.bottom)
py = rect.bottom; var dx = this.cx - px; var dy = this.cy - py;
(50)III Kiểm tra điểm nằm đoạn thẳng
Có nhiều cách để kiểm tra điểm có thuộc đường thằng (tương tự với đoạn thẳng) hay khơng: cách sử dụng cơng thức hình học thuật tốn vẽ đường thẳng,… Ngồi cách trên, ta giới thiệu phương pháp đơn giản sử dụng phép so sánh góc để giải vấn đề
Trong demo sau, bạn di chuyển chuột ngang lên đoạn thẳng, bạn đoạn thẳng chuyển sang màu đỏ
Ý tưởng:
Ta có đoạn thẳng AB tạo nên góc α (so với phương ngang phương bất kì), điểm thuộc AB tạo nên góc α so với phương ngang
Hay nói cách khác, ta xem đoạn thẳng AB đường chéo tam giác vuông ABM Tỉ lệ hai cạnh góc vng tam giác với tỉ lệ hai cạnh góc vng tạo tam giác vng AXN Minh họa hình đây:
Như việc xác định điểm X có thuộc AB hay khơng cần dựa vào yếu tố:
(1) X phải nằm vùng hình chữ nhật tạo đường chéo AB (ngoại tiếp tam giác ABM)
(2) Góc XAN BAM phải
(51)Muốn xác độ rộng đoạn thẳng thay đổi, bạn tính góc sai số cho phép cách dựa vào ½ độ rộng đoạn thẳng khoảng cách AX để tính Tuy nhiên, điều khơng quan trọng làm cho việc kiểm tra tốn thêm chi phí
Để đơn giản, ta tính tỉ lệ (tan) thay tính góc α:
Line.prototype.contains = function(x, y) {
if( x < Math.min(this.handler1.cx,this.handler2.cx) || x > Math.max(this.handler1.cx,this.handler2.cx) || y < Math.min(this.handler1.cy,this.handler2.cy) || y > Math.max(this.handler1.cy,this.handler2.cy)) return false;
var dx = this.handler1.cx-this.handler2.cx; var dy = this.handler1.cy-this.handler2.cy; var tan1 = Math.abs(dy/dx);
var tan2 = Math.abs((y-this.handler1.cy)/(x-this.handler1.cx)); return Math.abs(tan1 - tan2) < EPSILON;
}
IV. Vector 2D bản
Ứng dụng vector quan trọng lĩnh vực lập trình game đồ họa Thơng qua vector, ta mơ chuyển động, tính tốn lực, hướng di chuyển sau va chạm,…
1 Khái niệm
“…một vectơ phần tử không gian vectơ, xác định ba yếu tố: điểm đầu (hay điểm gốc), hướng (gồm phương chiều) độ lớn (hay độ dài).” (Wikipedia)
Từ đoạn thẳng AB ta xác định vector (mã giả): u.root = A;
u.x = B.x-A.x; u.y = B.y-A.y;
u.length = sqrt(u.x*u.x+u.y*u.y); // |u|
2 Vector đơn vị (Unit Vector, Normalized Vector)
Vector đơn vị u vector có chiều dài kí hiệu û Vector u sau gọi vector đơn vị v cách tính (mã giả):
û = u/|u|
Như vậy:
(52)3 Tích vơ hướng (Dot product, Scalar pro duct)
Phép tốn tích vơ hướng hai vector biểu diễn dấu chấm (nên gọi dot product) tính sau:
v.u = |v||u|cosθ
= v1u1 + v2u2 + … + vnun
Với θ (theta) góc v, u n chiều không gian Từ cơng thức trên, ta tính θ:
cosθ = (v.u)/(|v||u|) θ = arccos(cosθ)
4 Phép chiếu (Projection)
Một ứng dụng khác tích vơ hướng tính phép chiếu vector lên vector khác Tương tự việc chiếu vector v thành giá trị x y lên trục Oxy (Hình từ wikipedia)
(53)Tổng quát với hai vector bất kì, xét biểu thức: |a| = |v|cosθ
Với u vector tạo với v góc θ, từ cơng thức: cosθ = (v.u)/(|v||u|)
Suy ra:
|a| = |v|(v.u)/(|v||u|) = (v.u)/|v|
Vậy ta tính độ lớn vector v chiếu vector u Từ giá trị |x| này, ta tính vector x cách nhân với vector đơn vị u:
a = |a|û
5 Hiện thực với javascript
Một đối tượng Line biểu diễn điểm đầu (p1) cuối (p2) đoạn thẳng Từ hai điểm này, ta có phương thức getVector() để lấy đối tượng vector đoạn thẳng
Line.prototype.getVector = function() { var x = this.p2.x-this.p1.x; var y = this.p2.y-this.p1.y; return {
x: x, y: y,
root: this.p1,
length: Math.sqrt(x*x+y*y) };
}
Phương thức update() sau gọi đoạn thẳng bị thay đổi:
function update(){
var v1 = _line1.getVector(); var v2 = _line2.getVector(); // normalize vector v2
v2.dx = v2.x/v2.length; v2.dy = v2.y/v2.length; // dot product
var dp = v1.x*v2.x + v1.y*v2.y;
// length of the projection of v1 on v2 var length = dp/v2.length;
(54)x: length*v2.dx, y: length*v2.dy, };
// the projection line
_line3.p2.x = _line3.p1.x+v3.x; _line3.p2.y = _line3.p1.y+v3.y;
// calculate the angle between v1 and v2 // and convert it from radians into degrees
_angle = Math.acos(dp/(v1.length*v2.length))*(180/Math.PI); _angle = Math.round(_angle*100)/100;
}
Trong phần demo sau, ta thực phép chiếu vector màu đỏ lên vector xanh Kết phép chiếu vector màu xanh lam Bạn thay đổi hướng độ lớn vector cách nhấn nhấn rê chuột vào đầu mũi tên
Xem Demo.
V Khoảng cách từ điểm đến đoạn thẳng
Dựa vào phép chiếu từ tích vơ hướng (Dot product) hai vector, ta tính khoảng cách từ điểm đến đường thẳng, đoạn thẳng
Bài tốn: Tìm khoảng cách từ điểm C đến đoạn thẳng AB Giải quyết:
Ta tính vector chiếu AC lên AB gọi AH Vì CH vng góc với AB khoảng cách từ C đến AB CH Như ta tính khoảng cách từ điểm đến đường thẳng chứa AB
(55)Kết tích vơ hướng có khoảng giá trị để ta ước lượng góc hai vector (bỏ qua chiều âm/dương):
Case (Dot product < 0): Góc hai vector lớn 90 độ Điểm H nằm AB Case (Dot product = 0): Hai vector vng góc với (90 độ) Điểm H trùng với A
Case (Dot product > 0): Góc hai vector nhỏ 90 độ Điểm H nằm AB khơng Điểm H nằm AB độ dài AH > AB
Mã nguồn:
function pointLineDistance(){ // v1: AC
// v2: AB // v3: AH
(56)// normalize vector v2 v2.dx = v2.x/v2.length; v2.dy = v2.y/v2.length; // dot product
var dp = v1.x*v2.x + v1.y*v2.y;
// length of the projection of v1 on v2 var length = dp/v2.length;
// the projection vector of v1 on v2 var v3 = {
x: length*v2.dx, y: length*v2.dy, };
v3.length = Math.sqrt(v3.x*v3.x+v3.y*v3.y); // the projection line
_line3.p2.x = _line3.p1.x+v3.x; _line3.p2.y = _line3.p1.y+v3.y; var d; // distance
// d = -1 means H does not lie on AB if(dp < 0)
{
d = -1; }
else {
if(v3.length > v2.length) d = -1;
else {
var dx = v1.x-v3.x; var dy = v1.y-v3.y;
d = Math.sqrt(dx*dx+dy*dy); }
}
return d; }
VI. Giao điểm hai đường thẳng
Từ hai đoạn thẳng (hoặc vector) mặt phẳng 2D, ta tìm giao điểm chúng để tính tốn góc phản xạ hướng di chuyển
1 Tạo phương trình đường thẳng từ đoạn thẳng
(57)a = (y2 – y1)/(x2 – x1)
Thay x2, y2 hai biến x,y: a = (y – y1)/(x – x1)
=> y – y1 = a(x – x1) Hay:
y = ax + (y1 – ax1) Đặt b = y1 – ax1, ta có: y = ax + b
2 Tính giao điểm hai đường thẳng
Ta có hai phương trình đường thẳng (1): y = a1x + b1
(2): y = a2x + b2
Ta tính giao điểm chúng cách tìm giao điểm x trước: a1x + b1 = a2x + b2;
=> x(a1 – a2) = b2 – b1 => x = (b2 – b1)/(a1 – a2)
(58)3 Minh họa với HTML5 Canvas
(Đoạn mã dài nên xem trang yinyangit.wordpress.com)
VII Va chạm phản xạ
Để tính hướng phản xạ vật thể va chạm vào mặt phẳng, ta dựa vào góc nghiêng mặt phẳng vector theo vùng giá trị Tuy nhiên cách tổng quát sử dụng phép toán vector để thực
Xem Demo
1 Kiểm tra hai đoạn thẳng cắt nhau
Từ viết trước tìm giao điểm hai đường thẳng, ta kiểm tra xem giao điểm tìm có phải giao điểm hai đoạn thẳng hay không Cách kiểm tra đơn giản bạn xem điểm có thuộc hai phần bao hình chữ nhật hai đoạn thẳng Ta có phương thức kiểm tra sau:
// detect if a point is inside the rectangle bound of this line Line.prototype.insideRectBound = function(x, y){
if( Math.min(this.p1.x,this.p2.x) - x > EPSILON || x - Math.max(this.p1.x,this.p2.x) > EPSILON || Math.min(this.p1.y,this.p2.y) - y > EPSILON || y - Math.max(this.p1.y,this.p2.y) > EPSILON) return false;
return true; }
//
(59)2 Phương pháp
Trong hình minh họa sau, vector v hướng di chuyển vật thể va chạm vào u Vector phản xạ v b Từ vector b ta phân tích hai vector thành phần a c (như ta có a + c = b) Trong đó: a: vector chiếu v u c: vector chiếu v đường thẳng vng góc với u Đây vector pháp tuyến u có độ dài khoảng cách từ gốc v đến u Như để tìm vector phản xạ b, ta cần theo bước:
1 Tìm giao điểm hai đoạn thẳng (tương ứng với hai vector v u). 2 Tìm vector chiếu a v u.
3 Tìm vector pháp tuyến u có độ dài khoảng cách từ gốc v đến u. 4 Tính tổng vector a c.
Xem mã nguồn minh họa javascript trang sau để thấy chi tiết hơn:
http://yinyangit.wordpress.com/2012/04/10/gamedev-vector2d-va-cham- va-phan- xa/
VIII Va chạm đường tròn đoạn thẳng
Tìm điểm va chạm đường tròn với đoạn thẳng xác định hướng phản xạ di chuyển thơng qua phép tốn vector
1 Va chạm
(60)Ví dụ phần kiềm tra khoảng cách chưa xét trường hợp va chạm với hai đầu đoạn thẳng Bởi việc kiểm tra phản xạ va chạm với đầu đoạn thẳng cần tính tốn thêm vài bước nên ta bỏ qua
Phương thức sau trả điểm va chạm trái bóng với đoạn thẳng null khơng có
Ball.prototype.findCollisionPoint = function(line) {
// create a vector from the point line.p1 to the center of this ball var v1= {
x: this.cx - line.p1.x, y: this.cy - line.p1.y };
var v2 = line.getVector(); var v3 = findProjection(v1,v2);
v3.length = Math.sqrt(v3.x*v3.x+v3.y*v3.y); var collisionPoint = null;
if(v3.dp>0 && v3.length <= v2.length) {
var dx = v1.x-v3.x; var dy = v1.y-v3.y;
var d = Math.sqrt(dx*dx+dy*dy); // distance if(d>this.radius)
return null; collisionPoint = {
x: line.p1.x + v3.x, y: line.p1.y + v3.y };
// don't overlap if(d < this.radius) {
this.cx -=
(this.movement.x/this.speed)*(this.radius-d); this.cy -=
(this.movement.y/this.speed)*(this.radius-d); }
}
return collisionPoint; }
2 Phản xạ
(61)Ball.prototype.bounceAgainst = function(line) { if(this.findCollisionPoint(line))
{
var v1 = this.movement; var v2 = line.getVector(); var v3 = findProjection(v1,v2); // perpendicular vector of v2 var v4 = {
x: v2.y, y: -v2.x };
v4 = findProjection(v1,v4); v4.x = -v4.x;
v4.y = -v4.y; // bounce vector var v5 = {
x: v3.x + v4.x, y: v3.y + v4.y };
v5.length = Math.sqrt(v5.x*v5.x+v5.y*v5.y); // normalize vector
v5 = {
x: v5.x/v5.length, y: v5.y/v5.length };
this.setMovement(v5); }
}
Đối với việc kiểm tra phản xạ trái bóng va chạm với điểm (như hai đầu đoạn thẳng), bạn cần xác định vector vng góc với đường thẳng nối tâm trái bóng đến điểm va chạm coi mặt phẳng va chạm
(62)IX. Va chạm nhiều đường tròn
Để xác định hướng di chuyển hai cầu có khối lượng khác sau va chạm, ta sử dụng công thức định luật bảo toàn động lượng hệ cô lập
Xét trường hợp va chạm không gian chiều (1D – vật di chuyển đường thẳng), ta gọi m khối lượng vận tốc cầu C, u vận tốc trước va chạm v vận tốc sau va chạm Tại thời điểm hai cầu C1 C2 va chạm nhau, ta có cơng thức: m1u1 + m2u2 = m1v1 + m2v2
Suy ra:
v1 = (u1*(m1- m2) + 2*m2*u2)/(m1+m2) v2 = (u2*(m2- m1) + 2*m1*u1)/(m1+m2)
Ta áp dụng cơng thức khơng gian hai chiều (2D) để tính vận tốc theo phương xác định
(63)var angle1 = Math.atan2(u1.y, u1.x);
var ux1 = u1.length * Math.cos(angle1-angle); var uy1 = u1.length * Math.sin(angle1-angle);
Áp dụng cơng thức bên trên, ta tính vector vận tốc theo phương ngang vx1 Do không gian 1D nên vận tốc uy1 không ảnh hưởng
var vx1 = ((ball1.mass-ball2.mass)*ux1+(ball2.mass+ball2.mass)*ux2)/(ball1.mass+ball2.mass); var vy1 = uy1;
Đã có hai vector thành phần vx1 vy1, ta cần chuyển lại vector sang không gian 2D hợp thành vector vector chuyển động v1 Do vector vx1 vng góc với vy1 nên ta cần cộng với angle góc 90 độ hay PI/2 Ở đây, angle góc tạo đoạn thẳng nối hai tâm đường tròn
var v1 = {};
v1.x = Math.cos(angle)*vx1+Math.cos(angle+Math.PI/2)*vy1; v1.y = Math.sin(angle)*vx1+Math.sin(angle+Math.PI/2)*vy1;
1 Xử lý va chạm nhiều đường tròn
Cách đơn giản có độ phức tạp tương đối nhỏ (số lần lặp n(n-1)/2) việc kiểm tra xử lý va chạm cho nhiều đối tượng sử dụng hai vòng lặp lồng theo dạng sau:
for(var i=0;i<_balls.length;i++) {
for(var j=i+1;j<_balls.length;j++)
(64)Hàm checkCollision:
function checkCollision(ball1,ball2){ var dx = ball1.cx - ball2.cx; var dy = ball1.cy - ball2.cy;
// check collision between two balls var distance = Math.sqrt(dx*dx+dy*dy); if(distance > ball1.radius + ball2.radius)
return;
var angle = Math.atan2(dy,dx); var u1 = ball1.getVelocity(); var u2 = ball2.getVelocity();
var angle1 = Math.atan2(u1.y, u1.x); var angle2 = Math.atan2(u2.y, u2.x);
var ux1 = u1.length * Math.cos(angle1-angle); var uy1 = u1.length * Math.sin(angle1-angle); var ux2 = u2.length * Math.cos(angle2-angle); var uy2 = u2.length * Math.sin(angle2-angle);
var vx1 =
((ball1.mass-ball2.mass)*ux1+(ball2.mass+ball2.mass)*ux2)/(ball1.mass+ball2.mass);
var vx2 = ((ball1.mass+ball1.mass)*ux1+(ball2.ma ss-ball1.mass)*ux2)/(ball1.mass+ball2.mass);
var vy1 = uy1; var vy2 = uy2;
// now we transform the 1D coordinate back to 2D var v1 = {}, v2 = {};
v1.x = Math.cos(angle)*vx1+Math.cos(angle+Math.PI/2)*vy1; v1.y = Math.sin(angle)*vx1+Math.sin(angle+Math.PI/2)*vy1; v2.x = Math.cos(angle)*vx2+Math.cos(angle+Math.PI/2)*vy2; v2.y = Math.sin(angle)*vx2+Math.sin(angle+Math.PI/2)*vy2; ball1.velocity = v1;
ball2.velocity = v2; }
X Kiểm tra va chạm dựa pixel
(65)1 Một wrapper Image
Để tiện xử lý hình ảnh canvas dạng đối tượng game với chức cần thiết, ta tạo lớp ImageObj chứa bên đối tượng Image để lưu trữ hình ảnh Phương thức quan trọng mà ImageObj phải có draw(), nhiên việc nạp ảnh diễn lâu ta cần cách thức xử lý trường hợp Chẳng hạn ta viết dịng thơng báo thay cho ảnh với kích thước mặc định trường hợp ảnh chưa tải xong:
function ImageObj(){ //
this.draw = function(context){ if(ready){
context.drawImage(this.img,this.left,this.top); }
else{
// image has not finished loading // draw something useful instead context.save();
context.fillText("Image is not ready",this.left+10,this.top+10); context.restore(); } context.strokeRect(this.left,this.top,this.width,this.height); context.stroke(); }; // }
Chưa đủ, để đối tượng ImageObj tự vẽ lại sau ảnh nạp xong, đồng thời lấy đối tượng ImageData (chứa mảng pixel), ta viết số lệnh xử lý kiện onload Image
function ImageObj(){ //
var self = this;
this.img.onload = function(){ self.width = this.width; self.height = this.height;
ready = true; // this image is ready to use // draw image after loading
context.clearRect(self.left,self.top,self.width,self.height); self.draw(context);
// get ImageData from this image
self.data =
context.getImageData(self.left,self.top,self.width,self.height).data; };
(66)Do vấn đề bảo mật, ta không lấy ImageData ảnh không nằm host (khác host mà script thực thi)
2 Xác định vùng giao hai hình chữ nhật
Thay lặp qua tồn hai hình ảnh kiểm tra pixel chúng Ta giới hạn lại phần ảnh cần kiểm tra cách xác định vùng giao hai hình ảnh Phần giúp ta xác định nhanh hai đối tượng xảy va chạm hay không
function findIntersectionRect(img1,img2){ var rect = {
left: Math.max(img1.left,img2.left), top: Math.max(img1.top,img2.top),
right: Math.min(img1.left+img1.width,img2.left+img1.width), bottom: Math.min(img1.top+img1.height,img2.top+img2.height) };
if(rect.left>rect.right || rect.top>rect.bottom) return null;
return rect; }
(67)3 Kiểm tra va chạm
Nếu tìm hiểu đối tượng ImageData, bạn biết pixel lưu trữ mảng chiều Mỗi pixel bao gồm bốn phần tử đứng liền mảng theo thứ tự [Red, Green, Blue, Alpha] Như ảnh có kích thước 20×30 có 600 pixel có 600×4=2400 phần tử ImageData
function checkPixelCollision(img1,img2){ if(!img1.data || !img2.data)
return null;
var rect = findIntersectionRect(img1,img2); if(rect)
{
// this array will hold all collision points of two images var points = [];
for(var i=rect.left;i<=rect.right;i++){
for(var j=rect.top;j<=rect.bottom;j++){ var index1 = ((i-img1.left)+(j-img1.top)*img1.width)*4+3;
var index2 = ((i-img2.left)+(j-img2.top)*img2.width)*4+3;
if(img1.data[index1+3]!=0 && img2.data[index2+3]!=0)
{
points.push({x:i,y:j});
// you can exit here instead of continue looping
} }
}
return {
rect: rect, points: points };
}
return null; }
(68)(69)E Kỹ thuật lập trình Game – Nâng cao
I Cuộn ảnh đồ (Map Scrolling)
Cuộn đồ chức thiếu game có đồ lớn vượt q kích thước khung nhìn (viewport) hình Bài viết giúp bạn tìm hiểu số phương pháp để tạo hiệu ứng, cuộn ảnh đồ game
1 Ảnh nhiều tầng
(70)2 Cuộn giả
Bạn thấy nhiều game có đồ lớn ảnh lặp lặp lại theo mẫu Điều thực cách sử dụng ảnh có kích thước nhỏ vẽ lặp lặp lại viewport Tùy theo kích thước ảnh mà ta sử dụng phương pháp vẽ khác
Cách thông thường sử dụng ảnh có kích thước viewport Sau ta chia ảnh thành hai phần theo chiều dọc dựa vào đường phân cách:
- Cắt phần ảnh từ vị trí đường phân cách đến hết vẽ lên viewport vị trí {0,0}
- Cắt phần ảnh từ vị trí đến đường phân cách (phần cịn lại) vẽ lên viewport vị trí tiếp nối với phần lúc
// position that divide the background into two parts offsetX++;
if(offsetX>width) offsetX = 0; // draw the first part
ctx.drawImage(bgimg,offsetX,0,width-offsetX,height,0,0,width-offsetX,height); // the second part
ctx.drawImage(bgimg,0,0,offsetX,height,width-offsetX,0,offsetX,height);
3 … cuộn thật
Với đồ lớn, việc cuộn để giữ nhân vật game ln hình đơn giản Ta xác định tọa độ (left top) đồ dùng để làm viewport cách:
// inside Map object // obj = character
this.offsetX = obj.left - viewWidth/2; this.offsetY = obj.top - viewHeight/2;
Tuy nhiên việc thay đổi khung nhìn liên tục khiến cho người chơi nhức mắt, tập trung ảnh hưởng đến hiệu suất Vì ta áp dụng giải phải tạo vùng giới hạn viewport gọi “dead zone“ Khi nhân vật di chuyển nằm vùng này, viewport không bị thay đổi
// inside Map object
(71)this.offsetX = obj.left - this.deadzone.left; else if(dx+obj.size>this.deadzone.right)
this.offsetX = obj.right - this.deadzone.right; if(dy<this.deadzone.top)
this.offsetY = obj.top - this.deadzone.top; else if(dy+obj.size>this.deadzone.bottom)
this.offsetY = obj.bottom - this.deadzone.bottom;
Xem Demo
II Tạo Amimated Sprite
Sprite phương pháp để tạo đối tượng chuyển động từ hình ảnh Bằng cách xếp nhiều đối tượng theo thứ tự chuyển động, sprite giúp cho việc quản lý tài nguyên xử lý hiệu so với việc phải sử dụng nhiều tập tin ảnh riêng lẻ
Xem Demo.
(72)với mục đích tạo lớp Sprite linh động, ta phải cho phép kết hợp nhiều sprite với chuyển đổi qua lại sprite dễ dàng
Mã nguồn lớp AnimatedSprite:
var AnimatedSprite = function(data) { this.init(data);
this.isFinished = false; this.currentSprite = null; this.currentFrame = 0; this.lastTick = 0; };
AnimatedSprite.prototype = { start: function(){
this.isFinished = false; this.currentFrame = 0; }
init: function(data) {
if(data){
this.isLooping = data.isLooping; if(typeof this.isLooping!="boolean")
this.isLooping = true; this.image = data.image;
this.frameWidth = data.frameWidth;
this.frameHeight = data.frameHeight || this.frameWidth; this.sprites = [];
this.interval = data.interval; this.left = data.left;
this.top = data.top;
this.width = data.width || this.frameWidth; this.height = data.hegiht || this.frameHeight; this.onCompleted = data.onCompleted;
} },
addSprite: function(data){
(73)name : data.name,
startFrame : data.startFrame || 0, framesCount : data.framesCount || 1,
framesPerRow :
Math.floor(this.image.width/this.frameWidth) };
this.currentSprite = this.currentSprite || this.sprites[data.name];
},
setSprite: function(name){
if(this.currentSprite != this.sprites[name]) {
this.currentSprite = this.sprites[name]; this.currentFrame = 0;
} },
update: function(){
if(this.isFinished) return;
var newTick = (new Date()).getTime(); if(newTick-this.lastTick>=this.interval) { this.currentFrame++; if(this.currentFrame==this.currentSprite.framesCount) { if(this.isLooping) this.currentFrame=0; else {
this.isFinished = true; if(this.onCompleted)
this.onCompleted(); }
}
this.lastTick = newTick; }
},
draw: function(context){ if(this.isFinished)
return; var realIndex =
this.currentSprite.startFrame+this.currentFrame; var row =
Math.floor(realIndex/this.currentSprite.framesPerRow);
var col = realIndex % this.currentSprite.framesPerRow;
(74)} }
Để sử dụng, ta tạo lớp AnimatedSprite để cung cấp chức di chuyển, kiểm tra va chạm Ở ta tạo lớp Character thừa kế từ AnimatedSprite:
function Character(data){
this.mapWidth = data.mapWidth; this.mapHeight = data.mapHeight; this.speedX = 0;
this.speedY = 0; this.init(data); }
Character.prototype = new AnimatedSprite(); Character.prototype.update = function(){
var left = this.left+this.speedX; var top = this.top+this.speedY; var right = left+this.frameWidth; var bottom = top+this.frameHeight; if(left>0 && right<this.mapWidth)
this.left = left;
if(top>0 && bottom<this.mapHeight) this.top = top;
AnimatedSprite.prototype.update.call(this); };
Sau tạo đối tượng từ lớp Character ảnh bên để minh họa:
sprite = new Character({
(75)sprite.addSprite({ name: "walk_right", startFrame: 8, framesCount: }); sprite.addSprite({ name: "walk_up", startFrame: 12, framesCount: }); sprite.setSprite("walk_left"); setInterval(function(){ sprite.update();
canvas.width = canvas.width; sprite.draw(context);
},1000/FPS);
III Nạp trước hình ảnh tài nguyên
Đối với game sử dụng nhiều hình ảnh (âm thanh, video,…), ta cần phả i nạp toàn ảnh cần thiết trước vào game Bài viết cung cấp giải pháp đơn giản cho việc nạp trước hình ảnh để sử dụng canvas game
Từ ví dụ trang HTML5 Canvas Image Loader Tutorial, ta bổ sung thêm event cho phép cập nhật tiến trình nạp ảnh (onProgressChanged) Dựa vào cách này, bạn thay đổi để giúp việc nạp quản lý loại tài nguyên khác thuận tiện:
function ImgLoader(sources,onProgressChanged,onCompleted) { this.images = {};
var loadedImages = 0; var totalImages = 0; // get num of sources
if(onProgressChanged || onCompleted) for(var src in sources) {
totalImages++; }
var self = this;
for(var src in sources) {
this.images[src] = new Image();
this.images[src].onload = function() { loadedImages++;
if(onProgressChanged) {
var percent =
Math.floor((loadedImages/totalImages)*100);
onProgressChanged(this,percent); }
if(onCompleted && loadedImages >= totalImages) onCompleted(self.images);
}
this.images[src].src = sources[src]; }
}
(76)window.onload = function(images) {
var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d");
var barWidth = 200;
var barLeft = (canvas.width-barWidth)/2; context.fillRect(barLeft,10,barWidth,18); context.fillStyle = "blue";
var sources = {
img1: "Spring/img (1).jpg", img2: "Spring/img (2).jpg", img3: "Spring/img (3).jpg", img4: "Spring/img (4).jpg", img5: "Spring/img (5).jpg", img6: "Spring/img (6).jpg", };
var left = 100;
var foo = new ImgLoader(sources, function(image,percent) {
context.drawImage(image, left, 55, 50,50); //context.clearRect(0,0,200,30);
context.fillStyle = "blue";
context.fillRect(barLeft,12,percent*barWidth/100,12); context.fillStyle = "white";
context.fillText("Loading: "+percent+"%",barLeft+10,22);
left+=60; },
function(images){ // completed }
); };
Minh họa:
IV. Phóng to/thu nhỏ game nút cuộn chuột
Để thay đổi kích thước nội dung canvas, cách tốt sử dụng phương thức context.scale(double x, double y) để thiết lập tỉ lệ co giãn vẽ context Phương thức giúp hình ảnh canvas giữ độ nét với ảnh vector (khơng việc bạn thay đổi kích thước canvas CSS chức zoom trình duyệt)
(77)Bạn áp dụng cách làm ví dụ cho ứng dụng tương tự, chủ yếu thay đổi kích thước đơn vị (CELL_SIZE), đối tượng thay đổi tọa độ
(78)1 Sự kiện Mouse Wheel javascript
Hoạt động nút cuộn chuột xác định giá trị delta lấy từ tham số event kiện mousewheel (hoặc DOMMouseScroll Firefox) Giá trị cho biế t chiều độ lớn vịng quay bánh xe Bạn đọc hướng dẫn cụ thể Mouse wheel programming in JavaScript
Trong ví dụ ta quan tâm đến chiều quay bánh xe: với giá trị âm, ta giảm kích thước nội dung bên canvas 1/2 với giá trị dương ta tăng lên lần
function canvas_mousewheel(e){ var delta = 0;
if (!e) /* For IE */ e = window.e;
if (e.wheelDelta) { /* IE/Opera */ delta = e.wheelDelta/120; } else if (e.detail) { /** Mozilla case */
delta = -e.detail/3; }
if (delta) {
_map.changeZoom(delta); _ball.setZoom(_map.zoom); draw();
}
if (e.preventDefault)
e.preventDefault(); }
2 Thay đổi kích thước đồ
Trong ví dụ sử dụng đồ, ta định nghĩa CELL_SIZE quy định kích thước theo pixel Như để thay đổi kích thước đồ khơng có phức tạp
Trong ví dụ ta tạo thuộc tính zoom=1 xác định tỉ lệ kích thước ban đầu Giá trị tối thiểu nhận 0.5 lớn 4:
Map.prototype = {
changeZoom : function(zoom){
if(zoom == || (zoom<0 && this.zoom<=0.5) || (zoom>0 && this.zoom>=4))
return;
this.zoom *= zoom < ? 0.5 : 2; this.reset();
},
reset : function() {
(79)this.width = MAP_WIDTH*this.cellSize ; this.height = MAP_HEIGHT*this.cellSize ; }
// }
Phương thức reset() thực cập nhật ba thuộc tính quan trọng đồ thay đổi tỉ lệ kích thước
3 Vẽ vùng đồ
Với liệu đồ lưu trữ mảng hai chiều Ta cần xác định vị trí, số lượng dịng cột cần hiển thị vẽ vùng lên canvas
// draw method
var col = Math.floor(this.offsetX/this.cellSize); var row = Math.floor(this.offsetY/this.cellSize); ctx.fillStyle = "black";
for(var i=col;i<col+Math.floor(this.viewWidth/this.cellSize)+1;i++) {
if(this.data[i])
for(var j=row;j<row+Math.floor(this.viewHeight/this.cellSize)+1;j++) {
if(this.data[i][j]==1)
ctx.fillRect(i*this.cellSize -this.offsetX,j*this.cellSize -this.offsetY,this.cellSize ,this.cellSize );
} }
Nếu sử dụng ảnh để vẽ, bạn cần chia tọa độ độ lớn vùng ảnh cho tỉ lệ zoom Với tỉ lệ zoom lớn, vùng ảnh vẽ nhỏ ngược lại:
ctx.drawImage(this.background,this.offsetX/this.zoom,this.offsetY/this.zoom,t his.viewWidth/this.zoom,this.viewHeight/this.zoom,0,0,this.viewWidth,this.vie wHeight);
4 Áp dụng cho nhân vật đồ
Mỗi nhân vật hay đối tượng thêm vào đồ cần thay đổi kíc h thước vị trí tương ứng với tỉ lệ zoom Ta tính tọa độ (left, top) đối tượng cách:
new_left = old_left/old_zoom*new_zoom new_top = old_top/old_zoom*new_zoom
(80)this.size = BALL_SIZE*zoom;
this.left = this.left/this.zoom*zoom; this.top = this.top/this.zoom*zoom; this.right = this.left + this.size; this.bottom = this.top + this.size; this.zoom = zoom;
} };
V Thay đổi kích thước Canvas theo trình duyệt
Một yêu cầu thường thấy game thay đổi kích thước cửa cửa sổ game theo trình duyệt Việc thay đổi cần đảm bảo tốc độ game không bị ảnh hưởng hình ảnh khơng bị biến dạng (giữ ngun tỉ lệ chiều cao vào rộng)
Một lợi trình duyệt đại hỗ trợ tính nănghardware accelerated cho canvas Với pixel, trình duyệt hiển thị canvas nhiều kích thước khác Ví dụ bạn gán canvas.width = 200 canvas.height = 100, canvas có tất 20000 pixel Và bạn tăng gấp đơi kích thước canvas lên lượng pixel mà bạn thao tác với canvas 20000 Hay nói cách khác, việc xử lý vẽ canvas với kích thước khác xử lý đồ họa (GPU) thực thi cách tự động
Ta phân biệt kích thước thực kích thước hiển thị canvas Kích thước thực canvas gán thơng qua hai thuộc tính width height:
canvas.width = 200; canvas.height = 100;
Kích thước hiển thịcủa canvas xác định thuộc tính style.width style.height (bằng javascript CSS):
canvas.style.width = “400px”; canvas.style.height = “200px”;
Độ nét canvas phụ thuộc vào kích thước thực nó, giữ tỉ lệ vừa phải hai loại kích thước
1. Điều chỉnh canvas thao kích thước trình duyệt
(81)function fitSize(canvas){
(82)var width = window.innerWidth-5; var height = window.innerHeight-5; if(width/height>ratio)
width = height*ratio; else
height = width/ratio; canvas.style.width = width; canvas.style.height = height;
canvas.style.top = (window.innerHeight-height)/2; canvas.style.left = (window.innerWidth-width)/2; }
VI. Sử dụng Full Screen API
1 Giới thiệu
HTML5 cho phép bạn đưa thành phần/thẻ trang vào trạng thái hiển thị fullscreen (khác với chế độ fullscreen trình duyệt (F11)) Ngồi ra, bạn sử dụng CSS để thay đổi cách hiển thị thành phần trạng thái fullscreen
API gói gọn mục sau:
element.requestFullScreen(): Phương thức kích hoạt trạng thái fullscreen thành phần trang web
document.cancelFullScreen(): Phương thức hủy bỏ trạng thái fullscreen
document.fullscreenchange: Sự kiện kích hoạt trạng thái fullscreen thay đổi document.fullScreen: Thuộc tính boolean dùng để kiểm tra trạng thái fullscreen
:full-screen: Một pseudo-class CSS dùng để định nghĩa cách hiển thị thành phần
Hiện API hỗ trợ Mozilla (Firefox) WebKit (Chrome/Safari) Bạn cần sử dụng tiền tố (vendor) webkit moz cho phương thức hay thuộc tính, style bên để áp dụng cho loại trình duyệt
Nếu cần thiết, bạn thêm trước hai vendor ms o để chúng hoạt động sau IE, Opera tích hợp API này:
Phương thức ente rFullScreen():
function enterFullScreen(id){
var element = document.getElementById(id);
element.requestFullscreen ? element.requestFullscreen() :
element.mozRequestFullScreen ? element.mozRequestFullScreen() :
element.webkitRequestFullScreen ? element.webkitRequestFullScreen() :
(83)}
Phương thức exitFullScreen():
function exitFullScreen(){
document.exitFullscreen ? document.exitFullscreen() :
document.mozCancelFullScreen ? document.mozCancelFullScreen() :
document.webkitCancelFullScreen ? document.webkitCancelFullScreen() :
alert("Fullscreen API is not supported in this browser"); }
Sự kiện fullscreenchange thuộc tính fullScreen:
document.addEventListener("fullscreenchange", function (e) { console.log(document.fullscreen);
});
document.addEventListener("mozfullscreenchange", function (e) { console.log(document.mozFullScreen);
});
document.addEventListener("webkitfullscreenchange", function (e) { console.log(document.webkitIsFullScreen);
});
CSS:
div:full-screen {
background-color: gray; }
div:-moz-full-screen { background-color: gray; }
(84)2 Ví dụ
<HTML> <head> <style> #mydiv {
padding-top: 200px; display: none; height: 400px; background: black; }
#mydiv:full-screen { display: block; } :full-screen h1 { color: wheat; }
#mydiv:-moz-full-screen { display: block; } :-moz-full-screen h1{ color: wheat; }
#mydiv:-webkit-full-screen { display: block; } :-webkit-full-screen h1{ color: wheat; }
</style>
<script type="text/javascript"> function enterFullScreen(id){
var element = document.getElementById(id);
element.requestFullscreen ? element.requestFullscreen() :
element.mozRequestFullScreen ? element.mozRequestFullScreen() :
element.webkitRequestFullScreen ? element.webkitRequestFullScreen() :
alert("Fullscreen API is not supported in this browser"); }
(85)document.exitFullscreen ? document.exitFullscreen() :
document.mozCancelFullScreen ? document.mozCancelFullScreen() :
document.webkitCancelFullScreen ? document.webkitCancelFullScreen() :
alert("Fullscreen API is not supported in this browser"); }
document.addEventListener("fullscreenchange", function (e) { console.log(document.fullscreen);
});
document.addEventListener("mozfullscreenchange", function (e) { console.log(document.mozFullScreen);
});
document.addEventListener("webkitfullscreenchange", function (e) { console.log(document.webkitIsFullScreen);
});
</script> </head> <body> <center>
<div id="mydiv">
<h1>You are in fullscreen mode.</h1>
<input type="button" value="Exit full screen" onclick="exitFullScreen('mydiv')" />
</div>
<input type="button" value="Enter full screen" onclick="enterFullScreen('mydiv')"/>
(86)VII Tạo menu chuyển đổi hình Game
Mỗi game làm cần có hình chào mừng menu để giúp người chơi chuyển đổi hình khác như: giới thiệu, chơi game, hướng dẫn, tùy chọn Từ yêu cầu bạn, ta hướng dẫn cách thực phần cách đơn giản nhanh chóng
Xem Demo
1 Lớp MenuItem
Mỗi đối tượng MenuItem tương ứng với mục menu hình Một đối tượng cần giá trị xác định tọa độ kích thước Tuy nhiên để dễ thay đổi phát triển Ta tạo thêm số thuộc tính cung cấp kiện onclick cho
Dựa vào thuộc tính isMouseOver, ta thay đổi màu sắc hiển thị MenuItem chuột hover Phần đơn giản, cần coi cấu trúc liệu để lưu giá trị hình chữ nhật với phương thức vẽ để hiển thị
function MenuItem(data){
this.left = data.left || 0; this.top = data.top || 0;
this.width = data.width || 100; this.height = data.height || 30; this.text = data.text || "Menu Item"; this.onclick = data.onclick;
this.right = this.left+this.width; this.bottom = this.top+this.height; this.centerX = this.left+this.width/2; this.centerY = this.top+this.height/2; this.isMouseOver = false;
this.update = function(){ };
this.draw = function(context){
context.font = "16px Arial"; context.textAlign = "center"; if(this.isMouseOver)
context.fillStyle = "rgba(255,255,255,0.7)"; else
context.fillStyle = "rgba(255,255,255,0.2)";
context.fillRect(this.left,this.top,this.width,this.height); context.fillStyle = "white";
context.fillText(this.text,this.centerX,this.centerY);
(87)this.contain = function(x,y){
return !(x<this.left || x>this.right || y<this.top || y>this.bottom);
}; }
function MenuItem(data){
this.left = data.left || 0; this.top = data.top || 0;
this.width = data.width || 100; this.height = data.height || 30; this.text = data.text || "Menu Item"; this.onclick = data.onclick;
this.right = this.left+this.width; this.bottom = this.top+this.height; this.centerX = this.left+this.width/2; this.centerY = this.top+this.height/2; this.isMouseOver = false;
this.update = function(){ };
this.draw = function(context){
context.font = "16px Arial"; context.textAlign = "center"; if(this.isMouseOver)
context.fillStyle = "rgba(255,255,255,0.7)"; else
context.fillStyle = "rgba(255,255,255,0.2)";
context.fillRect(this.left,this.top,this.width,this.height); context.fillStyle = "white";
context.fillText(this.text,this.centerX,this.centerY);
context.strokeRect(this.left,this.top,this.width,this.height); };
this.contain = function(x,y){
return !(x<this.left || x>this.right || y<this.top || y>this.bottom);
}; }
2 Lớp Screen
Lớp phần tạo nên game Một game tạo từ nhiều Screen người chơi chuyển qua lại Screen vài nút bấm (trong ví dụ MenuItem)
(88)game Ví dụ start() stop() Vì ví dụ tập trung vào việc thiết kế menu chuyển đổi hình, đối tượng Screen ta tạo chứa MenuItem
Chú ý: Vì lớp Screen đăng kí event onmousemove, onclick cho canvas, nên start() Screen, bạn cần phải đăng kí event lại event Nếu khơng bị overwrite event từ Screen khác
var CELL_SIZE = 10; var FPS = 10 ; var WIDTH = 400; var HEIGHT = 400;
function Screen(canvas){ var timer;
var width = canvas.width; var height = canvas.height;
var context = canvas.getContext("2d"); this.items = [];
// this method/event is actived in the end of draw() method this.afterDraw = null;
this.update = function(){
for(var i=0;i<this.items.length;i++){ this.items[i].update();
} };
this.draw = function(){
context.fillStyle = "black";
context.fillRect(0,0,width,height); for(var i=0;i<this.items.length;i++){ this.items[i].draw(context); } if(this.afterDraw) this.afterDraw(context); };
this.start = function(){ this.stop(); var self = this; // register events
canvas.onclick = function(e){
// raise the onclick event of each MenuItem when it is clicked
var x = e.pageX - this.offsetLeft; var y = e.pageY - this.offsetTop; for(var i=0;i<self.items.length;i++){
if(self.items[i].onclick && self.items[i].contain(x,y))
self.items[i].onclick(x,y); }
};
canvas.onmousemove = function(e){
(89)var y = e.pageY - this.offsetTop; canvas.style.cursor = 'default'; for(var i=0;i<self.items.length;i++){
self.items[i].isMouseOver = self.items[i].contain(x,y);
// change the cursor type to hand if(self.items[i].isMouseOver)
canvas.style.cursor = 'pointer'; }
};
timer = setInterval(function(){ self.update();
self.draw(); },1000/FPS);
};
this.stop = function(){ if(timer)
clearInterval(timer); timer = null;
};
this.addItem = function(item){ this.items.push(item); };
}
3 Kiểm tra kết quả
(90)F AI game
I Giới thiệu
AI hay trí tuệ nhân tạo sử dụng nhiều game, từ đơn giản đến phức tạp Tùy theo trường hợp mà AI tính tốn vị trí để di chuyển, tìm đường đi, phân tích trạng thái để đưa định,…
Một game thuộc loại AI đơn giản trò chơi Pong phát hành năm 1970 Dạng game bóng bàn cho phép người chơi đấu với máy cách điều khiển paddle dọc để đón đánh trả bóng Và bạn hình dung AI máy hoạt động nào: cần thay đổi vị trí paddle lên xuống theo trái bóng
Sau này, thể loại game đời địi hỏi trình độ AI phải nâng lên tầm Thường thấy thể loại game với thuật tốn tìm kiếm cấu trúc liệu dạng để tìm đường hay đưa định (decision tree) Trong thuật toán duyệt cây, tùy theo trường hợp độ lớn liệu, lập trình viên thay đổi chọn lựa thuật toán Hill climbing, Breadth First Search, Depth First Search, Best First Search, Iterative Depth First Search hay chí kết hợp một, hai loại thuật toán với
Với game Rắn Săn Mồi, ý tưởng ta áp dụng AI game để người chơi đấu với máy Như toán đặt phải để rắn mà máy điều khiển tìm đường ngắn đến thức ăn mà khơng lao đầu vào tường cắn nhầm thân
II Phân tích để lựa chọn thuật tốn
(91)Ở kích thước đồ, với loại game giới hạn khoảng 50×50 đủ
2 Cần tìm đường ngắn
Với hai yếu tố này, bạn dễ dàng nhận lựa chọn giải thuật tìm kiếm theo chiều sâu (Breadth First Search) hợp lý Ta loại trừ để chọn thuật giải đúng: - Hill climbing hay Best First Search: không khả thi cần hàm lượng giá hay heuristic Với loại game mà vị trí hay trạng thái nhân vật không bị ảnh hưởng nhiều yếu tố mơi trường điều khơng cần thiết, chí đưa kết sai
- Depth First Search hay Iterative Depth First Search: tìm kiếm theo chiều sâu theo kiểu may rủi khiến nhân vật “đi lạc” đà làm chậm thời gian tìm thấy đường xác Hơn nữa, kết tìm khơng phải đường ngắn
III Thuật toán Breadth First Search
(Nếu bạn quen thuộc với thuật tốn Breadth First Search, bỏ qua phần này.) Cơ chế làm việc thuật toán tương tự vết dầu loang, tìm kiếm điểm gần trước Bạn thấy vài game sử dụng đồ hay liên quan đến AI sử dụng thuật tốn Một ví dụ bạn áp dụng thuật toán n-puzzle mà ta cài đặt thuật toán A* để giải
Trong giải thuật ta cần định nghĩa thành phần sau:
Open Danh sách chứa vị trí chờ xét Close Danh sách chứa vị trí xét start Vị trí bắt đầu
goal Vị trí kết thúc
n vị trí xét
(92)Giải thuật mô tả mã sau:
Begin
Open := {start}; Close := ø;
While (Open <> ø) begin
n:= Dequeue(Open);
if (n=goal) then Return True; Open := Open + G(n);
Close := Close + {n}; end;
Return False; End;
Ta thấy G(n) dựa vào tập Close để kiểm tra vị trí có cần kiểm tra hay khơng Nếu bạn cài đặt hai tập collection tốc độ thực thi tương đối chậm phải thực tìm kiếm phần tử danh sách
Do ví dụ ta có dạng đồ nên thay dùng collection, ta sử dụng mảng hai chiều để truy xuất trực tiếp đến phần tử dựa vào vị trí Khi ta kiểm tra thuộc tính phần tử xem duyệt chưa
IV. Các quy tắc game
Do có cách chơi khác với game loại nên ta cần đặt thêm vài quy tắc để xây dựng game:
(93)- Hai rắn di chuyển xun qua (nếu khơng chúng cắn bị cắn chết)
- Khi độ dài rắn máy đạt đến mốc đó, người chơi thua - Để giảm mức độ khó, độ dài rắn mà máy cần đạt cao người chơi Tạm ổn, phần tới ta thực việc cài đặt game HTML5-Canvas
V Xây dựng lớp Queue dựa mảng
Việc sử dụng thuật toán Breadth First Search (BFS) cần phải sử dụng cấu trúc liệu kiểu hàng đợi (Queue) để làm tập Open
Định nghĩa:
“Hàng đợi (tiếng Anh: queue) cấu trúc liệu dùng để chứa đối tượng làm việc theo chế FIFO (viết tắt từ tiếng Anh: First In First Out), nghĩa “vào trước trước Trong hàng đợi, đối tượng thêm vào hàng đợi lúc nào, có đối tượng thêm vào phép lấy khỏi hàng đợi Thao tác thêm vào lấy đối tượng khỏi hàng đợi gọi „enqueue‟ „dequeue‟ Việc thêm đối tượng diễn cuối hàng đợi phần tử lấy từ đầu hàng đợi.” (Wikipedia);
Trong javascript, ta dễ dàng tạo lớp Queue nhờ phương thức sẵn có mảng:
function Queue(){
var data = [];
this.clear = function(){ data.length = 0; }
this.getLength = function(){ return data.length; }
this.isEmpty = function(){
return data.length == 0; }
this.enqueue = function(item){ data.push(item);
}
this.dequeue = function(){
if (data.length == 0) return undefined; return data.shift();
}
this.peek = function(){
return (data.length > ? data[0] : undefined); }
(94)VI. Cài đặt thuật toán Breadth First Search
Tốc độ tìm đường game quan trọng lên level cao, việc di chuyển rắn lên tới 60 giây (tương ứng FPS = 60) hay chí cao Nếu tốc độ chậm khiến game bị “đứng” thuật toán sử dụng tệ rắn lao đầu vào tường xe phanh
Nếu test bạn thấy tốc độ chưa ưng ý, đặc biệt máy yếu, thử làm chậm tốc độ game lại tăng thêm độ phức tạp đồ Một cách khác giảm kích thước đồ để giảm khơng gian tìm kiếm Tuy nhiên bạn khơng cần q lo lắng tốc độ tìm đường, theo thử nghiệm ta với FPS = 1000 độ dài rắn lên tới 100, game chạy ổn rắn không để “viên kẹo”
Để cài đặt thuật toán này, trước tiên ta tạo lớp Node dùng để lưu giữ thông tin phần tử mảng hai chiều Ta cần hai thuộc tính x,y để lưu lại vị trí dịng cột Node mảng Như ta xác định vị trí đối tượng Node mảng mà không cần lặp qua mảng để tìm kiếm (do Node lưu Queue)
function Node(x,y,value){ this.x = x;
this.y = y;
this.value = value; this.visited = false; }
Mặc dù dễ hiểu cách khiến việc tìm chưa đạt tốc độ mà ta mong muốn phải so sánh thuộc tính value visited để xác định Node “ghé thăm” chưa Vì vậy, ta bỏ thuộc tính visited tận dụng thuộc tính value Ta có lớp Node mới:
var BLANK = 0; var WALL = 1; var SNAKE = 2; var VISITED = 3;
function Node(x,y,value){ this.x = x;
this.y = y;
this.value = value; }
Vịng lặp thuật toán:
//
// add the start node to queue open.enqueue(start);
// the main loop
while(!open.isEmpty()) {
node = open.dequeue(); if(node)
{
(95){ return getSolution(node); } genMove(node); } else break; } //
Phương thức sinh nước vị trí đầu rắn:
// generate next states by adding neighbour nodes function genMove(node)
{
if (node.x < cols - 1)
addToOpen(node.x + 1, node.y, node); if (node.y < rows - 1)
addToOpen(node.x, node.y + 1, node); if (node.x > 0)
addToOpen(node.x - 1, node.y, node); if (node.y > 0)
addToOpen(node.x, node.y - 1, node); }
Phương thức thêm nước vào tập Open, ta cần thêm node rỗng (BLANK) Khi thêm vào, ta đặt lại trạng thái node VISITED để không thêm vào lần thứ 2:
function addToOpen(x,y, previous) {
var node = nodes[x][y]; if (node.value==BLANK) {
// mark this node as visited to avoid adding it multiple times node.value = VISITED;
// store the previous node
// so that we can backtrack to find the optimum path // (by using the getSolution() method)
node.previous = previous; open.enqueue(node);
} }
Phương thức dùng để lấy đường sau tìm vị trí node đích Ta cần backtracking node dựa vào thuộc tính previous node:
function getSolution(p) {
var nodes = []; nodes.push(p); while (p.previous) {
nodes.push(p.previous); p = p.previous;
(96)return nodes; }
Như vậy, có đường đến đích, ta cần cho rắn di chuyển ô dựa theo đường hết độ dài
VII Di chuyển đối tượng theo đường đi
Trong lớp Snake, ta tạo biến autoMoving để xác định quyền điều khiển đối tượng thuộc người chơi hay tự động (máy tính) Mỗi viên thức ăn tạo ra, ta cập nhật lại đường cho rắn Chỉ cần thêm dòng lệnh vào phương thức createFood()
function createFood() {
var x = Math.floor(Math.random()*_cols); var y;
do {
y = Math.floor(Math.random()*_rows);
} while(_walls[x][y] || _comSnake.contain(x, y) || _playerSnake.contain(x, y));
_food = {x: x, y: y};
// find new path for the com player
_comSnake.setPath(_bfs.findPath(_comSnake.data,_comSnake.getHead(),_fo od));
}
Trong lớp Snake, ta cần tạo phương thức giúp rắn di chuyển tự động Bởi việc di chuyển rắn dựa vào bốn hướng (do cho phép người chơi điều khiển), ta xác định hướng di chuyển dựa vào vị trí cũ đầu rắn:
// Snake
this.move = function(){ if(this.stepIndex>0) {
this.stepIndex ;
var newPos = this.path[this.stepIndex]; if(newPos.x<this.data[0].x)
this.direction = LEFT; else if(newPos.x>this.data[0].x)
this.direction = RIGHT; else if(newPos.y<this.data[0].y)
this.direction = UP; else if(newPos.y>this.data[0].y)
this.direction = DOWN; }
};
VIII Vịng lặp game
(97)function update() { if(!_running)
return;
_playerSnake.handleKey(_pressedKey); // player has priority to eat
var ret =_playerSnake.update(_walls,_food); if(ret==1) // player eated the food
{
_scores += _level*2; createFood();
}
else if(ret==2) // player collided with something {
if(_scores>=0) {
_scores -= _level*2; if(_scores<0)
_scores = 0; } endGame(); return; }else{ if(!_comSnake.path) _comSnake.setPath(_bfs.findPath(_comSnake.data,_comSnake.getHead(),_fo od),_food);
ret = _comSnake.update(_walls,_food); if(ret==1) // com player eated the food
createFood(); }
draw();
// Player's snake reached the maximum length // so the game will start the next level
if(_playerSnake.data.length==MAX_PLAYER_LENGTH) {
// go to next level _level++;
_scores += _level*100; _running = false; _context.save();
_context.fillStyle = "rgba(0,0,0,0.2)"; _context.fillRect(0,0,WIDTH,HEIGHT); _context.restore();
_context.fillStyle = "red"; _context.textAlign = "center";
(98)G Một tảng game 2D side-scrolling
Từ phần Map Scrolling, ta thấy sườn game phổ biến cho loại game sử dụng đồ (như Pac-Man, Battle City hay Mario,…) Trong ta thay đổi vài đặc điểm để tạo demo dạng game phiêu lưu hình ngang
I Cơ bản 1 Tạo đồ
Bản đồ dùng loại game xem lưới với ô chứa giá trị đại diện cho loại đối tượng (thường vật cản tường, đá, cây, …) Để đơn giản, ta tạo đồ gồm loại vật cản phân bố ngẫu nhiên:
map.js:
for(var i=1;i<MAP_WIDTH-1;i+=2) {
this.data[i] = []; this.data[i+1] = [];
for(var j=1;j<MAP_HEIGHT;j++) {
this.data[i][j] = Math.floor(Math.random()*6); this.data[i+1][j] = this.data[i][j];
// create image buffer if(this.data[i][j]==BRICK) {
context.fillRect(i*CELL_SIZE,j*CELL_SIZE,CELL_SIZE*2,CELL_SIZE); context.fill();
} }
}
2 Kiểm tra va chạm
Ta kiểm tra nhanh va chạm nhân vật với vật cản cách xác định tọa độ ô tương ứng đồ từ vị trí nhân vật
map.js:
this.contain = function(x,y){
var col = Math.floor(x/CELL_SIZE); var row = Math.floor(y/CELL_SIZE); if(!this.data[col])
return false;
(99)var b = {
left: col*CELL_SIZE, top: row*CELL_SIZE };
b.right = b.left+CELL_SIZE; b.bottom = b.top+CELL_SIZE; return b;
}
return false; }
Đối với nhân vật, ta sử dụng điểm bao quanh nhân vật để kiểm tra (các điểm màu đỏ hình sau) Muốn việc kiểm tra xác hơn, bạn cần tăng thêm số điểm nhiên thời gian kiểm tra lâu Dựa vào điểm ta xác định va chạm ngừng việc di chuyển nhân vật theo trường hợp điểm va chạm là:
- R1 R2: Va chạm bên phải Nếu speedX>0, ta gán speedX = - L1 L2: Va chạm bên trái Nếu speedX – H1 H2: Nhân vật nhảy lên đụng đầu vào vật cản Ta dặt speedY = falling = true để nhân vật dừng lại bắt đầu rơi - H1 H2: Nhân vật nhảy rớt xuống đất vật cản Ta dặt speedY = falling = false
Player.prototype.update = function(){ if(this.isJumping)
this.speedY += GRAVITY; var vtop = this.top+this.speedY; var vbottom = vtop+this.height; var vleft = this.left+this.speedX; var vright = vleft+this.width; if(this.isJumping)
(100)var b;
this.isJumping = true;
if(vbottom >= this.map.height) {
this.top = this.map.height-this.height; this.speedY = 0;
this.isJumping = false; }
else if(b = this.map.contain(this.left+2,vbottom) || this.map.contain(this.right-2,vbottom))
{
this.top = b.top-this.height; this.speedY = 0;
this.isJumping = false; }
else if(b = this.map.contain(this.left+2,vtop) || this.map.contain(this.right-2,vtop))
{
this.top = b.bottom; this.speedY = 0; }
vtop = this.top+this.speedY; vbottom = vtop+this.height;
if(this.left<=0 || (b = this.map.contain(vleft,vtop+6) || this.map.contain(vleft,vbottom-4)))
{
if(b)
this.left = b.right; if(this.speedX<0)
this.speedX = 0;
}else if(this.right>=this.map.width || (b =
this.map.contain(vright,vtop+6) || this.map.contain(vright,vbottom-4))) {
if(b)
this.left = b.left-this.width; if(this.speedX>0)
this.speedX = 0; }
this.top = vtop;
this.bottom = vbottom; this.left += this.speedX;
(101)II Thêm chức nhân vật 1 Lớp Character
Đây lớp tảng cho đối tượng chuyển động (có thể thay đổi vị trí) game Các đối tượng người chơi, quái vật hay NPC (non-player character) Ta định nghĩa nhiều thuộc tính lớp để xác định vị trí, tốc độ, kích thước nhân vật Bên cạnh đó, ta thêm số thuộc tính boolean hành vi (hay khả năng) nhân vật:
- canDestroyObstacles: khả phá hủy chướng ngại vật (bằng cách dùng đầu) nhân vật Khi nhân vật nhảy lên, đầu chạm trúng chướng ngại vật phá hủy (gạch), ta kiểm tra thuộc tính để xác định xem chướng ngại vật có bị phá hủy hay khơng - isAutoMoving: Dùng cho nhân vật không bị người chơi (player) điều khiển (quái vật) - canJump: Nhân vật nhảy hay không
function Character(map,options){ if(!options)
options = {}; this.map = map;
this.left = options.left || 0; this.top = options.top || 300; this.height = options.height || 15; this.width = options.width || 15;
this.jumpPower = options.jumpPower || 5; this.speed = options.speed || 2;
(102)this.speedY = 0;
this.isJumping = true; this.isDead = false;
this.bottom = this.top+this.height; this.right = this.left+this.width; // behaviors
this.canDestroyObstacles = options.canDestroyObstacles; this.isAutoMoving = options.isAutoMoving;
this.canJump = options.canJump; }
Nhìn chung phương thức update() tương tự part Ngoại trừ việc nhân vật nhảy lên phá gạch, đối thủ (trong game Monster) bên bị tiêu diệt Vì ta bổ sung đoạn mã vào phần kiểm tra va chạm phía trên:
// top collision
if(this.canDestroyObstacles) {
// destroy all monsters that are standing on the brick for(m in this.map.monsters)
{
var mon = this.map.monsters[m]; if(!mon)
continue;
var cx = mon.left + mon.width/2;
if(mon.bottom==b.top && cx>=b.left && cx<=b.right ) mon.die();
}
this.top = b.bottom; this.speedY = 0; }
Thay đổi hướng di chuyển nhân vật thuộc tính isAutoMoving có giá trị true nhân vật bị va chạm phía trái phải:
// update() method
if(this.left<0 || (b = this.map.colllide(vleft,vtop+6,false) ||
this.map.colllide(vleft,vbottom-4,false,this.canEat))) // left {
if(b && b!=true)
this.left = b.right; if(this.speedX<0)
this.speedX = this.isAutoMoving? this.speed: 0;
}else if(this.right>=this.map.width || (b = this.map.colllide(vright,vtop+6,false) ||
this.map.colllide(vright,vbottom-4,false))) // right {
if(b && b!=true)
this.left = b.left-this.width; if(this.speedX>0)
(103)2 Lớp Monster
Monster (hay Enemy) đối tượng có khả hoạt động tích hợp AI nhằm tiêu diệt cản trở người chơi Trong game loại đối tượng đa dạng thường nguyên nhân tạo lơi game Vì phần bắt đầu nên ta tạo loại Monster có khả tiêu diệt người chơi thơng qua va chạm
Bởi thừa kế từ lớp Character bên trên, lớp cần cơng việc thực phương thức draw() để vẽ nó:
function Monster(map,left,top){
// call the super-constructor Character.call(this,map,{
left: left, top: top, width: 20, height: 20, speed: 1,
isAutoMoving: true });
}
Monster.prototype = new Character();
Monster.prototype.draw = function(context){ context.save();
context.beginPath();
var left = this.left-this.map.offsetX; var top = this.top-this.map.offsetY; var right = left+this.width;
var bottom = top+this.height; var hw = this.width/2;
var cx = left+hw;
context.fillStyle = "violet";
context.arc(cx,top+hw,hw-2,0,Math.PI*2,true); //context.rect(left,top,this.width,this.height); context.fill();
(104)context.restore(); }
3 Lớp Player
Cũng tương tự Monster, nhiên công việc phức tạp ta cần kiểm tra nhiều thứ phải cung cấp phím bấm điều khiển Phương thức update mặc định trả Trường hợp player hồn thành vịng chơi, trả 1; player chết, trả 2:
Player.prototype.update = function(){ if(this.right>=this.map.width){
alert("Game Over!"); return 1;
}
if(this.isFalling) // die {
this.speedY += GRAVITY; this.top += this.speedY;
return (this.top>this.map.height)? : ; }
Character.prototype.update.call(this); this.collide(this.map.monsters);
}
Kiểm tra va chạm với monster, ta xem player monster hình chữ nhật cần kiểm tra xem hai hình có cắt không:
Player.prototype.collide = function(monsters){ for(m in monsters)
{
var mon = monsters[m]; if(!mon)
continue;
(105)4 Lớp Map
Nếu coi lập trình viên lập trình viên thượng đế, đồ “thế giới” thu nhỏ nơi mà thượng đế cho vận hành quy luật để tạo “cuộc sống” Mặc dù đồ đơn giản nhiều game, dạng game phiêu lưu này, đóng vai trị quan trọng (có thể so với Monster) Một “thế giới” nhỏ bé khiến người chơi nhanh kết thúc vòng chơi khơng thể khơi lên tị mị muốn khám phá họ
Khi vật thể (chướng ngại vật), ta xóa vùng tương ứng buffer (ảnh đồ) gán giá trị lại cho ô liệu mặc định (0):
function clearCell(left,top,col,row) {
data[col+row*COLS] = 0; context.save();
context.globalCompositeOperation = "destination-out"; context.fillStyle = "rgba(0,0,0,1)";
context.fillRect(left,top,CELL_SIZE,CELL_SIZE); context.restore();
}
Để kiểm tra va chạm với chướng ngại vật, ta tạo phương thức collide() với giá trị trả đối tượng lưu giữ giá trị vị trí, kích thước chướng ngại vật đó:
this.colllide = function(x,y,canDestroy){ var b = this.contain(x,y);
if(b) {
if(canDestroy && b.type==BRICK) { clearCell(b.left,b.top,b.col,b.row); } return b; } return false; };
this.contain = function(x,y){
var col = Math.floor(x/CELL_SIZE); var row = Math.floor(y/CELL_SIZE); var val = data[col+row*COLS]; if(val>0)
{
var b = {
left: col*CELL_SIZE, top: row*CELL_SIZE, col: col, row: row, type: val, };
b.right = b.left+CELL_SIZE; b.bottom = b.top+CELL_SIZE; return b;
(106)return false; };
Trong game này, lớp Map nơi chứa Monster Để làm Monster liên tục số lượng giá trị Ta viết phương thức update() sau:
this.update = function(){
// generate random monsters
if(this.monsters.length<MONSTER_IN_VIEW) this.monsters.push(new
Monster(this,this.offsetX+Math.random()*this.viewWidth+50,1)); i = 0;
var length = this.monsters.length; while(i<length){
if(this.monsters[i].isDead || this.monsters[i].left<this.offsetX)
{
this.monsters.splice(i,1); // remove this monster from array
length ; }else
{
this.monsters[i].update(); i++;
} }
(107)H Một số ứng dụng minh họa
Trong trình thực báo cáo này, thực vài dự án nhỏ để minh họa kiến thức trình bày Các dự án hồn tồn phát triển trở thành game online để cung cấp cho người chơi thực tế
Các dự án thực HTML5 không sử dụng thêm thư viện
I Game đua xe
1 Các thông số xe
Trong game này, ta sử dụng bốn phím mũi tên để điều khiển xe Hai phím UP, DOWN để di chuyển tiến lùi LEFT, RIGHT dùng để quay hướng xe Một xe đua cần thuộc tính sau:
- max/min speed: tốc độ tối đa/tối thiểu xe Nên để tốc độ tối đa có giá trị tuyệt đối cao tốc độ tối thiểu
- acceleration: Khả tăng tốc xe Giá trị lớn xe nhanh đạt vận tốc tối đa - rotationAngle: Khả điều chỉnh góc quay xe
- friction: độ ma sát xe loại địa hình Giá trị giúp xe giảm tốc độ người chơi khơng giữ phím di chuyển
2 Di chuyển quay xe
Với mục đích kiểm tra va chạm, ta sử dụng mảng điểm bao quanh xe hay xảy tiếp xúc Ở đây, điểm mà ta chọn đỉnh từ vùng bao hình chữ nhật xe (có thể coi bánh xe) Khi xe xoay góc alpha di chuyển, ta phải tính lại tọa độ điểm tương ứng Công thức để tính tọa độ điểm sau xoay góc alpha là:
(108)Ví dụ góc độ, với gốc tọa độ nằm tâm hình chữ nhật, điểm A có tọa độ {x: width/2,y: -height/2} Vậy với góc xoay alpha, tọa độ A là:
x: cos(alpha)*(-width/2) – sin(alpha)*(- height/2) y: sin(alpha)*(-width/2) + cos(alpha)*(- height/2)
3 Kiểm tra va chạm (tiếp xúc) với địa hình
Với đồ hình ảnh, ta cần kiểm tra cách dựa vào pixel Tuy nhiên cách lặp mà dựa vào kiểm tra vài vị trí xác định Các vị trí ta điểm thuộc góc xe mà ta xác định phần trước
- Đầu tiên ta cần lấy đối tượng ImageData ảnh làm đồ
- Với điểm dùng để kiểm tra va chạm xe, ta tính vị trí alpha chúng ImageData ((x+y*width)*4+3) so sánh giá trị alpha với (tương ứng với mặt đường) Thay kiểm tra giá trị alpha, ta kiểm tra màu sắc pixel cho loại địa hình khác
- Tăng, giảm ma sát ứng với loại địa hình
(109)4 Hạn chế xe di chuyển xoay bị va chạm
Sau biết điểm va chạm với đá, ta xác định xem xe di chuyển xoay hay khơng Xem hình vẽ sau, ta xét trường hợp điểm va chạm xe là:
- 3: không cho phép xe lùi - 2: không cho phép xe tiến - 1: không cho phép xoay trái - 3: không cho phép xoay phải
5 Tạo checkpoint
Để biết xe có đường hướng, ta cần tạo điểm kiểm tra đường gọi checkpoint Nếu người chơi muốn “đi tắt” đến checkpoint, đảm bảo họ đến chậm so với đường cách tạo vật cản tăng ma sát
Với đồ game này, ta tạo checkpoint Phương thức reachNextCheckPoint() kiểm tra xem xe có chạm checkpoint hay không Khi đến checkpoint cuối cùng, ta đưa biến currentCheckPoint tăng lap lên
6 Kết quả
(110)II Game bắn đại bác 1 Bản đồ địa hình
Đối với dạng game này, địa hình đồ ảnh hưởng lớn đến người chơi Ví dụ người chơi rơi xuống hố sâu khơng thể bắn hay chí “thiệt mạng” Tuy nhiên phần ta chưa cần quan tâm đến vấn đề Phần mà ta hướng dẫn để người chơi tương tác chịu tác động địa di chuyển, bắn phá
Về vấn đề kiểm tra va chạm với địa hình, khơng có phương pháp khác việc kiểm tra dựa pixel (do địa hình có hình dạng phức tạp bất kì) Vì ta tạo ImageData từ ảnh đồ, sau thêm phương thức kiểm tra điểm có nằm vùng ImageDa ta có độ alpha hay không
this.contain = function(x,y){ if(!imageData)
return false;
var index = Math.floor((x+y*width)*4+3); return imageData.data[index]!=0;
(111)2 Phá hủy phần địa hình
Sử dụng phương thức contain bên trên, ta kiểm tra va chạm đạn bắn người chơi rơi xuống đất Với trường hợp đạn bắn, ta phải phá hủy vùng địa hình nơi đạn bay vào Rất may API Canvas cung cấp phương pháp dùng để vẽ vùng có độ alpha 0, tương tự với việc xóa bỏ hồn tồn vùng
Ta thực việc cách gán hai thuộc tính context canvas
globalCompositeOperation thành “destination-out” fillStyle thành “rgba(0,0,0,1)” Và vị trí bị đạn bắn, ta vẽ hình trịn để “khoan” vùng địa hình Bạn nên lưu phục hồi lại context sử dụng thiết lập
3 Trọng lực Gió
Trong dạng game này, đạn bắn chịu tác động lúc loại lực là: lực bắn, trọng lực gió Mỗi lực minh họa vector (Lực bắn giới thiệu phần trước) Tại thời điểm đạn bay không gian, vị trí tính cách dùng vị trí cộng với vector tổng lực
Lưu ý: Bạn thấy tốc độ đạn bay nhanh khiến cho việc kiểm tra va chạm khơng xác (do game đạn “biến” từ nơi đến nơi khác) Vì cần giảm tốc độ đạn lại tăng FPS lên giá trị thích hợp Để tạo gió, ta sử dụng vector với hai giá trị x,y thay đổi ngẫu nhiên sau khoảng thời gian khoảng 20 giây
4 Di chuyển Cannon
Do có địa hình phức tạp, việc di chuyển cannon tương đối phức tạp tùy thuộc vào hình dạng Việc cần quan tâm cách để cannon lên địa hình dốc vừa phải Một mẹo nhỏ mà ta làm sử dụng cách “nhảy cóc” thay Như bước theo chiều ngang ta đồng thời nâng cao vị trí cannon chút Nếu cannon xuống dốc, rơi xuống vị trí thấp (nhờ phương thức update()); cannon lên dốc, vị trí cao Đối với dốc có độ nghiêng lớn, ta kiểm tra tọa độ bên trái (hoặc phải tùy theo hướng di chuyển) xem có chạm vào mặt địa hình khơng Nếu có, ta khơng cho phép cannon di chuyển theo hướng
5 Sát thương c đạn
(112)trúng mục tiêu Vậy ta tạo thuộc tính lưu trữ lượng viên đạn bay dựa vào công thức động lượng (E = 1/2*m*v^2)
Lượng hp bị tổn thất cannon trúng đạn tính tổng động viên đạn sát thương phát nổ Lực phát nổ ta cho giá trị từ đến 100 với phạm vi sát thương khác đạn Với cách tính này, bạn đảm bảo sử dụng loại đạn có phạm vi sát thương lớn mức độ sát thương tương đương với loại có phạm vi sát thương nhỏ (cần phân biệt lực phạm vi sát thương đạn)
6 Hỗ trợ nhiều người chơi
Bởi game khơng chơi qua mạng nên nhiều người phải chia sẻ hình Mỗi đạn phát nổ bay khỏi đồ, ta thực chuyển lượt chơi đến người Nếu người chơi chết, tiếp tục gọi đệ quy với điều kiện dừng số người chơi sống nhỏ (_alivePlayers<1)
Ngồi nhiệm vụ trên, phương thức changeTurn() cịn có nhiệm vụ hiển thị hiệu ứng nổ vị trí viên đạn, thời điểm sau phát nổ
7 Kết quả
(113)III Game Mario
Đây kết việc phát triển “Một tảng game 2D side-scrollong” từ phần trước
(114)I. Lời kết
Hiện có nhiều sản phẩm game ứng dụng mang tính đồ họa tương tác cao làm tảng HTML5 Các thiết bị di động hệ điều hành Windows tập trung vào hỗ trợ sử dụng công nghệ HTML5 Kỉ nguyên HTML5 mở tiêu chuẩn thống cho ứng dụng rich user experience không cần phải đắn đo việc lựa chọn thư viện hỗ trợ
Các nội dung trình bày sách chưa thật nâng cao tập trung vào việc phát triển dự án thành sản phầm game thực cạnh tranh thị trường Tuy nhiên bước khởi đầu vững cho tương lai việc phát triển game mạng với công nghệ HTML5 Kết hợp với mô hình client-server, ta phát triển game nhiều người chơi (MMO) lưu trữ liệu trực tiếp người chơi server Đây điều mà chưa đề cập tới báo cáo
J. Tài liệu tham khảo
1 HTML Tutorial (http://html5tutorial.net/)
2 Dive Into HTML (http://diveintohtml5.info/offline.html)
3 HTML Specification (http://www.w3.org/TR/2011/WD- html5-20110525/) HTML Rocks (http://www.html5rocks.com/en/)
5 HTML Doctor (http://html5doctor.com/)
6 HTML Demos and Examples (http://html5demos.com/) Mozilla Development Netword – HTML (
Mozilla Firefox, http://msdn.microsoft.com/en-us/library/cc197062(VS.85).aspx key oldValue newValue url Storage storageArea; SQLite Database openDatabase( DatabaseCallback void transaction in SQLTransactionCallback SQLTransactionErrorCallback SQLVoidCallback void executeSql ObjectArray SQLStatementCallback SQLStatementErrorCallback WindowAnimationTiming, long requestAnimationFrame( FrameRequestCallback void cancelAnimationFrame( Window WindowAnimationTiming; (DOMTimeStamp requestAnimationFrame API http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.HTML#pixel-manipulation: ImageData “Javascript Keycode Enums” requestAnimationFrame (Dot product) giao điểm hai đường thẳng, http://yinyangit.wordpress.com/2012/04/10/gamedev-vector2d-va-cham- va-phan- xa/ Tính khoảng cách từ điểm đến đoạn thẳng HTML5 Canvas Image Loader Tutorial, Mouse wheel programming in JavaScript. Pong Rắn Săn Mồi, http://artint.info/html/ArtInt_54.html Map Scrolling, Online Demo Online Demo Online Demo (http://html5tutorial.net/ (http://diveintohtml5.info/offline.html (http://www.w3.org/TR/2011/WD- html5-20110525/ (http://www.html5rocks.com/en/ (http://html5doctor.com/ (http://html5demos.com/ https://developer.mozilla.org/en/html/html5/)