• 售前

  • 售后

热门帖子
入门百科

详解CocosCreator游戏之鱼群算法

[复制链接]
话筒他常te 显示全部楼层 发表于 2021-10-26 13:03:36 |阅读模式 打印 上一主题 下一主题
前言

最近想学一下CocosCreator,于是,编辑器下载,启动。
众所周知,边写边学才是最快的学习方法,得写个Demo练练手,那么写什么呢?听说现在《墨虾探蝌》挺火的,那就抄(学习的事怎么能叫抄呢?)写一个类似的小游戏吧!
(在《墨虾探蝌》中,鱼的位置固定,到达肯定数量后玩家会升级,不会出现一大群鱼的环境,本项目着实和它差别,没有升级进化,是会有一大群鱼的,每条鱼也不是固定位置,而是有本身的运动逻辑,着实和另一个游戏更像,不外我不知道叫什么。。。)
结果展示:

正文

首先整一个玩家player:

图片资源用的是CocosCreator官方Demo的图片,照着官方Demo学习了一下,懒得找鱼的图片就直接把图片拿来用了,这个项目目前只用了两张图片

有了player就得写个player控制脚本,点击一个方向,player就会一直向这个方向移动。那么我们首先必要获取玩家点击的位置,然后盘算出player移动的方向,我们把这个写在GameManager内里,以是新建一个脚本GameManager,这个脚本挂在Canvas上。
先界说两个变量,玩家节点和方向向量:
  1. @property(cc.Node)
  2. player: cc.Node = null;
  3. ir: cc.Vec2 = cc.Vec2.ZERO;
复制代码
获取方向的方法:
  1. getClickDir(event) {
  2.     let pos: cc.Vec2 = event.getLocation();
  3.     //转本地坐标
  4.     let localPos = this.node.convertToNodeSpaceAR(pos);
  5.     let playerPos: cc.Vec2 = new cc.Vec2(
  6.         this.player.position.x,
  7.         this.player.position.y
  8.     );
  9.     let len = localPos.sub(playerPos).mag();
  10.     this.dir.x = localPos.sub(playerPos).x / len;
  11.     this.dir.y = localPos.sub(playerPos).y / len;
  12. }
复制代码
这方法在onMouseDown和onMouseMove时调用:
  1. onMouseDown(event) {
  2.     if (event.getButton() == cc.Event.EventMouse.BUTTON_LEFT) {
  3.         this.getClickDir(event);
  4.     }
  5. }
  6. onMouseMove(event) {
  7.     if (event.getButton() == cc.Event.EventMouse.BUTTON_LEFT) {
  8.         this.getClickDir(event);
  9.     }
  10. }
  11. onLoad() {
  12.     cc.director.getCollisionManager().enabled = true;
  13.     cc.director.getPhysicsManager().enabled = true;
  14.     this.node.on(cc.Node.EventType.MOUSE_DOWN, this.onMouseDown, this);
  15.     this.node.on(cc.Node.EventType.MOUSE_MOVE, this.onMouseMove, this);
  16. }
  17. onDestroy() {
  18.     this.node.off(cc.Node.EventType.MOUSE_DOWN, this.onMouseDown, this);
  19.     this.node.off(cc.Node.EventType.MOUSE_MOVE, this.onMouseMove, this);
  20. }
复制代码
有了方向向量,就可以让玩家移动了,新建一个FishPlayer脚本。
为了不让玩家乱跑,我们先 build the wall:

墙上加上物理碰撞体:

然后就可以开始写FishPlayer脚本了,先把要用到的变量都界说一下:
  1. @property(cc.Node)
  2. camera: cc.Node = null;
  3. @property(cc.Node)
  4. gameManager: cc.Node = null;
  5. game: GameManager;
  6. speed: number = 170;
  7. velocity: cc.Vec3 = cc.Vec3.ZERO;
复制代码
在onLoad()中给game赋值:
  1. onLoad() {
  2.     this.game = this.gameManager.getComponent("GameManager");
  3. }
复制代码
通过射线来检测界限,判定玩家是否能移动的方法:
  1. canMove() {
  2.     var flag: boolean = true;
  3.     //前方有障碍物
  4.     var pos = this.node.convertToWorldSpaceAR(cc.Vec3.ZERO);
  5.     var endPos = pos.add(this.node.up.mul(40));
  6.     var hit: cc.PhysicsRayCastResult[] = cc.director
  7.         .getPhysicsManager()
  8.         .rayCast(
  9.             new cc.Vec2(pos.x, pos.y),
  10.             new cc.Vec2(endPos.x, endPos.y),
  11.             cc.RayCastType.All
  12.         );
  13.     if (hit.length > 0) {
  14.         flag = false;
  15.     }
  16.     return flag;
  17. }
