注:那个,秉着敬业爱业的精神,代码我会尽量解释清楚和一些路上捡的心得体会外加一堆废话(这段也是废话!!!)。如果没有解释的,那是我也没看懂,(╯3╰),百度去吧。倒是感觉这代码注释过多也是影响了代码的可见度,so如有影响,直接参照源代码。(这尼玛HelloWorld也要注释?额~~请轻喷)
一、材料准备
http://download.csdn.net/detail/dyyaries/6014343
这个是我在百度的时候找到的素材,微信飞机大战。百度真方便!!!真心慷慨,这不是做广告!嘿嘿,别动手,别~~~。
因为这个游戏比较简单,适合新手练手,所以拿他来开刀是再好不过了。当然里面许多素材是不需要的,实现基本的游戏逻辑只需要几张图片就够了,用到哪些后面有提及。
打开项目文件夹,把需要用到的游戏图片资源导入游戏项目根目录下的Resourse文件夹中。注:游戏里要用到的资源是默认从这里开始为根目录去读取的,比如Resourse下有一张simple.png的图片,到时候可以在项目中直接读取资源路径”simple.png”即可。
二、 屏幕大小调节
下面就需要我们调戏,啊不是调试和编写我们的代码了。
先打开vs2012,这个是我当前的IDE,依次点击文件->打开->项目/解决方案,打开游戏项目的proj.win32下的planegame.sln即可以导入游戏项目到vs 2012中。
打飞机当然是竖屏了才有感觉,在AppDelagate那里修改下窗口的属性,调整游戏窗口的大小。
打开src目录下的AppDelegate,src目录是我们游戏代码存放的地方,在applicationDidFinishLaunching()的如下位置添加如下代码
glview = GLViewImpl::create(“飞机大战”); //这里是修改后的窗口左上角显示的标题
glview->setFrameSize(@H_403_21@480,@H_403_21@800); //添加的代码
director->setOpenGLView(glview);
现在可以跑下看看窗口改变的效果。
三、 背景图片与滚动
打开HelloWorldScene.cpp。
以下段落可以略过。
注意,这个是我们游戏的第一个场景,Cocos2d引擎帮我们封装了许多逻辑,以至于我们只需关注我们的游戏场景和处理逻辑。其实游戏的开始是从win32下的main.cpp里面的APIENTRY _tWinMain方法开始,这是个宏,其实就是c++语言的main方法,他定义了AppDelegate的一个对象,随后他会初始化AppDelegate的父类Application然后把他本身的指针给了父类的一个父类自身变量,这样做的目的是为了随后调用AppDelegate的applicationDidFinishLaunching()方法,这个是我们可以接触到用来初始化游戏窗口初始化导演类,还有初始化我们的第一个场景HelloWorld。最后main方法执行到了Application::getInstance()->run(),这里面封装的是我们游戏的循环和渲染等等逻辑。这时候我们的游戏已经跑起来了。这个就是简单的游戏启动过程。更加深层的代码有兴趣可以去研究研究源码,这也是开源带来的好处。
打开HelloWorldScene.cpp,删除原有的创建菜单和背景的代码,再添加如下代码:
//背景精灵
auto bg1 = Sprite::create("background.png");
//注意,Vec2是cocos2d的数据结构,里面包含两个float型的数据,分别用来表示x轴和y轴。
//setPosition是设置精灵的相对屏幕的位置(也就是世界坐标)。当前这两个参数表示设置精灵在屏幕的宽度一半,高度为零的位置,即屏幕最下方的中间位置。cocos2d是以OpenGl的坐标的右手坐标为标准,屏幕的左下方为原点。visibleSize.width和height是屏幕的宽高,
bg1->setPosition(Vec2(origin.x + visibleSize.width/2,0));
//注意这里的锚点设置,为了图片永远在屏幕中间,x轴必须设置为0.5,其中的意思就是这个精灵向左移动精灵的一半宽度,y轴则是向下
bg1->setAnchorPoint(Vec2(0.5,0));
bg1->setTag(101); //设置Tag,以后可以通过这个标签找到这个精灵,以便对这个精灵进行操作。
this->addChild(bg1,0); //将背景精灵添加到层中。可以认为是当前的屏幕。其中的0是设置可见优先权,数值越小优先权越小,即当如果有其他的精灵的优先权比他大时,他会被遮挡住的。
//第二张背景图,是跟在第一张的上面,无缝连接,两张图形成不间断的地图滚动,getContentSize是获取精灵的size其中有它的宽度和高度。
auto bg2 = Sprite::create("background.png");
bg2->setPosition(Vec2(origin.x + visibleSize.width/@H_403_21@2,bg1->getPositionY()+bg1->getContentSize().height));
bg2->setAnchorPoint(Vec2(@H_403_21@0.5,@H_403_21@0));
bg2->setTag(@H_403_21@102);
this->addChild(bg2,@H_403_21@0);
为什么有两个一样的背景精灵呢?这个是为了滚动地图设定的。
先在HelloWorldScene.h文件中声明定时器的回调方法。添加如下代码:
void backgroundMove(float f); //定时器回调的背景滚动方法
然后在HelloWorldScene.cpp中定义他。
void HelloWorld::backgroundMove(float f) //背景滚动的回调方法
{
//背景滚动逻辑
auto bg1 = this->getChildByTag(@H_403_21@101);
auto bg2 = this->getChildByTag(@H_403_21@102);
//当第二张图片退出屏幕时,把第一张图片设置到屏幕中,由于我的背景图片的高度是大于屏幕的高度的,所以判断逻辑要复杂点
if(bg2 -> getPositionY() + bg2->getContentSize().height <= Director::getInstance()->getVisibleSize().height)
{
bg1->setPositionY(-bg1->getContentSize().height + Director::getInstance()->getVisibleSize().height);
}
bg1->setPositionY(bg1->getPositionY()-@H_403_21@3);
bg2->setPositionY(bg1->getPositionY()+bg1->getContentSize().height);
}
背景滚动逻辑第一次接触也是件麻烦事,下面一张图告诉你。下面的图表示的逻辑也是上面的回调方法的实现。
游戏一开始是①状态,定时器会不断得修改背景精灵一和背景精灵二的坐标,当两者的坐标到达②的状态时,修改两者的坐标到③状态,其实也就是回到了游戏刚开始的状态①,然后就是你看到的无限背景滚动的效果了。注意背景图片的高度比屏幕小时两张图片就不足以做到以上效果了,该如何做呢,自己斟酌斟酌。不过这里的逻辑容易想清楚,但是其中的坐标计算也不是件头疼的事。
最后,启动定时器,在添加两个背景精灵下面添加如下代码:
//背景滚动的定时器,0.01执行一次传入的函数
this->schedule(schedule_selector(HelloWorld::backgroundMove),0.01); 这里写代码片
好,背景滚动大功告成。跑一下看看效果如何吧。
四、 游戏菜单项
现在这个项目还比较简单,只有简单的开始游戏、退出游戏这两个选项,玩过游戏的都知道还有游戏设置啊、关于啊帮助啊这些。
首先在HelloWorldScene.h中声明两个回调方法,这两个方法是在点击两菜单项时触发的函数。
添加如下代码:
// 菜单项的回调方法
void menuCloseCallback(cocos2d::Ref* pSender); //这个是系统默认写好的下面的才是新添加的。
void menuStartCallback(cocos2d::Ref* pSender); //参数什么的先不用去了解,循规蹈矩再去创新。
创建如下的菜单项:
//这个是开始游戏菜单项,其中的”game_start.png”,“game_start2.png”是读取本地资源,作为未点击前和点击之后的显示图片。
// HelloWorld::menuStartCallback是他的回调函数,即点击这个菜单项的时候会执行该函数
auto startItem = MenuItemImage::create( "game_start.png","game_start2.png",CC_CALLBACK_1(HelloWorld::menuStartCallback,this));
startItem->setPosition(Vec2(visibleSize.width/@H_403_21@2 + origin.x,visibleSize.height/@H_403_21@2 + origin.y));
//这个是结束游戏菜单项
auto closeItem = MenuItemImage::create( "game_exit.png","game_exit2.png",CC_CALLBACK_1(HelloWorld::menuCloseCallback,this));
closeItem->setPosition(Vec2(origin.x + visibleSize.width/@H_403_21@2,visibleSize.height/@H_403_21@2 + origin.y - startItem->getContentSize().height));
//把菜单项添加到菜单精灵中
auto menu = Menu::create(startItem,closeItem,NULL);
menu->setPosition(Vec2::ZERO);
//把菜单精灵添加到当前的层中
this->addChild(menu,@H_403_21@1);
下面是回调函数的实现
结束游戏的回调函数就直接调用创建时默认生成的。
void HelloWorld::menuStartCallback(Ref* pSender)
{
//开始游戏
}
好,菜单项搞定,编译运行下,是不是发现有两个菜单项在屏幕中间了!!!点击退出还可以退出游戏。不过开始游戏逻辑还没写。呵呵,小有成就是吧。好我们继续。
五、蠢蠢欲动的飞机
为了体现出我们的飞机饥渴难耐啊不,是迫不及待想直捣敌人基地的雄姿,我们给他一个动画展示,展示它飞翔的雄姿。
添加如下代码:
//添加飞机
auto plane = Sprite::create("hero1.png");
plane->setPosition(visibleSize.width/@H_403_21@2+origin.x,@H_403_21@200);
this->addChild(plane);
//飞机添加帧动画,添加格式如下,别问我为什么,呵呵。
Animation * animation = Animation::create();
//本来是通过一张缓存图片去获取的,这样性能比较好。不过方便起见,so~~~。
//其中的Rect(0,102,126)聪明人都可以看出是读取图片资源的矩形区域的纹理。什么?你看不懂?呵呵。什么?纹理是什么?呵呵。
SpriteFrame * spriteFrame1 = SpriteFrame::create("hero1.png",Rect(@H_403_21@0,@H_403_21@0,@H_403_21@102,@H_403_21@126));
SpriteFrame * spriteFrame2 = SpriteFrame::create("hero2.png",@H_403_21@126));
animation->addSpriteFrame(spriteFrame1);
animation->addSpriteFrame(spriteFrame2);
animation->setDelayPerUnit(@H_403_21@0.15f); //两张图片交互播放的间隔
Animate * animate = Animate::create(animation);
plane->runAction(RepeatForever::create(animate)); //开始执行动画
动画的执行是以精灵为单位,即可以为精灵添加动画,创建完动画,只要让精灵执行这个动作即可。如果执行完毕后,必须使用下面代码移除动画
sprite->stopAllActions();
//停止所有动画,应该是清除了动画的内存。
Animate 是继承了Ref,所以的他的内存的cocos2d框架自动回收的,我们不用自动释放他。但是如果不需要时也要手动停止它。因为我们当前的飞机精灵动画是在当前场景无限存活,不需手动停止它,当我们切换到开始游戏启动另一个场景时,它会自动清除内存,吧。为什么这么没自信,这是我猜的。嘿嘿,别冲动别冲动,有话好好说。
ok,飞机动画创建成功,启动运行一下吧。是不是看到小飞机的飞翔在天空中的雄姿了???
2-6 最终界面
如图: