原文地址 http://www.is17.com/40/
学习cocos2dx3.1中,网上教程不算特别多,参照各种教程,慢慢总结编写,跟引用了一些优秀类,总算做出来了个超简单的像素鸟游戏。
现在讲解一下核心内容跟,我遇到的一些问题的处理方法。
一、新建项目:
其实网上各种各样的环境配置眼花缭乱的也不一定配置成功,我们要做的仅仅是建一个可以显示helloworld的东西。
我这里有个超简单的方法。
原材料:vs2012,cocos2dx3.1.1 没错,只需要这些。
打开某英文(中文将导致编译失败)目录下的D:\cocos2d-x-3.1.1\build\cocos2d-win32.vc2012.sln
右键解决方案,生成解决方案,等十几分钟吧大概。i3i5表示都是100%cpu,卡的一比。
完了之后,右键cpp-empty-test->设为启动项目。---->F5。不出意外的话出现了helloworld,这么简单都失败的话请百度。
我们的新项目就是改这个东西,我们仅仅是学习,现在不用看那些各种Python的东西来生成新项目====。
二、分析游戏:
其实我写的时候是俩眼一抹黑,先做核心,然后在添加各种功能,然后代码各种改,对于一个没有学过c++的人,各种问题各种出。
真正做东西我们要理清思路,设计类什么的更好一些。思路清晰方能事半功倍。
小鸟,背景,水管,地面各做一个类,我写的仓促,地面并没有写成类。
小鸟类核心代码:Player
bool Player::init()
{
//当时为了显高端引用了别人的一个大图类,所有资源打包的那种,其实并不好用,而且我并不知道他的包怎么打,但是思路挺好,真正的官方用法是引用cocostudio打包
playerSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bird0_0"));
// 注释掉这一句跟上句相同效果。
// playerSp = Sprite::create("bird0_0.png");
this->addChild(playerSp);
//小鸟飞行动画
auto animation = Animation::create();
char szName[100] = {0};
//将小鸟的三张图片做成动画,
for( int i=0;i<3;i++)
{
sprintf(szName, "bird0_%d.png", i);
animation->addSpriteFrameWithFile(szName);
}
animation->setDelayPerUnit(1.8f / 14.0f);
auto action = Animate::create(animation);
// 小鸟循环飞行
playerSp->runAction(RepeatForever::create(action));
// 添加刚体,这个类中小鸟自动附加到刚体上
auto body = PhysicsBody::createCircle(playerSp->getContentSize().width * 0.3f);
body->getShape(0)->setFriction(0);
body->getShape(0)->setRestitution(1.0f);
//以下三行设定可以碰撞,具体参数不懂,反正这样就触发碰撞了。
body->setCategoryBitmask(1); // 0001
body->setCollisionBitmask(1); // 0001
body->setContactTestBitmask(1); // 0001
this->setPhysicsBody(body);
return true;
}
//小鸟死亡,仅作了停止播放动画
void Player::die(){
playerSp->stopAllActions();
}
背景类:bggroundlayer
Size visibleSize = Director::getInstance()->getVisibleSize();
/* 生成两张图片放在0和游戏宽度位置 */
m_bg1= Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bg_day"));
m_bg1->setPosition(Point(0, 0));
m_bg1->setAnchorPoint(Point::ZERO);
//设置抗锯齿修正拼图缝隙(这个很有用不加会有一个显示bug可自己测试)
m_bg1->getTexture()->setAliasTexParameters();
this->addChild(m_bg1);
m_bg2= Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bg_day"));
m_bg2->setPosition(Point(visibleSize.width, 0));
m_bg2->setAnchorPoint(Point::ZERO);
m_bg1->getTexture()->setAliasTexParameters();
this->addChild(m_bg2);
//这个是根据帧频率触发的一个公共方法,来实现地图的横向滚动
void BackgroundLayer::logic(float dt)
{
int posX1 = m_bg1->getPositionX(); // 背景地图1的X坐标
int posX2 = m_bg2->getPositionX(); // 背景地图2的X坐标
int iSpeed = 1; // 地图滚动速度,其实这个可以提出来的
/* 两张地图向上滚动(两张地图是相邻的,所以要一起滚动,否则会出现空隙) */
posX1 -= iSpeed;
posX2 -= iSpeed;
/* 屏幕宽 */
int iVisibleWidth = Director::getInstance()->getVisibleSize().width;
/* 当第1个地图完全离开屏幕时,让第2个地图完全出现在屏幕上,同时让第1个地图紧贴在第2个地图后面 */
if (posX1 < -iVisibleWidth) {
posX2 = 0;
posX1 = iVisibleWidth;
}
/* 同理,当第2个地图完全离开屏幕时,让第1个地图完全出现在屏幕上,同时让第2个地图紧贴在第1个地图后面 */
if (posX2 < -iVisibleWidth) {
posX1 = 0;
posX2 = iVisibleWidth;
}
m_bg1->setPositionX(posX1);
m_bg2->setPositionX(posX2);
}
管道类:Conduit
写这个遇到了很大问题,我并没有接触过cocos,只是找例子来参考做,使用了跟小鸟一样的生成方法,要么只能生成一个刚体,要么生成之后变成了类似于单例的样子,无法生成多个。后来终于看到了这个方法解决了问题。
int iVisibleWidth = Director::getInstance()->getVisibleSize().width;
int jianHeight = 100;
auto pipeUpSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("pipe_up"));
pipeUpSp->setPosition(Point(0, 0));
this->addChild(pipeUpSp);
auto pipeDownSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("pipe_down"));
pipeDownSp->setPosition(Point(0, pipeUpSp->getContentSize().height+jianHeight));
this->addChild(pipeDownSp);
// 添加刚体
auto body = PhysicsBody::create();
body->setDynamic(false);
//我们生成了一个刚体,向这里放入两个元素。这样我们就生成了一堆可操作的管道了。
body->addShape(PhysicsShapeBox::create(pipeUpSp->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, 0)));
body->addShape(PhysicsShapeBox::create(pipeDownSp->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, pipeUpSp->getContentSize().height+jianHeight)));
body->setCategoryBitmask(1); // 0001
body->setCollisionBitmask(1); // 0001
body->setContactTestBitmask(1); // 0001
this->setPhysicsBody(body);
地面我并没有写到类里边,加载地面也并没有新技术,就是创建一个Sprite,一个Body。其移动方法如下
ground->setPositionX(ground->getPositionX() - 1);
if(ground->getPositionX() < ground->getContentSize().width/2-24) {
ground->setPositionX(ground->getContentSize().width/2-1);
}
然后我们将这些东西添加到场景类中,在这里之前我们要修改自带的类,使之能跳转到我们的场景类。
我们修改AppDeleGate.cpp-->applicationDidFinishLaunching使其加载loadscene,
director->setOpenGLView(glview);
/* 设置Win32屏幕大小为288X512, */
glview->setFrameSize(288, 512);
/* 简单的屏幕适配,按比例拉伸,可能有黑边 */
glview->setDesignResolutionSize(288, 512, ResolutionPolicy::SHOW_ALL);
// turn on display FPS
director->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0 / 60);
// 这里添加load类
// auto scene = TollgateScene::scene();
auto scene = LoadingScene::create();
// run
director->runWithScene(scene);
loadscene类:loadscene中实现了一个开头切换场景动画,也可用作实现load进度条之类。
void LoadingScene::onEnter(){
// add background to current scene
auto fileUtils = FileUtils::getInstance();
std::vector searchPaths;
searchPaths.push_back("image");
fileUtils->setSearchPaths(searchPaths);
Sprite *background = Sprite::create("splash.png");
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
background->setPosition(origin.x + visibleSize.width/2, origin.y + visibleSize.height/2);
this->addChild(background);
//加载1024的一张大图
Director::getInstance()->getTextureCache()->addImageAsync("atlas.png", CC_CALLBACK_1(LoadingScene::loadingCallBack, this));
}
void LoadingScene::loadingCallBack(Texture2D *texture){
//配合atlastxt
AtlasLoader::getInstance()->loadAtlas("atlas.txt", texture);
//切换场景,TollgateScene便是我们的主要游戏场景类
auto scene = TollgateScene::scene();
TransitionScene *transition = TransitionFade::create(1, scene);
Director::getInstance()->replaceScene(transition);
}
然后就是最重要的TollgateScene类了,这里我们实现了游戏逻辑。
首先我们添加重力环境,当时我顺手把背景也写在了这里边,背景不用重复加载也就没改。
Scene* TollgateScene::scene()
{
auto scene = Scene::createWithPhysics();
/**设置重力*/
Vect gravity(0, -300.1f);
scene->getPhysicsWorld()->setGravity(gravity);
/* 开启测试模式 这将会给所有刚体加一个红框 */
// scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
auto backgroundLayer = BackgroundLayer::create();
//addChild方法添加到场景,第二个参数是层级,越大层级越高越靠前
scene->addChild(backgroundLayer, 0);
auto layer = TollgateScene::create();
scene->addChild(layer, 10);
layer->m_backgroundLayer = backgroundLayer;
return scene;
}
[/cc]
然后我们在init函数里边添加一些初始显示且不怎么变动的东西。
[cc lang="c++"]
bool TollgateScene::init()
{
if (!Layer::init())
{
return false;
}
//场景尺寸
visibleSize = Director::getInstance()->getVisibleSize();
//初始化一个个性字体类
Number::getInstance()->loadNumber(NUMBER_FONT.c_str(), "font_0%02d", 48);
//添加开始按钮
this->initGame();
//添加地面
this->setGround();
return true;
}
/**添加开始按钮 按钮点击会触发游戏开始事件。*/
void TollgateScene::initGame(){
auto closeItem = MenuItemImage::create( "play_0.png", "play_1.png", CC_CALLBACK_1(TollgateScene::startGame,this));
closeItem->setPosition(Point(visibleSize.width / 2, visibleSize.height /2 ));
// create menu, it's an autorelease object
menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
}
最最重点的游戏开始了:(其实并不复杂,我们设定了初始等级,隐藏开始按钮,添加小鸟与管道,添加随帧事件,添加碰撞事件,逻辑很清晰)
void TollgateScene::startGame(Ref* sender){
level = 0;
showLevel(level);
menu->setVisible(false);
this->setConduit();
this->setBird();
this->schedule(schedule_selector(TollgateScene::logic));
//监听事件
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = CC_CALLBACK_2(TollgateScene::onTouchBegan, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(TollgateScene::onContactBegin,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
}
//加载小鸟
void TollgateScene::setBird(){
player = Player::create();
player->setPosition(Point(visibleSize.width / 2, visibleSize.height /2 +100));
this->addChild(player,5);
}
//加载管道,我们把管道添加到一个数组里边,以遍来播放管道动画,其实我们只用到了两组管道来重复播放。
void TollgateScene::setConduit(){
for (int i = 0; i < 2; i++) {
conduit = Conduit::create();
conduit->setPosition(Point(visibleSize.width + i*PIP_INTERVAL + PIP_WIDTH, this->getRandomHeight()));
this->addChild(conduit,4);
this->pips.push_back(conduit);
}
}
//给管道一个随机高度
int TollgateScene::getRandomHeight() {
Size visibleSize = Director::getInstance()->getVisibleSize();
return rand()%(int)(2*PIP_HEIGHT + PIP_DISTANCE - visibleSize.height);
}
//点击屏幕 简单的给小鸟添加一个向上的力量
bool TollgateScene::onTouchBegan(Touch* touch, Event* event) {
player->getPhysicsBody()->setVelocity(Vect(0, 180));
return true;
}
//碰撞触发游戏结束事件,显示按钮,游戏结束,小鸟死亡。(好吧这个函数里并没写)
bool TollgateScene::onContactBegin(const PhysicsContact& contact) {
menu->setVisible(true);
this->gameOver();
return true;
}
//游戏结束 停止帧事件,结束侦听事件,清空数组,移除显示元素。
void TollgateScene::gameOver(){
this->unschedule(schedule_selector(TollgateScene::logic));
_eventDispatcher->removeAllEventListeners();
for (auto singlePip : this->pips) {
this->removeChild(singlePip);
}
pips.clear();
player->die();
this->removeChild(player);
this->removeChild(menu);
this->removeChild(scoreSprite);
this->initGame();
}
还有一些动态的东西,并不多:
[cc lang="c++"]
//背景移动
void TollgateScene::logic(float dt)
{ //调用背景移动
m_backgroundLayer->logic(dt);
//这里好像介绍过了
ground->setPositionX(ground->getPositionX() - 1);
if(ground->getPositionX() < ground->getContentSize().width/2-24) {
ground->setPositionX(ground->getContentSize().width/2-1);
}
for (auto singlePip : this->pips) {
//判断过关,当管道x<小鸟x
singlePip->setPositionX(singlePip->getPositionX() - 1);
if(singlePip->getPositionX() == visibleSize.width/2-PIP_WIDTH) {
level++;
showLevel(level);
}
//当管道从这个移出屏幕了再让他从另一边进来。
if(singlePip->getPositionX() < -PIP_WIDTH) {
singlePip->setPositionX(visibleSize.width+PIP_WIDTH/2);
singlePip->setPositionY(this->getRandomHeight());
}
}
}
//显示关卡等级 Number类会根据数字返回一个精灵,也就是图片版的数字,比较好看而已。当过关了就刷一下。
void TollgateScene::showLevel(int lev){
this->removeChild(scoreSprite);
scoreSprite = (Sprite* )Number::getInstance()->convert(NUMBER_FONT.c_str(), lev);
scoreSprite->setPosition(Point(visibleSize.width/2-scoreSprite->getContentSize().width/2,visibleSize.height*7/8));
this->addChild(scoreSprite,20);
}
讲解跟流程写完了,可能还遗漏了一些东西大致流程就是这样了,请忽略排版,我的编辑器是最原始的。