Phát triển game dựa trên game engine đa nền tảng cocos2d-x (P5) - Advanced actions
Sau khi đã qua 4 bài giới thiệu về cocos2d-x, cũng như các thành phần cơ bản trong game : Phần 1 : https://viblo.asia/ThanhTa/posts/jaqG0lrxGEKw. Phần 2 : https://viblo.asia/ThanhTa/posts/NPVMaDb2RQOk. Phần 3 : https://viblo.asia/ThanhTa/posts/6BkGyK8XR5aV. Phần 4 : https://viblo.asia/Thanh ...
Sau khi đã qua 4 bài giới thiệu về cocos2d-x, cũng như các thành phần cơ bản trong game :
Phần 1 : https://viblo.asia/ThanhTa/posts/jaqG0lrxGEKw.
Phần 2 : https://viblo.asia/ThanhTa/posts/NPVMaDb2RQOk.
Phần 3 : https://viblo.asia/ThanhTa/posts/6BkGyK8XR5aV.
Phần 4 : https://viblo.asia/ThanhTa/posts/dWrvwWK5vw38.
Trong bài viết lần này sẽ chỉ tập trung vào các kỹ thuật tạo animation cho một character - Sprite. Những vẫn đề đưa ra sẽ đi từ mức cơ bản tới những kỹ thuật nâng cao hiệu suất và giảm tài nguyên bộ nhớ. Các bạn có thể download resources tại link : https://goo.gl/ybe31c để dễ quan sát.
Bạn có thể tạo một Animation từ tập hợp nhiều ảnh khác nhau. Mỗi ảnh thể hiện 1 hành động của character. Chúng ta có thể nhìn vào resource có đính kèm trong bài viết, ảnh từ grossini_dance_01.png tới grossini_dance_14.png thể hiện 1 loạt các động tác của nhân vật.
Vector<SpriteFrame*> animFrames(15); char str[100] = {0}; for(int i = 1; i < 15; i++) { sprintf(str, "grossini_dance_%02d.png",i); auto frame = SpriteFrame::create(str,Rect(0,0,40,40)); //we assume that the sprites' dimentions are 40*40 rectangles. animFrames.pushBack(frame); } auto animation = Animation::createWithSpriteFrames(animFrames, 0.2f); auto animate = Animate::create(animation); sprite->runAction(animate);
Chú ý rằng, Animation được tạo bởi khung hình - frame của sprite, độ trễ thời gian, và thời gian lặp.
Problem
Mặc dù việc tạo Animation như trên dễ dàng sử dụng để phát triển game. Nhưng việc nạp từng file ảnh vào bộ nhớ quả thực sẽ rất tốn nhiều memory, và thời gian thực hiện cũng tăng lên. Nếu bạn load quá nhiều resource thì thời gian delay khá lớn, làm cho Animation của character rất chậm, bị giật. Giải pháp đưa ra là thay vì load nhiều file ảnh, chúng ta sẽ chỉ load 1 file ảnh lớn chứa toàn bộ động tác của character, ví dụ như:
Solution
Giải pháp đưa ra dựa trên những điểm thay đổi của OpenGL ES 1.1:
- Giảm thời gian ghi đọc file. Load một file ảnh lớn sẽ nhanh hơn nếu load nhiều file ảnh nhỏ.
- Giảm thiểu việc sử dụng bộ nhớ. OpenGL ES 1.1 chỉ có thể sử dụng texture có kích thước là luỹ thừa của 2, nghĩa là kích thước chiều rộng hoặc chiều cao của một ảnh có thể là 2,4,8,64,126,256,512,1024... Trong một số trường hợp khác, OpenGL ES 1.1 cấp phát bộ nhớ có kích thước luỹ thừa của 2 cho mỗi texture trong khi texture đó có chiều cao và chiều rộng nhỏ. Vì vậy việc sử dụng 1 ảnh lớn - sprite sheet sẽ giảm bộ nhớ bị phân mảnh.
- Giảm số lần gọi phương thức vẽ - draw của OpenGL ES và tăng tốc độ vẽ - rendering.
Mặc dù cocos2d-x v3.0 trở lên đã update OpenGL ES 2.0, với OpenGL ES 2.0 không còn sử dụng cơ chế cấp phát bộ nhớ bằng luỹ thừa của 2 cho một texture bất kỳ nữa, nhưng với lợi thế giảm được thời gian đọc ghi file và gọi phương thức vẽ, thì giải pháp sử dụng sprite sheet vẫn đem lại hiệu quả hơn so với việc sử dụng từ file ảnh riêng lẻ để tạo Animation.
Crate Animation
Cocos2d-x sử dụng đối tượng SpriteBatchNode để chứa tất cả texture của từng frame. Chúng ta cần phải khởi tạo SpriteBatchNode trong một Scene, cho dù nó không thực sự dùng để vẽ. Nghĩa là SpriteBatchNode đơn giản chỉ là một container mà thôi.
SpriteBatchNode* spritebatch = SpriteBatchNode::create("animations/grossini.png");
Tiếp theo, chúng ta sử dụng SpriteFrameCache để xác định từng frame nhỏ trong file ảnh lớn đã được load vào SpriteBatchNode.
SpriteFrameCache* cache = SpriteFrameCache::getInstance(); cache->addSpriteFramesWithFile("animations/grossini.plist");
Khi sprite sheet và frame đã được load, chúng ta có thể tạo ra Sprite bằng cách sử dụng phương thức createWithSPriteFrameName, và add nó vào sprite sheet
Sprite1 = Sprite::createWithSpriteFrameName("grossini_dance_01.png"); spritebatch->addChild(Sprite1); addChild(spritebatch);
Phương thức createWithSpriteFrameName sẽ tìm vị trí và vùng ảnh tương ứng từ file plist, sau đó tạo texture với các tham số tương ứng đó từ file ảnh lớn grossini.png để tạo ra một Sprite. Từ đây chúng ta có 2 cách để tạo Animation.
- Tạo đối tượng Vector and thêm tất cả frame của Animation vào đó. Trong trường hợp này, animation sẽ gồ 14 frame có dùng kích thước, nên chúng ta cần sử dụng vòng lặp
Vector<SpriteFrame*> animFrames(15); char str[100] = {0}; for(int i = 1; i < 15; i++) { sprintf(str, "grossini_dance_%02d.png", i); SpriteFrame* frame = cache->getSpriteFrameByName( str ); animFrames->addObject(frame); }
rồi tạo 1 Animation khá đơn giản như sau:
Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.3f); Sprite1->runAction( RepeatForever::create( Animate::create(animation) ) );
- Hoặc sử AnimationCache để load file xml/plist đã được mô tả trong batch node.
// "caches" are always singletons in cocos2d auto cache = AnimationCache::getInstance(); cache->addAnimationsWithFile("animations/animations-2.plist"); // should be getAnimationByName(..) in future versions auto animation = cache->animationByName("dance_1"); // Don't confused between Animation and Animate auto animate = Animate::create(animation); sprite->runAction(animate);
Về cách thức tạo ra Sprite sheet các bạn có sử dụng công cụ khá quen thuộc TexturePacker : https://www.codeandweb.com/texturepacker/download
Nội dung tiếp theo chúng ta sẽ tìm hiểu cách thức tạo Scene và chuyển đổi giữa các Scene như thế nào. Thank for reading.