【cocos2d-x游戏开发】 从零单排之(二)

时间:2023-02-07 21:27:38

这一章,我们要实现游戏开始菜单(其实只有一个按钮O(∩_∩)O~),然后给游戏场景添加人物和地图背景。先剧透一下今天要实现的效果

【cocos2d-x游戏开发】 从零单排之(二)

首先,介绍一下要实现的功能,进入游戏之后,有一个开始界面,点击里面的开始游戏按钮,进入游戏界面,游戏里有向后滚动的背景,和跑动的主角。 我们先根据需求,把需要的类定义出来,然后一个一个去实现。

SceneStart 游戏开始界面
SceneGame 游戏场景,人物地图等等都显示在这里面
|
|------------GameCamera 游戏视角(就是我们在屏幕上看见的区域)
|
|------------Player  玩家
|
|------------MapBg 地图背景


一 实现开始界面:SceneStart
我们先定义出需要实现的方法
class SceneStart:public cocos2d::Scene
{
public:
SceneStart();
~SceneStart();
//初始化方法
bool init();
//按钮点击回调
void menuItemClicked(cocos2d::Ref*);
//获取实例方法
static SceneStart* create();
private:
};
下面是具体的实现
#include "SceneStart.h"
#include "Utils.h"
#include "SceneGame.h"
USING_NS_CC;
SceneStart::SceneStart()
{

}

SceneStart::~SceneStart()
{
}

SceneStart* SceneStart::create()
{
SceneStart* tscene = new SceneStart();
if(tscene && tscene->init())
{
tscene->autorelease();
return tscene;
}
return nullptr;
}

bool SceneStart::init()
{
if(Scene::init())
{
//添加开始按钮
MenuItemFont* titem = MenuItemFont::create(Utils::getInstance()->G2U("开始游戏"),CC_CALLBACK_1(SceneStart::menuItemClicked,this));
Menu* tmenu = Menu::createWithItem(titem);
addChild(tmenu);

return true;
}
return false;
}

//开始按钮点击回调
void SceneStart::menuItemClicked(cocos2d::Ref*)
{
SceneGame* tgame = SceneGame::create();
Director::getInstance()->replaceScene(tgame);
}
这里要注意一点,Cocos2d-x里支持的语言是UTF-8格式,但windows里的汉语文字gb2312,我们需要手动用转换成unicode格式。我这里直接用了一个网上找的方法char* Utils::G2U(const char* gb2312)  ,定义在了Utils.h中,下面贴出实现方法
char* Utils::G2U(const char* gb2312)  
{
int len = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len+1];
memset(wstr, 0, len+1);
MultiByteToWideChar(CP_ACP, 0, gb2312, -1, wstr, len);
len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len+1];
memset(str, 0, len+1);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
if(wstr) delete[] wstr;
return str;
}

二 游戏视口 GameCamera,因为地图可能很大,玩家角色也可能在地图上的任何位置出现,但我们的屏幕大小是固定的,只能显示出游戏世界的一部分,所以,我们抽象出这个游戏视角类,来走位玩家看游戏世界的窗口。
#include "cocos2d.h"
class GameCamera:public cocos2d::Ref
{
public:
GameCamera();
~GameCamera();
static GameCamera* getInstance();
//视口的位置和大小
const cocos2d::Rect getRect();
//设置水平移动速度(暂时只实现水平移动)
void setSpeed(float speed);
//充值视口位置
void reset();
//随时间刷新
void refresh(float dt);
private:
static GameCamera* s_instance;
float m_time;
float m_speed;
cocos2d::Rect m_rect;
};
#include "GameCamera.h"
USING_NS_CC;
GameCamera* GameCamera::s_instance = nullptr;
GameCamera::GameCamera():m_time(0),m_rect(Rect(0,0,960,570)),m_speed(0)
{
}
GameCamera::~GameCamera()
{
}
GameCamera* GameCamera::getInstance()
{
if(!s_instance)
{
s_instance = new GameCamera();
}
return s_instance;
}
void GameCamera::setSpeed(float speed)
{
m_speed = speed;
}
void GameCamera::reset()
{
m_rect.origin = m_rect.origin.ZERO;
}
const Rect GameCamera::getRect()
{
return m_rect;
}
void GameCamera::refresh(float dt)
{
m_time += dt;
m_rect.origin.x = m_time * m_speed;
}

三 循环滚动的地图 MapBg , 因为主角要在X方向上一直跑动,所以地图要在横向上能循环接上的,我们这里用2张同样的图片,前后相接,实现地图的循环滚动。
class MapBg:public cocos2d::Sprite
{
public:
MapBg();
~MapBg();
bool init();
static MapBg* create();
//根据视口的位置更新地图显示
void updateByCameraPos(float posx,float posy);
private:
cocos2d::Sprite* m_bg1;
cocos2d::Sprite* m_bg2;
};
MapBg* MapBg::create()
{
MapBg* tbg = new MapBg();
if(tbg && tbg->init())
{
tbg->autorelease();
return tbg;
}else
{
CC_SAFE_DELETE(tbg);
}
return nullptr;
}

