新知一下
海量新知
6 0 7 1 3 6 5

模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

Cocos | 让游戏开发更简单 2022/01/14 14:04

游戏中可交互的液体是一种颇为吸引人的元素,比如经典人气解谜游戏《鳄鱼小顽皮爱洗澡》就通过简单的「引水入缸」玩法收获了一大票玩家。

在 Cocos Creator 3.x 中,若想实现 2D 液体、同时兼顾运行效率,可以选择使用 Box2D 的物理粒子效果 来进行模拟。 物理粒子系统除了适合用于模拟液体以外,我们也可以用于模拟任何可变形的物体。 这里将解析由引擎技术支持中心带来的 Cocos Creator 的 动态 2D 液体 解决方案。

点击文末 【阅读原文】 下载 DEMO:

https://store.cocos.com/app/detail/

PART 1

使用方式

场景搭建

在 Cocos Creator 中新建一个空场景,并创建一个

UICanvas

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

创建一个用于液体渲染的相机

Camera-001

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

对于这个相机,使其

ClearFlag = SOLID_COLOR

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

这个相机的作用是将液体绘制到一张 RT 之后将这个动态纹理投射到 UI 内的某个

sprite

上面。

之后就在场景内 布置碰撞体 ,既然是 Box2D,碰撞体记得选 2D,不然碰撞体就会使用 Bullet 和 Box2d 无关了:

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

Cocos Creator 里封装了两种物理引擎 Bullet 和 Box2D,两者处于单独的世界。

在 Box2d 里面如果希望碰撞体之间的碰撞有效,那么至少有一方需要持有 Rigidbody2D 组件。因此需要给碰撞体添加一个 RigidBody2D,其类型选择为

static

。这样物理引擎不会去模拟他的速度和受力情况。

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

添加液体

新建一个空的

UINode

,也就是只有 UITransform 组件的一个 Node:

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

为其添加

WaterRender

这个组件:

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

之后指定好他的一些值:

  • 自定义材质;

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

  • FixError :FixError 指向的是一张1个 2x2 的纯色小纹理;

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

  • 水管:水管由多个碰撞体构成,这样可以约束液体,使其往我们想要的地方流动;

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

效果预览

播放预览一下效果(这里开启了物理调试,可以更清晰的观察到粒子的运行状态):

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

PART 2

前置知识

物理引擎

Box2D 是一款轻量级的 2D 游戏物理引擎。 主流游戏引擎的 2D 物理部分大都使用 Box2D 来完成的。在物理引擎模拟中,通过质心的受力,计算出其速度和加速度等最终得到物体所在的位置,之后渲染引擎会读取物理引擎的计算结果并将其应用到渲染上。

LiquidFun


LiquidFun 是基于 Box2D 的扩展库,作用就是给 Box2D 添加了模拟液体的粒子系统。 该库由谷歌高级程序员 Kentaro Suto 开发,源代码由 C++ 编写,并翻译为 JavaScript。

组装器

在游戏引擎中,绘制精灵或者模型时,都需要通过生成特定的顶点,并调用驱动方法(OpenGL,DirectX ...等)绘制到屏幕上。 在 Cocos Creator 里面,如果我们要绘制一系列顶点到屏幕上,需要使用到

Assembler

组装器

组装器顾名思义就是将顶点组装起来,以供渲染组件使用。 通过这个

Assembler

,可以自定义顶点的位置、颜色、纹理坐标、索引。

Cocos Creator 里有多种

Assembler

/**

 * simple 组装器

 * 可通过 `UI.simple` 获取该组装器。

 */+

export const simple: IAssembler 

/**

 Tiled组装器

*/

export const tiled: IAssembler = 

...

DEMO 中通过读取物理引擎内粒子的位置,计算出了顶点缓存内所有顶点的相关信息。

PART 3

原理解析

render.ts

里面有两个类

WaterRender

WaterAssembler

WaterRender 解析

WaterRender

是整个 DEMO 的核心类,负责粒子的创建和渲染。

Renderable2D

WaterRender

继承自

Renderable2D

在 Cocos Creator 中,任何需要渲染的

Node

对象都会持有一个

RenderableComponent

,其中

Renderable2D

是 Cocos Creator 中渲染 2D 组件的基类。

通过重写

_render

方法,自定义自己的渲染方案。 这里通过使用自定义的

_assembler

来组装需要绘制的几何体。

/**

*commitComp会提交当前的渲染数据给渲染管线

*/

protected _render(render: any) {

    render.commitComp(thisthis.fixError, this._assembler!, null);

}

创建粒子系统

我们可以把液体理解为由很多个小的水滴组成。 这样对于物理引擎来说,就可以选择使用粒子系统,以一种高效的方式,来模拟大量水滴运动的行为。

创建粒子系统:

  var psd_def = {

            strictContactCheck: false,

            density: 1.0,

            gravityScale: 1.0,

            radius: 0.35,  //这里指定了粒子的半径

         

            ...

   }

this._particles = this._world.physicsWorld.impl.CreateParticleSystem(psd);

创建粒子组:

var particleGroupDef = {

   ...

    shape: null,

    position: {

        x: this.particleBox.node.getWorldPosition().x / PHYSICS_2D_PTM_RATIO,

        y: this.particleBox.node.getWorldPosition().y / PHYSICS_2D_PTM_RATIO

    },

    // @ts-ignore

    shape: this.particleBox._shape._createShapes(1.01.0)[0]

};

this._particleGroup = this._particles.CreateParticleGroup(particleGroupDef);

this.SetParticles(this._particles);

