Bài 10: Làm game đầu tiên - Sự kiện chạm màn hình và bắn đạn (Part 2)

Người đăng: share-nhungdieuhay on Thứ Ba, 13 tháng 5, 2014

Hi!

Đi làm ngồi rảnh quá, tranh thủ post bài vậy. Trong bài trước chúng ta đang triển khai dang dở 1 Project game đầu tiên nhỉ? Hoành tráng chưa. Cũng bình thường mà, Project nào chẳng bắt đầu từ sự đơn giản, sau đó phức tạp hóa lên dần dần.

Ở phần đầu chúng ta đã học được cách làm thế nào để tạo 1 nhân vật, 1 đám quái từ những hình ảnh thông qua lớp Sprite. Và trong phần thêm quái, bạn chú ý 1 đoạn tính toán vị trí xuất hiện, và tốc độ di chuyển cũng khá hay nhỉ. Chỉ là tính toán thôi mà, cũng chưa phải thuật toán gì ghê ghớm cả, nhưng thực sự nó là thuật toán ( đơn giản ) mà.

Trong phần tiếp theo này, chúng ta sẽ làm công việc sau:

+ Bắt sự kiện khi chạm màn hình
+ Khi chạm vào màn hình, nhân vật của ta sẽ bắn ra đạn
+ Xử lý viên đạn bay theo hướng của điểm chạm trên màn hình

Lets GOOOOÔ!

B1 - Sự kiện chạm màn hình và cách phát hiện

Các bạn mở file HelloWorldScene.cpp lên , trong hàm init() tìm đến cuối hàm trước lệnh return true; thêm vào đoạn lệnh sau:

//Tạo đối tượng truyền tải thông tin của các sự kiện
auto dispatcher = Director::getInstance()->getEventDispatcher();
//Tạo 1 đối tượng lắng nghe sự kiện Chạm màn hình theo cách One by One, xử lý 1 chạm tại 1 thời điểm
auto listener1 = EventListenerTouchOneByOne::create();
//Thiết lập "nuốt" sự kiện Touch khi xảy ra, ngăn ko cho các đối tượng Bắt sự kiện khác sử dụng event này
listener1->setSwallowTouches(true);

//Bắt sự kiện Touch, khi xảy ra sự kiện Touch nào thì sẽ gọi đến hàm tương ứng của lớp HelloWorld
listener1->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
listener1->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
listener1->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);

//Gửi cho dispatcher xử lý
dispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

return true;

* Lưu ý: dù rằng ko dùng onTouchMoved, nhưng chúng ta cũng nen cho vào để sự kiện Touch được xử lý đầy đủ.

B2 - Xử lý sự kiện chạm - tạo khả năng bắn đạn cho nhân vật

Bạn mở file HelloWorldScene.h, thêm vào 3 nguyên mẫu hàm:

void onTouchEnded (cocos2d::Touch* touches, cocos2d::Event* event);
void onTouchMoved (cocos2d::Touch* touches, cocos2d::Event* event);
//Chú ý hàm onTouchBegan phải trả về bool
bool onTouchBegan (cocos2d::Touch* touches, cocos2d::Event* event);

Sau đó mở file HelloWorldScene.cpp để định nghĩa 3 hàm này

Mặc dù chúng ta ko dùng hết cả 3 hàm nhưng để sự kiện Touch được xử lý đầy đủ bạn phải khai báo cả 3 hàm như sau


bool HelloWorld::onTouchBegan(Touch* touch, Event* event)

{  

return true; // Phải trả về True

}



void HelloWorld::onTouchMoved(Touch* touch, Event* event)
{  

// Không xử lý gì ở đây

}

