最近想学一下CocosCreator,于是,编辑器下载,启动。

众所周知,边写边学才是最快的学习方法,得写个Demo练练手,那么写什么呢?听说现在《墨虾探蝌》挺火的,那就抄(学习的事怎么能叫抄呢?)写一个类似的小游戏吧!

(在《墨虾探蝌》中,鱼的位置固定,到达一定数量后玩家会升级,不会出现一大群鱼的情况,本项目其实和它不同,没有升级进化,是会有一大群鱼的,每条鱼也不是固定位置,而是有自己的运动逻辑,其实和另一个游戏更像,不过我不知道叫什么。。。)

(初学CocosCreator和TypeScript,有写的不好的地方你TM来打我啊还请大家多多包涵。)

本文算法参考了大佬的文章:http://blog.sina.com.cn/s/blog_4a2183a60101avwt.html

效果展示:

正文开始

首先整一个玩家player:

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

有了player就得写个player控制脚本,点击一个方向,player就会一直向这个方向移动。那么我们首先需要获取玩家点击的位置,然后计算出player移动的方向,我们把这个写在GameManager里面,所以新建一个脚本GameManager,这个脚本挂在Canvas上。

先定义两个变量,玩家节点和方向向量:

    @property(cc.Node)player: cc.Node = null;dir: cc.Vec2 = cc.Vec2.ZERO;

