系列文章

《球球大作战》源码解析(7):游戏循环

《球球大作战》源码解析(8):消息广播

服务端处理了游戏的各种游戏逻辑,怎样让小球移动是重点之一。若想做服务端运算的游戏,这一部分代码是很值得参考的。

SetInterval

nodejs中的setInterval是定时器,如下的代码表示每隔1秒执行一次myfunc方法。

setInterval(myfunc,1000);

function myfunc(){

console.log("hehe");

};复制代码

3个定时器

源码中设置了3个定时器,分别是moveloop、gameloop和sendUpdates。其中moveloop每秒执行60次,它主要处理小球的移动计算。

setInterval(moveloop, 1000 / 60);

setInterval(gameloop, 1000);

setInterval(sendUpdates, 1000 / c.networkUpdateFactor);复制代码

moveloop

Moveloop方法的代码如下,它对每个玩家执行tickPlayer,处理每个小球的移动。

function moveloop() {

for (var i = 0; i < users.length; i++) {

tickPlayer(users[i]);

}

for (i=0; i < massFood.length; i++) {

if(massFood[i].speed > 0) moveMass(massFood[i]);

}

}复制代码

moveMass

moveMass就是移动质量,小球可以喷射身体的一部分出去,喷出去的部分变成了食物。每个喷射出去的“质量”都会被保存在massFood列表里。moveMass会处理这些“质量”的运动轨迹,先是喷射出去、然后减速、停止。

image001.png (39.99 KB, 下载次数: 6)

2019-3-6 16:50 上传

moveMass的代码比较简单,先看看这一部分。Mass的运动分是个减速过程,喷射时Mass带有较高的初速度,然后每一帧减速0.5,直到停止。代码后半部分还对Mass撞上边缘的情况做处理。

function moveMass(mass) {

var deg = Math.atan2(mass.target.y, mass.target.x);

var deltaY = mass.speed * Math.sin(deg);

var deltaX = mass.speed * Math.cos(deg);

mass.speed -= 0.5;

if(mass.speed < 0) {

mass.speed = 0;

}

if (!isNaN(deltaY)) {

mass.y += deltaY;

}

if (!isNaN(deltaX)) {

mass.x += deltaX;

}

var borderCalc = mass.radius + 5;

if (mass.x > c.gameWidth - borderCalc) {

mass.x = c.gameWidth - borderCalc;

}

if (mass.y > c.gameHeight - borderCalc) {

mass.y = c.gameHeight - borderCalc;

}

if (mass.x < borderCalc) {

mass.x = borderCalc;

}

if (mass.y < borderCalc) {

mass.y = borderCalc;

}

}复制代码

tickPlayer

tickPlayer是处理玩家移动的核心部分,它处理了小球的移动、分身、吞食,代码也比较长,整体代码结构如下所示,首先做心跳判断,如果客户端太久没有发送心跳协议,那么向客户端发送kick协议,断开连接。其后调用movePlayer,处理玩家移动。

image002.png (42.14 KB, 下载次数: 6)

2019-3-6 16:50 上传

分身的过程

movePlayer计算了小球各个分身的移动过程,在游戏中,玩家可以让小球分身,具体过程如下。

1、玩家点击按钮后,大球分身,分出来的小球沿着移动方向弹射

image003.png (38.36 KB, 下载次数: 6)

2019-3-6 16:50 上传

2、弹射过后,分身们逐步靠近,最终连在一起

image004.png (13.39 KB, 下载次数: 9)

2019-3-6 16:50 上传

3、经过一段时间,分身合并,又成为一个大球

image005.png (10.84 KB, 下载次数: 6)

2019-3-6 16:50 上传

分身的过程分为弹射、靠近、紧靠、合并四个阶段。

怎样表示小球

既然小球有可能分身,那么它便不能单一的由x,y坐标和质量(半径)表示。该项目采用这样的表示方法(伪代码)。如果小球没有分身,那么cells数组长度为1,表示只有1个“细胞”,如果分裂成多个,则cells数组长度增加。player的x和y表示所有cell的中心坐标,由程序计算得出,target是目的地的坐标,即玩家鼠标指向的位置。每个cell都有质量,而cell的半径由质量换算而来,计算公式为radius=4+Math.sqrt(mass)*6。

class player

{

var x;  //中心点坐标x

var y;  //中心点坐标y

var  target.x  //目的地坐标

var  target.y

var[]  cells//分身数组

}

class cells

{

var x;

var y;

var mass; //质量

var radius //半径,由mass换算而来

}复制代码

计算每个cell的目的地

由于每个cell都是独立运动的,每一个cell都有自己的移动目的地,计算每个cell目的地的代码如下。

