微信小游戏 彩色试管 倒水游戏 逻辑 (二)

 最近开始研究微信小游戏,有兴趣的 可以关注一下 公众号, 记录一些心路历程和源代码。

定义一个 Water class

1. **定义接口和枚举**:
   - `WaterInfo` 接口定义了水的颜色、高度等信息。
   - `PourAction` 枚举定义了水的倒动状态,包括无动作、加水、倒水。

2. **类 `Water`**:
   - `Water` 类继承自 `Component`,用于控制水杯中的水。
   - 包含了私有变量 `_action` 用于记录当前倒动状态,`infos` 数组用于存储每一层水的信息,`stopIdx` 和 `curIdx` 分别记录停止倒水和当前水层的索引。
   -初始化方法 `initInfos` 和 `addInfo`,用于设置和添加水层信息。
   - `setPourOutCallback` 和 `setPourInCallback` 方法,用于设置倒水和加水的回调函数。
   - `getPourStartAngle` 和 `getPourEndAngle` 方法,用于计算倒水的起始和结束角度。
   - `onStartPour` 方法,用于开始倒水。
   - `update` 方法,用于每帧更新水的状态。
   - `addStep` 和 `pourStep` 方法,用于每帧增加或减少水的高度。
   - `initSizeColor` 和 `updateAngleHeight` 方法,用于初始化和更新材质属性。
   - showDebugCenter` 方法,用于调试显示水面中心点。

3. **辅助函数**:
   - `angle2radian` 和 `radian2angle` 函数用于角度和弧度的转换。

### 实现原理

- **材质和着色器**:通过 `Material` 和 `EffectAsset` 来应用自定义的着色器效果,模拟水的倒动效果。
- **物理模拟**:通过每帧更新水的高度来模拟水的流动,使用三角函数计算倾斜角度和水面中心点。
- **回调函数**:通过设置回调函数,可以在倒水和加水的特定时刻执行特定的操作。

### 用途

这段代码可以用于游戏或应用中模拟水杯中水的倒动效果,例如在游戏中模拟饮料的倒动,或者在应用中模拟水杯的倒水效果。

import { Color, Component, EffectAsset, Label, Material, Sprite, UITransform, v2,Node, v3, _decorator, Vec4, color, v4, log } from "cc";
import { DEV, EDITOR } from "cc/env";

const { ccclass, property, requireComponent, executeInEditMode, disallowMultiple, executionOrder } = _decorator;

export interface WaterInfo{
    colorId:number,
    color:Color,//颜色
    height:number,//默认情况下,占杯子的高度
}

const MAX_ARR_LEN = 6;

enum PourAction{
    none,
    /**往里加水 */
    in,
    /**向外倒水 */
    out,
} 

@ccclass
@requireComponent(Sprite)
@executeInEditMode
@disallowMultiple
@executionOrder(-100)
export default class Water extends Component {
    private _action:PourAction = PourAction.none;
    private infos:WaterInfo[] = [];
    /**到这里停止倒水 */
    private stopIdx = -1;
    /**当前是有几层水 */
    private curIdx = 0;

    /**节点高宽比 */
    private _ratio:number = 1;
    @property(EffectAsset)
    private effect:EffectAsset = null;
    @property private _skewAngle: number = 0;
    @property({ tooltip: DEV && '旋转角度' })
    public get skewAngle() { return this._skewAngle; }
    public set skewAngle(value: number) { 
        value = Math.round(value*100)/100;
        // log("angle",value)
        this._skewAngle = value; 
        this.updateAngleHeight();
    }

    private _material: Material = null;
    private get material(){
        if(this._material==null){
            let sp = this.node.getComponent(Sprite);
            if(sp){
                if (sp.spriteFrame) sp.spriteFrame.packable = false;
                // 生成并应用材质
                if(this.effect){
                    this._material = new Material();
                    this._material.initialize({
                        effectAsset:this.effect
                    })
                    sp.setMaterial( this._material,0);
                }
                this._material = sp.getSharedMaterial(0)
                this._material.setProperty("mainTexture",sp.spriteFrame.texture)
            }
        }

        return this._material
    }

    protected onLoad() {
        this._ratio = this.node.getComponent(UITransform).height/this.node.getComponent(UITransform).width;
    }

    public initInfos(infos:Array<WaterInfo>){
        this.infos = infos;
        this.curIdx = this.infos.length-1;

        this.initSizeColor();
        this.updateAngleHeight();
    }

    private addHeight = 0;
    public addInfo(info:WaterInfo){
        this.addHeight = info.height;
        info.height = 0;
        this.infos.push(info);
        this._action = PourAction.in;
        this.curIdx = this.infos.length-1;

        this.initSizeColor();
    }

    private onOutStart:Function = null;
    private onOutFinish:Function = null;
    public setPourOutCallback(onOutStart:Function,onOutFinish:Function){
        this.onOutStart = onOutStart;
        this.onOutFinish = onOutFinish;
    }

    private onInFInish:Function = null;
    public setPourInCallback(onInFInish:Function){
        this.onInFInish = onInFInish;
    }

    /**
     * 倾斜到哪个角度开始往外边倒水
     */
    public getPourStartAngle(){
        let _height = 0;
        for(let i=0;i<=this.curIdx;i++){
            _height+=this.infos[i].height;
        }
        
        return this.getCriticalAngleWithHeight(_height);
    }

    /**
     * 倾斜到哪个角度开始停止倒水(当前颜色的水倒完了)
     */
    public getPourEndAngle(){
        this.stopIdx = this.curIdx-this.getTopSameColorNum();

        let _height = 0;
        for(let i=0;i<=this.stopIdx;i++){
            _height+=this.infos[i].height;
        }
        
        return this.getCriticalAngleWithHeight(_height);
    }

    /**获取某一高度的水刚好碰到瓶口的临界倾斜角度 */
    private getCriticalAngleWithHeight(_height){
        
        let ret = 0;
        if(_height==0){
            ret = 90;
            return ret;
        }

        if(_height<0.5){//水的体积小于杯子的一半,先碰到下瓶底
            let tanVal = this._ratio/(_height*2.0);
            ret = Math.atan(tanVal);
        }else{
            let tanVal = 2.0*this._ratio*(1.0-_height); 
            ret = Math.atan(tanVal);
        }
        ret = radian2angle(ret);
        return ret;
    }

    private getTopSameColorNum(){
        let sameColorNum = 0;
        let colorId=null;
        for(let i=this.curIdx;i>=0;i--){
            if(colorId==null){
                sameColorNum++;
                colorId = this.infos[i].colorId;
            }else if(this.infos[i].colorId==colorId){
                sameColorNum++;
            }else{
                break;
            }
        }
        return sameColorNum
    }

    /**
     * 开始倒水
     * 一直倒水直到不同颜色的水到达瓶口,为当前最大能倾斜的角度
     * @returns 返回值为倾斜角度的绝对值
     */
    public onStartPour(){
        this._action = PourAction.out;
        
        this.stopIdx = this.curIdx-this.getTopSameColorNum();
    }

    update(){
        if(this._action==PourAction.out){
            this.pourStep();
        }else if(this._action==PourAction.in){
            this.addStep()
        }
    }

    /**
     * 每帧调用,升高水面高度
     */
    addStep(){
        if(this.curIdx<0){
            return;
        }
        let info = this.infos[this.curIdx];
        info.height = Math.round((info.height + 0.005)*1000)/1000;
        // log("--------info.height",info.height)
        if(info.height>=this.addHeight){
            info.height = this.addHeight;
            this._action = PourAction.none;
            if(this.onInFInish){
                this.onInFInish();
                this.onInFInish = null;
            }
        }

        this.updateAngleHeight();
    }

    /**
     * * 每帧调用
     * * 降低水面高度 
     */
    pourStep(){
        if(this.curIdx<0){
            this._action = PourAction.none;
            return;
        }
        let _height = 0;
        for(let i=0;i<=this.curIdx;i++){
            _height+=this.infos[i].height;
        }
        let is_top = false;
        let angle = (this.skewAngle%360) * Math.PI / 180.0
        let _t = Math.abs(Math.tan(angle));
        if(_height<0.5){//水的体积小于杯子的一半,先碰到下瓶底
            is_top = _t>(this._ratio)/(_height*2.0);
        }else{
            is_top = _t>2.0*this._ratio*(1.0-_height);
        }

        let info = this.infos[this.curIdx];
        if(!is_top){//没到瓶口,不往下倒
            if(info.height<0.05){//可能还留了一点点水,要继续倒出去
                
            }else{
                return;
            }
        }
        if(this.onOutStart){
            this.onOutStart();
            this.onOutStart = null;
        }
        
        info.height = Math.round((info.height - 0.005)*1000)/1000;
        if(info.height<0.01){
            info.height = 0;

            this.infos.pop();
            this.curIdx--;
            // log("------this.curIdx",this.curIdx,this.stopIdx)
            if(this.curIdx==this.stopIdx){
                if(this.onOutFinish){
                    this.onOutFinish();
                    this.onOutFinish = null;
                }
                this._action = PourAction.none;
            }
        }
        // log("this.curIdx",this.curIdx,"info.height",info.height.toFixed(2),"angle",this.skewAngle.toFixed(2))
        this.updateAngleHeight();
    }

    private initSizeColor(){
        for(let i=0;i<this.infos.length;i++){
            const c = this.infos[i].color; 
            this.material.setProperty('color'+i, c);
        }
        let size = v2(this.node.getComponent(UITransform).width,this.node.getComponent(UITransform).height)
        this.material.setProperty('sizeRatio', size.y/size.x);
        this.material.setProperty('waveType', 0);
        this.material.setProperty("layerNum",this.infos.length)
    }

    private updateAngleHeight() { 
        for(let i=0;i<6;i++){
            if(i<this.infos.length){
                let h = this.infos[i].height;
                this.material.setProperty('height'+i, h);
            }else{
                this.material.setProperty('height'+i, 0);
            }
        }
        
        let radian = angle2radian(this._skewAngle)
        this.material.setProperty('skewAngle', radian*1.0);

        let waveType = 0.0;
        if(this._action==PourAction.in){
            waveType = 1.0;
        }else if(this._action==PourAction.out){
            waveType = 2.0;
        }
        this.material.setProperty('waveType', waveType);
        this.material.setProperty("layerNum",this.infos.length)
        
        this.showDebugCenter();
    }

    private dot:Node = null;
    /**显示水面的中心点,调试shader脚本用 */
    private showDebugCenter(){
        if(EDITOR){
            return;
        }
        if(this.dot==null){
            this.dot = new Node();
            this.dot.parent = this.node;
            this.dot.addComponent(UITransform)
            let label = this.dot.addComponent(Label);
            label.string = "·";
            label.fontSize = 60;
            label.color = Color.RED;
        }

        let ratio = this.node.getComponent(UITransform).height/this.node.getComponent(UITransform).width;
        let angle = angle2radian(this.skewAngle);
        let _height = 0;
        if(this.curIdx>=this.infos.length){
            return
        }
        for(let i=0;i<=this.curIdx;i++){
            _height+=this.infos[i].height;
        }
        
        let toLeft = Math.sin(angle)>=0.0;
        let center = v2(0.5,1.0-_height);//水面倾斜时,以哪个店为中心店

        let _t = Math.abs(Math.tan(angle));
        if(_height<=0.5){//水的体积小于杯子的一半,先碰到下瓶底
            let is_bottom = _t>ratio*2.0*_height;//倾斜角度达到瓶底
            if(is_bottom){
                center.x = Math.sqrt((2.0*_height/_t)*ratio)/2.0;
                center.y = 1.0 - Math.sqrt((2.0*_height*_t)/ratio)/2.0;

                let is_top = _t>(ratio)/(_height*2.0);//倾斜角度达到瓶口
                if(is_top){
                    center.y = 0.5;
                    center.x = _height;
                }
            }
            if(!toLeft){
                center.x = 1.0-center.x;
            }
            if(Math.abs(center.x-0.25)<0.01){
                let i = 0;
            }
            // log("aa-------center",center.x.toFixed(2),center.y.toFixed(2));
        }else{//水比较多,先碰到上瓶底
            let is_top = _t>2.0*ratio*(1.0-_height);
            if(is_top){
                center.x = Math.sqrt(2.0*ratio*(1.0-_height)/_t)/2.0;
                center.y = Math.sqrt(2.0*ratio*(1.0-_height)*_t)/2.0/ratio;
                let is_bottom = _t>ratio/(2.0*(1.0-_height));
                if(is_bottom){
                    center.y = 0.5;
                    center.x = 1.0-_height;
                }
            }else{
            }

            if(toLeft){
                center.x = 1.0-center.x;
            }
            // log("bb-------center",center.x.toFixed(2),center.y.toFixed(2));
        }
        center.x = center.x - 0.5;
        center.y = -center.y + 0.5;
        let pt = v3(center.x*this.node.getComponent(UITransform).width,center.y*this.node.getComponent(UITransform).height);
        this.dot.position = pt;
    }
}

function angle2radian(angle:number){
    while(angle>360){
        angle-=360;
    }
    while(angle<-360){
        angle+=360;
    }
    return (angle%360) * Math.PI / 180.0;
}

function radian2angle(radian:number) {
    return radian/Math.PI*180;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/799264.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于5个K7的多FPGA PCIE总线架构的高性能数据预处理平台

板载FPGA实时处理器&#xff1a;XCKU060-2FFVA15172个QSFP光纤接口&#xff0c;最大支持10Gbps/lane板载DMA控制器&#xff0c;能实现双向DMA高速传输支持x8 PCIE主机接口&#xff0c;系统带宽5GByte/s1个R45自适应千兆以太网口1个FMC子卡扩展接口 基于PCIE总线架构的高性能数据…

【JavaEE】网络编程——TCP

&#x1f921;&#x1f921;&#x1f921;个人主页&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;JavaEE专栏&#x1f921;&#x1f921;&#x1f921; 文章目录 前言1.网络编程套接字1.1流式套接字(TCP)1.1.1特点1.1.2编码1.1.2.1ServerSo…

同四千价位闺蜜机,当贝PadGO Air和KTCPro谁更好?

市面上闺蜜机品牌那么多,那么在4K这个价位,如果想购买一台高性价比的闺蜜机应该选择当贝PadGO Air还是KTCPro呢?我们一起来看一下对比 一、外观对比 当贝PadGO Air机身底部为CD型底盘设计,非常有设计感及美感。并且创新性地融入了磁吸快拆布面设计,极大提升了用户体验的便捷…

编程入门(九)【linux系统下docker的部署与发布网站】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️木道寻的主页 文章目录 &#x1f525;前言&#x1f680;什么是docker?&#x1f680;docker三要素&#x1f680;linux系统下docker的基…

算法题-字符串

1.C字符串 c提供了一下两种类型的字符串表示形式&#xff1a; c风格字符串c引入的string类类型 1.1C风格字符串 C 风格的字符串起源于 C 语言&#xff0c;并在 C 中继续得到支持。字符串实际上是使用 null 字符 \0 终止的一维字符数组。因此&#xff0c;一个以 null 结尾的…

KnoBo:医书学习知识,辅助图像分析,解决分布外性能下降和可解释性问题

KnoBo&#xff1a;从医书中学习知识&#xff0c;辅助图像分析&#xff0c;解决分布外性能下降问题 提出背景KnoBo 流程图KnoBo 详解问题构成结构先验瓶颈预测器参数先验 解法拆解逻辑链对比 CLIP、Med-CLIPCLIPMed-CLIPKnoBo 训练细节预训练过程OpenCLIP的微调 构建医学语料库文…

【Nuxt3】vue3+tailwindcss+vuetify引入自定义字体样式

一、目的 在项目中引入自定义的字体样式&#xff08;全局页面都可使用&#xff09; 二、步骤 1、下载好字体 字体的后缀可以是ttf、otf、woff、eot或者svg&#xff08;推荐前三种&#xff09; 以抖音字体为例下载好放在静态文件夹&#xff08;font&#xff09;下 案例字…

notepad++中文出现异体汉字,怎么改正

notepad显示异体字&#xff0c;如何恢复&#xff1f; 比如 “门” 和 “直接” 的"直"字&#xff0c;显示成了 方法 修改字体&#xff0c; 菜单栏选择 Settings(设置&#xff09;&#xff0c;Style Configurator…&#xff08;语言格式设置…&#xff09;&#xf…

《昇思25天学习打卡营第22天|onereal》

文本解码原理--以MindNLP为例 回顾&#xff1a;自回归语言模型 根据前文预测下一个单词 一个文本序列的概率分布可以分解为每个词基于其上文的条件概率的乘积 &#x1d44a;_0:初始上下文单词序列&#x1d447;: 时间步当生成EOS标签时&#xff0c;停止生成。 MindNLP/huggi…

C++基础(三)

1.再探构造函数 之前的构造函数&#xff0c;初始化成员变量主要使用函数体内赋值&#xff0c;构造函数初始化还有一种方式&#xff0c;就是初始化列表&#xff0c;初始化列表的使用方式是以一个冒号开始&#xff0c;接着是一个以逗号分隔开的数据成员列表&#xff0c;每个“成…

【JavaScript】解决 JavaScript 语言报错:Uncaught SyntaxError: Unexpected identifier

文章目录 一、背景介绍常见场景 二、报错信息解析三、常见原因分析1. 缺少必要的标点符号2. 使用了不正确的标识符3. 关键词拼写错误4. 变量名与保留字冲突 四、解决方案与预防措施1. 检查和添加必要的标点符号2. 使用正确的标识符3. 检查关键词拼写4. 避免使用保留字作为变量名…

C# 解析省份、城市、区域 json文件

一、json文件内容如下&#xff0c;&#xff08;小程序里好像有用到...&#xff09;: 二、读取包含省份城市区域的json文件&#xff0c;并整理成想要的结果&#xff1a; string path Server.MapPath("/js"); string file System.IO.Path.Combine(path, "数据.…

数字孪生技术在元宇宙的应用

数字孪生技术与元宇宙有着天然的契合性&#xff0c;两者在技术、应用场景等方面都具有高度的互补性。数字孪生技术可以为元宇宙提供逼真、实时的数据模型和场景&#xff0c;而元宇宙可以为数字孪生技术提供广阔的应用平台和场景。北京木奇移动技术有限公司&#xff0c;专业的软…

从零开始学习深度学习库-5:自动微分(续)

引言 欢迎来到这个从头开始构建深度学习库系列的第5部分。这篇文章将介绍库中自动微分部分的代码。自动微分在上一篇文章中已经讨论过了&#xff0c;所以如果你不知道自动微分是什么&#xff0c;请查看一下。 自动微分系统的核心是计算图&#xff0c;这是一种有向图&#xff…

仅在少数市场发售?三星Galaxy Z Fold 6 Slim折叠屏手机更轻更薄

在智能手机的创新之路上&#xff0c;三星一直是行业的领跑者之一。随着Galaxy Z Fold系列的不断进化&#xff0c;三星再次突破技术边界&#xff0c;推出了更为轻薄的Galaxy Z Fold 6 Slim。 这款新型折叠屏手机以其独特的设计和卓越的性能&#xff0c;为用户带来了全新的使用体…

浅谈RLHF---人类反馈强化学习

浅谈RLHF&#xff08;人类反馈强化学习&#xff09; RLHF&#xff08;Reinforcement Learning fromHuman Feedback&#xff09;人类反馈强化学习 RLHF是[Reinforcement Learning from Human Feedback的缩写&#xff0c;即从人类反馈中进行强化学习。这是一种结合了机器学习中…

java实现资产管理系统图形化用户界面

创建一个&#x1f495;资产管理系统的GUI&#xff08;图形用户界面&#xff09;❤️画面通常需要使用Java的Swing或者JavaFX库。下面我将提供一个简单的资产管理系统GUI的示例代码&#xff0c;使用Java Swing库来实现。这个示例将包括一个主窗口&#xff0c;一个表格来显示资产…

SD card知识总结

一、基础知识 1、简介 SD Card 全称(Secure Digital Memory Card)&#xff0c;日本电子公司松下&#xff08;Panasonic&#xff09;、瑞典公司爱立信&#xff08;Ericsson&#xff09;、德国公司西门子&#xff08;Siemens&#xff09;共同开发的&#xff0c;于1999年发布根…

网络文件系统—NFS

目录 一、概述 二、NFS挂载原理 三、NFS相关协议及软件安装管理 1.协议&#xff1a; 2.软件&#xff1a; 四、NFS系统守护进程 五、NFS服务器的配置 六、NFS服务器的启动与停止 1. 启动NFS服务器 2.查询NFS服务器状态 3.停止NFS服务器 4.设置NFS服务器的自动启动状…

Redis的配置优化、数据类型、消息队列

文章目录 一、Redis的配置优化redis主要配置项CONFIG 动态修改配置慢查询持久化RDB模式AOF模式 Redis多实例Redis命令相关 二、Redis数据类型字符串string列表list集合 set有序集合sorted set哈希hash 三、消息队列生产者消费者模式发布者订阅者模式 一、Redis的配置优化 redi…