实验2 游戏交互界面设计(贪食豆)

说明:

课程教材《计算机游戏程序设计》(基础篇)(第3版) 提供示例代码,而课程实验在示例代码的基础上提出更高的实验要求。除此之外,本人也会额外加入些个人创意,希望同学们在参考之余也能加入自己的想法。

实现效果:

 

实验报告:

一、实验目的与要求

1.熟悉交互界面设计原理。

2.了解Cocos2d-x中的用户交互、触摸事件、碰撞检测机制。

二、实验内容与方法

1.完成游戏编译(70分)

按照“计算机游戏程序设计-第二次试验.pdf”文件“源码实验结果”,成功编译并运行本次实验游戏。

2.完成方案一 (15分)

修改游戏代码,实现方案一,即贪食豆在屏幕范围内能上下左右移动。

3.完成方案二 (15分)

修改游戏代码,实现方案二,即增加计分板功能。

三、实验步骤与过程

记录关键步骤/设计过程/设计结果的截图

1、  按实验一的步骤创建工程及拖入相关文件,编译运行。初始图如下图所示:

图 1

此时游戏能实现鼠标点击拖动摇杆令移动精灵左右移动(且不越界),以及移动机灵“吃掉”小球的功能。

 

2、  修改游戏代码,使贪食豆在屏幕范围能360度全方位移动(不仅仅是上下左右)。

观察Joystick.h源代码,发现其声明了一个枚举类型enum Joystick_dir{_LEFT,_RIGHT,_STOP}; 再结合其余文件的代码可知,该枚举类型的作用是用来判断移动精灵的左右移动的。

然而,我打算设计一种“直接根据摇杆的移动角度 来确定 移动精灵的移动方向”的新的角色移动方案,替代掉上面这种方向枚举的方法。

 

设计思路:

Joystick.h摇杆文件加入一个新的私有变量angle,以及获取该变量值的共有函数getAngle() ;变量angle用来记录摇杆中下图所示的角度(准确说是记录弧度),取值范围是:-π ~ π   

(其实不计算弧度直接传原始值也可以进行后续的计算,这里只是为了方便说明)

图 2

然后在HelloWorldScene.cpp文件中获取angle值,根据角度(弧度)值,我们就可以确定角色(移动精灵)的移动方向了,设其每次的移动距离为6,则可确定其在x轴和y轴上的移动距离了。

同时,移动时也不要忘了检测移动精灵与边界的碰撞。确保角色在将要越界时,其对应坐标要在边界上。

 

核心代码:

Joystick.cpp:

// 获取摇杆力度

float Joystick::getVelocity()

{

  return m_centerPoint.getDistance(m_currentPoint);

}

//获取角度

float Joystick::getAngle()

{

  return angle;

}

// 更新摇杆按钮位置以及移动角度

void Joystick::update(float dt)

{

    m_jsSprite->setPosition(m_jsSprite->getPosition() + (m_currentPoint - m_jsSprite->getPosition()) * 0.5);

 

   //反余弦结果只能是正值,所以要判断上下象限确定角度(弧度)的正负。(-pi~pi)

   if(m_currentPoint.y >= m_centerPoint.y)

  angle = acos((m_currentPoint.x - m_centerPoint.x) / Joystick::getVelocity());

   else

  angle = -acos((m_currentPoint.x - m_centerPoint.x) / Joystick::getVelocity());

}

 

HelloWorldScene.cpp:

void HelloWorld::update(float dt){

for (auto ball:ballVector)

{

//进行碰撞检测

if (bean->getBoundingBox().intersectsRect(ball->getBoundingBox()))

{

HelloWorld::updateScore(ball);

auto actionDown =

CallFunc::create(CC_CALLBACK_0(HelloWorld::removeBall, this, ball));

ball->runAction(actionDown);

}

}

 

// 控制角色移动

float angle = m_joystick->getAngle();   //角色移动角度

float xx = 6 * cos(angle);   //x轴移动距离

float yy = 6 * sin(angle);   //y轴移动距离

 

//判断是否到边界,是 则沿边界移动

if (xx > 0)

{

if (bean->getPositionX() + bean->getContentSize().width / 2 + xx <= 680)

bean->setPositionX(bean->getPositionX() + xx);

else

bean->setPositionX(680 - bean->getContentSize().width / 2);

}

else if (xx < 0)

{

if (bean->getPositionX() - bean->getContentSize().width / 2 + xx >= 200)

bean->setPositionX(bean->getPositionX() + xx);

else

bean->setPositionX(200 + bean->getContentSize().width / 2);

}

else {}

if (yy > 0)

{

if (bean->getPositionY() + bean->getContentSize().height / 2 + yy <= 768)

bean->setPositionY(bean->getPositionY() + yy);

else

bean->setPositionY(768 - bean->getContentSize().height / 2);

}

else if (yy < 0)

{

if (bean->getPositionY() - bean->getContentSize().height / 2 + yy >= 0)

bean->setPositionY(bean->getPositionY() + yy);

else

bean->setPositionY(0 + bean->getContentSize().height / 2);

}

else {}

}

 