复制代码
在update中控制玩家移动:
  1. update(dt) {
  2.     if (this.game.dir.mag() < 0.5) {
  3.         this.velocity = cc.Vec3.ZERO;
  4.         return;
  5.     }
  6.     let vx: number = this.game.dir.x * this.speed;
  7.     let vy: number = this.game.dir.y * this.speed;
  8.     this.velocity = new cc.Vec3(vx, vy);
  9.     //移动
  10.     if (this.canMove()) {
  11.         this.node.x += vx * dt;
  12.         this.node.y += vy * dt;
  13.     }
  14.     //相机跟随
  15.     this.camera.setPosition(this.node.position);
  16.     //向运动方向旋转
  17.     let hudu = Math.atan2(this.game.dir.y, this.game.dir.x);
  18.     let angle = hudu * (180 / Math.PI);
  19.     angle = 360 - angle + 90;
  20.     this.node.angle = -angle;
  21. }
复制代码
玩家的移动逻辑写完了,接下来写鱼群。
新建一个FishGroupManager脚本和一个FishGroup脚本,FishGroupManager挂在Canvas上,FishGroup挂在player上。
FishGroupManager中界说一个静态fishGroups变量,用来管理全部Group(因为场景中可能有多个玩家,多个鱼群,现在只有一个玩家,这里方便之后扩展):
  1. static fishGroups: FishGroup[]; //所有组
复制代码
来一个把group参加groups的静态方法:
  1. static AddGroup(group: FishGroup) {
  2.     if (this.fishGroups == null) this.fishGroups = new Array();
  3.     if (this.fishGroups.indexOf(group) == -1) this.fishGroups.push(group);
  4. }
复制代码
再来一个获取group的静态方法(根据索引获取):
  1. static GetFishGroup(index: number) {
  2.     for (var i = 0; i < this.fishGroups.length; i++)
  3.         if (this.fishGroups[i].groupID == index) return this.fishGroups[i];
  4. }
复制代码
FishGroupManager就写完了,接下来再写FishGroup,把上面用到的groupID界说一下,另有鱼群数组:
  1. groupID: number = 0; //组id   
  2. fishArr: cc.Component[] = new Array<cc.Component>();
复制代码
在onLoad中将自身加到fishGroups内里:
  1. onLoad() {
  2.     FishGroupManager.AddGroup(this);
  3. }
复制代码
现在鱼群有了,但是内里一条鱼都没有,以是我们还要一个抓鱼的方法:
  1. catchFish(fish) {
  2.     this.fishArr.push(fish);
  3. }
复制代码
再界说一些要用到的参数,FishGroup就写完了:
  1. keepMinDistance: number = 80;
  2. keepMaxDistance: number = 100;
  3. keepWeight: number = 1; //成员保持距离和保持距离权重
  4. moveWeight: number = 0.8; //和成员移动权重
复制代码
接下来就到了重头戏了——鱼群中其他小鱼的运动逻辑。
直接将player复制一下,把挂载的FishPlayer和FishGroup脚本去掉,定名为fish,这就是我们的小鱼了,把它做成预制。然后新建一个FishBehaviour脚本,这个脚本挂在player和普通小鱼身上。
首先实现“抓鱼”功能,当player靠近小鱼后,小鱼就被捕获,成为该player鱼群中的一员。
界说相关变量:
  1. @property(cc.Node)
  2. gameManager: cc.Node = null;
  3. game: GameManager;
  4. isPicked: boolean = false;
  5. pickRadius: number = 50; //抓取距离
  6. groupId: number = -1; //组 id
  7. myGroup: FishGroup;
复制代码
同样,在onLoad()中给game赋值:
  1. onLoad() {
  2.     this.game = this.gameManager.getComponent(GameManager);
  3. }
复制代码
判定和player间隔的方法:
  1. getPlayerDistance() {
  2.     let dist = this.node.position.sub(this.game.player.position).mag();
  3.     return dist;
  4. }
复制代码
参加鱼群方法:
  1. onPicked() {
  2.     //设置group
  3.     this.groupId = this.game.player.getComponent(FishGroup).groupID;
  4.     this.myGroup = FishGroupManager.GetFishGroup(this.groupId);
  5.     if (this.myGroup != null) {
  6.         this.myGroup.catchFish(this);
  7.         this.isPicked = true;
  8.     }
  9. }
复制代码
在update中调用:
  1. update(dt) {
  2.     if (this.isPicked) {
  3.         //随着鱼群移动
  4.     }
  5.     else {
  6.         if (this.getPlayerDistance() < this.pickRadius) {
  7.             this.onPicked();
  8.         }
  9.     }
  10. }
复制代码
OK,现在小鱼到鱼群中了,怎么随着鱼群一起移动呢?
这里主要有两个点:
1.小鱼会随着四周“邻居鱼”一起移动
2.小鱼之间要保持间隔,不能太过拥挤
以是我们必要盘算小鱼四周肯定范围内鱼群运动向量的均匀值,这样还不敷,还要判定是否“拥挤”,“拥挤”的话就增长一个阔别的趋势,太远的话就增长一个靠近的趋势,再分别乘以权重,加起来,就可以得到我们要的向量了,代码如下:
界说变量:
  1. moveSpeed: number = 170;
  2. rotateSpeed: number = 40; //移动旋转速度
  3. neighborRadius: number = 500; //距离小于500算是邻居
  4. speed: number = 0;
  5. currentSpeed: number = 0;
  6. myMovement: cc.Vec3 = cc.Vec3.ZERO;
