摘要
我们都知道水面的运动形式是极其复杂的。但是在一些二维的小游戏中,我们只是想模拟其波动特性,做一些出水入水的效果。如何简单的实现波动的水面呢?
正文
看看效果

准备工作
一张背景图片即可
层级结构
Main Camera 摄像机组件的 Background Color 设置白色(不是节点颜色)。
Mask 节点挂载 cc.Mask 组件,无需其他处理。
water 节点挂载精灵组件,就是背景图。正常情况下,应该是全被遮住。
wave 节点挂载 wave.js 脚本即可。

点的结构
现实中,水面是由一个个水分子组成。
那么我们代码里就可以抽象成一个个对象
好了,我们现在有了水面宽度(设计分辨率宽度 720),有了点的对象。
那么我们放多少个点合适呢?要知道水分子的直径约为 0.3 纳米,我们用这么多对象电脑不炸了。
那这个数量就由你决定了,数量越多效果越真实,但性能就会下降。
这里我设置了
能量传递
现在,我们规定了在这 720 宽的水面上有 20 个点。
那么点与点之间是有能量传递的呀,比如最右侧的点(this.nodeArray[19])向上运动了,那么其旁边的点(this.nodeArray[18])必然受到影响呀。
所以我们应该用一个数组来表征能量
用循环的方式代表能量传递,循环次数越多就相当于传递的越远。
for (let k = 0; k < 2; k++) { for (let i = 0; i < this.n; i++) { if (i > 0) { this.nodeEnergy[i - 1] += 0.98 * (this.nodeArray[i].y - this.nodeArray[i - 1].y) } if (i < this.n - 1) { this.nodeEnergy[i + 1] += 0.98 * (this.nodeArray[i].y - this.nodeArray[i + 1].y) } } }
|
速度衰减
能量传递写好了,那么我们自身也要有能量损失的。(主要是表面张力与重力)
for (let i = 0; i < this.n - 1; i++) { this.nodeEnergy[i] *= 0.98 this.nodeArray[i].y += this.nodeEnergy[i] * dt }
|
遮罩显示
不知小伙伴们有没有这样用过 cc.Mask 组件。
let draw = this.mask._graphics
|
遮罩组件是含有 _graphics 这个 cc.Graphics 对象的,我们用其画图就是擦除遮罩的效果。
那么我们只要根据水面上的点与水底封闭图形进行擦除就能模拟出水面形状了。
因为是 720 x 1280 找到最下的两个点(-360,-640)和(360, -640),与水面点连接进行图形封闭。
注意不要用 lineTo 要用贝塞尔进行曲线。(因为会出现尖尖的角)
showWater () { let draw = this.mask._graphics; draw.clear(); draw.lineWidth = 1; draw.strokeColor = cc.color(255,0,0); draw.fillColor = cc.color(0,255,0); draw.moveTo(-360, this.h); for (let i = 0; i < this.n; i+=2) { draw.quadraticCurveTo(this.nodeArray[i].x, this.nodeArray[i].y, this.nodeArray[i+1].x, this.nodeArray[i+1].y); } draw.lineTo(360, -640); draw.lineTo(-360, -640); draw.lineTo(-360, this.h); draw.fill(); draw.stroke(); }
|
赋予动力
在 start 方法下让最右侧的点呈 sin 缓动。(没看懂的一定要去官方文档里看 cc.tween)
let obj = this.nodeArray[this.n - 1] let time = 0.5 cc.tween(obj).repeatForever( cc .tween() .to(time, { y: 40 + this.h }, { easing: 'sineOut' }) .to(time, { y: 0 + this.h }, { easing: 'sineIn' }) .to(time, { y: -40 + this.h }, { easing: 'sineOut' }) .to(time, { y: 0 + this.h }, { easing: 'sineIn' }) )
|
完整代码
wave.js
cc.Class({ extends: cc.Component,
properties: { mask: cc.Mask, },
onLoad() { this.h = 200 this.n = 20 this.nodeArray = [] this.nodeEnergy = [] for (let i = 0; i < this.n; i++) { this.nodeEnergy[i] = 0 } },
start() { for (let i = 0; i < this.n; i++) { let node = { x: 0, y: 0 } node.y = this.h node.x = -360 + ((i + 1) * 720) / this.n this.nodeArray[i] = node } let obj = this.nodeArray[this.n - 1] let time = 0.5 cc.tween(obj) .repeatForever( cc .tween() .to(time, { y: 40 + this.h }, { easing: 'sineOut' }) .to(time, { y: 0 + this.h }, { easing: 'sineIn' }) .to(time, { y: -40 + this.h }, { easing: 'sineOut' }) .to(time, { y: 0 + this.h }, { easing: 'sineIn' }) ) .start() },
showWater() { let draw = this.mask._graphics draw.clear() draw.lineWidth = 1 draw.strokeColor = cc.color(255, 0, 0) draw.fillColor = cc.color(0, 255, 0) draw.moveTo(-360, this.h) for (let i = 0; i < this.n; i += 2) { draw.quadraticCurveTo(this.nodeArray[i].x, this.nodeArray[i].y, this.nodeArray[i + 1].x, this.nodeArray[i + 1].y) } draw.lineTo(360, -640) draw.lineTo(-360, -640) draw.lineTo(-360, this.h) draw.fill() draw.stroke() },
update(dt) { for (let k = 0; k < 2; k++) { for (let i = 0; i < this.n; i++) { if (i > 0) { this.nodeEnergy[i - 1] += 0.98 * (this.nodeArray[i].y - this.nodeArray[i - 1].y) } if (i < this.n - 1) { this.nodeEnergy[i + 1] += 0.98 * (this.nodeArray[i].y - this.nodeArray[i + 1].y) } } } for (let i = 0; i < this.n - 1; i++) { this.nodeEnergy[i] *= 0.98 this.nodeArray[i].y += this.nodeEnergy[i] * dt } this.showWater() }, })
|
结语
想改变水面上某一点的能量或者位置时
this.nodeArray this.nodeEnergy
|
工程源码在我的微信公众号回复关键词【水波】即可获得
O(∩_∩)O~~
微信公众号