3.  增加计分板。

设计思路:

在HelloWorldScene.cpp文件的addBall函数中创建小球的精灵后,通过ball->setTag(int)给小球精灵设置识别标签。

当在update函数中通过遍历所有的球,检验出角色与小球相碰后,跳到updateScore(ball)函数,通过getTag() 获取tag,然后识别角色碰到是哪一个球,再增加适当的分数(白球增加10分,红球50分,绿球100分)。

关于得分的显示则可用Label类型的Label::createWithSystemFont(...)来实现 ,并且每次更新得分后,通过windows提供的itoa函数把得分变量score转换为字符串类型,再用Label->setString(str) 设置新的得分内容即可。

计分板如下图所示:

图 3

核心代码:

HelloWorldScene.cpp:

bool HelloWorld::init()

{

  ...

      //添加得分层

  auto labelScore =

Label::createWithSystemFont("Score:", "Consolas", 30, Size(100, 100));

  labelScore->setPosition(Vec2(60, 200));

  this->addChild(labelScore, 1);

  score = 0;

  label = Label::createWithSystemFont("0", "Consolas", 30, Size

  (100, 100), TextHAlignment::RIGHT);

  label->setPosition(Vec2(130,200));

this->addChild(label, 1, 100);

  ...

}

 

//球1

void HelloWorld::addBall1(float dt){

  auto ball1 = Sprite::create("Chapter10/ball.png");//使用图片创建小球

  ball1->setPosition(Point(220 + rand() % 440, visibleSize.height));//设置小球的初始位置,这里在x方向使用了随机函数rand使得小球在随机位置出现

  ball1->setTag(1);

  this->addChild(ball1, 5);

  this->ballVector.pushBack(ball1);//将小球放进Vector数组

  auto moveTo = MoveTo::create(rand() % 5 + 1, Point(ball1->getPositionX(), -10));

  auto actionDone = CallFunc::create(CC_CALLBACK_0(HelloWorld::removeBall,

this, ball1));//当小球移动到屏幕下方时回调removeBall函数,移除小球

  auto sequence = Sequence::create(moveTo, actionDone, nullptr);

  ball1->runAction(sequence);//执行动作

}

 

//........球2、球3......省略

//更新得分

void HelloWorld::updateScore(Sprite* ball)

{

  char str[20];

  if (ball->getTag() == 1)

   score += 10;

  else if (ball->getTag() == 2)

   score += 50;

  else if (ball->getTag() == 3)

   score += 100;

  else if (ball->getTag() == 4)

   HelloWorld::gameover();

  itoa(score, str, 10);

  label->setString(str);   //分数转成字符串类型再更新分数显示

}

 

void HelloWorld::update(float dt){

  for (auto ball:ballVector)

  {

  //进行碰撞检测

  if (bean->getBoundingBox().intersectsRect(ball->getBoundingBox()))

     {

   HelloWorld::updateScore(ball);

auto actionDown =

CallFunc::create(CC_CALLBACK_0(HelloWorld::removeBall, this, ball));

ball->runAction(actionDown);

  }

}

.......

}

 

 

 

以下为个人附加内容:

 

4.  不固定摇杆位置。

设计思路:

通过init函数初始化摇杆Joystick时,随意为摇杆设置一个位置,但是通过精灵的setVisible(false)函数给摇杆设置隐藏属性。

下图显示初始状态摇杆被隐藏看不见:

图 4

 

当我们用鼠标点击游戏屏幕时,监听器监听到该事件,调用函数onTouchBegan(Touch *pTouch, Event *pEvent) 。在该函数中,如果鼠标点击位置在设定的范围之内,我们则把该点设定为摇杆新的中心点m_centerPoint ,并通过setVisible(true)重新显示摇杆精灵。

此时我们就看到在指定的游戏屏幕范围内,以鼠标点击位置为中心,隐藏的摇杆出现了。

下面两图显示鼠标点击不同位置,摇杆出现的位置不同:

图 5

图 6

而监听鼠标移动的调用函数onTouchMoved则与源代码相同。

当鼠标松开后,onTouchEnded函数将被调用,此时再通过setVisible(false)把摇杆隐藏掉即可。

另外,为了扩大摇杆的可使用范围,摇杆可能会出现在小球和移动精灵所在的游戏屏幕上。因此,为避免摇杆遮挡实际的游戏画面,通过setOpacity(90)函数把摇杆图片设置为半透明。

 

