1 Game Main Loop trong Cocos2d-x
1.1 Update Loop
- Trong lập trình game, thường là game action, có nhiều chuyển động, ví dụ như một đám mây trôi dần trên màn hình, kẻ địch di chuyển ngược chiều lại nhân vật chính của người chơi… Để hỗ trợ thực hiện các action như vậy, chúng ta cần biết đến khái niệm “vòng lặp game”, đây thường là 1 “main loop” của game, vòng lặp này sẽ hoạt động liên tục để cập nhật lại các trạng thái của các đối tượng game. Ví dụ như: cập nhật lại vị trí của đám mây, tiếp tục như thế chúng ta sẽ có được hình ảnh đám mây đang trôi.
- Trong Cocos2d-x, Main Loop này được định nghĩa trong hàm Update của lớp Node
/** Update method will be called automatically every frame if "scheduleUpdate" is called, and the node is "live"*/
virtual void update(float delta);
- Hàm update sẽ được gọi theo mỗi frame rate của thiết bị. Tuy nhiên, như mô tả của hàm, để “kích hoạt” được vòng lặp này thì chúng ta cần gọi hàm scheduleUpdate
this->schedule(schedule_selector(HelloWorld::update));
- Ví dụ chúng ta muốn thực hiện di chuyển Sprite Bird theo chiều ngang mỗi lần frame refresh là 1 điểm, ta sẽ thực hiện như sau trong hàm update
void HelloWorld::update(float fDelta)
{
this->_sprtBird->setPosition(Vec2(this->_sprtBird->getPosition().x + 1, this->_sprtBird->getPosition().y));
}
1.2 Khái niệm FPS
- Nếu vẫn giữ đoạn mã nguồn như trên và ta đem test đoạn game này trên các thiết bị khác nhau: máy tính, iPhone, Galaxy… ta sẽ cảm nhận được có sự khác nhau trong việc di chuyển của đối tượng Bird, trên thiết bị này ít mượt mà hơn trên thiết bị khác, PC có card màn hình tốt có thể thấy Bird di chuyển tốt hơn, trên các thiết bị Android khác nhau cũng cho kết quả khác nhau. Như vậy, điều này là do đâu? Nguyên nhân nằm ở FPS.
- FPS: Frame Per Second, tạm dịch là số khung hình trên giây, chỉ số này cho biết card màn hình của thiết bị có thể vẽ được bao nhiêu khung hình trong vòng 1 giây, mức độ refresh lại màn hình là bao nhiêu lần trong 1 giây. Khái niệm mà chúng ta khá quen thuộc 24 hình/giây sẽ tạo ra chuyển động mà mắt thường chúng ta có thể cảm nhận được.
- Và như vậy, hàm Update của chúng ta sẽ hoạt động dựa vào chỉ số FPS này. Trong file AppDelegate.cpp, chúng ta thấy đoạn mã
- FPS: Frame Per Second, tạm dịch là số khung hình trên giây, chỉ số này cho biết card màn hình của thiết bị có thể vẽ được bao nhiêu khung hình trong vòng 1 giây, mức độ refresh lại màn hình là bao nhiêu lần trong 1 giây. Khái niệm mà chúng ta khá quen thuộc 24 hình/giây sẽ tạo ra chuyển động mà mắt thường chúng ta có thể cảm nhận được.
- Và như vậy, hàm Update của chúng ta sẽ hoạt động dựa vào chỉ số FPS này. Trong file AppDelegate.cpp, chúng ta thấy đoạn mã
// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0 / 60);
Đoạn mã này thiết lập chỉ số FPS, 1.0/60 cho biết khoảng thời gian delay giữa một frame của 1 chuyển động Animation là 1.0/60, điều này cũng có nghĩa là đoạn mã đang thiết lập cho thiết bị có chỉ số FPS là 60 ( 1 giây vẽ được 60 khung hình), vòng lặp Update sẽ được gọi lại sau mỗi 1/60 giây.
- Như vậy giả sử chúng ta thiết lập thời gian delay của vòng lặp là 1.0/60 và game hoạt động trên thiết bị có FPS = 30. Điều gì sẽ xảy ra? Cứ mỗi 1/60 giây hàm Update được gọi, tọa độ Bird được tăng lên 1 điểm, trong khi 1/30 thì màn hình mới được vẽ lại (phần cứng), rõ ràng là cứ mỗi lần vẽ lại này, Bird sẽ nhảy tới 2 điểm. Ngược lại, với FPS = 120, thì phải 2 lần refresh màn hình thì Bird của chúng ta mới đi được 1 điểm. Như vậy nếu FPS = 30 việc di chuyển sẽ không được mịn như FPS = 60 hoặc 120.
- Trong hàm Update, tham số fDelta của hàm này trả về thời gian mà delay tại thời điểm hàm Update được gọi, có nghĩa là lần gọi này cách lần gọi trước bao nhiêu thời gian. Về mặt lý thuyết thì fDelta sẽ luôn bằng giá trị mà ta đã thiết lập (ví dụ 1/60), tuy nhiên con số này thực tế sẽ khác rất nhiều, chủ yếu do xử lý liên quan đến phần cứng, do CPU quá tải, số FPS có thể sẽ bị giảm xuống (ví dụ < 60), nên độ trễ sẽ bị thay đổi, và khi đó fDelta cũng sẽ khác. Điều này có thể sẽ ảnh hưởng đến các tính toán của chúng ta, ví dụ: có thể chúng ta muốn 1 vòng lặp Bird di chuyển 1 điểm, sau 1 giây sẽ đi được 60 điểm, nhưng nếu FPS bị giảm đi, thì đồng nghĩa số vòng lặp được gọi sẽ ít lại, như thế sẽ không đảm bảo 1 giây Bird di chuyển đúng được 60 điểm. Khi đó, một mẹo nhỏ chúng ta nên dùng để khắc phục tình trạng này, đó là: đem số khoảng cách di chuyển mong muốn (ví dụ 60) nhân với tham số fDelta để gán vào cho đoạn di chuyển trong mỗi famerate.
- Như vậy giả sử chúng ta thiết lập thời gian delay của vòng lặp là 1.0/60 và game hoạt động trên thiết bị có FPS = 30. Điều gì sẽ xảy ra? Cứ mỗi 1/60 giây hàm Update được gọi, tọa độ Bird được tăng lên 1 điểm, trong khi 1/30 thì màn hình mới được vẽ lại (phần cứng), rõ ràng là cứ mỗi lần vẽ lại này, Bird sẽ nhảy tới 2 điểm. Ngược lại, với FPS = 120, thì phải 2 lần refresh màn hình thì Bird của chúng ta mới đi được 1 điểm. Như vậy nếu FPS = 30 việc di chuyển sẽ không được mịn như FPS = 60 hoặc 120.
- Trong hàm Update, tham số fDelta của hàm này trả về thời gian mà delay tại thời điểm hàm Update được gọi, có nghĩa là lần gọi này cách lần gọi trước bao nhiêu thời gian. Về mặt lý thuyết thì fDelta sẽ luôn bằng giá trị mà ta đã thiết lập (ví dụ 1/60), tuy nhiên con số này thực tế sẽ khác rất nhiều, chủ yếu do xử lý liên quan đến phần cứng, do CPU quá tải, số FPS có thể sẽ bị giảm xuống (ví dụ < 60), nên độ trễ sẽ bị thay đổi, và khi đó fDelta cũng sẽ khác. Điều này có thể sẽ ảnh hưởng đến các tính toán của chúng ta, ví dụ: có thể chúng ta muốn 1 vòng lặp Bird di chuyển 1 điểm, sau 1 giây sẽ đi được 60 điểm, nhưng nếu FPS bị giảm đi, thì đồng nghĩa số vòng lặp được gọi sẽ ít lại, như thế sẽ không đảm bảo 1 giây Bird di chuyển đúng được 60 điểm. Khi đó, một mẹo nhỏ chúng ta nên dùng để khắc phục tình trạng này, đó là: đem số khoảng cách di chuyển mong muốn (ví dụ 60) nhân với tham số fDelta để gán vào cho đoạn di chuyển trong mỗi famerate.
void HelloWorld::update(float fDelta)
{
this->_sprtBird->setPosition(Vec2(this->_sprtBird->getPosition().x + 60 * fDelta, this->_sprtBird->getPosition().y));
}
2 Khái niệm Action
- Cocos2d-x cung cấp một hệ thống các action để định nghĩa các chuyển động hoặc animation cho các Node. Các action được thể hiện qua lớp cha gốc là Action. Ví dụ như việc di chuyển Bird ở trên có thể thực hiện bởi một action là Move Action.
- Các action trong Cocos2d-x thường có 2 định là là actionBy hoặc actionTo, 2 kiểu này có sự khác nhau nhất định. Ví dụ với MoveBy và MoveTo
- MoveBy(x,y): tức là di chuyển đến đích với đoạn di chuyển là (x,y) theo 2 trục Hoành & Tung
- MoveTo(x,y): tức là di chuyển đến tọa độ đích là (x,y)
- Action hữu hạn: tức là số lượng thực hiện action này là hữu hạn, theo số lần hoặc là trong 1 khoảng thời gian nào đó, là tham số của action, ví dụ: Move, Hide,…
- Action vô hạn là các action được thực hiện lặp lại mãi, ví dụ: ActionRepeatForever,…
- Để thực thi một action cho một Node (thường là các Sprite), chúng ta sẽ gọi hàm runAction của Node đó. Ta lấy ví dụ tương tự trên, giả sử ta muốn di chuyển Bird, thay vì dùng hàm Update, ta muốn Bird sẽ di chuyển 300 điểm sau 5 giây (1 giây di chuyển 60 điểm), khi đó mã nguồn sẽ là
auto moveAction = MoveTo::create(5.0, Vec2(this->_sprtBird->getPosition().x + 300, this->_sprtBird->getPosition().y));
_sprtBird->runAction(moveAction);
Ta sử dụng action MoveTo, đây là action hữu hạn, ta đưa vào thời gian thực hiện là 5 giây, đích đến, gọi hàm runAction của Bird để bắt đầu di chuyển, sau 5 giây, Bird sẽ đến đích và dừng lại (vì action này chỉ có hiệu lực trong 5 giây).
3 Kết luận
Như vậy, qua phần tìm hiểu khái quát này, chúng ta nắm được các khái niệm cơ bản của Cocos2d-x: Node, Sence, Layer, Sprite, quản lý tọa độ giữa các Node, Main Loop, Action. Với các khái niệm này chúng ta có thể bắt đầu phát triển các game cơ bản, từ đó sẽ tiếp tục tìm hiểu sâu và nắm thêm các kiến thức khác của Cocos2d-x.
No comments:
Post a Comment