自己设计一个 横版过关手游的游戏。具体步骤

推荐:等级5
等级:5.4(97个好评)
《雨血》系列虽然不是一款非常顶级作品,但是面对目前国内日渐下降的制作水准,《雨血》系列让我们再一次对国产的武侠风格有了期望。从发布到现在,几部作品都得到非常好的评价与反响,这绝对是一款值得你去玩一下的国产武侠系列。
【游戏介绍】《雨血前传:蜃楼
推荐:等级3
等级:8.7(582个好评)
        胧村正妖刀传是MMV-i在2009年发布的一款横版动作过关游戏,游戏在画面的华丽程度,和游戏打击感,连击方面表现的都非常出色,喜欢dnf的朋友,这款游戏一定不能错过。【故事背景】     
推荐:等级5
等级:7.5(48个好评)
《雷曼:传奇》是育碧公司于2013年发行的横版动作《雷曼:起源》续作,在本作中增加了大量精妙而又颇具娱乐性的游戏模式、新的角色和新的环境。游戏中包含有各种各样具有独特主题风格的关卡,包括几张用于打造创意音轨的音乐地图和一个令人毛骨悚然的中世纪主题地图。玩家也可以与好友联机,进行一系列技巧、速度方面的挑战。
推荐:等级5
等级:7.9(23个好评)
提到‘雷曼’很多人的脑中都会想起一群疯疯癫癫的兔子大肆搞破坏的场景。《雷曼》系列近几年已经演变成疯兔那样的恶搞作品,2012年育碧发行的《雷曼:起源》终于又回归到了动作游戏的征途,游戏的故事模式支持最大四人合作通关,在游戏中玩家可以解锁新的技能,并使用新技能重新挑战之前的关卡以重新发现新的道路和隐藏道具等。
推荐:等级5
等级:7.5(12个好评)
魔幻三杰2 讲的是 法师,武士和女飞贼 三个主人公的故事。
共有六个关卡,还有适用于原始游戏的新技能,以及提升后的画面性能。
一直以惊人的画面而著称的魔幻三杰2在9月6日发布了他们的最新dlc哥布林的威胁,这次的dlc中,不仅仅是将画面的品质再一次提升,
推荐:等级3
等级:8.7(14个好评)
《时空幻境》是一款奇幻题材的横版冒险闯关游戏,玩家们在游戏中通过控制主角,在形形色色的关卡中,寻找公主。【游戏介绍】由Jonathan Blow开发的小游戏《Braid》PC版延期到4月10日发售。千万别小看这款小游戏!这款游戏曾经在2006年IGF独立游戏节(Independe
推荐:等级5
等级:6.7(19个好评)
这款原本宣布为360独占的游戏,如今推出了它的PC版。与《忍者印记》和《闪客》风格不同的是,它不是一款刺杀游戏,也是一款暴力美学的游戏,而是一款为了生存而与丧尸较量的游戏。
【游戏介绍】世界末日来临,没有遗留任何希望,也没有新纪元。只有这些幸存者。
推荐:等级3
等级:9.1(73个好评)
&命运召唤尔茄的精灵石是2009年Lizsoft发行的一款少女动作冒险游戏,这是,艾尔切和使用水属性魔法的温柔的Sana,以及使用火属性魔法的才色兼备的大小姐Stella三个少女的小小冒险故事。游戏制作精美,虽然是2D横版形式展现,但是处处都显示出制作团队的用心,
推荐:等级3
等级:10.0(0个好评)
《吉娜姐妹》有着跟《三位一体》系列一样的画面,但是没有那样复杂的解密,玩家不需要通过三个不同的角色来不断切换,只需要切换自己的形态来完成游戏的解密即可。【游戏介绍】《吉娜姐妹:扭曲梦境 - 猫头鹰领主(Giana Sisters: Twisted Dreams - Rise of th
推荐:等级3
等级:7.1(10个好评)
由世嘉公司推出的全新一款横版冒险游戏《哦耶!愤怒的死亡兔子》,通过夸张的表现手法,极富有创新力和想象力,饱满的游戏色彩带来极致的冒险体验。
【游戏介绍】《哦耶!愤怒的死亡兔子》是世嘉推出的新动作冒险游戏,玩家将会在游戏中扮演一只全副武装的兔子,并
推荐:等级5
等级:8.3(5个好评)
游戏是一款支持双人合作的横版过关游戏,虽然看起来有点像地狱边境的感觉,但是明朗的色彩以及独特的音乐给玩家带来的却是完全不一样的感受。
【游戏介绍】&《皮德》是一款通过数字发行的冒险游戏,背景设定在一个幻想的世界里,玩家要在一个遥远的星球在展
推荐:等级6
等级:4.4(202个好评)
《闪克2》是2010年横版动作游戏黑马《闪克》的续作,基于一代的创作灵感与基础,二代更进一步的增强了“合作模式”,增加了“合作生存模式”的玩法。在画面上比一代更加美丽,采用以2D为主+部分的3D效果,音乐多以摇滚乐为主,操作更加灵活,在战斗上,玩家可以通过战斗而获取敌人的武器来使用,并非一代只能使用已解锁的现有武器,整体效果和感觉与横版的PC《忍者龟》相似。
推荐:等级3
等级:2.3(18个好评)
闪客是最近新出的一款非常火爆的2D横版闯关游戏,整个画面以美式漫画的形式展现,饱满丰富,打斗的动作非常流畅,连击感觉很爽。不过游戏的时候注意,简单模式可以存档,但是困难模式以一命通关,而且不能存档的。由Klei Entertainment制作EA发行的2D横版动作游
推荐:等级5
等级:7.7(93个好评)
《忍者印记》是Klei Entertainment在2013年制作的一款2D横版冒险游戏,与‘闪客’相同的采用欧美漫画风格的方式呈现,游戏因为以暗杀为主,所以当玩家单独面对游戏中的敌人时,玩家将非常难以应付,如果你同时面对两个敌人,那就几乎只有GAMEOVER的结果了。侧重于潜行的游戏风格,将会把游戏的耐玩性大大提高。游戏大部分在黑暗中进行,充分阐释了黑暗中忍者的行动,通过攀爬,聆听在感知敌人。
推荐:等级3
等级:8.6(228个好评)
《地狱边境》是一款具有鲜明特色的2D横版过关类游戏,充满神秘的游戏世界,通过黑白两种颜色展现的诡异世界,让玩家们体验到不一样的游戏魅力。【游戏介绍】整个游戏主轴没有任何故事元素贯串,甚至没有出现任何对白,这个作法相当大胆。但是《地狱边境》将细节
推荐:等级3
等级:10.0(2个好评)
《火箭鸟:铁汉雄鸡》又名《鸡本无情》,玩家在游戏中操纵一个体积庞大,动作灵敏的雄鸡来进入敌方基地,消灭敌人。
【游戏介绍】加坡华人团队Ratllop Asia制作最新3D横向卷轴动作过关游戏。游戏作为经典横轴冒险游戏来说是完全符合条件,但在战斗体验的创新方面
推荐:等级3
等级:4.7(8个好评)
超级食肉男孩是一款独立益智游戏,游戏使用传统的平台型2D版面动作游戏,创意独特,难度不低。玩家将扮演一坨肉感十足的玩意,独立闯过各种设计巧妙的陷阱及刑具,称的上是一款快节奏,高难度的多平台汤汁四溢的游戏。23560人阅读
3.1 Cocos2d-x(22)
& & & & 在第一篇《》基础上,增加角色运动、碰撞、敌人、AI和音乐音效,原文《》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。
步骤如下:
1.使用上一篇的工程;
2.移动英雄。在第一部分我们创建了虚拟方向键,但是还未实现按下方向键移动英雄,现在让我们进行实现。打开Hero.cpp文件,在init函数attack animation后面,添加如下代码:
//walk&animation
CCArray&*walkFrames&=&CCArray::createWithCapacity(8);
for&(i&=&0;&i&&&8;&i++)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&hero_walk_%02d.png&,&i)-&getCString());
&&&&walkFrames-&addObject(frame);
CCAnimation&*walkAnimation&=&CCAnimation::createWithSpriteFrames(walkFrames,&float(1.0&/&12.0));
this-&setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation)));
打开ActionSprite.cpp文件,实现walkWithDirection方法,代码如下:
void&ActionSprite::walkWithDirection(CCPoint&direction)
&&&&if&(_actionState&==&kActionStateIdle)
&&&&&&&&this-&stopAllActions();
&&&&&&&&this-&runAction(_walkAction);
&&&&&&&&_actionState&=&kActionStateW
&&&&if&(_actionState&==&kActionStateWalk)
&&&&&&&&_velocity&=&ccp(direction.x&*&_walkSpeed,&direction.y&*&_walkSpeed);
&&&&&&&&if&(_velocity.x&&=&0)
&&&&&&&&&&&&this-&setScaleX(1.0);
&&&&&&&&}&
&&&&&&&&else
&&&&&&&&&&&&this-&setScaleX(-1.0);
这段代码,检查前置动作状态是否空闲,若是的话切换动作到行走。在行走状态时,根据_walkSpeed值改变精灵速度。同时检查精灵的左右方向,并通过将精灵scaleX设置为1或-1来翻转精灵。要让英雄的行走动作跟方向键联系起来,需要借助方向键的委托:GameLayer类。打开GameLayer.cpp文件,实现如下方法:
void&GameLayer::didChangeDirectionTo(SimpleDPad&*simpleDPad,&CCPoint&direction)
&&&&_hero-&walkWithDirection(direction);
void&GameLayer::isHoldingDirection(SimpleDPad&*simpleDPad,&CCPoint&direction)
&&&&_hero-&walkWithDirection(direction);
void&GameLayer::simpleDPadTouchEnded(SimpleDPad&*simpleDPad)
&&&&if&(_hero-&getActionState()&==&kActionStateWalk)
&&&&&&&&_hero-&idle();
此时,编译运行程序的话,通过方向键移动英雄,发现英雄只是原地踏步。改变英雄的位置是ActionSprite和GameLayer共同的责任。一个ActionSprite永远不会知道它在地图上的位置。因此,它并不知道已经到达了地图的边缘,它只知道它想去哪里。而GameLayer的责任就是将它的期望位置转换成实际的位置。打开ActionSprite.cpp文件,实现以下方法:
void&ActionSprite::update(float&dt)
&&&&if&(_actionState&==&kActionStateWalk)
&&&&&&&&_desiredPosition&=&ccpAdd(this-&getPosition(),&ccpMult(_velocity,&dt));
这个方法在每次游戏更新场景的时候都会进行调用,当精灵处于行走状态时,它更新精灵的期望位置。位置+速度*时间,实际上就是意味着每秒移动X和Y点。打开GameLayer.cpp文件,在init函数this-&initTileMap();后面添加如下代码:
this-&scheduleUpdate();
在析构函数,添加如下代码:
GameLayer::~GameLayer(void)
&&&&this-&unscheduleUpdate();
增加如下两个方法:
void&GameLayer::update(float&dt)
&&&&_hero-&update(dt);
&&&&this-&updatePositions();
void&GameLayer::updatePositions()
&&&&float&posX&=&MIN(_tileMap-&getMapSize().width&*&_tileMap-&getTileSize().width&-&_hero-&getCenterToSides(),
&&&&&&&&MAX(_hero-&getCenterToSides(),&_hero-&getDesiredPosition().x));
&&&&float&posY&=&MIN(3&*&_tileMap-&getTileSize().height&+&_hero-&getCenterToBottom(),
&&&&&&&&MAX(_hero-&getCenterToBottom(),&_hero-&getDesiredPosition().y));
&&&&_hero-&setPosition(ccp(posX,&posY));
设定GameLayer的更新方法,每次循环时,GameLayer让英雄更新它的期望位置,然后通过以下这些值,将期望位置进行检查是否在地图地板的范围内:
mapSize:地图tile数量。总共有10x100个tile,但只有3x100属于地板。
tileSize:每个tile的尺寸,在这里是32x32像素。
GameLayer还使用到了ActionSprite的两个测量值,centerToSides和centerToBottom,因为ActionSprite要想保持在场景内,它的位置不能超过实际的精灵边界。假如ActionSprite的位置在已经设置的边界内,则GameLayer让英雄达到期望位置,否则GameLayer会让英雄留停在原地。
3.编译运行,此时点击方向键,移动英雄,如下图所示:
但是,很快你就会发现英雄可以走出地图的右边界,然后就这样从屏幕上消失了。
4.以上的问题,可以通过基于英雄的位置进行滚动地图,这个方法在文章《》中有描述过。打开GameLayer.cpp文件,在update函数里最后添加如下代码:
this-&setViewpointCenter(_hero-&getPosition());
添加如下方法:
void&GameLayer::setViewpointCenter(CCPoint&position)
&&&&CCSize&winSize&=&CCDirector::sharedDirector()-&getWinSize();
&&&&int&x&=&MAX(position.x,&winSize.width&/&2);
&&&&int&y&=&MAX(position.y,&winSize.height&/&2);
&&&&x&=&MIN(x,&(_tileMap-&getMapSize().width&*&_tileMap-&getTileSize().width)&-&winSize.width&/&2);
&&&&y&=&MIN(y,&(_tileMap-&getMapSize().height&*&_tileMap-&getTileSize().height)&-&winSize.height&/&2);
&&&&CCPoint&actualPosition&=&ccp(x,&y);
&&&&CCPoint&centerOfView&=&ccp(winSize.width&/&2,&winSize.height&/&2);
&&&&CCPoint&viewPoint&=&ccpSub(centerOfView,&actualPosition);
&&&&this-&setPosition(viewPoint);
以上代码让英雄处于屏幕中心位置,当然,英雄在地图边界时的情况除外。编译运行,效果如下图所示:
5.创建机器人。我们已经创建了精灵的基本模型:ActionSprite。我们可以重用它来创建游戏中电脑控制的角色。新建Robot类,派生自ActionSprite类,增加如下方法:
CREATE_FUNC(Robot);
bool&init();
打开Robot.cpp文件,init函数代码如下:
bool&Robot::init()
&&&&bool&bRet&=&false;
&&&&&&&&CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName(&robot_idle_00.png&));
&&&&&&&&int&i;
&&&&&&&&//idle&animation
&&&&&&&&CCArray&*idleFrames&=&CCArray::createWithCapacity(5);
&&&&&&&&for&(i&=&0;&i&&&5;&i++)
&&&&&&&&&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(
&&&&&&&&&&&&&&&&CCString::createWithFormat(&robot_idle_%02d.png&,&i)-&getCString());
&&&&&&&&&&&&idleFrames-&addObject(frame);
&&&&&&&&CCAnimation&*idleAnimation&=&CCAnimation::createWithSpriteFrames(idleFrames,&float(1.0&/&12.0));
&&&&&&&&this-&setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));
&&&&&&&&//attack&animation
&&&&&&&&CCArray&*attackFrames&=&CCArray::createWithCapacity(5);
&&&&&&&&for&(i&=&0;&i&&&5;&i++)
&&&&&&&&&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(
&&&&&&&&&&&&&&&&CCString::createWithFormat(&robot_attack_%02d.png&,&i)-&getCString());
&&&&&&&&&&&&attackFrames-&addObject(frame);
&&&&&&&&CCAnimation&*attackAnimation&=&CCAnimation::createWithSpriteFrames(attackFrames,&float(1.0&/&24.0));
&&&&&&&&this-&setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation),&CCCallFunc::create(this,&callfunc_selector(Robot::idle)),&NULL));
&&&&&&&&//walk&animation
&&&&&&&&CCArray&*walkFrames&=&CCArray::createWithCapacity(6);
&&&&&&&&for&(i&=&0;&i&&&6;&i++)
&&&&&&&&&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(
&&&&&&&&&&&&&&&&CCString::createWithFormat(&robot_walk_%02d.png&,&i)-&getCString());
&&&&&&&&&&&&walkFrames-&addObject(frame);
&&&&&&&&CCAnimation&*walkAnimation&=&CCAnimation::createWithSpriteFrames(walkFrames,&float(1.0&/&12.0));
&&&&&&&&this-&setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation)));
&&&&&&&&this-&setWalkSpeed(80.0);
&&&&&&&&this-&setCenterToBottom(39.0);
&&&&&&&&this-&setCenterToSides(29.0);
&&&&&&&&this-&setHitPoints(<span style="color:#ff.0);
&&&&&&&&this-&setDamage(10.0);
&&&&&&&&bRet&=&true;
&&&&}&while&(0);
&&&&return&bR
跟英雄一样,以上代码创建一个带有3个动作的机器人:空闲、出拳、行走。它也有两个测量&#20540;:centerToBottom和centerToSides。注意到机器人的属性比英雄低一点,这是合乎逻辑的,不然英雄永远打不赢机器人。让我们开始添加一些机器人到游戏中去。打开GameLayer.h文件,添加如下代码:
CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*,&_robots,&Robots);
打开GameLayer.cpp文件,添加头文件如下:
#include&&Robot.h&
在构造函数里,添加如下代码:
_robots&=&NULL;
在init函数this-&initTileMap();的后面添加如下代码:
this-&initRobots();
添加如下方法:
void&GameLayer::initRobots()
&&&&int&robotCount&=&50;
&&&&this-&setRobots(CCArray::createWithCapacity(robotCount));
&&&&for&(int&i&=&0;&i&&&robotC&i&#43;&#43;)
&&&&&&&&Robot&*robot&=&Robot::create();
&&&&&&&&_actors-&addChild(robot);
&&&&&&&&_robots-&addObject(robot);
&&&&&&&&int&minX&=&SCREEN.width&&#43;&robot-&getCenterToSides();
&&&&&&&&int&maxX&=&_tileMap-&getMapSize().width&*&_tileMap-&getTileSize().width&-&robot-&getCenterToSides();
&&&&&&&&int&minY&=&robot-&getCenterToBottom();
&&&&&&&&int&maxY&=&3&*&_tileMap-&getTileSize().height&&#43;&robot-&getCenterToBottom();
&&&&&&&&robot-&setScaleX(-1);
&&&&&&&&robot-&setPosition(ccp(random_range(minX,&maxX),&random_range(minY,&maxY)));
&&&&&&&&robot-&setDesiredPosition(robot-&getPosition());
&&&&&&&&robot-&idle();
这些代码做了以下事情:
创建一个包含50个机器人的数组,并把它们添加到精灵表单中。使用Defines.h里面的随机函数随机放置50个机器人到地图地板上。同时,让最小随机&#20540;大于屏幕宽度,以确保不会有任何机器人出现在起点处。让每个机器人都处于空闲状态。
编译运行,让英雄向前走,直到看到地图上的机器人,如下图所示:
试着走到机器人区域中,你会发现机器人的绘制有些不对。如果英雄是在机器人的下面,那么他应该被绘制在机器人的前面,而不是在后面。我们需要明确的告诉游戏,哪个对象先绘制,这就是Z轴来进行控制的。添加英雄和机器人时,并没有明确指定其Z轴,默认下,后面添加的对象会比前面的对象Z轴&#20540;高,这就是为什么机器人挡住了英雄。为了解决这个问题,我们需要动态的处理Z轴顺序。每当精灵在屏幕上垂直移动时,它的Z轴&#20540;应该有所改变。屏幕上越高的精灵,其Z轴&#20540;应越低。打开GameLayer.cpp文件,添加如下方法:
void&GameLayer::reorderActors()
&&&&CCObject&*pObject&=&NULL;
&&&&CCARRAY_FOREACH(_actors-&getChildren(),&pObject)
&&&&&&&&ActionSprite&*sprite&=&(ActionSprite*)pO
&&&&&&&&_actors-&reorderChild(sprite,&(_tileMap-&getMapSize().height&*&_tileMap-&getTileSize().height)&-&sprite-&getPosition().y);
然后在update函数this-&updatePositions();的后面,添加如下代码:
this-&reorderActors();
每当精灵的位置更新,这个方法会让CCSpriteBatchNode重新设置它的每个子节点Z轴&#20540;,其根据子节点离地图底部的距离,当子节点离底部更高时,其Z轴&#20540;就会下降。编译运行,可以看到正确的绘制顺序,如下图所示:
6.出拳猛击机器人,碰撞检测。为了让英雄能够出拳,并且能够实际上打在了机器人身上,需要实现一种方式的碰撞检测。在这篇文章中,我们使用矩形创建一个非常简单的碰撞检测系统。在这个系统中,我们为每个角色定义两种矩形/盒子:
Hit box:代表精灵的身体Attack box:代表精灵的手
假如某个ActionSprite的Attack box碰撞到另一个ActionSprite的Hit box,那么这就是一次碰撞发生。这两个矩形之间的区别,将帮助我们知道谁打了谁。Defines.h文件中的BoundingBox定义,包含两种矩形:实际的,和原始的:
①原始矩形,每个精灵的基本矩形,一旦设置后就不会改变。
②实际矩形,这是位于世界空间中的矩形,当精灵移动时,实际的矩形也跟着变动。
打开ActionSprite.h文件,添加如下代码:
CC_SYNTHESIZE(BoundingBox,&_hitBox,&Hitbox);
CC_SYNTHESIZE(BoundingBox,&_attackBox,&AttackBox);
BoundingBox&createBoundingBoxWithOrigin(cocos2d::CCPoint&origin,&cocos2d::CCSize&size);
以上创建了ActionSprite的两个包围盒:Hit box和Attack box。还定义了一个方法,用于根据给定的原点和大小来创建一个BoundingBox结构体。打开ActionSprite.cpp文件,添加如下方法:
BoundingBox&ActionSprite::createBoundingBoxWithOrigin(CCPoint&origin,&CCSize&size)
&&&&BoundingBox&boundingB
&&&&boundingBox.original.origin&=&
&&&&boundingBox.original.size&=&
&&&&boundingBox.actual.origin&=&ccpAdd(this-&getPosition(),&ccp(boundingBox.original.origin.x,&boundingBox.original.origin.y));
&&&&boundingBox.actual.size&=&
&&&&return&boundingB
void&ActionSprite::transformBoxes()
&&&&_hitBox.actual.origin&=&ccpAdd(this-&getPosition(),&ccp(_hitBox.original.origin.x,&_hitBox.original.origin.y));
&&&&_attackBox.actual.origin&=&ccpAdd(this-&getPosition(),&ccp(_attackBox.original.origin.x&&#43;&
&&&&&&&&(this-&getScaleX()&==&-1&?&(-&_attackBox.original.size.width&-&_hitBox.original.size.width)&:&0),
&&&&&&&&_attackBox.original.origin.y));
void&ActionSprite::setPosition(CCPoint&position)
&&&&CCSprite::setPosition(position);
&&&&this-&transformBoxes();
第一个方法创建一个新的包围盒,这有助于ActionSprite的子类创建属于它们自己的包围盒。第二个方法,基于精灵的位置、比例因子,和包围盒原本的原点和大小来更新每个包围盒实际测量的原点和大小。之所以要用到比例因子,是因为它决定着精灵的方向。位于精灵右侧的盒子,当比例因子设置为-1时,将会翻转到左侧。打开Hero.cpp文件,在init函数后面添加如下代码:
this-&setHitbox(this-&createBoundingBoxWithOrigin(ccp(-this-&getCenterToSides(),&-this-&getCenterToBottom()),
&&&&CCSizeMake(this-&getCenterToSides()&*&2,&this-&getCenterToBottom()&*&2)));
this-&setAttackBox(this-&createBoundingBoxWithOrigin(ccp(this-&getCenterToSides(),&-10),&CCSizeMake(20,&20)));
打开Robot.cpp文件,在init函数后面添加如下代码:
this-&setHitbox(this-&createBoundingBoxWithOrigin(ccp(-this-&getCenterToSides(),&-this-&getCenterToBottom()),
&&&&CCSizeMake(this-&getCenterToSides()&*&2,&this-&getCenterToBottom()&*&2)));
this-&setAttackBox(this-&createBoundingBoxWithOrigin(ccp(this-&getCenterToSides(),&-5),&CCSizeMake(25,&20)));
现在我们已经有了英雄和机器人各自的Hit box和Attack box。如果是可视化的箱子,它们会像下面这样:
无论何时,当一个attack box(红色)跟一个hit box(蓝色)交叉,即一次碰撞发生。在开始编写代码,检测包围盒交叉前,需要确保ActionSprite能够对被击中有所反应。我们已经添加了空闲、出拳、行走动作,但还未创建受伤和死亡动作。打开ActionSprite.cpp文件,实现如下方法:
void&ActionSprite::hurtWithDamage(float&damage)
&&&&if&(_actionState&!=&kActionStateKnockedOut)
&&&&&&&&this-&stopAllActions();
&&&&&&&&this-&runAction(_hurtAction);
&&&&&&&&_actionState&=&kActionStateH
&&&&&&&&_hitPoints&-=&
&&&&&&&&if&(_hitPoints&&=&0)
&&&&&&&&&&&&this-&knockout();
void&ActionSprite::knockout()
&&&&this-&stopAllActions();
&&&&this-&runAction(_knockedOutAction);
&&&&_hitPoints&=&0;
&&&&_actionState&=&kActionStateKnockedO
只要精灵还未死亡,被击中时状态将会切换到受伤状态,执行受伤动画,并且精灵的生命&#20540;将会减去相应的伤害&#20540;。如果生命&#20540;少于0,那么死亡的动作将会触发。为了完成这两个动作,我们还需更改Hero类和Robot类。打开Hero.cpp文件,在init函数walk
animation后面添加如下代码:
//hurt&animation
CCArray&*hurtFrames&=&CCArray::createWithCapacity(3);
for&(i&=&0;&i&&&3;&i&#43;&#43;)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&hero_hurt_%02d.png&,&i)-&getCString());
&&&&hurtFrames-&addObject(frame);
CCAnimation&*hurtAnimation&=&CCAnimation::createWithSpriteFrames(hurtFrames,&float(1.0&/&12.0));
this-&setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation),&CCCallFunc::create(this,&callfunc_selector(Hero::idle)),&NULL));
//knocked&out&animation
CCArray&*knockedOutFrames&=&CCArray::createWithCapacity(5);
for&(i&=&0;&i&&&5;&i&#43;&#43;)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&hero_knockout_%02d.png&,&i)-&getCString());
&&&&knockedOutFrames-&addObject(frame);
CCAnimation&*knockedOutAnimation&=&CCAnimation::createWithSpriteFrames(knockedOutFrames,&float(1.0&/&12.0));
this-&setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation),&CCBlink::create(2.0,&10.0),&NULL));
打开Robot.cpp文件,在init函数walk animation后面添加如下代码:
//hurt&animation
CCArray&*hurtFrames&=&CCArray::createWithCapacity(3);
for&(i&=&0;&i&&&3;&i&#43;&#43;)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&robot_hurt_%02d.png&,&i)-&getCString());
&&&&hurtFrames-&addObject(frame);
CCAnimation&*hurtAnimation&=&CCAnimation::createWithSpriteFrames(hurtFrames,&float(1.0&/&12.0));
this-&setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation),&CCCallFunc::create(this,&callfunc_selector(Robot::idle)),&NULL));
//knocked&out&animation
CCArray&*knockedOutFrames&=&CCArray::createWithCapacity(5);
for&(i&=&0;&i&&&5;&i&#43;&#43;)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&robot_knockout_%02d.png&,&i)-&getCString());
&&&&knockedOutFrames-&addObject(frame);
CCAnimation&*knockedOutAnimation&=&CCAnimation::createWithSpriteFrames(knockedOutFrames,&float(1.0&/&12.0));
this-&setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation),&CCBlink::create(2.0,&10.0),&NULL));
以上代码应该不陌生了。我们用创建其他动作同样的方式创建了受伤和死亡动作。受伤动作结束时,会切换到空闲状态。死亡动作结束时,精灵进行闪烁。打开GameLayer.cpp文件,添加碰撞处理,在ccTouchesBegan函数后面添加如下代码:
if&(_hero-&getActionState()&==&kActionStateAttack)
&&&&CCObject&*pObject&=&NULL;
&&&&CCARRAY_FOREACH(_robots,&pObject)
&&&&&&&&Robot&*robot&=&(Robot*)pO
&&&&&&&&if&(robot-&getActionState()&!=&kActionStateKnockedOut)
&&&&&&&&&&&&if&(fabsf(_hero-&getPosition().y&-&robot-&getPosition().y)&&&10)
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&if&(_hero-&getAttackBox().actual.intersectsRect(robot-&getHitbox().actual))
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&robot-&hurtWithDamage(_hero-&getDamage());
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&}&&&&&&&
以上代码通过三个简单步骤来检测碰撞:
①.检测英雄是否处于攻击状态,以及机器人是否处于非死亡状态。
②.检测英雄的位置和机器人的位置垂直相距在10个点以内。这表明它们在同一平面上站立。
③.检测英雄的attack box是否与机器人的hit box进行交叉。
如果这些条件都成立,那么则一次碰撞发生,机器人执行受伤动作。英雄的伤害&#20540;作为参数进行传递,这样该方法就会知道需要减去多少生命&#20540;。
7.编译运行,出拳攻击机器人吧,效果如下图所示:
8.简单机器人AI的实现。为了使机器人能够移动,并且能够使用我们为它们所创建的动作,就需要开发一个简单的AI(人工智能)系统。这个AI系统基于决策机制。在特定的时间间隔里,我们给每个机器人一个机会来决定接下来该做什么。它们需要知道的第一件事情就是何时做出选择。打开Robot.h文件,添加如下代码:
CC_SYNTHESIZE(float,&_nextDecisionTime,&NextDecisionTime);
打开Robot.cpp文件,在init函数后面,添加如下代码:
_nextDecisionTime&=&0;
这个属性保存下一次机器人可以作出决定的时间。打开Defines.h文件,修改成如下代码:
#pragma&once
#include&&cocos2d.h&
//&1&-&convenience&measurements
#define&SCREEN&CCDirector::sharedDirector()-&getWinSize()
#define&CENTER&ccp(SCREEN.width&/&2,&SCREEN.height&/&2)
#define&CURTIME&GetCurTime()
//&2&-&convenience&functions
#ifndef&UINT64_C
#define&UINT64_C(val)&val##ui64
#define&random_range(low,&high)&(rand()&%&(high&-&low&&#43;&1))&&#43;&low
#define&frandom&(float)rand()&/&UINT64_C(0x)
#define&frandom_range(low,&high)&((high&-&low)&*&frandom)&&#43;&low
//&3&-&enumerations
typedef&enum&_ActionState&{
&&&&kActionStateNone&=&0,
&&&&kActionStateIdle,
&&&&kActionStateAttack,
&&&&kActionStateWalk,
&&&&kActionStateHurt,
&&&&kActionStateKnockedOut
//&4&-&structures
typedef&struct&_BoundingBox&{
&&&&cocos2d::CCRect&
&&&&cocos2d::CCRect&
}&BoundingB
inline&float&GetCurTime(){
&&&&timeval&
&&&&gettimeofday(&time,&NULL);
&&&&unsigned&long&millisecs&=&(time.tv_sec&*&<span style="color:#ff)&&#43;&(time.tv_usec&/&<span style="color:#ff);
&&&&return&(float)
打开GameLayer.cpp文件,添加如下方法:
void&GameLayer::updateRobots(float&dt)
&&&&int&alive&=&0;
&&&&float&distanceSQ;
&&&&int&randomChoice&=&0;
&&&&CCObject&*pObject&=&NULL;
&&&&CCARRAY_FOREACH(_robots,&pObject)
&&&&&&&&Robot&*robot&=&(Robot*)pO
&&&&&&&&robot-&update(dt);
&&&&&&&&if&(robot-&getActionState()&!=&kActionStateKnockedOut)
&&&&&&&&&&&&//1
&&&&&&&&&&&&alive&#43;&#43;;
&&&&&&&&&&&&
&&&&&&&&&&&&//2
&&&&&&&&&&&&if&(CURTIME&&&robot-&getNextDecisionTime())
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&distanceSQ&=&ccpDistanceSQ(robot-&getPosition(),&_hero-&getPosition());
&&&&&&&&&&&&&&&&//3
&&&&&&&&&&&&&&&&if&(distanceSQ&&=&50&*&50)
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&robot-&setNextDecisionTime(CURTIME&&#43;&frandom_range(0.1,&0.5)&*&<span style="color:#ff);
&&&&&&&&&&&&&&&&&&&&randomChoice&=&random_range(0,&1);
&&&&&&&&&&&&&&&&&&&&if&(randomChoice&==&0)
&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&if&(_hero-&getPosition().x&&&robot-&getPosition().x)
&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&robot-&setScaleX(1.0);
&&&&&&&&&&&&&&&&&&&&&&&&}&
&&&&&&&&&&&&&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&robot-&setScaleX(-1.0);
&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&//4
&&&&&&&&&&&&&&&&&&&&&&&&robot-&setNextDecisionTime(robot-&getNextDecisionTime()&&#43;&frandom_range(0.1,&0.5)&*&<span style="color:#ff);
&&&&&&&&&&&&&&&&&&&&&&&&robot-&attack();&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&if&(robot-&getActionState()&==&kActionStateAttack)
&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&if&(fabsf(_hero-&getPosition().y&-&robot-&getPosition().y)&&&10)
&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&if&(_hero-&getHitbox().actual.intersectsRect(robot-&getAttackBox().actual))
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&_hero-&hurtWithDamage(robot-&getDamage());
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//end&game&checker&here
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&robot-&idle();
&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&else&if&(distanceSQ&&=&SCREEN.width&*&SCREEN.width)
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&//5
&&&&&&&&&&&&&&&&&&&&robot-&setNextDecisionTime(CURTIME&&#43;&frandom_range(0.5,&1.0)&*&<span style="color:#ff);
&&&&&&&&&&&&&&&&&&&&randomChoice&=&random_range(0,&2);
&&&&&&&&&&&&&&&&&&&&if&(randomChoice&==&0)
&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&CCPoint&moveDirection&=&ccpNormalize(ccpSub(_hero-&getPosition(),&robot-&getPosition()));
&&&&&&&&&&&&&&&&&&&&&&&&robot-&walkWithDirection(moveDirection);
&&&&&&&&&&&&&&&&&&&&}&
&&&&&&&&&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&robot-&idle();
&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&//end&game&checker&here
这是一个漫长的代码片段。将代码分解为一段段。对于游戏中的每个机器人:
①.使用一个计数来保存仍然存活着的机器人数量。一个机器人只要不是死亡状态,就被认为仍然存活着。这将用于判断游戏是否应该结束。
②.检查当前应用程序时间的推移是否超过了机器人的下一次决定时间。如果超过了,意味着机器人需要作出一个新的决定。
③.检查机器人是否足够接近英雄,以便于有机会出拳攻击落在英雄身上。如果接近英雄了,那么就进行一个随机选择,看是要朝着英雄出拳,还是要继续空闲着。
④.假如机器人决定攻击,我们就用之前检测英雄攻击时相同的方式来进行检测碰撞。只是这一次,英雄和机器人的角色互换了。
⑤.如果机器人和英雄之间的距离小于屏幕宽度,那么机器人将作出决定,要么朝着英雄移动,要么继续空闲。机器人的移动基于英雄位置和机器人位置产生的法向量。
每当机器人作出决定,它的下一个决定的时间被设定为在未来的一个随机时间。在此期间,它将继续执行上次作出决定时所做出的动作。接着在update函数里,this-&updatePositions();前添加如下代码:
this-&updateRobots(dt);
在updatePositions函数后面,添加如下代码:
CCObject&*pObject&=&NULL;
CCARRAY_FOREACH(_robots,&pObject)
&&&&Robot&*robot&=&(Robot*)pO
&&&&posX&=&MIN(_tileMap-&getMapSize().width&*&_tileMap-&getTileSize().width&-&robot-&getCenterToSides(),
&&&&&&&&MAX(robot-&getCenterToSides(),&robot-&getDesiredPosition().x));
&&&&posY&=&MIN(3&*&_tileMap-&getTileSize().height&&#43;&robot-&getCenterToBottom(),
&&&&&&&&MAX(robot-&getCenterToBottom(),&robot-&getDesiredPosition().y));
&&&&robot-&setPosition(ccp(posX,&posY));
确保每次游戏循环时,机器人AI方法都被调用。遍历每个机器人,并让它们朝着期望的位置进行移动。
9.编译运行,将会看到沿着走廊过来的机器人。效果如下图所示:
10.为游戏添加重新开始的按钮。打开GameLayer.cpp文件,添加头文件引用:
#include&&GameScene.h&
添加如下方法:
void&GameLayer::endGame()
&&&&CCLabelTTF&*restartLabel&=&CCLabelTTF::create(&RESTART&,&&Arial&,&30);
&&&&CCMenuItemLabel&*restartItem&=&CCMenuItemLabel::create(restartLabel,&this,&menu_selector(GameLayer::restartGame));
&&&&CCMenu&*menu&=&CCMenu::create(restartItem,&NULL);
&&&&menu-&setPosition(CENTER);
&&&&menu-&setTag(5);
&&&&_hud-&addChild(menu,&5);
void&GameLayer::restartGame(CCObject*&pSender)
&&&&CCDirector::sharedDirector()-&replaceScene(GameScene::create());
第一个方法创建显示一个重新开始的按钮,当按下它时,触发第二个方法。后者只是命令导演用新的GameScene实例替换当前场景。接着在updateRobots函数里面,在第一个end game checker here注释后面,添加如下代码:
if&(_hero-&getActionState()&==&kActionStateKnockedOut&&&&_hud-&getChildByTag(5)&==&NULL)
&&&&this-&endGame();
在第二个end game checker here注释后面,添加如下代码:
if&(alive&==&0&&&&_hud-&getChildByTag(5)&==&NULL)
&&&&this-&endGame();
这些语句都是检测游戏结束的条件。第一个检测英雄被机器人攻击后,是否还存活着。如果英雄死亡了,那么游戏就结束了。第二个检测是否所有的机器人都死亡了。如果都死亡了,那么游戏也结束了。另外,在endGame方法里,可以看到游戏结束菜单的tag&#20540;为5。因为检测是在循环里面,需要确保游戏结束菜单之前没被创建过。否则的话,将会一直创建游戏结束菜单。
11.编译运行,可以看到游戏结束时的样子,如下图所示:
12.音乐和音效。打开GameLayer.cpp文件,添加头文件引用:
#include&&SimpleAudioEngine.h&
在init函数里,CC_BREAK_IF(!CCLayer::init());后面添加如下代码:
//&Load&audio
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadBackgroundMusic(&latin_industries.aifc&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&playBackgroundMusic(&latin_industries.aifc&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadEffect(&pd_hit0.wav&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadEffect(&pd_hit1.wav&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadEffect(&pd_herodeath.wav&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadEffect(&pd_botdeath.wav&);
打开ActionSprite.cpp文件,添加头文件引用:
#include&&SimpleAudioEngine.h&
在hurtWithDamage函数,第一个条件语句里添加如下代码:
int&randomSound&=&random_range(0,&1);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&playEffect(CCString::createWithFormat(&pd_hit%d.wav&,&randomSound)-&getCString());
打开ActionSprite.h文件,将knockout方法声明修改如下:
virtual&void&knockout();
打开Hero.cpp文件,添加头文件引用:
#include&&SimpleAudioEngine.h&
添加如下方法:
void&Hero::knockout()
&&&&ActionSprite::knockout();
&&&&CocosDenshion::SimpleAudioEngine::sharedEngine()-&playEffect(&pd_herodeath.wav&);
打开Robot.cpp文件,添加头文件引用:
#include&&SimpleAudioEngine.h&
添加如下方法:
void&Robot::knockout()
&&&&ActionSprite::knockout();
&&&&CocosDenshion::SimpleAudioEngine::sharedEngine()-&playEffect(&pd_botdeath.wav&);
13.编译运行,现在游戏将有配乐,效果图:
参考资料:
1.How To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 2
2.如何使用cocos2d制作类&#20284;Scott Pilgrim的2D横版&#26684;斗过关游戏part2(翻译)
非常感谢以上资料,本例子源代码附加资源下载地址:
如文章存在错误之处,欢迎指出,以便改正
对此示例的内存泄露修正说明:《》
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:2725299次
积分:30198
积分:30198
排名:第126名
原创:383篇
转载:88篇
评论:3301条
联系方式:
(1)(1)(1)(3)(1)(1)(1)(1)(1)(1)(1)(2)(1)(1)(1)(1)(1)(3)(1)(1)(1)(1)(2)(1)(3)(3)(3)(1)(1)(3)(1)(1)(8)(1)(3)(2)(2)(3)(2)(3)(2)(1)(1)(2)(3)(6)(1)(4)(3)(1)(3)(5)(5)(5)(5)(1)(3)(5)(4)(4)(4)(5)(5)(1)(13)(11)(7)(5)(2)(5)(4)(5)(2)(7)(14)(18)(23)(19)(5)(35)(22)(21)(8)(10)(42)(49)

我要回帖

更多关于 ps4横版过关游戏 的文章

 

随机推荐