bool MapBg::init()
{
if(Sprite::init())
{
Texture2D* tex = TextureCache::getInstance()->addImage("map.jpg");

//因为2张地图背景是来自同一个texture,所以可以使用SpriteBatchNode同时渲染,
//这样opengl在渲染时可以只draw一次,提高渲染效率
SpriteBatchNode* batch = SpriteBatchNode::createWithTexture(tex);
addChild(batch);

m_bg1 = Sprite::createWithTexture(tex);
m_bg1->setAnchorPoint(Point(0,0));
m_bg2 = Sprite::createWithTexture(tex);
m_bg2->setAnchorPoint(Point(0,0));

batch->addChild(m_bg1);
batch->addChild(m_bg2);

return true;
}
return false;
}

void MapBg::updateByCameraPos(float posx,float posy)
{
float twidth = m_bg1->getContentSize().width;
int dis = static_cast(posx)%static_cast(twidth);
m_bg1->setPositionX(-dis);
m_bg2->setPositionX(m_bg1->getPosition().x + twidth);
}

四 跑动的主角 Player
主角的跑动动作是用一个3帧的序列帧循环播放实现的,(这里为了节省时间没有实现站立、跑动、跳跃等动作的切换,统一使用了一个跑动动作)。动画的实现有很多种办法,这里用的是每隔一定的时间间隔改变Sprite的Frame。也可以用Sprite自带的runAction,通过传给action系统一个定义好的animate来实现(其实是因为我开始没有想到用这个o(╯□╰)o)。
主角在游戏世界里,就要有世界坐标,我们用m_x和m_y这2个世界坐标,来区别主角在opengl坐标系上的坐标;然后为主角的移动,增加speedx和speedy 2个属性,这样,我们就可以在update方法里,刷新主角的坐标了(世界坐标+速度*时间 计算出世界坐标后,在根据视口的位置,更新主角在屏幕上的显示位置);
class Player:public cocos2d::Sprite
{
public:
Player(float posx,float posy);
virtual ~Player();
virtual bool init();
void update(float dt);
static Player* create(float posx,float posy);
private:
float timelast;
int m_currentFrame;
float m_x;
float m_y;
float m_speedY;
float m_speedX;
void updateViewByCurrentFrame();
};
bool Player::init()
{
Texture2D* tex = Director::getInstance()->getTextureCache()->addImage("role.png");
SpriteFrame* tframe;
char* tname = new char[10];
for(int i = 0 ;i < 3 ;i++)
{
tname = new char[10];
sprintf(tname,"body%d",i+1);
tframe = SpriteFrame::createWithTexture(tex,Rect(82*i,0,82,103));
SpriteFrameCache::getInstance()->addSpriteFrame(tframe,tname);
}

if(Sprite::initWithSpriteFrameName("body1"))
{
this->scheduleUpdate();
return true;
}
return false;
}

Player* Player::create(float posx,float posy)
{
Player* tp = new Player(posx,posy);
if(tp && tp->init())
{
tp->autorelease();
return tp;
}else
{
CC_SAFE_DELETE(tp);
}
return nullptr;
}

void Player::update(float dt)
{
Sprite::update(dt);

timelast += dt;
//play movie
if(timelast > 0.2f)
{
timelast -= 0.2f;

++m_currentFrame;
if(m_currentFrame > 3)
{
m_currentFrame = 1;
}

updateViewByCurrentFrame();
}
//position change
float targetX = m_x + m_speedX*dt;
float targetY = m_y + m_speedY*dt;
GameCamera* camera = GameCamera::getInstance();
const Rect trect = camera->getRect();
this->setPosition(targetX - trect.origin.x,targetY - trect.origin.y);
}

void Player::updateViewByCurrentFrame()
{
char* texName = new char[10];
sprintf(texName,"body%d",m_currentFrame);
this->setSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName(texName));
}

五 都到游戏场景里来 GameScene ,把之前写好得游戏元素都添加到游戏场景中。
#include "Player.h"
class SceneGame:public cocos2d::Scene
{
public:
SceneGame();
~SceneGame();

static SceneGame* create();
bool init();
void update(float dt);
private:
MapBg* m_map;
Player* m_player;
};
bool SceneGame::init()
{
if(Scene::init())
{
//启动刷新
this->scheduleUpdate();

//初始化游戏视角
GameCamera::getInstance()->setSpeed(50);

//添加地图
m_map = MapBg::create();
addChild(m_map);

//添加人物
m_player = Player::create(600,110);
addChild(m_player);

return true;
}
return false;
}

void SceneGame::update(float dt)
{
Scene::update(dt);

//刷新游戏视口
GameCamera* camera = GameCamera::getInstance();
camera->refresh(dt);

//更新地图
const Rect trect = camera->getRect();
m_map->updateByCameraPos(trect.origin.x,trect.origin.y);
}



本章内容到这里就结束了,下一章,我们将会给主角加上奔跑跳跃的能力。