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

news2024/11/13 21:54:59

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

定义一个 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Gil-Pelaez inversion

一、特征函数 A.随即变量的特征函数定义与性质 B.特征函数与PDF的关系 傅里叶变换:C.特征函数与矩函数关系 二、Gil-Pelaez反演定理 输入功率 P i n P_{in}

Kotlin标准函数(语法糖)let with run also apply快速讲解

目录 1、知识储备——扩展函数 原理 定义扩展函数 调用扩展函数 2、返回值为上下文对象的标准函数 apply also 3、返回值为Lambda表达式结果 let run with 4、一表总结 1、知识储备——扩展函数 原理 Kotlin 在不继承父类或实现接口下&#xff0c;也能扩展一个类的…

Linux进程通信--共享内存

文章目录 概述共享内存基本原理共享内存的操作创建共享内存函数接口形成key--fotk创建共享内存代码演示补充指令集--ipc的指令key和shmid区别创建并获取共享内存代码 删除共享内存函数接口删除共存内存函数代码演示 共享内存段连接到进程地址空间函数接口代码演示 取消关联代码…

真空油炸机的特点是什么?

真空油炸机的特点&#xff0c;如同一位技艺精湛的厨师&#xff0c;不仅确保了食材的完美呈现&#xff0c;更在科技与传统工艺之间找到了完美的平衡。 首先&#xff0c;真空油炸机以其独特的真空环境&#xff0c;为食材打造了一个低氧、低压的烹饪空间。在这样的环境中&#xff…

LabVIEW比例压力控制阀自动测试系统

开发了一套基于LabVIEW编程和PLC控制的比例控制阀自动测试系统。该系统能够实现共轨管稳定的超高压供给&#xff0c;自动完成比例压力控制阀的耐久测试、流量滞环测试及压力-流量测试。该系统操作简便&#xff0c;具有高精度和高可靠性&#xff0c;完全满足企业对自动化测试的需…

vue3中谷歌地图+外网申请-原生-实现地址输入搜索+点击地图获取地址回显 +获取国外的geoJson实现省市区级联选择

一. 效果&#xff1a;输入后显示相关的地址列表&#xff0c;选中后出现标示图标和居中定位 1.初始化谷歌地图 在index.html加上谷歌api请求库 <script src"https://maps.googleapis.com/maps/api/js?key申请到的谷歌地图密钥&vweekly&librariesgeometry,place…

指针!!C语言(第一篇)

指针1 指针变量和地址1.取地址操作符(&)2.指针变量和解引用操作符(*) 指针变量的大小和类型指针的运算特殊指针1.viod*指针2.const修饰指针3.野指针 assert断言指针的使用和传址调用1.strlen的模拟实现2.传值调用和传址调用 指针变量和地址 在认识指针之前&#xff0c;我们…

鸿蒙实训笔记

第一天 #初始化一个新的NPM项目(根据提示操作) npm init #安装TSC、TSLint和NodeJS的类型声明 npm install -s typescript tslint types/node 在根目录中新建一个名为tsconfig.json的文件&#xff0c;然后在代码编辑器中打开&#xff0c;写入下述内容&#xff1a; {"co…

SpringBoot+Vue实现简单的文件上传(Excel篇)

SpringBootVue实现简单的文件上传 1 环境 SpringBoot 3.2.1&#xff0c;Vue 2&#xff0c;ElementUI 2 页面 3 效果&#xff1a;只能上传xls文件且大小限制为2M&#xff0c;选择文件后自动上传。 4 前端代码 <template><div class"container"><el…

性能测试(2)

jmeter参数化 loadrunner Jmeter IP欺骗&#xff0c;也称为IP欺诈&#xff0c;是指通过伪装、篡改IP地址的方式&#xff0c;进行网络攻击或欺骗行为。这种行为可能会导致网络安全问题&#xff0c;包括身份盗窃、数据泄露、DDoS攻击等。为了保护自己的网络安全&#xff0c;用户…