复制代码
求均匀向量:
  1. GetGroupMovement() {
  2.         var v1: cc.Vec3 = cc.Vec3.ZERO;
  3.         var v2: cc.Vec3 = cc.Vec3.ZERO;
  4.         for (var i = 0; i < this.myGroup.fishArr.length; i++) {
  5.             var otherFish: FishBehaviour = this.myGroup.fishArr[i].getComponent(
  6.                 FishBehaviour
  7.             );
  8.             var dis = this.node.position.sub(otherFish.node.position); //距离
  9.             //不是邻居
  10.             if (dis.mag() > this.neighborRadius) {
  11.                 continue;
  12.             }
  13.             var v: cc.Vec3 = cc.Vec3.ZERO;
  14.             //大于最大间隔,靠近
  15.             if (dis.mag() > this.myGroup.keepMaxDistance) {
  16.                 v = dis.normalize().mul(1 - dis.mag() / this.myGroup.keepMaxDistance);
  17.             }
  18.             //小于最小间隔,远离
  19.             else if (dis.mag() < this.myGroup.keepMinDistance) {
  20.                 v = dis.normalize().mul(1 - dis.mag() / this.myGroup.keepMinDistance);
  21.             } else {
  22.                 continue;
  23.             }
  24.             v1 = v1.add(v); //与周围单位的距离
  25.             v2 = v2.add(otherFish.myMovement); //周围单位移动方向
  26.         }
  27.         //添加权重因素
  28.         v1 = v1.normalize().mul(this.myGroup.keepWeight);
  29.         v2 = v2.normalize().mul(this.myGroup.moveWeight);
  30.         var ret = v1.add(v2);
  31.         return ret;
  32.     }
复制代码
现在,可以把update补全了:
  1. update(dt) {
  2.         //随着鱼群移动
  3.         if (this.isPicked) {
  4.             var direction = cc.Vec3.ZERO;
  5.             if (this.node.name != "player") {
  6.                 direction = direction.add(this.GetGroupMovement());
  7.             }
  8.             this.speed = cc.misc.lerp(this.speed, this.moveSpeed, 2 * dt);
  9.             this.Drive(direction, this.speed, dt); //移动
  10.         }
  11.         //捕获
  12.         else {
  13.             if (this.getPlayerDistance() < this.pickRadius) {
  14.                 this.onPicked();
  15.             }
  16.         }
  17.     }
复制代码
Drive()方法:
  1. Drive(direction: cc.Vec3, spd: number, dt) {    var finialDirection: cc.Vec3 = direction.normalize();    var finialSpeed: number = spd;    var finialRotate: number = 0;    var rotateDir: number = cc.Vec3.dot(finialDirection, this.node.right);    var forwardDir: number = cc.Vec3.dot(finialDirection, this.node.up);    if (forwardDir < 0) {        rotateDir = Math.sign(rotateDir);    }    //防抖    if (forwardDir < 0.98) {        finialRotate = cc.misc.clampf(            rotateDir * 180,            -this.rotateSpeed,            this.rotateSpeed        );    }    finialSpeed *= cc.misc.clamp01(direction.mag());    finialSpeed *= cc.misc.clamp01(1 - Math.abs(rotateDir) * 0.8);    if (Math.abs(finialSpeed) < 0.01) {        finialSpeed = 0;    }    //移动    if (this.canMove()) {        this.node.x += this.node.up.x * finialSpeed * dt;        this.node.y += this.node.up.y * finialSpeed * dt;    }    //旋转    var angle1 = finialRotate * 8 * dt;    var angle2 = this.node.angle - angle1;    this.node.angle = angle2 % 360;    this.currentSpeed = finialSpeed;    this.myMovement = direction.mul(finialSpeed);}canMove() {
  2.     var flag: boolean = true;
  3.     //前方有障碍物
  4.     var pos = this.node.convertToWorldSpaceAR(cc.Vec3.ZERO);
  5.     var endPos = pos.add(this.node.up.mul(40));
  6.     var hit: cc.PhysicsRayCastResult[] = cc.director
  7.         .getPhysicsManager()
  8.         .rayCast(
  9.             new cc.Vec2(pos.x, pos.y),
  10.             new cc.Vec2(endPos.x, endPos.y),
  11.             cc.RayCastType.All
  12.         );
  13.     if (hit.length > 0) {
  14.         flag = false;
  15.     }
  16.     return flag;
  17. }
复制代码
以上就是详解CocosCreator游戏之鱼群算法的具体内容,更多关于CocosCreator鱼群算法的资料请关注草根技能分享其它相关文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作