学游戏开发最重要的是刚开始就能做出个小游戏,这才能激发兴趣。 那么,今天咱们就来做一个小游戏–忍者来袭。网上有不少相关的文章(我参考的是这篇文章),但是很多已经不适用V4.0了。所以我在V4.0下重新写了一下。

话不多说,进入正题。 先看一下最终效果: 在这里插入图片描述 这就是我们目标,开始吧!

我们就在HelloWorldScene.cpp和HelloWorldScene.h里面改写代码。 首先把HelloWorldScene.cpp里的init函数,就留下面这些,其他都删除。

bool HelloWorld::init(){//// 1. super init firstif ( !Scene::init() ){return false;}// My code herereturn true;}

1.我们设置一下舞台背景,为灰色,看起来清晰一些。

auto winSize = Director::getInstance()->getVisibleSize();auto origin = Director::getInstance()->getVisibleOrigin();// set Background with grey colourauto background = DrawNode::create();background->drawSolidRect(origin, winSize, cocos2d::Color4F(0.6, 0.6, 0.6, 1.0));this->addChild(background);

2.请出我们的主角–忍者精灵。 在这里插入图片描述 大家可以下载这个图片,保存到Resource里。 创造精灵分3步,还记得吧。(定义,设定位置,加到场景) 因为我们的player不光是用在init()函数中的,其他的函数也会用到,所以我们在HelloWorldScene.h中定义_player为一个私有变量。

//HelloWorldScene.hprivate:cocos2d::Sprite* _player;//HelloWorldScene.cpp// Add player_player = Sprite::create("player.png");_player->setPosition(Vec2(winSize.width * 0.1, winSize.height * 0.5));this->addChild(_player);

这样,我们的_player就站好位置了。 在这里插入图片描述 3.增加敌人 在这里插入图片描述 我们做一个方法,专门来生成。不要忘了在头文件中声明一下哦。

void HelloWorld::addMonster(float dt) {auto monster = Sprite::create("monster.png");// Add monsterauto monsterContentSize = monster->getContentSize();auto selfContentSize = this->getContentSize();int minY = monsterContentSize.height / 2;int maxY = selfContentSize.height - minY;int rangeY = maxY - minY;int randomY = (rand() % rangeY) + minY;monster->setPosition(Vec2(selfContentSize.width + monsterContentSize.width/2, randomY));this->addChild(monster);// Let monster runint minDuration = 2.0;int maxDuration = 4.0;int rangeDuration = maxDuration - minDuration;int randomDuration = (rand() % rangeDuration) + minDuration;// 定义移动的object// 在randomDuration这个时间内(2-4秒内),让怪物从屏幕右边移动到左边。(怪物有快有慢)auto actionMove = MoveTo::create(randomDuration, Vec2(-monsterContentSize.width / 2, randomY));// 定义消除的Object。怪物移出屏幕后被消除,释放资源。auto actionRemove = RemoveSelf::create();monster->runAction(Sequence::create(actionMove, actionRemove, nullptr));}

这里面的知识点是物体移动,有两个移动类: MoveTo:是在多长时间内,移动到指定的点。 MoveBy:是在当前位置移动多少步。 MoveSelf:是为了释放这个精灵的资源,虽然精灵走出了画面,你要是一直不删除,那么精灵越来越多,系统就会崩溃的。 runAction:就是让物体移动起来。

之后,我们需要再生成Player后面调用这个方法:

// 初始化了随机数生成器。如果不执行这一步,每次运行程序都会产生一样的随机数。srand((unsigned int)time(nullptr));// 每隔1.5秒生成一个怪物this->schedule(CC_SCHEDULE_SELECTOR(HelloWorld::addMonster), 1.5);

schedule为我们提供一个连续动作的功能,用起来非常方便。 这样我们的敌人就出场了: 在这里插入图片描述 4.发射飞镖 在这里插入图片描述 我们发射飞镖是由点击屏幕触发的。这涉及到事件的概念。cocos使用EventDispatcher来处理各种各样的事件,如触摸和其他键盘事件。为了从EventDispatcher中获取事件,你需要注册一个EventListener,它有两种触摸事件的监听器:

EventListenerTouchOneByOne:此类型对每个触摸事件调用一次回调方法。EventListenerTouchAllAtOnce:此类型对所有的触摸事件调用一次回调方法。

每个事件监听器支持4个回调,但你只需要为自己关心的事件绑定方法。

onTouchBegan:手指第一次碰到屏幕时被调用。如果你使用的是EventListenerTouchOneByOne,你必须返回true才能获取另外3个触摸事件。onTouchMoved:手指接触屏幕并移动(保持接触)时被调用。onTouchEnded:手指离开屏幕时被调用。onTouchCancelled:在特定的结束事件处理的环境中被调用,如你正在触屏的时候,一个电话打了进来打断了这个app进程。在本游戏中,你只用关心触摸发生的时间就好了。

我们先在头文件中声明回调函数:

// HelloWorldScene.hbool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* unused_event);

然后实现它:

// HelloWorldScene.cppbool HelloWorld::onTouchBegan(Touch* touch, Event* unused_event) {// 1 - Just an example for how to get the player object// 说明一下作为第二个参数传递给addEventListenerWithSceneGraphPriority(eventListener, _player)的_player对象被访问的方式。// auto node = unused_event->getcurrentTarget();// 2.获取触摸点的坐标,并计算这个点相对于_player的偏移量。Vec2 touchLocation = touch->getLocation();Vec2 offset = touchLocation - _player->getPosition();// 如果offset的x值是负值,这表明玩家正试图朝后射击。在本游戏中这是不允许的。if (offset.x return false;}// 初始化Physicsif (!Scene::initWithPhysics()){return false;}

b.给敌人和飞镖都加上physicsBody。这样我们精灵对象就承载了他们的物理属性。我们就可以用物理碰撞检测了。 分别在敌人和飞镖的addChild前,加上下面的代码:

// Add monster's physicsBodyauto physicsBody = PhysicsBody::createBox(monster->getContentSize(), PhysicsMaterial(0.0f, 0.0f, 0.0f));physicsBody->setDynamic(false);physicsBody->setContactTestBitmask(0xFFFFFFFF);monster->setPhysicsBody(physicsBody);// Add projectile's physicsBodyauto physicsBody = PhysicsBody::createBox(projectile->getContentSize(), PhysicsMaterial(0.0f, 0.0f, 0.0f));physicsBody->setDynamic(false);physicsBody->setContactTestBitmask(0xFFFFFFFF);projectile->setPhysicsBody(physicsBody);projectile->setTag(10);

c.碰撞也是事件,那么我们这回应该注册一个什么样的Listener呢,是EventListenerPhysicsContact(Contact有联系人的意思,这里我们可以理解为接触)。这个监听器里也有4个回调函数:

onContactBegin:It will called at two shapes start to contact, and only call it once.onContactPreSolve:Two shapes are touching during this step.onContactPostSolve:Two shapes are touching and their collision response has been processed.onContactSeparate:It will called at two shapes separated, and only call it once.

在发射飞镖处理后,加入下面代码:

// 碰撞检测auto contactListener = EventListenerPhysicsContact::create();contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin, this);_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

d.我们选用第一个回调函数,并实现它。内容就是当飞镖击中敌人的时候,把敌人删除。

bool HelloWorld::onContactBegin(cocos2d::PhysicsContact& contact){auto nodeA = contact.getShapeA()->getBody()->getNode();auto nodeB = contact.getShapeB()->getBody()->getNode();if (nodeA && nodeB){if (nodeA->getTag() == 10){nodeB->removeFromParentAndCleanup(true);}else if (nodeB->getTag() == 10){nodeA->removeFromParentAndCleanup(true);}}return true;}

上面的代码稍微解释一下。当两个物体碰撞后,会调用这个方法。首先取得两个Node,因为是回调函数,你确定不了哪个是A哪个是B。所以,我在做成它们PhysicsBody的时候,只有飞镖setTag(10)了,这样哪个getTag是10,哪个就是飞镖,另一个就是敌人,然后删除敌人就OK了。 关于碰撞里面的BitMask(比特掩码)是如何应用的,可以参考官网Collision好好学学。这里就不多介绍了。

其实检测碰撞不只这一种方法,这是物理碰撞检测。还有一种方法就是图形边缘检测–就是之前用的很广的intersectsRect()。现在这个也能用,但是你需要进行更多的计算位置来满足你想要的条件,比较费脑。

好了,游戏初版就这样了。再运行一下,试试你的杀敌效果吧。 在这里插入图片描述 完整代码(Gitee)

你还可以继续关注,忍者来袭升级版。

(苏州铁艺楼梯)