5.3 需求分析

软件需求 定义 分类 真题 需求工程 需求获取 真题 需求分析 状态转换图 数据流图 数据流图分层 顶层数据流图、0层数据流图 1层数据流图 真题 需求规约 需求定义方法 需求验证 需求验证内容 需求管理 版本控制 需求跟踪 变更控制 真题

mysql不初始化升级

1、下载mysql&#xff0c;下载地址&#xff1a;MySQL :: Download MySQL Community Server 2、解压下载好的mysql&#xff0c;修改配置文件的datadir指定目录为当前数据存储的目录 3、通过管理员cmd进入新版本mysql的bin目录&#xff0c; 然后执行命令安装mysql服务&#xff…

2024年山东省安全员B证证考试题库及山东省安全员B证试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年山东省安全员B证证考试题库及山东省安全员B证试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人员上岗证考试大…

LeetCode 441, 57, 79

目录 441. 排列硬币题目链接标签思路代码 57. 插入区间题目链接标签思路两个区间的情况对每个区间的处理最终的处理 代码 79. 单词搜索题目链接标签原理思路代码 优化思路代码 441. 排列硬币 题目链接 441. 排列硬币 标签 数学 二分查找 思路 由于本题所返回的 答案在区间…

通过 PPPOE 将 linux 服务器作为本地局域网 IPv4 外网网关

将 linux 服务器作为本地外网网关&#xff0c;方便利用 Linux 生态中的各种网络工具&#xff0c;对流量进行自定义、精细化管理… 环境说明 拨号主机&#xff1a;CentOS 7.9, Linux Kernel 5.4.257 拨号软件: rp-pppoe-3.11-7.el7.x86_64初始化 1、升级系统到新的稳定内核&a…

半年GMV狂飙166亿!酒水赛道正在崛起自播之路

从2022年开始&#xff0c;酒水以“兴趣”为核心的直播电商迎来爆发式增长。以抖音电商为例&#xff0c;2022年下半年整体销售额破百亿&#xff0c;环比增幅超100%&#xff0c;2023年全年更是破300亿大关&#xff01;兴趣电商成为酒行业的第二增长曲线。 今年上半年&#xff0c;…

机器学习第四十七周周报 CF-LT

文章目录 week47 CF-LT摘要Abstract1. 题目2. Abstract3. 网络结构3.1 CEEMDAN&#xff08;完全自适应噪声集合经验模态分解&#xff09;3.2 CF-LT模型结构3.3 SHAP 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程 5. 结论6.代码复现小结参考文献 week47 CF-LT 摘要 本周…

可视耳勺是不是智商税?五款好用挖耳勺推荐!

随着人们追求健康生活方式的需求日益增长&#xff0c;可视耳勺这一产品逐渐走入了人们的视野&#xff0c;并受到了广泛的青睐。 然而&#xff0c;市场上可视耳勺品牌和种类繁多&#xff0c;部分产品存在清晰度不高、亮度较暗等问题&#xff0c;在使用过程很容易存在损坏耳道的风…

负载箱如何帮助维持电气系统的最佳性能

负载箱在维持电气系统最佳性能方面发挥着至关重要的作用&#xff0c;以下是负载箱如何帮助维持电气系统最佳性能的详细分析&#xff1a; 一、保护电气设备 负载箱能够在电气系统中产生恒定的负载&#xff0c;使电气设备在正常工作状态下运行。这避免了因负载波动过大而导致的…

数据库管理1

数据库管理 数据库运维。 sql语句 数据库用来增删改查的语句 备份 数据库的数据进行备份 主从复制&#xff0c;读写分离&#xff0c;高可用。 数据库的概念和相关的语法和规范&#xff1a; 数据库&#xff1a;组织&#xff0c;存储&#xff0c;管理数据的仓库。 数据库的管理系…