Cũng nhanh nhỉ, Blog đã được 14-15 bài rồi đấy. Chắc các bạn cũng đã nắm được một vài kỹ thuật cơ bản về Engine Cocos2d-x V3 này rồi. Với 1 chút kiến thức ấy liệu chúng ta có thể làm được game không. Tất nhiên là được chứ, tuy chỉ là game đơn giản thôi.
Sau đây mình sẽ hướng dẫn các bạn làm 1 game đơn giản nhất nhé, game này hồi mình mới tìm hiểu về Cocos2dx cũng đã học và làm theo ( bài này nằm trong phần learn, các bạn có thể tìm trên trang chủ, không biết đã được cập nhật chưa. Bài hướng dẫn đó đã cũ rồi, nếu bạn copy code vào V3.0 chắc chắn sẽ ko chạy. Ngoài ra 1 lỗi nữa đó là phần phát hiện Physics quá phức tạp trong bản cũ. V3 sẽ cực kỳ đơn giản. Mình sẽ trình bày lại trong bài của mình nha).
Bài gốc cũ ở đây ( cho Cocos2d-x V2.2.3)
Mô tả 1 chút về game này như sau: Chúng ta có 1 Nhân vật chính và 1 đám quái, Nhân vật chính sẽ tiêu diệt đám quái bằng cách xả đạn về phía quái. Trò chơi có tính điểm đơn giản (mỗi chú nằm xuống được 1 điểm), và có cả màn GameOver, có thể chèn nhạc nền nếu muốn.
GOooooooooooooooooo!
B1 - Tạo Project, thêm Resource ảnh, âm thanh + v..v
Đầu tiên, bạn phải tạo 1 Project mới (bằng cmd nhé). Có một số bạn hỏi sao không tạo = VS2012, hay Eclipse cho trực quan. Mình cũng tìm hiểu rồi, hình như là ko có ( V3.0). Mà thôi, cách quái nào chả được. Dùng dòng lệnh thì trên hệ máy nào ( WIN, MAC, LINUX) cũng đều giống nhau nhé.
>cocos new firstgame -p com.vn.firstgame -l cpp -d f:/android/project
Bạn cần download thê Resource ( file ảnh ) rồi chép đè vào Resource của Project firstgame nhé. À, nếu bạn build cho Android thì chép vào Resource trong thư mục proj.android của firstgame. LINK đây
B2 - Tạo nhân vật
1/ Nhân vật chính
Trong hàm bool HelloWorld::init(), các bạn xóa hết đi, chỉ chừa lại đoạn sau
if ( !Layer::init() )
{
return false;
}
----Xóa
----Xóa
return true;
Sau đó thêm vào trước lệnh return true; đoạn code sau đây ( mình giải thích ý nghĩa luôn )
// Lấy kích thước màn hình
Size winSize = Director::getInstance()->getWinSize();
// Tạo 1 Sprite, nhân vật của chúng ta
auto player = Sprite::create("Player.png");
// Đặt lên màn hình ở phía trái
player->setPosition( Point(player->getContentSize().width/2, winSize.height/2) );
// Thêm vào layer nằm trong Scene game
this->addChild(player,1);
// Gọi tới hàm gameLogic , hàm này có nhiệm vụ tạo ra đám quái với thời gian 1 giây 1 quái
this->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 );
2/ Tạo Quái
Bạn mở file HelloWorld.h ra thêm vào đó 3 nguyên mẫu hàm sau
void addTarget();
void gameLogic(float dt);
void spriteMoveFinished(cocos2d::Node* sender);
Sau đó bạn phải khai báo các hàm này trong HelloWorld.cpp như sau:
*
void HelloWorld::gameLogic(float dt)
{
this->addTarget();
}
**
// Hàm này tạo ra Quái và di chuyển chúng nè
// Hàm này tạo ra Quái và di chuyển chúng nè
void HelloWorld::addTarget()
{
auto target = Sprite::create("Target.png");
Size winSize = Director::getInstance()->getWinSize();
// Đoạn này tính toán vùng xuất hiện quái sao cho ko bị khuất quái vào viền màn hình
int minY = target->getContentSize().height/2;
int maxY = winSize.height
- target->getContentSize().height/2;
int rangeY = maxY - minY;
int actualY = ( rand() % rangeY ) + minY;
//
// Đặt quái vào khoảng vị trí trên actualY (random)
target->setPosition(Point(winSize.width + (target->getContentSize().width/2),actualY));
this->addChild(target,1);
//Tính toán tốc độ di chuyển của quái
int minDuration = (int)2.0;
int maxDuration = (int)4.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = ( rand() % rangeDuration )
+ minDuration;
// Di chuyển quái với 1 tốc độ nằm trong khoảng actualDuration , từ điềm xuất hiện tới điểm Point(0,y)
auto actionMove = MoveTo::create( (float)actualDuration, Point(0 - target->getContentSize().width/2, actualY) );
// Kết thúc việc di chuyển của quái khi đã tới điểm cuối
auto actionMoveDone = CallFuncN::create(CC_CALLBACK_1(HelloWorld::spriteMoveFinished,this));
/
// Chạy 2 Action trên 1 cách tuần tự = lệnh Sequence sau
target->runAction( Sequence::create(actionMove, actionMoveDone, NULL) );
}
Bạn hãy tham khảo bài về Sprite và các lệnh Action cơ bản ở bài trước nhé.
***
void HelloWorld::spriteMoveFinished(Node* sender)
{
// Hàm này có mỗi công việc là loại bỏ Target ( đang là Sprite) ra khỏi layer của game
// Ép kiểu Contrỏ Sprite của 1 Node*
auto sprite = (Sprite *)sender;
this->removeChild(sprite, true);
}
B3 - Chạy thử
OK men, bây giờ các bạn có thể build và chạy trên window với lệnh sau
>cocos run -s f:android/project/firstgame -p win32
Kết quả đê, ra được thế này là thành công Part 1 nhé
Bạn hoàn toàn có thể build ra apk rồi cài trên Android nhé
>cocos compile -s f:android/project/firstgame -p android --ap 16 ( cho Android 4.1.2 trở lên)
>cocos compile -s f:android/project/firstgame -p android --ap 16 ( cho Android 4.1.2 trở lên)
Vậy là hết phần 1 rồi. Đơn giản quá phải không mọi người. Toàn là kiến thức cơ bản thôi mà. Ở bài sau chúng ta sẽ nghiên cứu xem làm thế nào để nhân vật chính bắn ra được viên đạn khi ta chạm vào màn hình, việc phát hiện ra sự kiện chạm màn hình sẽ phải làm như thế nào. Hẹn ở phần sau nhé, sẽ rất thú vị đây
Cảm ơn bạn nhiều nhé ! Bạn ra bài sớm nhé :) Mong đợi bài 10 từng phút, từng giây ")
ReplyDeleteBạn build ra có chạy không? Mình cũng chưa build lại, hehe
DeleteCũng sắp ra bài mười rồi, trong thời gian chờ đợi. Hãy ghé qua trang Youtube này để đọc thêm , bổ lắm:
Trang này của nước ngoài, chứ mình ko quảng cáo cho ai hết à. :-)
https://www.youtube.com/watch?v=qXqgSNUf9Cc&list=PLRtjMdoYXLf4od_bOKN3WjAPr7snPXzoe
chạy ok bạn ạ :) Cảm ơn bạn nhiều nhé
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteÝ bạn là khi quái di chuyển đập vào thành màn hình di chuyển ngược lại à?
DeleteÀ mình chưa biết làm thế nào, để nghiên cứu tiếp.
This comment has been removed by the author.
ReplyDeleteÝ bạn là dạng điều khiển à? Nếu vậy thì cho nó MoveTo tới vị trí mình nhấn trên màn hình thôi, còn phức tạp hơn thì phải dùng Steering ( bẻ lái ). Project nào thế?
DeleteThis comment has been removed by the author.
Delete// Kết thúc việc di chuyển của quái khi đã tới điểm cuối
ReplyDeleteauto actionMoveDone = CallFuncN::create(CC_CALLBACK_1(HelloWorld::spriteMoveFinished,this));
Bạn có thể giải thích rõ cách sử dụng hàm CC_CALLBACK_1 được không?
cái này là Macro, bạn tìm hiểu trong thư viện của nó, để như thế này có khi dễ hiểu hơn việc viết rõ ràng ra đấy, hãy mặc định đi
DeleteTheo mình biết là: CC_CALLBACK_0/1/2/3 dùng để bạn gọi hàm, Còn 0, 1, 2, 3 là số tham chiếu truyền vào cho cái hàm bạn gọi tới
DeleteVí dụ hàm HelloWorld::spriteMoveFinished bạn khai báo 3 tham số thì dùng CC_CALLBACK_3
Nếu muốn Build game ra nền tảng iOS thì phải làm sao hả bạn? Mình cần có các công cụ gì nữa?
ReplyDeleteanh ơi, cho em hỏi trong thư mục proj.android của firstgame em ko thấy thư mục Resource nơi, con ngoài thì có
ReplyDeleteKhi build Android, nó sẽ tự động tạo ra thư mục assets chứa Resource
DeleteBuild Android phải đọc chú ý này http://laptrinhgamecocos2dx.blogspot.com/2014/04/chu-y-khi-bien-dich-project-cocos2dx.html
Bạn giải thích đoạn code này giùm mình:
ReplyDeletethis->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 );
ở đây hàm schedule_selector mình coi trong thư viện thì thấy tham số của nó là một selector nhưng ở đây bạn truyền vào là HelloWorld::gameLogic.Vấn đề ở chỗ này gameLogic mình thấy là một hàm void HelloWorld::gameLogic(float dt) nên mình không hiểu cách bạn gọi lắm. Tại sao có thể gọi HelloWorld::gameLogic được mình đã thử gọi một hàm void khác như HelloWorld::addTarget theo kiểu này thì nó bị lỗi.
addTarget làm gì có tham số thời gian
ReplyDeletehãy xem gameLogic (float dt) dt là thời gian, truyền vào qua this->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 );, dt = 1 giây, cứ 1 giây lại gọi gameLogic và gameLogic lại gọi addTarget = add 1 quái
Có thể thay this->schedule (xxx) = scheduleUpdate(), và phải bắt buộc thêm hàm update(float dt) dt của hàm update này nằm trong AppDelegate.cpp (
Bạn hãy hiểu chỗ này là, cứ 1 khoảng thời gian dt ta lại update Scene 1 lần, trong lần update đó Scene sẽ có 1 thay đổi nào đó (ở đây là thêm quái sau 1 giây ). Hãy hiểu ý nghĩa của schedule = lịch biểu, liên quan tới thời gian. schedule_selector là chọn hàm theo lịch biểu thời gian ( hàm có tham số truyền thời gian )
Bạn nên nghiên cứu kỹ code, và ý nghĩa của hàm 1 chút ( tên tiếng Anh nó thường bao hàm luôn công dụng của hàm ) . nghiên cứu chậm thôi, đừng đi quá nhanh. OK?
Cách viết các đoạn code của bạn rất rõ ràng và dễ hiểu rồi mình chỉ không rõ ở phần cú pháp lắm thôi
Deletethis->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 );
trong HelloWorld.h bạn định nghĩa hàm gameLogic:
void HelloWorld::gameLogic(float dt)
{
this->addTarget();
}
Khi muốn sử dụng hàm này trong file HelloWorld.h mình phải viết theo
HelloWorld::gameLogic(dt) nên mình mới không rõ bạn viết theo cách nào (có thể mình chưa biết). Cách bạn gọi là HelloWorld:gameLogic nên mình mới thắc mắc.
Hãy tham khảo ở đây, search "schedule" schedule_selector để hiểu được phần nào
Deletehttp://www.cocos2d-x.org/reference/native-cpp/V3.2alpha0/index.html
this->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 );
làm 2 việc
+ gọi hàm schedule truyền vào 2 tham số ( 1 là schedule_selector(), 1 là 1.0, cứ 1 giây gọi 1 lần
+ là 1 Macro ( xem Macro trong C, C++ để hiểu cách dùng), Macro này gọi đến hàm gameLogic với thời gian 1.0 giây
Bạn dùng theo kiểu HelloWorld::gameLogic(dt) , vậy dt bạn lấy đâu ra??
Hàm schedule được dựng để xử lý theo thời gian cho game bạn nhé, rất hay dùng đấy.. Cũng giống như mấy hàm onTouchBegan, onContactBegin đó, cũng toàn gọi qua Macro.
Bạn ơi mình dựa trên cài bài tạo Menu của bạn, giờ mình thêm đoan code này vào để khi nhấn vào New Game thì tao ra quái, nhưng mà nó chỉ tạo có 1 con mà đứng im nữa. Còn nếu mình đưa vào class AppDelagate thì vẫn chạy tốt à. Hình như là nó không có loop thì phải :3
ReplyDeletevoid HelloWorld::init()
{
Scene *scene = Scene::create();
Layer *layer = HelloWorld::create();
scene->addChild(layer);
director->runWithScene(scene);
this->addChild(scene);
}
Mình nhầm, tức là mình bỏ đoạn code vào playerlayer.cpp, trong hàm init đó
DeleteBạn dùng 1 Scene thì phải đảo layer cũ = layer mới, Bạn tạo 1 lớp SceneManager trong đó có hàm đảo Scene ( tạo scene, đảo layer - xem bài menu 7,8). Ở file AppDelege.app thì gọi SceneManager::goMenu();
DeleteNếu bạn muốn ấn vào menu NewGame, thì bạn xây dựng lớp PlayGame ( có đầy đủ nhân vật và quái ), sau đó khi ấn Newgame thì gọi hàm SceneManager::goNewgame ( tạo 1 layer của lớp PlayGame, sau đó gọi hàm đảo Layer). Hãy xem kỹ lại bài 7-8 và bài 9-10-11-12
Mà bạn viết cái đoạn này
Scene *scene = Scene::create();
Layer *layer = HelloWorld::create();
scene->addChild(layer);
director->runWithScene(scene);
this->addChild(scene);
Sao khó hiểu thế
Scene *scene = Scene::create();
this->addChild(scene);
hóa ra Layer add Scene à, Mình toàn thấy Scene add Layer làm child thôi, chưa thấy điều ngược lại.
mình khai báo lại hàm void addTarget() thành void addTarget(float)
ReplyDeletevà sửa câu lệnh this->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 ); thành
this->schedule( schedule_selector(HelloWorld::addTarget), 1.0 );
thì thấy game vẫn chạy cùng kết quả..vậy mình bỏ hàm gameLogic đi được không bạn? hay hàm gameLogic còn có tác dụng khác?
Được hết, ý nghĩa của nó là cứ 1 giây thêm 1 quái vậy thôi, bạn xây dựng code sao cho mềm dẻo, dễ hiểu, dễ sửa chữa là được
DeleteThis comment has been removed by the author.
ReplyDeletemình bị lổi này có ai giúp mình với
ReplyDeleted:\android\project\firstgame\classes\helloworldscene.cpp(34): error C2440: '' : cannot convert from 'const float' to 'cocos2d::Point'
Bạn gửi file helloWorldScene.cpp, mình xem thử
ReplyDelete// Đặt lên màn hình ở phía trái
ReplyDeleteplayer->setPosition( Point(player->getContentSize().width/2, winSize.height/2) );
Bạn có thể giải thích rõ hơn mình câu lệnh này với ! Cảm ơn
getContenSize lấy kích thức của nhân vật, winsize lấy kích thước màn hình
@@ Tại sao lại đặt nó vào vị trí bên trái ở giữa @@
Bạn xem lại kỹ câu lệnh này, phân tích từng đoạn 1 nhé
Delete- player->setPosition () là đặt vị trí nhân vật ở đâu đó ( điểm neo của nhân vật thường là Tâm )
- Point() là tọa độ của 1 điểm trên màn hình
- player->getContentSize().width/2 có giá trị = 1/2 chiều rộng của nhân vật
- winSize.height/2 có giá trị = 1/2 chiều cao màn hình
=> Point(x,y) như trên sẽ trả về 1 điểm ở vị trí x = 1/2 chiều rộng nhân vật ( ví dụ 10px) , và y = 1/2 chiều cao màn hình( ví dụ 512px )
=> - player->setPosition (Point(x,y)) , sẽ đăt nhân vật ở phía trái, ở chiều cao ngang màn hình đó
Anh ơi. Anh có thể cho em địa chỉ gmail của anh được không ạ?
ReplyDeleteplayer->setPosition( Point(player->getContentSize().width/2, winSize.height/2) );lúc buid ra iphone6 bị lỗi exc bad access (code=1, address=0x0) bạn giải hộ mình vs cocos 3.6. thank
ReplyDeleteAnh cho em hỏi dựa vào tham số nào mà chương trình chỉ loại bỏ đi Target cán đích chứ không loại bỏ các Target khác vậy ạ:
ReplyDeletevoid HelloWorld::spriteMoveFinished(Node* sender)
{
// Hàm này có mỗi công việc là loại bỏ Target ( đang là Sprite) ra khỏi layer của game
// Ép kiểu Contrỏ Sprite của 1 Node*
auto sprite = (Sprite *)sender;
this->removeChild(sprite, true);
}
Hay! Cám ơn bác.
ReplyDeleteHay quá
ReplyDeletee build cho android nó báo lỗi thế này,xử lý thế nào a ơi?
ReplyDeleteBUILA FAILED
D:\ANDROID\SDK\sdk\tools\ant\build.xml:649:The following error occourred while executing this line:
D:\ANDROID\SDK\sdk\tools\ant\build.xml:694: null returned :1
các bài trước e build cho android đều được mà đến bài này nó lại lỗi thế này nên k hiểu. Nếu k debug được chỗ này có khi e bỏ cuộc mất, vì mình làm game ra chủ yếu cho android,mà k build ra cho nó được thì đi tiếp còn ý nghĩa gì nữa :(
DeleteImport vào Eclipse build sẽ ổn hơn, có lỗi nó sẽ báo cụ thể nhé. Nếu không rõ lỗi gì, copy đoạn báo lỗi, dán lên Google, thường là sẽ có hướng giải quyết
DeleteThis comment has been removed by the author.
ReplyDeleteChào bạn!
ReplyDeleteBạn cho mình hỏi là trong hàm actionMoveDone không truyền vào tham số nào hết, làm sao nó biết remove cái gì.
Cám ơn bạn!