玩游戏的大小孩开发整理笔记:平台cocos2d-x 2.2.6。联网版本,第一版本暂时单人游戏,开发从开始到上线用时一个月。
遇到的难点:客户端
1:鱼和子弹碰撞问题
2:鱼游动倾斜问题
3:UI动画
服务器:
1:鱼路线设计
2:鱼群
3:数据延迟问题
4:数据同步
整体设计:通过协议方式,传输数据,通过协议头,客户端或者服务器响应响应的功能。子弹客户端先于服务器,鱼服务器先于客户端。点击屏幕发子弹,服务器产生鱼。CS同时存在list记录子弹和鱼。防止假数据和数据同步。
客户端:
1碰撞问题:未使用cocos自带的物理引擎,用了OBB包围盒,添加了圆和其他形状的扩展。
2鱼游动倾斜问题:
设置了鱼类,通过鱼游动过程中会不挺的设置位置,于前一个位置比较设置倾斜量,重写了setPosition()
代码如下:
void UIFish::setPosition(const CCPoint& pos)
{
CCPoint l_ccp = this->getPosition();
if (l_ccp.y == pos.y)
{
if (pos.x > l_ccp.x)
{
this->setRotation(0);
}
else if (pos.x < l_ccp.x)
{
this->setRotation(180);
}
}
else if (l_ccp.y < pos.y)
{
float rota = 0;
if (l_ccp.x == pos.x)
{
this->setRotation(-90);
}
else if (l_ccp.x < pos.x)
{
float k = (l_ccp.y - pos.y) / (l_ccp.x - pos.x);
rota = 180 * atan(k) / M_PI;
this->setRotation((-1)*rota);
}
else
{
float k = (l_ccp.y - pos.y) / (l_ccp.x - pos.x);
rota = 180 * atan(k) / M_PI;
this->setRotation((-1)*rota - 180);
}
}
else
{
float rota = 0;
if (l_ccp.x == pos.x)
{
this->setRotation(90);
}
else if (l_ccp.x < pos.x)
{
float k = (l_ccp.y - pos.y) / (l_ccp.x - pos.x);
rota = 180 * atan(k) / 3.1415926;
this->setRotation((-1)*rota);
}
else
{
float k = (l_ccp.y - pos.y) / (l_ccp.x - pos.x);
rota = 180 * atan(k) / 3.1415926;
this->setRotation(180 - rota);
}
}
m_obb->setcenterpoint(pos.x, pos.y);
m_obb->setRotation(getRotation());
CCSprite::setPosition(pos);
}
3:UI动画
那不是全靠美工么。
服务器:
本文开始于9月中 现在已经是11/11 过去。2.0版本已经上了 ,2.1开发已经开发大半勒。
所以并不打算写上文中的东西了,打算在1.0中遇到的问题:
被刷币两次。
1:描述:我的捕鱼里面,一颗子弹打中两条鱼,那么每天鱼捕获的概率就会下降到原来的1/2。
在这个基础上,刷币:子弹碰撞了2条,每次碰撞都会有碰撞信息(鱼信息,子弹信息),其中子弹信息里面有这颗子弹碰撞到了几条鱼。用户把这个值用模拟器改成了,那么概率恒变高。刷币成功。(ps:讨厌安卓用户 哎)。
解决思路,服务器子弹list 没碰撞以前有crash_count值-1;一旦有第一条消息到时,给定值N,然后每次这颗子弹就在crash_count自减。那么当用户改了数据以后,第一次的子弹信息碰撞改成了1 ,第二次的消息就被抛弃了。
for (t_ite_bullet = m_Plist_Bullet_user.begin(); t_ite_bullet != m_Plist_Bullet_user.end(); t_ite_bullet++)
{
t_bullet_temp = *t_ite_bullet;
if (t_bullet_temp->_tag == struct_data->_int_bullet_tag)
{
if (t_bullet_temp->_crash_count == -1)
t_bullet_temp->_crash_count = struct_data->_int_count_crash;
t_bullet_temp->_crash_count--;
if (t_bullet_temp->_crash_count == -1)
{
t_bullet_exist = false;
m_Plist_Bullet_user.erase(t_ite_bullet);
t_bullet_temp->_crash_count =- 1;
m_Plist_Bullet_wait.push_back(t_bullet_temp);
show_output(74, struct_data->_int_count_crash, struct_data->_int_fish_type);
break;
}
t_bullet_exist = true;
break;
}
}
2:描述:10倍的炮,当发送的时候,用户把值改成了1000倍。那么回报也多了100倍。
问题:(这里所有的钱,以服务器为准,客户端显示)子弹是点击屏幕然后发射的,换炮有两种模式,A(点击换,客户端换,然后发送服务器)B(点击换,服务器响应,通知客户换)这里无论哪种都会造成数据的不一致,在一直发炮的情况下B玩家点了换这个时候发送的是10,但是服务器收到了换的通知,接着收到子弹消息,子弹变成了1000了。A会有假数据。这里解决是粗暴的:一旦子弹的权重和服务器炮台的权重不同,这颗子弹直接抛弃。
3:统计数据不一致,我有2套统计,数据一套放到服务器内存,一套每次结算的时候写入数据库(1~2)分钟写入一次,目的是多桌子,不定时分流,减小服务器压力。
统计不一致原因:1,子弹还在飞,客户端强退。2救济金 3比较有意思,就是快速发射一串子弹碰撞同一条鱼,服务器收到了好多碰撞信息,在前几条,如第一条的时候,鱼已经被捕获了,活动的鱼列表中那条鱼已经消失了,那条鱼已经被清空(相当于鱼初始化了),放入了等待队列。后面 的子弹检查不到鱼,变成了假数据。解决方案:建立一个捕获队列,当前是10,当时真实的子弹的时候,上诉情况监测捕获队列。很好的统计了一部分子弹的消耗。
2.0版本增加:鱼阴影,炮台重做,能量炮,大鱼来的时候,小鱼害怕逃跑,自动发炮,子弹屏幕回弹,暂停(产品取消了,可能2.x上),背景切换,音乐切换模式改等。
比较有意思的一些注意点,子弹碰撞屏幕,就是用数学的方式求斜率K,点斜-k,求运动路线。这里有种特殊情况,正好子弹的顶点,数学上要注意。
小鱼害怕:那么需要不同的计时器老控制不同鱼的速度。
m_sched_small_fish = new CCScheduler();
defaultScheduler->scheduleUpdateForTarget(m_sched_small_fish, 0, false);
m_actionmanager_small_fish = new CCActionManager();
m_sched_small_fish->scheduleUpdateForTarget(m_actionmanager_small_fish, 0, false);
m_sched_big_fish = new CCScheduler();
defaultScheduler->scheduleUpdateForTarget(m_sched_big_fish, 0, false);
m_actionmanager_big_fish = new CCActionManager();
m_sched_big_fish->scheduleUpdateForTarget(m_actionmanager_big_fish, 0, false);
//对不同的鱼--设置不同的动作管理器
t_fish->setActionManager(m_actionmanager_small_fish);
这个是我一开始没有找到的方法。是在看CCnode源码的时候发现的。ps:英语好太重要了,可惜太差,不然也不用找半天了
CCActionManager *m_pActionManager; ///< a pointer to ActionManager singleton, which is used to handle all the actions CCActionManager *m_pActionManager;
鱼影音用shader做的,最近也在看。好难,到时候重新写博客仅仅关于shader的相关。网上资料找的看的真累。
背景切换,其实好low但是,没想到好方法。效果地图1 地图2,先显示地图1,然后潮汐过来地图2慢慢显示,当然2不是移动过来,而是显示。会有左半边地图1,潮汐,右半边地图2的这样显示。
解决方法:开始是背景A(没看到),一个大的遮罩B(真实的背景),当要换的时候。B遮罩放大,慢慢挡住B的地图,露出A的地图。结束后AB地图背景设置,AB数据初始化。
初始化背景
int t_int_bg_id = rand() % 6;
CCString* t_str_background = CCString::createWithFormat("buyudaren/UI/bg_%d.png", t_int_bg_id);
CCString* t_str_bg = CCString::createWithFormat("buyudaren/UI/bg_%d.png", (t_int_bg_id+1)%6);
m_int_sound_id = 0;
m_img_background_new = UIImageView::create();
m_img_background_new->loadTexture(t_str_background->getCString());
m_img_background_new->setAnchorPoint(ccp(0.5,0));
addChild(m_img_background_new);
m_img_background_old = UIImageView::create();
m_img_background_old->loadTexture(t_str_bg->getCString());
m_img_background_old->setAnchorPoint(ccp(0.5, 0));
m_int_bg_id = t_int_bg_id;
CCSize the_back_size = m_img_background_new->getSize();
float float_scale_width = the_size.width / the_back_size.width;
float float_scale_height = the_size.height / the_back_size.height;
float float_scale = float_scale_height > float_scale_width ? float_scale_height : float_scale_width;
float float_ca = float_scale*the_back_size.width / 2 - the_size.width / 2;
m_img_background_new->setScale(float_scale);
m_img_background_new->setPosition(ccp(the_size.width / 2, 0));
m_img_background_old->setScale(float_scale);
m_img_background_old->setPosition(ccp(-the_size.width-40-float_ca, -the_size.height / 2));
m_img_background_old->setAnchorPoint(ccp(0,0));
stencil = CCSprite::create("buyudaren/UI/kongbai.png");
CCSize the_stencil_size = stencil->getContentSize();
stencil->setAnchorPoint(ccp(1,0.5));
m_clip_wave = CCClippingNode::create();
m_clip_wave->setInverted(true);
m_clip_wave->setAnchorPoint(ccp(0, 0));
m_clip_wave->setPosition(ccp(the_size.width+40, the_size.height / 2 ));
m_clip_wave->setStencil(stencil);
CCNode::addChild(m_clip_wave,1,10);
m_clip_wave->addChild(m_img_background_old);
移动:
CCScaleTo* t_scale = CCScaleTo::create(3.0f, (the_size.width + 40) / 10);
stencil->runAction(t_scale);
CCMoveTo* t_move_wave = CCMoveTo::create((364+the_size.width)/((the_size.width+40)/3), ccp(-364, 0));
CCSequence* l_seq_wave = CCSequence::create(t_move_wave, CCCallFunc::create(this, callfunc_selector(layer_game::action_wave_end)),NULL);
m_sprite_wave->runAction(l_seq_wave);
这里提一句:换潮汐的时候,鱼应该是没有的,而服务器应该是在产生鱼的。即,客户段收到消息后统一放入消息池,队列的形式响应消息。当潮汐的时候应该阻塞消息分发。潮汐结束,继续开始消息分发。
后话:
捕鱼很关键的一点是动作,cocos提供了很好的动作管理器类:CCActionManager和动作CCAction,了解他们就能写出复杂的动作,比如气泡破裂等。正在整理下几节分享。sheder太难,刚刚摸到皮毛,有读者看到了如果能告诉资料啥的就太好了。2.0开发时读了烈火鸟网络科技公司的《cocos-2d高级开发教程–制作自己的《捕鱼达人》》程序上帮助不大,思路上帮助不错。。