采用Cocos引擎cocos studio开发 平台为Android,开发语言为C++;
时间: 2020.4.29-5.14
- 游戏说明
这是一款具有策略性的游戏。背景是僵尸入侵你的家,你需要用具有攻击性的植物才能抵御僵尸的入侵。玩家需要从第一关开始,选择左上角卡片放置豌豆射手,用豌豆射手防御僵尸,游戏结束条件是僵尸被打死或者僵尸越过屋子。过关就会得到卡片,收集植物卡片,用植物打倒僵尸!
- 概要设计
游戏设计的框架主要有几部分构成:
游戏界面、动画设计、定时器设计、交互功能设计、菜单功能设计。
1游戏界面:
界面的大小、背景设置;
设置游戏卡片;
标题标签内容;
声音,背景音乐。
2动画设计:
僵尸移动;
豌豆射手动画;
豌豆飞行;
豌豆豆渣炸裂效果;
3定时器设计:
僵尸进场定时器;
豌豆发射定时器;
豌豆检测定时器;
4交互功能设计:
选卡的状态动作;
放置豌豆射手;
5菜单功能设计:
退出功能菜单exit;
下一关功能菜单next;
重新再来菜单resume;
- 详细设计
3.1. 开始场景构造
3.1.1标题标签
auto dict = Dictionary::createWithContentsOfFile("chnString.xml");
const char* title = ((String*)dict->objectForKey("Title"))->getCString();
auto chnTitle = Label::createWithTTF(title, "fonts/HBYY.TTF", 72);
chnTitle->setPosition(320, 500);
chnTitle->setColor(Color3B::RED);
addChild(chnTitle);
利用Dictionary类的createWithContentsOfFile方法来读入XML文件,objectForKey方法来引用关键字,objectForKey方法来引用关键字,获得Title关键字代码的内容并转换为字符串title,并用字符串title创建标签。这种方法显示中文稳定,不需要引入外部库,增删改操作也比较方便,后续也很多用到的地方不再重复描述
3.1.2按钮菜单
auto startLabelMenuItem = MenuItemLabel::create(start, this, menu_selector(HelloWorld::menu_start));
auto StartMenu = Menu::create(startImgMenuItem, startLabelMenuItem, exitImgMenuItem, exitLabelMenuItem, NULL);
调用MenuItemLabel的create方法创建标签菜单项,点击后能产生相应的响应,并添加菜单项至菜单。
3.1.3音乐
auto engine = CocosDenshion::SimpleAudioEngine::getInstance();
engine->preloadBackgroundMusic("musics/bgMusic.wav");
engine->preloadEffect("musics/start.wav");
engine->preloadEffect("musics/exit.wav");
engine->playBackgroundMusic("musics/bgMusic.wav", true);
需要播放音乐时,通过getInstance()来“召唤”引擎,preloadBackgroundMusic为预加载背景音乐,参数为音乐的路径,playBackgroundMusic,播放背景音乐,第二个参数为是否重复播放。由preloadEffect预载音效。
3.2. 游戏场景
3.2.1鼠标监听器响应
auto listener_mouse = EventListenerMouse::create();
listener_mouse->onMouseMove = [=](Event* event)
{
auto e = (EventMouse*)event;
if (IsCardSelected)
{
beanShooter->setOpacity(150);
beanShooter->setPosition(e->getCursorX(), e->getCursorY());
}
else
{
beanShooter->setOpacity(0);
}
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener_mouse, this);
创建鼠标监听,设置如果卡片处于已选状态时,豌豆射手跟随鼠标移动x/y坐标,不然则设置豌豆射手不可见透明度为0;
3.2.2单点触摸
auto listener_touch = EventListenerTouchOneByOne::create();
listener_touch->onTouchBegan = [=](Touch* touch, Event* event)
{
auto cardSize = card->getContentSize();
auto cardRect = Rect(0, 0, cardSize.width, cardSize.height);
auto cardTouch = card->convertToNodeSpace(touch->getLocation());
创建单点触摸响应的监听器,设置为按下时响应,获得当前目标的大小,将目标大小所围成的范围定义为矩形类,最后获得触摸位置,该坐标要转换为目标坐标系中。
if(cardRect.containsPoint(cardTouch)){
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("card.wav");
IsCardSelected = !IsCardSelected;
auto TC = TextureCache::getInstance();
if (IsCardSelected){
auto texture = TC->addImage("card_selected.png");
card->setTexture(texture);}
else{
auto texture = TC->addImage("card_normal.png");
card->setTexture(texture);}
listener_mouse->setEnabled(true);
beanShooter->stopAllActions();
unschedule(schedule_selector(StartScene::shootBean));
else if (IsCardSelected){
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("plant.wa);
IsCardSelected = !IsCardSelected;
auto TC = TextureCache::getInstance();
auto texture = TC->addImage("card_normal.png");
card->setTexture(texture);
beanShooter->setOpacity(255);
listener_mouse->setEnabled(false);
如果触点在卡片内时,卡片为已选状态,切换为未选状态,更换素材为card_normal.png,若卡片为未选状态,切换为已选状态,更换素材为card_selected.png;如果触点不在卡片内,卡片为已选状态,放置豌豆射手,切换为未选状态,更换素材为card_normal.png,卡片为未选状态,则不作任何处理。
3.2.3豌豆射手动画
auto animation = Animation::create();
char name[20];
auto SFC = SpriteFrameCache::getInstance();
SFC->addSpriteFramesWithFile("beanShooter.plist");
for (int i = 1; i <= 9; i++){
sprintf(name, "beanShooter_%d.png", i);
animation->addSpriteFrame(SFC->getSpriteFrameByName(name));}
animation->setDelayPerUnit(0.15);
animation->setLoops(-1);
auto shake = Animate::create(animation);
beanShooter->runAction(shake);
schedule(schedule_selector(StartScene::shootBean), 3, -1, 0.01);
SpriteFrameCache,精灵帧缓存类,用来存放每帧图像,共享对象,getinstance获得该共享对象后,加载动画图像plist文件,通过plist中的动作名获取帧图像。Animation对象用循环依次加载剩余的图像,并设置帧间隔及重复播放,由配置好的Animation对象创建animate对象,让精灵执行animate对象动作。最后设置定时器,被执行的时间间隔为3s,在0.01s后启动。
3.2.4粒子系统
auto sun = ParticleSun::create();
sun->setScale(1.5);
sun->setPosition(850, 550);
addChild(sun);//
//auto starbg = ParticleSystemQuad::create("snow.plist");
//starbg->setPosition(450, 600);
//addChild(starbg);
3.2.5僵尸进场定时函数
auto SFC = SpriteFrameCache::getInstance();
SFC->addSpriteFramesWithFile("roadZombie.plist");
auto animation = Animation::create();
char name[20];
for (int i = 1; i <= 21; i++)
{
sprintf(name, "roadZombie_%d.png", i);
animation->addSpriteFrame(SFC->getSpriteFrameByName(name));
}
animation->setDelayPerUnit(0.1);
animation->setLoops(-1);
auto animate = Animate::create(animation);
auto roadZombie = Sprite::createWithSpriteFrameName("roadZombie_1.png");
roadZombie->setPosition(900, 100);
addChild(roadZombie, 0, "roadZombie");
auto move = MoveBy::create(100, Vec2(-1000, 0));
auto gameover = CallFunc::create(CC_CALLBACK_0(StartScene::GameOver, this));
auto seq = Sequence::create(move, gameover, NULL);
roadZombie->runAction(seq);
roadZombie->runAction(animate);
动作类moveby是指移动到指定的距离,创建CallFunc对象,绑定定义好的回调函数GameOver,在精灵执行完动作后,触发另一个动作。Sequence类让多个动作依次执行,形成一连串的动作,最后让精灵roadZombie执行这一串动作。
3.2.6回调函数GameOver
void StartScene::GameOver()
{
OverMenu();
CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("losemusic.wav");
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("scream.wav");
unschedule(schedule_selector(StartScene::shootBean));
unscheduleUpdate();
removeChildByName("roadZombie");
}僵尸越过屋子后执行函数overMenu,播放指定音乐音效,停止定时器并注销,移除僵尸精灵。
3.2.7豌豆发射器时间函数
void StartScene::shootBean(float dt)
{
auto beanShooter = getChildByName("beanShooter");
auto bean = Sprite::create("bean.png");
bean->setPosition(beanShooter->getPositionX() + 50, beanShooter->getPositionY() + 30);
auto move = MoveTo::create(3, Vec2(920, bean->getPositionY()));
auto removeBean = CallFunc::create(CC_CALLBACK_0(StartScene::removeBeanCB, this));
auto seq = Sequence::create(move, removeBean, NULL);
bean->runAction(seq);
addChild(bean, 0, "bean");
void StartScene::removeBeanCB()
{
removeChildByName("bean");
}
创建豆的精灵,设置好图片素材bean.png,豌豆初始位置相对豌豆射手的坐标为(50,30),豌豆水平飞行至右边界,水平坐标为920处,飞行时间为3s,再执行回调函数removeBeanCB移除原来的豌豆。效果为在豌豆射手放置后,每隔3秒时间发射一颗豌豆。
3.2.8豌豆检测器函数
void StartScene::update(float dt)
{
auto bean = getChildByName("bean");
auto roadZombie = getChildByName("roadZombie");
if (!bean || !roadZombie) return;
auto ZombieSize = roadZombie->getContentSize();
auto ZombieRect = Rect(100, 0, ZombieSize.width, ZombieSize.height);
auto beanPos = roadZombie->convertToNodeSpace(bean->getPosition());
if (ZombieRect.containsPoint(beanPos))
{ CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("shoot.wav");
ZombieHP--;
if (ZombieHP == 0)
{
unschedule(schedule_selector(StartScene::shootBean));
unscheduleUpdate();
removeChildByName("roadZombie");
WinMenu();
}
auto beanSplat = Sprite::create("pea_splats.png");
beanSplat->setPosition(bean->getPosition());
addChild(beanSplat);
auto fadeout = FadeOut::create(0.5);
beanSplat->runAction(fadeout);
removeChildByName("bean");
}
检测豌豆是否打中僵尸。若打中,进行下面处理:在当前豌豆位置,添加显示豆渣,并执行淡出动作,自行销毁删除豌豆,播放shoot.wav音效,设置僵尸减血Zombiehp--,若僵尸血为0,显示菜单,停止豌豆发射器和检测器,删除僵尸。
3.2.9僵尸血量显示函数
hp = 10;
auto hpLabel = Label::create("10", "Arial", 30);
hpLabel->setAnchorPoint(Vec2(1, 1));
hpLabel->setPosition(800, 550);
addChild(hpLabel, 0, "hpLabel");
void StartScene::ZombieHpshow(float dt)
{
auto hpLabel = (Label*)getChildByName("hpLabel");
char s[15];
sprintf(s, "ZombieHp=%d", hp);
hpLabel->setString(s);
}
3.2.10菜单功能
void StartScene::WinMenu()
{
if (!getChildByName("menu"))
{
auto resumeLabel = Label::create("Next", "Arial", 30);
auto resumeMI = MenuItemLabel::create(resumeLabel, this, menu_selector(StartScene::resume));
resumeMI->setPosition(-100, 100);
auto exitLabel = Label::create("Exit", "Arial", 30);
auto exitMI = MenuItemLabel::create(exitLabel, this, menu_selector(StartScene::exit));
exitMI->setPosition(100, 100);
auto menu = Menu::create(resumeMI, exitMI, NULL);
addChild(menu, 0, "menu");
}
else
getChildByName("menu")->setVisible(true);
}
void StartScene::OverMenu()
{
if (!getChildByName("menu"))
{
auto resumeLabel = Label::create("Lose!", "Arial", 30);
auto resumeMI = MenuItemLabel::create(resumeLabel, this, menu_selector(StartScene::resume));
resumeMI->setPosition(-100, 100);
auto exitLabel = Label::create("Exit", "Arial", 30);
auto exitMI = MenuItemLabel::create(exitLabel, this, menu_selector(StartScene::exit));
exitMI->setPosition(100, 100);
auto menu = Menu::create(resumeMI, exitMI, NULL);
addChild(menu, 0, "menu");
}
else
getChildByName("menu")->setVisible(true);
}当僵尸被击败或僵尸越过房子后出现菜单,菜单包含两个标签类菜单项,内容分别为Next和Exit。Next和Exit采用系统自带的Arial字体,大小为40;next的坐标为(-100,0);Exit的坐标为(100,0), 点击Next,重新启动僵尸进场定时器,卡片重置为未选状态,豌豆射手不可见,重新播放background.wav,并且进入下一个场景NextScene;点击Exit 退出程序。
- 测试
初始界面
游戏开始场景
豌豆发射动画和僵尸进场动画
豌豆发射检测器,攻击僵尸僵尸减血,数值显示在标签上。
击败僵尸弹出菜单选项进行下一关或者退出。
卡片的选取状态显示
豆渣出现
僵尸越过屋子,游戏失败。
- 总结
本次交互技术开发项目实践cocos类课设时间做的时间大概花了我半个月吧,但是这次课设让我学到了很多很多东西并且巩固了对Cocos引擎的理解,提高了动手实践能力。
开始的时候吧,也算是一点头绪都没有,要求是开发语言C++,可这咱也没系统学习过C++语言鸭。那然后我去不停得翻看上年学习cocos的书看老师的课件,问同学,上网查资料,这也豁然开朗了点。首先,在开发前,我需要先配置好环境。主要开发环境是visual studio2013;然后是Cocos Studio,cocos项目创建的强力辅助工具,少不了;然后是CocosFrameWork,作为cocos核心引擎,包含着不少cocos开发需要的各类函数。
接着是选择游戏主题,大概对自己的能力和创意担忧吧,我根据老师课件里的示例程序选择了植物大战僵尸,经典的一款游戏了。为满足基本要求,我先构思好游戏框架,需要额外添加中英文标签,音乐,两个场景(开始场景和游戏场景),一种交互功能,一种动画效果和一种定时器;拓展功能的我考虑了瓦片地图和粒子系统设计。
此外,这次开发过程里,我还学习了如何使用粒子编辑器ParticleEditor,瓦片地图编辑器Tiled和图片打包软件TexturePacker。因此此次开发主题是植物大战僵尸,我也在www.6m5m.com上找了些素材,但是都是得要钱得。吝啬的我只能每天签到得积分,于是半个月了我终于攒积分购买一些资源素材。
接着说一下我遇到的一些问题,刚开始做场景时编译后报错,说SimpleAudioEngine找不到,错误原因是没有导入#include "SimpleAudioEngine.h"头文件,即没有引用using namespace CocosDenshion;命名空间。然后在用VS2017编译后的工程,再用VS2013运行时报出如下错误:fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏,解决方法如下:项目\属性\配置属性\清单工具\输入和输出\嵌入清单:原来是“是”,改成“否”。另外,在完成项目之时,我在打包APK时发现打包失败,刚开始我以为是设置的SDK和NDK目录路径不正确,但调整后依然报错。后面在CSDN博客查了很久,发现要在proj.android/jni/路径下换一个android.mk,如果还报错就proj.android/assets里的fonts删掉,重新打包。
收获任何一门知识的掌握,仅靠学习理论知识是远远不够的,要与实际动手操 作相结合才能达到功效。在这次项目开发中,虽然对专业知识有了更深的理解,但是自己还是觉得学习编程任重道远。在上网查资料过程中,发现我在课程学习里使用的很多东西都是“过时”的、浅薄的。我需要学习的还有很多,也认识到不断学习不断实践的重要性。