详解CocosCreator游戏之鱼群算法
前言
最近想学一下CocosCreator,于是,编辑器下载,启动。
众所周知,边写边学才是最快的学习方法,得写个Demo练练手,那么写什么呢?听说现在《墨虾探蝌》挺火的,那就抄(学习的事怎么能叫抄呢?)写一个类似的小游戏吧!
(在《墨虾探蝌》中,鱼的位置固定,到达一定数量后玩家会升级,不会出现一大群鱼的情况,本项目其实和它不同,没有升级进化,是会有一大群鱼的,每条鱼也不是固定位置,而是有自己的运动逻辑,其实和另一个游戏更像,不过我不知道叫什么。。。)
效果展示
正文
整一个玩家player
图片资源用的是CocosCreator官方Demo的图片,照着官方Demo学习了一下,懒得找鱼的图片就直接把图片拿来用了,这个项目目前只用了两张图片
有了player就得写个player控制脚本,点击一个方向,player就会一直向这个方向移动。那么我们需要获取玩家点击的位置,然后计算出player移动的方向,我们把这个写在GameManager里面,所以新建一个脚本GameManager,这个脚本挂在Canvas上。
先定义两个变量,玩家节点和方向向量
@property(.Node) player: .Node = null; ir: .Vec2 = .Vec2.ZERO;
获取方向的方法
getClickDir(event) { let pos: .Vec2 = event.getLocation(); //转本地坐标 let localPos = this.node.convertToNodeSpaceAR(pos); let playerPos: .Vec2 = new .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() == .Event.EventMouse.BUTTON_LEFT) { this.getClickDir(event); } } onMouseMove(event) { if (event.getButton() == .Event.EventMouse.BUTTON_LEFT) { this.getClickDir(event); } } onLoad() { .director.getCollisionManager().enabled = true; .director.getPhysicsManager().enabled = true; this.node.on(.Node.EventType.MOUSE_DOWN, this.onMouseDown, this); this.node.on(.Node.EventType.MOUSE_MOVE, this.onMouseMove, this); } onDestroy() { this.node.off(.Node.EventType.MOUSE_DOWN, this.onMouseDown, this); this.node.off(.Node.EventType.MOUSE_MOVE, this.onMouseMove, this); }
有了方向向量,就可以让玩家移动了,新建一个FishPlayer脚本。
为了不让玩家乱跑,我们先 build the wall
墙上加上物理碰撞体
然后就可以开始写FishPlayer脚本了,先把要用到的变量都定义一下
@property(.Node) camera: .Node = null; @property(.Node) gameManager: .Node = null; game: GameManager; speed: number = 170; velocity: .Vec3 = .Vec3.ZERO;
在onLoad()中给game赋值
onLoad() { this.game = this.gameManager.getComponent("GameManager"); }
通过射线来检测边界,判断玩家是否能移动的方法
canMove() { var flag: boolean = true; //前方有障碍物 var pos = this.node.convertToWorldSpaceAR(.Vec3.ZERO); var endPos = pos.add(this.node.up.mul(40)); var hit: .PhysicsRayCastResult[] = .director .getPhysicsManager() .rayCast( new .Vec2(pos.x, pos.y), new .Vec2(endPos.x, endPos.y), .RayCastType.All ); if (hit.length > 0) { flag = false; } return flag; }
在update中控制玩家移动
update(dt) { if (this.game.dir.mag() < 0.5) { this.velocity = .Vec3.ZERO; return; } let vx: number = this.game.dir.x this.speed; let vy: number = this.game.dir.y this.speed; this.velocity = new .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: .Component[] = new Array<.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(.Node) gameManager: .Node = null; game: GameManager; isPicked: boolean = false; pickRadius: number = 50; //抓取距离 groupId: number = -1; //组 id myGroup: 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() { //设置group this.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: .Vec3 = .Vec3.ZERO;
求平均向量
GetGroupMovement() { var v1: .Vec3 = .Vec3.ZERO; var v2: .Vec3 = .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: .Vec3 = .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 = .Vec3.ZERO; if (this.node.name != "player") { direction = direction.add(this.GetGroupMovement()); } this.speed = .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: .Vec3, spd: number, dt) { var finialDirection: .Vec3 = direction.normalize(); var finialSpeed: number = spd; var finialRotate: number = 0; var rotateDir: number = .Vec3.dot(finialDirection, this.node.right); var forwardDir: number = .Vec3.dot(finialDirection, this.node.up); if (forwardDir < 0) { rotateDir = Math.sign(rotateDir); } //防抖 if (forwardDir < 0.98) { finialRotate = .misc.clampf( rotateDir 180, -this.rotateSpeed, this.rotateSpeed ); } finialSpeed = .misc.clamp01(direction.mag()); finialSpeed = .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(.Vec3.ZERO); var endPos = pos.add(this.node.up.mul(40)); var hit: .PhysicsRayCastResult[] = .director .getPhysicsManager() .rayCast( new .Vec2(pos.x, pos.y), new .Vec2(endPos.x, endPos.y), .RayCastType.All ); if (hit.length > 0) { flag = false; } return flag; }
以上就是详解CocosCreator游戏之鱼群算法的详细内容,更多关于CocosCreator鱼群算法的资料请关注狼蚁SEO其它相关文章!
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程