function movePlayer(player) {

var x =0,y =0;

for(var i=0; i

{

var target = {

x: player.x - player.cells[i].x + player.target.x,

y: player.y - player.cells[i].y + player.target.y

};

……

}

……复制代码……

具体来说,每个cell的目的地就是总目的地的坐标加上它自己相对中心点的偏移,如下图所示。

image006.png (57.4 KB, 下载次数: 3)

2019-3-6 16:50 上传

计算移动速度

分身过程中,每个阶段都有不同的速度,一开始弹射的时候速度很快,然后匀加速减少,减到一定程度后速度沿着对数函数曲线减小。

var dist = Math.sqrt(Math.pow(target.y, 2) + Math.pow(target.x, 2));

var deg = Math.atan2(target.y, target.x);

var slowDown = 1;

if(player.cells[i].speed <= 6.25) {

slowDown = util.log(player.cells[i].mass, c.slowBase) - initMassLog + 1;

}

……

if(player.cells[i].speed > 6.25) {

player.cells[i].speed -= 0.5;

}

……

var deltaY = player.cells[i].speed * Math.sin(deg)/ slowDown;

var deltaX = player.cells[i].speed * Math.cos(deg)/ slowDown;复制代码……

如果接近目的地,cell距离目的地只有50+player.cells.radius的距离。那么调整速度,越接近移动速度越慢。

var dist=Math.sqrt(Math.pow(target.y,2)+Math.pow(target.x,2));

……

if(dist.radius)){

deltaY*=dist/(50+player.cells.radius);

deltaX*=dist/(50+player.cells.radius);

}复制代码

在计算完y方向的移动速度deltaY和x方向的移动速度deltaX后,给cell的位置赋值,完成移动操作。

if (!isNaN(deltaY)) {

player.cells[i].y += deltaY;

}

if (!isNaN(deltaX)) {

player.cells[i].x += deltaX;

}复制代码

合并处理

若分身经过一定时间(其实是以距离来判断),便需要合并,在下面的代码中,它会遍历其他所有的cell,这里分为两种情况。(1)if(distance

for(var j=0; j

if(j != i && player.cells[i] !== undefined) {

var distance = Math.sqrt(Math.pow(player.cells[j].y-player.cells[i].y,2) + Math.pow(player.cells[j].x-player.cells[i].x,2));

var radiusTotal = (player.cells[i].radius + player.cells[j].radius);

if(distance < radiusTotal) {

……(1)

}

else if(distance < radiusTotal / 1.75) {

……(2)

}

}

}

}复制代码

如果distance

if(player.lastSplit > new Date().getTime() - 1000 * c.mergeTimer) {

if(player.cells[i].x < player.cells[j].x) {

player.cells[i].x--;

} else if(player.cells[i].x > player.cells[j].x) {

player.cells[i].x++;

}

if(player.cells[i].y < player.cells[j].y) {

player.cells[i].y--;

} else if((player.cells[i].y > player.cells[j].y)) {

player.cells[i].y++;

}

}复制代码

如果到了合并时间,且distance

player.cells.mass+=player.cells[j].mass;

player.cells.radius=util.massToRadius(player.cells.mass);

player.cells.splice(j,1);复制代码

边界处理

moveplayer后面的代码便是做边界处理,保证cell在边界之内,代码如下所示。

if(player.cells.length > i) {

var borderCalc = player.cells[i].radius / 3;

if (player.cells[i].x > c.gameWidth - borderCalc) {

player.cells[i].x = c.gameWidth - borderCalc;

}

if (player.cells[i].y > c.gameHeight - borderCalc) {

player.cells[i].y = c.gameHeight - borderCalc;

}

if (player.cells[i].x < borderCalc) {

player.cells[i].x = borderCalc;

}

if (player.cells[i].y < borderCalc) {

player.cells[i].y = borderCalc;

}

x += player.cells[i].x;

y += player.cells[i].y;

}复制代码

重新计算中心点

player.x和player.y是所有cell的中心点,重新计算。

player.x = x/player.cells.length;

player.y = y/player.cells.length;复制代码

经过上述步骤,实现了moveplayer的功能。由于判断是否被其他cell包含的时候,采用了两次for,效率上是否还有优化的空间呢?下一节再看看吞食食物、玩家相互吞食的处理方式。

还是放个广告吧,笔者出版的一本书《Unity3D网络游戏实战》充分的讲解怎样开发一款网络游戏,特别对网络框架设计、网络协议、数据处理等方面都有详细的描述,相信会是一本好书的。

image007.png (136.41 KB, 下载次数: 3)

2019-3-6 16:50 上传

文/罗培羽

专栏地址:https://zhuanlan.zhihu.com/p/27313638

球球大作战c语言源代码,《球球大作战》源码解析:移动算法相关推荐

  1. Go语言使用之JSO使用、源码解析和JSON工具类

    在go语言网络编程中,经常会有这样的需求:保存结构体和读取结构体数据.如果你使用redis数据库存储数据,你怎么做?Redis仅支持五种数据类型( String(字符串) .Hash (哈希).Lis ...

  2. 杨辉三角c语言程序jian,杨辉三角C语言程序队列实现(带源码+解析)

    杨辉三角,即如下 通过学习数据结构,解决杨辉三角,可以使用循环来实现:在循环队列中依次存放第 i-1 行上的元素,然后逐个出队并打印,同时生成第 i 行上的元素并入队. 如果要求计算并输出杨辉三角前 ...

  3. C语言成绩统计源代码,C语言课程设计学生成绩管理系统+源码

    <C语言课程设计学生成绩管理系统+源码>由会员分享,可在线阅读,更多相关<C语言课程设计学生成绩管理系统+源码(25页珍藏版)>请在人人文库网上搜索. 1.简易教学管理系统的设 ...

  4. c++ 游戏_C/C++编程笔记:C语言实现连连看游戏,项目源码分享

    本篇文章分享看题目就知道是写给初学者的,学的比较好的小伙伴也可以将自动算法等一些知识给加进去,希望对大家有帮助! 好了,当我们所有的准备工作做好之后,我们就可以来编写我们的C语言连连看游戏了! 其实这 ...

  5. IEC61131, IEC61499图形可视化,PLC图形可视化,PLC组态,VBScript和JavaScript脚本,解决方案源代码, 开发平台100% VC++源码2018!

    IEC61131, IEC61499图形可视化,PLC图形可视化,PLC组态,VBScript和JavaScript脚本,解决方案源代码, 开发平台100% VC++源码2018! -- 全球领先. ...

  6. java计算机毕业设计ssm基于大数据的汽车流量监控cvej1(附源码、数据库)

    java计算机毕业设计ssm基于大数据的汽车流量监控cvej1(附源码.数据库) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也 ...

  7. kcp 介绍与源代码分析_KCP-GO源码解析

    原标题:KCP-GO源码解析 原文作者:张伯雨 golang技术社区 概念 ARQ:自动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层的错误纠正协议之一. ...

  8. python在abaqus中的应用代码下载_Python-(source-code)-in--Abaqus Python语言在Abaqus中的应用(源码) - 下载 - 搜珍网...

    Python语言在Abaqus中的应用 (源码)/ Python语言在Abaqus中的应用 (源码)/Python语言在Abaqus中的应用 (源码)/ Python语言在Abaqus中的应用 (源码 ...

  9. 易语言网易云音乐登录post源码

    易语言网易云音乐登录post源码 以下是部分源码: .版本 2 .支持库 dp1 data = 文本_替换 (#常量2, , , , "账号", 账号, "密码" ...

最新文章

  1. 【Scala-spark.mlib】通过Maven工程导入Mlib库
  2. Java基础学习总结(17)——线程
  3. 深度学习语音降噪方法对比_人工智能-关于深度学习的基础方法
  4. 数据科学-通过数据探索了解我们的特征
  5. 成员变量和属性区别(@property那点事儿)
  6. poj 3486 A Simple Problem with Integers(树状数组第三种模板改段求段)
  7. linux配置内存buffer,Linux中内存buffer和cache的区别
  8. 如何删除计算机中的“天翼云盘”图标
  9. 虚拟机连接锐捷校园网
  10. linux ps2鼠标驱动,a1657苹果鼠标驱动 最新版:VoodooPS2Controller v1.9
  11. 第九届蓝桥杯省赛b组c/c++
  12. python如何打开npy文件_操作python如何实现npy格式文件转换为txt文件
  13. ffmpeg将amr文件转成mp3文件
  14. [zz] 导致你创业失败的18个错误 [2007-05-03]
  15. android开发代码实现对Apk签名,如何对apk进行签名
  16. android手机联调,Android第三课 联调华为手机
  17. imToken—钱包如何导出助记词?
  18. 自动驾驶IDM与MOBIL模型
  19. NLP--2 语言结构和传统pipeline
  20. 软件测试基础知识回顾复习

热门文章

  1. S7-1200PLC与昆仑通态触摸屏以太网通信的具体方法和步骤(图文)
  2. ArchiCAD 25 for Mac(三维模型制作工具)
  3. 解除自己微信绑定的小程序公众号开发者
  4. iOS 自动打包工具 ipa server,当然使用蒲公英分发平台更好
  5. 如何在matlab让adams模型里的电机旋转指定角度
  6. java计算机毕业设计高校食品安全系统源程序+mysql+系统+lw文档+远程调试
  7. macOS 关闭蓝牙发现
  8. CAD轴测图怎么画?快来试试浩辰CAD超级轴测命令!
  9. 3个月,入门网络安全并找到工作
  10. 五步法搞定BI业务需求梳理