粒子组为粒子发射器定义了一组粒子,这些粒子拥有自定义的形状:

//创建BoxCollider2D的几何形状

shape: this.particleBox._shape._createShapes(1.01.0)[0]

通过对液体的观察,可以发现液体有一些常见的特性:

  • 水往低处流, 水滴会沿着碰撞体的表面进行移动

    gravityScale: 1.0

    ,定义了粒子受重力影响的系数;

  • 黏连性, 可观察到两个水滴靠近时,会在液体的作用力下相互吸引,通过定义

    viscousStrength

    来定义粒子的黏连;

  • 压缩, 液体粒子间会进行压缩,由下面的值来定义粒子允许进行的压缩:

  pressureStrength 

  staticPressureStrength

  staticPressureRelaxation 

  staticPressureIterations

  • 表面张力, 我们都知道在水面上放硬币,硬币不会沉底的实验。这个就是液体的表面张力。通过下面两个属性,可以调整液体的表面张力:

    surfaceTensionPressureStrength: 0.2,

    surfaceTensionNormalStrength: 0.2,

WaterAssembler 解析

WaterAssembler

RenderableComponent

提供顶点缓存的定制。

在这个类里面,通过访问粒子系统的每一个粒子的位置,生成4个单独的顶点:

let posBuff = particles.GetPositionBuffer();

let r = particles.GetRadius() * PHYSICS_2D_PTM_RATIO * 3;

 

for (let i = 0; i < particleCount; ++i) {

    let x = posBuff[i].x * PHYSICS_2D_PTM_RATIO;

    let y = posBuff[i].y * PHYSICS_2D_PTM_RATIO;

    // left-bottom

    vbuf[vertexOffset++] = x - r; //x 

    vbuf[vertexOffset++] = y - r; //y

    vbuf[vertexOffset++] = 0// z 

    vbuf[vertexOffset++] = x; // u

    vbuf[vertexOffset++] = y; // v

   ...

}

最后计算索引缓存:

// fill indices

const ibuf = buffer.iData!;

for (let i = 0; i < particleCount; ++i) {

    ibuf[indicesOffset++] = vertexId;

    ibuf[indicesOffset++] = vertexId + 1;

    ibuf[indicesOffset++] = vertexId + 2;

    ibuf[indicesOffset++] = vertexId + 1;

    ibuf[indicesOffset++] = vertexId + 3;

    ibuf[indicesOffset++] = vertexId + 2;

    vertexId += 4;

}

顶点缓存描述了顶点的数据,索引缓存指定了顶点的绘制顺序。

这样就生成了一个基于粒子中心点的矩形。但是我们最终看到的是圆形, 这里的魔法就是通过材质和 Effect 系统来解决的。

材质和 Shader 解析

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

模拟时,需要使用

effect.effect

特效俩来模拟。

注意这里选择的是 transparent 的 technique:

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

effect.effect

vert

函数内,计算了两个传输到

frag

的变量:

v_corner

v_center

,这两个变量代表的是粒子位置的中心点和角落的位置:

  out vec2 v_corner;

  out vec2 v_center;

  vec4 vert () {

    vec4 pos = vec4(a_position.xy, 01);    

    // no a_corner in web version

    // use a_position instead of a_corner

    v_corner = a_position.xy * reverseRes;

    // 由于粒子是纯色的,texCoord 里面记录的是粒子的中心点位置

    v_center = a_texCoord.xy * reverseRes;

    v_corner.y *= yratio;

    v_center.y *= yratio;

    return cc_matViewProj * pos;

  }

这两个变量在

frag

里面通过

smoothstep

进行插值的计算:

smoothstep(edge0, edge1, x) 

这个函数会根据x计算赫尔米特插值:

t = clamp((x - edge0) / (edge1 - edge0), 0.01.0);

return t * t * (3.0 - 2.0 * t);

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

frag()

 函数内通过计算像素位置和粒子中心的距离,使用

smoothstep

进行插值, 粒子的半径就会被控制在3倍到1倍半径之间。 同时由于是根据中心和半径计算,粒子也会从矩形变成圆形:

  in vec2 v_corner;

  in vec2 v_center;

  vec4 frag () {

    float mask = smoothstep(radius * 3., radius, distance(v_corner, v_center));

    return vec4(1.01.01.0, mask);

  }

此时绘制出来的粒子颜色是白色:

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

最后通过

display.effect

配合 render texture 将其渲染为蓝色:

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

display.effect

使用了属性查看器内传入的颜色

color

  in vec4 color;

  #if USE_TEXTURE

    in vec2 uv0;

    #pragma builtin(local)

    layout(set = 2, binding = 10) uniform sampler2D cc_spriteTexture;

  #endif

  vec4 frag () {

    vec4 o = vec4(1111);

    #if USE_TEXTURE

      o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);

      #if IS_GRAY

        float gray  = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;

        o.r = o.g = o.b = gray;

      #endif

    #endif

    o.a = smoothstep(0.951.0, o.a);

    o *= color;

    ALPHA_TEST(o);

    return o;

  }

  

这个时候由于 alpha 的问题会出现一些毛边:

新知达人, 模拟经典流体解谜游戏!Cocos Creator 三步实现动态 2D 液体

因此通过

smoothstep(0.95, 1.0, o.a)

,将像素的 alpha 值都控制在0.95到1之间。

通过这个渲染我们可以看到,其实做游戏不一定非要真实的去模拟,我们只要骗过眼睛就能做出很好的效果了!




更多“游戏”相关内容

更多“游戏”相关内容

新知精选

更多新知精选