获取方向的方法:

    getClickDir(event) {let pos: cc.Vec2 = event.getLocation();//转本地坐标let localPos = this.node.convertToNodeSpaceAR(pos);let playerPos: cc.Vec2 = new cc.Vec2(this.player.position.x,this.player.position.y);let len = localPos.sub(playerPos).mag();this.dir.x = localPos.sub(playerPos).x / len;this.dir.y = localPos.sub(playerPos).y / len;}

这方法在onMouseDown和onMouseMove时调用:

    onMouseDown(event) {if (event.getButton() == cc.Event.EventMouse.BUTTON_LEFT) {this.getClickDir(event);}}onMouseMove(event) {if (event.getButton() == cc.Event.EventMouse.BUTTON_LEFT) {this.getClickDir(event);}}onLoad() {cc.director.getCollisionManager().enabled = true;cc.director.getPhysicsManager().enabled = true;this.node.on(cc.Node.EventType.MOUSE_DOWN, this.onMouseDown, this);this.node.on(cc.Node.EventType.MOUSE_MOVE, this.onMouseMove, this);}onDestroy() {this.node.off(cc.Node.EventType.MOUSE_DOWN, this.onMouseDown, this);this.node.off(cc.Node.EventType.MOUSE_MOVE, this.onMouseMove, this);}

有了方向向量,就可以让玩家移动了,新建一个FishPlayer脚本。

为了不让玩家乱跑,我们先 build the wall:

墙上加上物理碰撞体:

然后就可以开始写FishPlayer脚本了,先把要用到的变量都定义一下:

    @property(cc.Node)camera: cc.Node = null;@property(cc.Node)gameManager: cc.Node = null;game: GameManager;speed: number = 170;velocity: cc.Vec3 = cc.Vec3.ZERO;

在onLoad()中给game赋值:

    onLoad() {this.game = this.gameManager.getComponent("GameManager");}

通过射线来检测边界,判断玩家是否能移动的方法:

    canMove() {var flag: boolean = true;//前方有障碍物var pos = this.node.convertToWorldSpaceAR(cc.Vec3.ZERO);var endPos = pos.add(this.node.up.mul(40));var hit: cc.PhysicsRayCastResult[] = cc.director.getPhysicsManager().rayCast(new cc.Vec2(pos.x, pos.y),new cc.Vec2(endPos.x, endPos.y),cc.RayCastType.All);if (hit.length > 0) {flag = false;}return flag;}

在update中控制玩家移动:

    update(dt) {if (this.game.dir.mag() < 0.5) {this.velocity = cc.Vec3.ZERO;return;}let vx: number = this.game.dir.x * this.speed;let vy: number = this.game.dir.y * this.speed;this.velocity = new cc.Vec3(vx, vy);//移动if (this.canMove()) {this.node.x += vx * dt;this.node.y += vy * dt;}//相机跟随this.camera.setPosition(this.node.position);//向运动方向旋转let hudu = Math.atan2(this.game.dir.y, this.game.dir.x);let angle = hudu * (180 / Math.PI);angle = 360 - angle + 90;this.node.angle = -angle;}

玩家的移动逻辑写完了,接下来写鱼群。

新建一个FishGroupManager脚本和一个FishGroup脚本,FishGroupManager挂在Canvas上,FishGroup挂在player上。

FishGroupManager中定义一个静态fishGroups变量,用来管理所有Group(因为场景中可能有多个玩家,多个鱼群,现在只有一个玩家,这里方便之后扩展):

    static fishGroups: FishGroup[]; //所有组

来一个把group加入groups的静态方法:

    static AddGroup(group: FishGroup) {if (this.fishGroups == null) this.fishGroups = new Array();if (this.fishGroups.indexOf(group) == -1) this.fishGroups.push(group);}

再来一个获取group的静态方法(根据索引获取):

    static GetFishGroup(index: number) {for (var i = 0; i < this.fishGroups.length; i++)if (this.fishGroups[i].groupID == index) return this.fishGroups[i];}

FishGroupManager就写完了,接下来再写FishGroup,把上面用到的groupID定义一下,还有鱼群数组:

    groupID: number = 0; //组id    fishArr: cc.Component[] = new Array<cc.Component>();

在onLoad中将自身加到fishGroups里面:

    onLoad() {FishGroupManager.AddGroup(this);}

现在鱼群有了,但是里面一条鱼都没有,所以我们还要一个抓鱼的方法:

    catchFish(fish) {this.fishArr.push(fish);}

再定义一些要用到的参数,FishGroup就写完了:

    keepMinDistance: number = 80;keepMaxDistance: number = 100;keepWeight: number = 1; //成员保持距离和保持距离权重moveWeight: number = 0.8; //和成员移动权重

接下来就到了重头戏了——鱼群中其他小鱼的运动逻辑。

直接将player复制一下,把挂载的FishPlayer和FishGroup脚本去掉,命名为fish,这就是我们的小鱼了,把它做成预制。然后新建一个FishBehaviour脚本,这个脚本挂在player和普通小鱼身上。

首先实现“抓鱼”功能,当player靠近小鱼后,小鱼就被捕获,成为该player鱼群中的一员。

定义相关变量:

    @property(cc.Node)gameManager: cc.Node = null;game: GameManager;isPicked: boolean = false;pickRadius: number = 50; //抓取距离groupId: number = -1; //组 idmyGroup: FishGroup;

同样,在onLoad()中给game赋值:

    onLoad() {this.game = this.gameManager.getComponent(GameManager);}

判断和player距离的方法:

    getPlayerDistance() {let dist = this.node.position.sub(this.game.player.position).mag();return dist;}

加入鱼群方法:

    onPicked() {//设置groupthis.groupId = this.game.player.getComponent(FishGroup).groupID;this.myGroup = FishGroupManager.GetFishGroup(this.groupId);if (this.myGroup != null) {this.myGroup.catchFish(this);this.isPicked = true;}}

在update中调用:

    update(dt) {if (this.isPicked) {//随着鱼群移动}else {if (this.getPlayerDistance() < this.pickRadius) {this.onPicked();}}}

OK,现在小鱼到鱼群中了,怎么随着鱼群一起移动呢?

这里主要有两个点:

1.小鱼会随着周围“邻居鱼”一起移动

2.小鱼之间要保持距离,不能太过拥挤

所以我们需要计算小鱼周围一定范围内鱼群运动向量的平均值,这样还不够,还要判断是否“拥挤”,“拥挤”的话就增加一个远离的趋势,太远的话就增加一个靠近的趋势,再分别乘以权重,加起来,就可以得到我们要的向量了,代码如下:

定义变量:

    moveSpeed: number = 170;rotateSpeed: number = 40; //移动旋转速度neighborRadius: number = 500; //距离小于500算是邻居speed: number = 0;currentSpeed: number = 0;myMovement: cc.Vec3 = cc.Vec3.ZERO;

求平均向量:

GetGroupMovement() {var v1: cc.Vec3 = cc.Vec3.ZERO;var v2: cc.Vec3 = cc.Vec3.ZERO;for (var i = 0; i < this.myGroup.fishArr.length; i++) {var otherFish: FishBehaviour = this.myGroup.fishArr[i].getComponent(FishBehaviour);var dis = this.node.position.sub(otherFish.node.position); //距离//不是邻居if (dis.mag() > this.neighborRadius) {continue;}var v: cc.Vec3 = cc.Vec3.ZERO;//大于最大间隔,靠近if (dis.mag() > this.myGroup.keepMaxDistance) {v = dis.normalize().mul(1 - dis.mag() / this.myGroup.keepMaxDistance);}//小于最小间隔,远离else if (dis.mag() < this.myGroup.keepMinDistance) {v = dis.normalize().mul(1 - dis.mag() / this.myGroup.keepMinDistance);} else {continue;}v1 = v1.add(v); //与周围单位的距离v2 = v2.add(otherFish.myMovement); //周围单位移动方向}//添加权重因素v1 = v1.normalize().mul(this.myGroup.keepWeight);v2 = v2.normalize().mul(this.myGroup.moveWeight);var ret = v1.add(v2);return ret;}

现在,可以把update补全了:

update(dt) {//随着鱼群移动if (this.isPicked) {var direction = cc.Vec3.ZERO;if (this.node.name != "player") {direction = direction.add(this.GetGroupMovement());}this.speed = cc.misc.lerp(this.speed, this.moveSpeed, 2 * dt);this.Drive(direction, this.speed, dt); //移动}//捕获else {if (this.getPlayerDistance() < this.pickRadius) {this.onPicked();}}}

Drive()方法:

    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() {var flag: boolean = true;//前方有障碍物var pos = this.node.convertToWorldSpaceAR(cc.Vec3.ZERO);var endPos = pos.add(this.node.up.mul(40));var hit: cc.PhysicsRayCastResult[] = cc.director.getPhysicsManager().rayCast(new cc.Vec2(pos.x, pos.y),new cc.Vec2(endPos.x, endPos.y),cc.RayCastType.All);if (hit.length > 0) {flag = false;}return flag;}

最后这个Drive()方法我是拿大佬的原方法改的,里面那个“防抖”我也没弄懂啥意思,大家一起研究研究。

好了,本篇文章就到这里。

TypeScript:鱼群算法(CocosCreator小游戏)相关推荐

  1. 消消乐 游戏算法html,小游戏版消消乐

    概述:最近看了点算法,为了对其有深刻的体会,利用周末时间撸了一个简易版的三消游戏,采用JS+Canvas实现,没有使用额外的游戏引擎,对于初学者来说,也比较容易入门的.下面是小游戏效果展示: 效果展示 ...

  2. CocosCreator小游戏排行榜

    先贴上几个链接 : https://developers.weixin.qq.com/minigame/dev/tutorial/open-ability/open-data.html?q=     ...

  3. CocosCreator 小游戏H5主流渠道聚合SDK

    源码地址见:https://github.com/RainUnity/GameSDK/blob/main/description/SDK.js 或直接参考下方 /*** description:H5小 ...

  4. 游戏开发22课 cocoscreator 小游戏分包

    小游戏分包 部分小游戏平台支持分包功能以便对资源.脚本和场景进行划分.Creator 从 v2.4 开始支持 Asset Bundle,开发者可以将需要分包的内容划分成多个 Asset Bundle, ...

  5. webpack + typescript 开发微信小游戏实践

    源码地址 微信小游戏版本技术选型使用typescript开发 但是微信小游戏原生不支持 typescript 开发,于是探索一下使用ts开发微信小游戏 1. 创建小游戏 使用测试号,创建一个使用官方示 ...

  6. 个人使用CocosCreator开发小游戏路上的一些“坑坑洼洼”

    个人使用CocosCreator开发小游戏路上的一些"坑坑洼洼" 开场自白 微信小游戏 适配 一般界面弹窗节点适配 套路的需求适配 PS ps中的ps 摄像机的新手操作 tiled ...

  7. 用c语言将2048的分数存档,利用C语言实现2048小游戏的方法

    准备工作 首先上一张图,因为这里只是在用C语言验证算法,所以没有对界面做很好的优化,丑是理所应当的. 了解了游戏的工作原理,实际上可以将游戏描述为四个带有方向的同一操作: 1.将所有数字向一个方向移动 ...

  8. 基于js原生算法+cocos游戏引擎+uni框架Cloud托管网页:开发2048小游戏域名发布版本

    目录 首先看一下效果图 CocosCreator游戏引擎 block绑定: canvas绑定: cocos中创建脚本js以及js原生算法 然后我们先看game.js 背景方法及包含的原生算法代码: 开 ...

  9. CocosCreator物理小游戏实战-别离开碗(一)

    摘要 CocosCreator 物理小游戏实战教程开更啦!来跟 KUOKUO 一起学习吧!开发语言为 TypeScript! 正文 使用版本 CocosCreator 版本 2.3.4 游戏效果 游戏 ...

最新文章

  1. Windows平台搭建-----C语言
  2. C++中的new和delete用法
  3. Linux系统上用Sigil创建和编辑 EPUB 文件
  4. android jks sha1,Android 获取签名文件jks的SHA1值或者SHA256的值
  5. maven打war包
  6. python 日志 logging模块(详细解析)
  7. 排序提示若执行此操作所有合并单元格需大小相同
  8. PostgreSQL存储引擎源码分析五(原创,不断更新)
  9. 对软件工程Alpha迭代的反思与总结
  10. 将多个Excel工作簿合并到一个Excel工作簿中
  11. 国内的微软更新服务器地址,windows update 服务器
  12. UOJ #11. 【UTR #1】ydc的大树
  13. 下雨天,走一段路,是走淋雨少还是跑
  14. c语言如何编程硬件,很多硬件开发都用C语言编程.PPT
  15. 测试 RAM 盘和 DISK 盘的文件读写速度
  16. siri打电话功能测试用例编写
  17. Linux和Windows命令行中使用命令的输出(删除几天前的日志)
  18. TE、TM、TEM模式的区别
  19. c语言计算2的n次方(可以1000次方以上)(利用数组)
  20. C语言结构体的定义与使用

热门文章

  1. C#中PictureBox异步加载图片
  2. 发动机产品研发项目管理体系探讨 玉柴国六产品开发项目管理实践
  3. 黑马程序员-MyBatis笔记
  4. 视频转gif(一):前端(小程序)实现截取视频画面图片
  5. TutorABC跨界合作《挑战吧太空》与观众一起见证“太空C计划”志愿者非凡挑战
  6. 自媒体运营技巧:如何成功申请今日头条号?
  7. Switch蓝牙适配器
  8. 30岁以后还做码农一定没有前途
  9. 字节跳动测试岗,3面都过了,HR告诉我这个原因被刷了...
  10. android备忘录怎么设置闹钟,荣耀备忘录的提醒闹钟怎么设置?