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
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 )
{ 0 nhận xét... read them below or add one }
Đăng nhận xét