void HelloWorld::onTouchEnded (Touch* touches, Event* event){
   
// Lấy tọa độ của điểm chạm
Point location =  touches->getLocationInView();
location = Director::getInstance()->convertToGL(location);

Size winSize = Director::getInstance()->getWinSize();

//Tạo viên đạn là 1 Sprite, đặt vị trí đầu tiên gần nhân vật chính
auto projectile = Sprite::create("Projectile.png");
projectile->setPosition( Point(20, winSize.height/2) );

// Đoạn này tính toán điểm cuối cùng của viên đạn thông qua vị trí đầu và vị trí Touch, hình ảnh bên dưới sẽ minh họa cho điều này. Ở đây áp dụng 1 vài công thức toán học rất cơ bản thôi nhé. Không phức tạp lắm

// Lấy tọa độ điểm chạm trừ đi tọa độ đầu của viên đạn (offX, offY)
int offX = location.x - projectile->getPosition().x;
int offY = location.y - projectile->getPosition().y;

// Không cho phép bắn ngược và bắn thẳng đứng xuống dưới ( bên dưới nhân vật )

if (offX <= 0) return;

// Thỏa mãn điều trên thì tạo hình viên đạn trên màn
this->addChild(projectile,1);

//Tính toán tọa độ điểm cuối thông qua toa độ điểm đầu và khoảng offX, offY
// Tọa độ tuyệt đối realX = chiều rộng màn hình + 1/2 chiều rộng viên đạn, vừa lúc bay ra khỏi màn hình 
int realX = winSize.width  + (projectile->getContentSize().width/2); 

// Tỷ lệ giữa offY và offX
float ratio = (float)offY / (float)offX;

// Tọa độ tuyệt đối realY tính dựa trên realX và tỷ lệ trên + thêm tọa độ Y ban đầu của đạn ( tính theo Talet trong tam giác, hoặc theo tính tang 1 góc)

int realY = (realX * ratio) + projectile->getPosition().y; // Chỗ này theo mình là chưa đúng, đúng ra phải thế này int realY = ((realX-projectile->getPosition().x) * ratio) + projectile->getPosition().y; (realX-projectile->getPosition().x mới đúng là chiều dài từ điểm đầu tới điểm cuối trên trục X

//Tọa độ điểm cuối
auto realDest = Point(realX, realY);

//Chiều dài đường đi của viên đạn, tính theo Pitago a*a = b*b + c*c, a là cạnh huyền tam giác vuông
int offRealX = realX - projectile->getPosition().x;
int offRealY = realY - projectile->getPosition().y;
float length = sqrtf((offRealX * offRealX)  + (offRealY*offRealY));

// Thiết lập vận tốc 480pixels/1giây
float velocity = 480/1;

// Thời gian bay của đạn = quãng đường đạn bay chia vận tốc ở trên
float realMoveDuration = length/velocity;

// Di chuyển viên đạn tới điểm cuối với thời gian, và tọa độ đã tính ở trên. Khi qua viền màn hình thì biến mất
projectile->runAction( Sequence::create(
MoveTo::create(realMoveDuration, realDest),
CallFuncN::create(CC_CALLBACK_1(HelloWorld::spriteMoveFinished,this)), NULL) );

}

Đoạn code runAction nhìn có vẻ phức tạp nhưng nếu viết tường minh nó sẽ thế này

//Di chuyể đạn với thời gian và tọa độ tính toán ở trên
auto move= MoveTo::create(realMoveDuration,realDest);

//Khi move tới điểm cuối xong, bạn sẽ gọi hàm spriteMoveFinished để xóa bỏ hình ảnh viên đạn, nếu không xóa bỏ đi, thì viên đạn bay ra ngoài vẫn nằm trong Layer, ngày càng nhiều, xử lý ngày càng nặng. Đoạn này sẽ trả về 1 Action*
auto finish=CallFuncN::create(CC_CALLBACK1(HelloWorld::spriteMoveFinished,this));

//Thực hiện tuần tự 2 việc, Move sau đó Xóa 
auto run = Sequence::create(move,finish,NULL);

//Thực hiện công việc xử lý hàm Sequence
projectile->runAction(run);

Bạn nên xem lại bài về Các Action cơ bản của Sprite

Ảnh minh họa cho việc tính toán đường bay của đạn



Phần này như vậy là đã OK rồi nhé, tìm hiểu kỹ thì cũng không có gì phức tạp cả phải không nào. Tóm tắt lại công việc như sau

+ Bắt sự kiện chạm màn hình bằng các Listener
+ Xây dựng hàm xử lý khi có sự kiện Chạm màn hình, bắn ra viên đạn, tính toán đường bay, tọa độ, tốc độ viên đạn, có dính tí toán học, vật lý học nè
+ Tạo Action di chuyển viên đạn với tốc độ, tọa độ đã tính toán

Các bạn có thể down file nguồn ở đây, nếu ngại copy và kiểm tra code ở phía trên

Ảnh Demo chút


Tuy nhiên bạn sẽ thấy đạn bắn "xuyên táo" con quái luôn, hehe. Ở bài sau chúng ta sẽ thiết lập sự kiện va chạm cho viên đạn với quái nhé. 


{ 0 nhận xét... read them below or add one }

Đăng nhận xét