核心代码:

// 初始化 aPoint是摇杆中心 aRadius是摇杆半径 aJsSprite是摇杆控制点 aJsBg是摇杆背景

bool Joystick::init(Vec2 aPoint , float aRadius , char* aJsSprite, char* aJsBg)

{

.......

       m_jsSprite = Sprite::create(aJsSprite);//摇杆控制点

    m_jsSprite->setPosition(m_centerPoint);

    m_jsSprite->setOpacity(90);    //避免图片遮挡,摇杆图片加点透明化

    m_jsSprite->setVisible(false);   //默认情况下摇杆要隐藏

 

    _aJsBg = Sprite::create(aJsBg);//摇杆背景

    _aJsBg->setPosition(m_centerPoint);

    _aJsBg->setTag(88);

    _aJsBg->setOpacity(90);

_aJsBg->setVisible(false);

    this->addChild(_aJsBg);

this->addChild(m_jsSprite);

.......

}

bool Joystick::onTouchBegan(Touch *pTouch, Event *pEvent)

{

    auto touchPoint = pTouch->getLocation();

    //限定摇杆的范围

    if (touchPoint.x < m_radius || touchPoint.x > (400 - m_radius) || touchPoint.y < m_radius || touchPoint.y >(768 - m_radius))

    {

    return false;

    }

    m_centerPoint = touchPoint;

    m_currentPoint = touchPoint;

    m_jsSprite->setPosition(m_centerPoint);  //鼠标点击位置即为摇杆位置

    _aJsBg->setPosition(m_centerPoint);

 

    //摇杆设为可视

    m_jsSprite->setVisible(true);

    _aJsBg->setVisible(true);

    return true;

}

 

void Joystick::onTouchMoved(Touch *pTouch, Event *pEvent)

{

    auto touchPoint = pTouch->getLocation();

    if (touchPoint.getDistance(m_centerPoint) > m_radius)

    {

    m_currentPoint = m_centerPoint + (touchPoint - m_centerPoint).getNormalized() * m_radius;

    }else {

       m_currentPoint = touchPoint;

    }

}

 

void Joystick::onTouchEnded(Touch *pTouch, Event *pEvent)

{

    m_currentPoint = m_centerPoint;

    //摇杆操作结束,隐藏摇杆

    m_jsSprite->setVisible(false);

    _aJsBg->setVisible(false);

}

 

5. 游戏结束画面。

设计思路:

由于原本游戏没有结束标志,而是一直运行,因此给原游戏设计一个令游戏结束的机制。

在原来白红绿三球的基础上,加上第四个球——炸弹。“炸弹”如下图红圈中所示:

图 7

当移动角色碰到炸弹时,角色通过setVisible(false)函数隐藏,并创建新的精灵——“爆炸”图案,在当前角色的位置显示,以表示“角色碰到炸弹后爆炸”。

此时,通过this->unscheduleAllSelectors()停止所有的定时器调用,停止后就新的球就不会再生成了。

并且,通过Label::createWithSystemFont(...)创建新的文字显示,内容为“Game Over!”。

当前状态即为游戏结束状态,如下图所示:

图 8

核心代码:

//游戏结束

void HelloWorld::gameover() {

  this->unscheduleAllSelectors();  //停止所有的定时器

  auto boom2 = Sprite::create("Chapter10/boom2.png"); //创建角色碰到炸弹后爆炸的精灵

  boom2->setPosition(bean->getPosition());   //根据移动精灵的位置设定爆炸精灵的位置

  this->addChild(boom2, 5);

  bean->setVisible(false);   //隐藏移动精灵

  //创建“Game Over!”的label

  auto labelGameover =

Label::createWithSystemFont("Game Over!", "Consolas", 60, Size(500, 100));

  labelGameover->setPosition(Vec2(visibleSize.width/2 + 15, visibleSize.height/2));

  this->addChild(labelGameover, 6);

}

 

6.  其余。

源代码中各个地方都有一些小修改。如小球和移动角色的初始位置的相关参数,以及一些小bug。

这里举一个HelloWorldScene.cpp文件中修改的一个小bug,其余内容不再一一细述。

在创建小球的函数addBall中,有这么一条创建小球移动动作的代码:

auto moveTo = MoveTo::create(rand() % 5, Point(ball1->getPositionX(), -10));

其中,create的第一个参数描述了小球持续动作的时间。很明显,当rand()%5的随机值取到0时,游戏逻辑是错误的,运行程序后,会发现动作持续时间为0的小球出现一下就消失了,并没有掉落动作。

所以,把该代码中的“rand()%5”修改为“rand()%5 + 1”。

猜你喜欢

转载自blog.csdn.net/purers/article/details/82382321
今日推荐