Hi, Lâu nay bận kiếm cơm nuôi cái dạ dày nên ít có thời gian post bài. Cơ mà, nếu mọi người đã đọc được tới đây rồi thì đã có thể độc lập nghiên cứu rồi đấy. Mình thấy nhiều bạn tiến cũng khá xa rồi ( lạc hậu rồi ). Tuy nhiên lúc rảnh rỗi mình vẫn cứ viết bài thôi, vì đây là Blog học tập của mình mà, nếu bạn nào rảnh rỗi thì vào ôn luyện lại cũng không sao, có gì mình không đúng thì chỉ giúp, OK?
Trong bài học làm game thứ 5 này, mình sẽ cùng mọi người làm 1 cái game nho nhỏ, tên là Space Ship, tất nhiên là dạng đơn giản thôi, chứ phức tạp quá thì lại phải trình bày, chia part khá nhọc. Mọi người thông cảm, hi vọng trong tương lai sẽ post được những bài công phu, chuẩn mực hơn.
Bài code này mình nhặt nhạnh trên mạng thôi, ở trang http://www.raywenderlich.com/ thì phải, code trên phiên bản 2.x. Mình đã convert lại sang 3.x theo đúng cách làm ở bài trước, và có sửa đổi thêm thắt một chút để chuẩn hơn. Nói trước là bài này chỉ là một bài mẫu nên code khá thô, không thiết kế thành nhiều lớp phức tạp, chức năng cũng đơn giản, không cầu kỳ. Do đó bạn nào muốn tham khảo 1 bài học làm game chuẩn mực: Thiết kế lớp tốt, nhiều chức năng, màu mè đồ họa, code tối ưu, có khả năng tái sử dụng trong nhiều dự án thì không nên đọc bài này. Bạn có thể tìm được những bài nâng cao của Game này ở trên mạng nhé.
Trong Part 1 này, chúng ta sẽ có thể làm được những công việc sau:
+ Tái sử dụng lại lớp Parallax ở bài trước, bài 25
+ Thiết kế màn chơi cho game
Bắt đầu
B1: Tái sử dụng lớp Parallax
Trước tiên bạn tạo 1 Project mới, SpaceShip
Copy 2 file ParallaxNodeExtras.h, .cpp từ bài 25 vào Class
Mở file ParallaxNodeExtras.cpp lên ta sửa hàm updatePosition 1 chút như sau
Xóa hết đoạn code trong lệnh if(po->getChild() == node)
thay bằng đoạn sau
Friday, August 1, 2014
Bài 29: Học làm game thứ 5 - Space Ship ( Part 1 )
if(po->getChild() == node)
if (node->getContentSize().width<visibleSize.width)
{
po->setOffset(po->getOffset() + Point(visibleSize.width + node->getContentSize().width,0));
}else {
// Mục đích chỗ này áp dụng cho với những đối tượng có chiều rộng > màn hình sẽ di chuyển đúng po->setOffset(po->getOffset() + Point(node->getContentSize().width*2,0));
}
B2: Thiết kế màn chơi
#include "ParallaxNodeExtras.h"
USING_NS_CC;
using namespace cocos2d;
class HelloWorld : public cocos2d::Layer
{
private:
SpriteBatchNode * _batchNode; // Batch node để lưu các đối tượng có Action
Sprite * _ship;
ParallaxNodeExtras *_backgroundNode; // Backround là 1 đối tượng Parallax
Sprite *_spacedust1; // đám bụi 1
Sprite *_spacedust2; // đám bụi 2
Sprite *_planetsunrise; // hành tinh
Sprite *_galaxy; // thiên hà
Sprite *_spacialanomaly; // chịu
Sprite *_spacialanomaly2; // chịu
// Vector để lưu các thiên thạch, dạng con trỏ
Vector<Sprite*>* _asteroids;
// Chỉ số để truy cập
int _nextAsteroid;
float _nextAsteroidSpawn; // Thời gian xuất hiện thiên thạch tiếp theo
float _nextAsteroidtimer; // Bộ định thời ( hay bộ đếm thời gian, cứ 1 khoảng thời gian thì làm 1 việc gì đó
// 1 Vector để lưu đạn Laser của tàu, dạng con trỏ
Vector<Sprite*>* _shipLasers;
// index
int _nextShipLaser;
int _lives; // mạng
void update(float dt);
PhysicsWorld* _world;
void setPhyWorld(PhysicsWorld* world){ _world = world; };
public:
virtual bool init();
bool onContactBegin(const PhysicsContact& contact);
static cocos2d::Scene* createScene();
void menuCloseCallback(Ref* pSender);
CREATE_FUNC(HelloWorld);
float randomValueBetween(float low, float high);
// Ẩn đi
void setInvisible(Node * node);
void onTouchesBegan(const std::vector<Touch*>& touches, Event *event); // Multi Touch
};
#endif // __HELLOWORLD_SCENE_H__
OK, sang bước tiếp theo, mở file HelloWorldScene.cpp, ta thiết kế các function như sau:
À nhớ phần include thêm đoạn code sau
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
using namespace cocos2d;
using namespace CocosDenshion;
using namespace std;
// Định nghĩa các Tag, cho 3 loại đối tượng
enum
{
KSHIP,
KLASER,
KASTEROID
};
Scene* HelloWorld::createScene()
{
Scene *scene = Scene::createWithPhysics();
scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
Vect gravity(0.0f, 0.0f); // Vector gia tốc =0
scene->getPhysicsWorld()->setGravity(gravity);
HelloWorld *layer = HelloWorld::create();
layer->setPhyWorld(scene->getPhysicsWorld());
scene->addChild(layer);
return scene;
}
Hàm HelloWorld::init()
//Nạp Resource
Size visibaleSize = Director::getInstance()->getVisibleSize();
Size winSize = Director::getInstance()->getWinSize();
_batchNode = SpriteBatchNode::create("Sprites.pvr.ccz"); // File này là file ảnh đã mã hóa, tạo bởi TexturePacker nhé, ko mở được bằng trình xem ảnh thông thường, mở = PVR view của soft TexturePacker, hoặc soft tương tự
this->addChild(_batchNode);
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("Sprites.plist");
_ship = Sprite::createWithSpriteFrameName("SpaceFlier_sm_1.png");
_ship->setPosition(Point(visibaleSize.width * 0.1, winSize.height * 0.5));
_ship->setTag(KSHIP); // đặt tag để phân biệt trong va chạm
_batchNode->addChild(_ship, 1); // Insert vào BatchNode để thực hiện Action
auto shipBody = PhysicsBody::createCircle(_ship->getContentSize().width / 2-23);
// Ko có cái này thì không thể xử lý va chạm được
shipBody->setContactTestBitmask(0xf);
// Va chạm tĩnh
shipBody->setDynamic(false);
_ship->setPhysicsBody(shipBody);
_backgroundNode = ParallaxNodeExtras::create();
this->addChild(_backgroundNode,-1) ;
unsigned int dustQuantity = 2;
for(unsigned int i = 0; i < dustQuantity; i++)
{
auto dust = Sprite::create("bg_front_spacedust.png");
dust->setAnchorPoint(Point(0,0.5));
_backgroundNode->addChild(dust,
0, //order (thứ tự) lớp. Order lớn hơn thì nằm trên che khuất lớp có order nhỏ hơn
Point(0.5, 1), // tốc độ
Point( i*(dust->getContentSize().width),winSize.height/2)); // vị trí
}
_planetsunrise = Sprite::create("bg_planetsunrise.png");
_galaxy = Sprite::create("bg_galaxy.png"); _galaxy->setAnchorPoint(Point(0,0.5));
_spacialanomaly = Sprite::create("bg_spacialanomaly.png");
_spacialanomaly2 = Sprite::create("bg_spacialanomaly2.png");
Point dustSpeed = Point(0.5, 0);
Point bgSpeed = Point(0.05, 0);
_backgroundNode->addChild(_galaxy,-1, bgSpeed, Point(0,winSize.height * 0.7));
_backgroundNode->addChild(_planetsunrise, -1 , bgSpeed, Point(600, winSize.height * 0));
_backgroundNode->addChild(_spacialanomaly, -1, bgSpeed, Point(900, winSize.height * 0.3));
_backgroundNode->addChild(_spacialanomaly2, -1, bgSpeed, Point(1500, winSize.height * 0.9));
// Update scene
this->scheduleUpdate();
// Làm 3 cái Particle trang trí cho game lung linh 1 tí
HelloWorld::addChild(ParticleSystemQuad::create("Stars1.plist"));
HelloWorld::addChild(ParticleSystemQuad::create("Stars2.plist"));
HelloWorld::addChild(ParticleSystemQuad::create("Stars3.plist"));
// Giờ thì tạo bộ lưu trữ
// Bộ lưu mảng thiên thạch
#define KNUMASTEROIDS 15
_asteroids = new Vector<Sprite*>(KNUMASTEROIDS);
for(int i = 0; i < KNUMASTEROIDS; ++i) {
Sprite *asteroid = Sprite::createWithSpriteFrameName("asteroid.png");
asteroid->setVisible(false); // ẩn Sprite vừa tạo, nếu không ẩn, bạn sẽ thấy nó dồn hết về tọa độ 0,0
asteroid->setTag(KASTEROID); // đặt tag
_batchNode->addChild(asteroid); // insert vào batch node
_asteroids->pushBack(asteroid); // insert vào Vector
}
// Bộ lưu mảng đạn Laser #define KNUMLASERS 5
_shipLasers = new Vector<Sprite*>(KNUMLASERS);
for(int i = 0; i < KNUMLASERS; ++i) {
Sprite *shipLaser = Sprite::createWithSpriteFrameName("laserbeam_blue.png");
shipLaser->setVisible(false);
shipLaser->setTag(KLASER);
_batchNode->addChild(shipLaser);
_shipLasers->pushBack(shipLaser);
}
this->setTouchEnabled(true);
_lives = 3;
_nextShipLaser =0; // index
_nextAsteroid = 0; // index
_nextAsteroidtimer =0;
_nextAsteroidSpawn = 1.6f; // Cứ 1.6 giây thì xuất hiện 1 thiên thạch
SimpleAudioEngine::getInstance()->playBackgroundMusic("SpaceGame.wav",true);
SimpleAudioEngine::getInstance()->preloadEffect("explosion_large.wav");
SimpleAudioEngine::getInstance()->preloadEffect("laser_ship.wav");
return true;
Hàm update(float delta)
Point scrollDecrement = Point(5, 0); // Tốc độ di chuyển của Parallax
Size winSize = Director::getInstance()->getWinSize();
// Update vị trí của Parallax
_backgroundNode->setPosition(_backgroundNode->getPosition() - scrollDecrement);
_backgroundNode->updatePosition();
_nextAsteroidtimer+=delta; // Đếm thời gian
if (_nextAsteroidtimer > _nextAsteroidSpawn) {
_nextAsteroidtimer = 0; // reset bộ đếm timer
float randY = randomValueBetween(0.0,winSize.height);
float randDuration = randomValueBetween(2.0,10.0); // Thời gian di chuyển trên màn hình
Sprite *asteroid = (Sprite *)_asteroids->at(_nextAsteroid);
_nextAsteroid++;
if (_nextAsteroid >= _asteroids->size())
_nextAsteroid = 0;
asteroid->stopAllActions(); // dừng mọi Action
// Đặt lên màn hình, và hiển thị
asteroid->setPosition( Point(winSize.width+asteroid->getContentSize().width/2, randY));
asteroid->setVisible(true);
auto asbody = PhysicsBody::createCircle(asteroid->getContentSize().width/2-15);
asbody->setContactTestBitmask(0xf);
asteroid->setPhysicsBody(asbody);
asteroid->runAction(Sequence::create(
MoveBy::create(randDuration, Point( - winSize.width - asteroid->getContentSize().width, 0)),
CallFuncN::create(CC_CALLBACK_1(HelloWorld::setInvisible,this)),
NULL
));
}
OK, giờ xây dựng thêm 1 số hàm phụ là có thể chạy game được rồi
void HelloWorld::setInvisible(Node * node) {
node->setVisible(false); // ẩn đi
_world->removeBody(node->getPhysicsBody()); // remove Body
}
float HelloWorld::randomValueBetween(float low, float high) {
return (((float) rand() / RAND_MAX) * (high - low)) + low;
}
OK rồi đó, build thử game xem có chạy nổi không, Ngon lành nhé
Cũng khá đẹp đấy chứ!
Trong bài này chúng ta đã cùng nghiên cứu 1 số vấn đề nhỏ sau đây:
+ Sử dụng lại lớp đã được thiết kế từ bài trước, có mở rộng 1 chút
+ Sử dụng Vector để lưu trữ dữ liệu, cái này mình thấy dùng khá nhiều, và cũng khá là hay
+ Tạo bộ lưu trữ đối tượng bằng vector, nạp đối tượng vào bộ lưu trữ khi bắt đầu vào game, ẩn chúng đi...
+ Định thời cho 1 sự kiện theo thời gian
Vậy thôi nhỉ, tuy chỉ là những vấn đề nhỏ, nhưng nhiều vấn đề nhỏ kết hợp lại với nhau nhuần nhuyễn sẽ cho ra vấn đề LỚN đấy.
Download CODE+RESOURCE
Mình dừng bài này ở đây. Hẹn gặp lại
Bài 30: Học làm game thứ 5 - Space Ship ( Part 2 - End )
Subscribe to:
Post Comments (Atom)
Cảm ơn cái nha... Bạn nhiệt tình quá
ReplyDeleteTui cũng kiếm cơm nửa, nuôi tới 2 cái dạ dày lựng...hihi
ban oi dowload code roi sao bo zo no khong chay'
DeleteKo liên quan đến bài 1 chút nhưng bác có biết cái Follow ko ạ.
ReplyDeleteKhông bạn à, nó là gì thế?
DeleteCho nó theo dõi 1 thằng nào đó ấy bác
Deletebác hay dùng cocos2d::vector hay std::vector thế.
ReplyDeleteMình thì hay dùng của std, thấy dùng nó thì ko cần retain object những không biết khi hủy thì nó tự giải phóng các sprite lưu trong đó không nhỉ?
Không có autorelease đâu nên bạn phải thêm code sau vào hàm destruction
DeleteYourClass::~YourClass {
CC_SAFE_RELEASE_NULL(_yourvector);
}
Cảm ơn bạn đã chia sẻ và hướng dẫn khá tỉ mỉ.
ReplyDeleteChúc bạn luôn thành công :)
bạn ơi, mình cài đặt theo bài 3 của bạn rồi, build được ra thành công file exe trên win rồi nhưng khi build ra android nó hiện lỗi này :
ReplyDeletehttp://i.imgur.com/KNh1GAp.jpg
bạn sửa giúp minh với
Check lại NDK_ROOT, nhớ là phiên bản r9d nhé, và phải đúng 32, hoặc 64 tùy window.
DeleteWin8 hình như vẫn bị lỗi chưa fix
Aa, hóa ra mình dùng bản Win7 32bit nhưng lại lấy cái r9d 64, bạn có bản 32 k gửi cho mình với :(
DeleteCảm ơn bạn rất nhiều
Mình đã down đúng bản r9d 32bit vài cài, khi build ra android nó đã chạy :">
ReplyDeletenhưng sau khi chạy một hồi nó lại hiện ra lỗi mới :( bạn xem giùm mình cái:
http://i.imgur.com/hZKG6Sn.jpg
Chưa cài API 16 ( Android 4.1.2 ) Mở SDK Manager lên ( trong SDK root ) cài thêm API 10 + các loại nếu thích
Deletecảm ơn bạn mình đã build thành công file .apk ^ ^
Deletebây giờ mình muốn thử chạy trên điện thoại thì chỉ cần copy mỗi file .apk đó thôi hay cần gì nữa ạ ?
Bạn có code mẫu nào thuộc loại này không?
ReplyDelete" Thiết kế lớp tốt, nhiều chức năng, màu mè đồ họa, code tối ưu, có khả năng tái sử dụng"
@Ngoc Quang Tran bạn có thể tham khảo source game Crazy Mario code trên cocos2d-x v2.2 sau: https://github.com/cjlaaa/CrazyMario
ReplyDeleteCho mình hỏi mình tải source code trên về rồi build nhưng chỉ có máy bay có physics còn thiên thạch với đạn thì không có mặc dù đã set physics, mình dùng cocos2d-x 3.1.1
ReplyDeletesao em copy class với resoure vào thì lại bị lỗi ở bool AppDelegate từ create bị báo lỗi ad
ReplyDeleteerror LNK1120: 2 unresolved externals D:\GAME\projects\diabay\proj.win32\Debug.win32\
ReplyDeletemình bị lỗi như thế này ad xem giúp mình cái
Cho mình hỏi ngốc ngếch chút nha, làm sao để mấy cái vùng đỏ xác định va chạm trên MC và các planets biến mất vậy
ReplyDeleteĐóng comment cho lệnh này
Deletescene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
Lệnh này để cho phép xem đường biên của body trong physic
có gạn nào chỉnh sửa đc code của game online ko. mình muốn chỉnh sửa code game " hack game " ấy ạ.bác nào giúp đc em xin hậu tạ chu đáo.
ReplyDeletequá hay lun, tìm cái này hoài giờ mới thấy, cảm ơn bác nhiều
ReplyDeleteYou can Download games pc torrent