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

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

定义一个 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.0let _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.rhkb.cn/news/377252.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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

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

数学建模入门

目录 文章目录 前言 一、数学建模是什么&#xff1f; 1、官方概念&#xff1a; 2、具体过程 3、适合哪一类人参加&#xff1f; 4、需要有哪些学科基础呢&#xff1f; 二、怎样准备数学建模&#xff08;必备‘硬件’&#xff09; 1.组队 2.资料搜索 3.常用算法总结 4.论文撰写的…

【密码学】数字签名

一、数字签名的基本概念 数字签名是一种用于验证电子文档完整性和身份认证的密码学技术。它通过使用公钥加密体系中的私钥对文档的一部分&#xff08;通常是文档的摘要&#xff09;进行加密&#xff0c;从而创建一个“签名”。这个签名可以附在文档上&#xff0c;或作为一个单独…

【数据结构】--- 堆的应用

​ 个人主页&#xff1a;星纭-CSDN博客 系列文章专栏 :数据结构 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 一.堆排序 在前一个文章的学习中&#xff0c;我们使用数组的物理结构构造出了逻辑结构上的堆。那么堆到底有什么用呢&…

【Linux】01.Linux 的常见指令

1. ls 指令 语法&#xff1a;ls [选项] [目录名或文件名] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息 常用选项&#xff1a; -a&#xff1a;列出当前目录下的所有文件&#xff0c;包含隐藏文件…

Spring webflux基础核心技术

一、 用操作符转换响应式流 1 、 映射响应式流元素 转换序列的最自然方式是将每个元素映射到一个新值。 Flux 和 Mono 给出了 map 操作符&#xff0c;具有 map(Function<T&#xff0c;R>) 签名的方法可用于逐个处理元素。 当操作符将元素的类型从 T 转变为 R 时&#xf…

「豆包Marscode体验官」我用豆包Marscode画数据大屏

认识豆包Marscode 豆包 MarsCode IDE 是一个 AI 原生的云端集成开发环境&#xff08;IDE&#xff09;。内置的 AI 编程助手和开箱即用的开发环境让我们可以更加专注于各种项目的开发。豆包 MarsCode 编程助手&#xff0c;具备以智能代码补全为代表的 AI 功能。支持了多种编程语…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(十二)-无人机群在物流中的应用

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

解读网传《深圳IT圈⭕新解读八小时工作制》

网传深圳IT圈的新解读八小时工作制 工作时间安排&#xff1a; 10:00-12:0014:00-18:0019:00-21:00 初看&#xff1a;有惊喜 上午开始时间晚&#xff1a;相对于传统的9点开始&#xff0c;这种安排允许员工有更多的早晨时间&#xff0c;可以用来休息或处理个人事务。下午和晚上分…

Amazon EC2 部署Ollama + webUI

最近和同事闲聊&#xff0c;我们能不能内网自己部署一个LLM&#xff0c;于是便有了Ollama webUI的尝试 对于Linux&#xff0c;使用一行命令即可 curl -fsSL https://ollama.com/install.sh | shollama --help Large language model runnerUsage:ollam…

架构设计-NX的二次开发API架构设计介绍

1.与整体的关系 2.API设计目标 能够允许用户访问NX的所有UI工具组件&#xff0c;二次开发用户能够编写外观和运行行为类似NX的应用程序。能够允许用户直接访问NX数据模型即使底层数据结构和功能实现发生很大变化&#xff0c;API接口保持稳定&#xff0c;不会影响上层用户。 3…

智能家居开发新进展:乐鑫 ESP-ZeroCode 与亚马逊 ACK for Matter 实现集成

日前&#xff0c;乐鑫 ESP-ZeroCode 与亚马逊 Alexa Connect Kit (ACK) for Matter 实现了集成。这对智能家居设备制造商来说是一项重大进展。开发人员无需编写固件或开发移动应用程序&#xff0c;即可轻松设计符合 Matter 标准的产品。不仅如此&#xff0c;开发者还可以在短短…

网络协议 — Keepalived 高可用方案

目录 文章目录 目录Keepalived 是实现了 VRRP 协议的软件Keepalived 的软件架构VRRP StackCheckersKeepalived 的配置Global configurationvrrp_scriptVRRP Configurationvrrp synchroization groupvrrp instancevirtual ip addressesvirtual routesLVS Configurationvirtual_s…

知识图谱与LLMs:实时图分析(通过其关系的上下文理解数据点)

大型语言模型 (LLM) 极大地改变了普通人获取数据的方式。不到一年前&#xff0c;访问公司数据需要具备技术技能&#xff0c;包括熟练掌握各种仪表板工具&#xff0c;甚至深入研究数据库查询语言的复杂性。然而&#xff0c;随着 ChatGPT 等 LLM 的兴起&#xff0c;随着所谓的检索…

不常用的第三方服务集成

1.ldap 1.1.ldap服务搭建 docker方式搭建:包含了ldap服务和ldap admin图形化界面服务 参考ldap服务:http://127.0.0.1:81 用户名:CN=admin,DC=ldap,DC=com 密码:123456 docker-compose.yml文件内容如下 version: 3services:ldap:image: osixia/openldap:latestcontainer…

0基础学会在亚马逊云科技AWS上利用SageMaker、PEFT和LoRA高效微调AI大语言模型(含具体教程和代码)

项目简介&#xff1a; 小李哥今天将继续介绍亚马逊云科技AWS云计算平台上的前沿前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS上的AI软甲开发最佳实践&#xff0c;并应用到自己的日常工作里。本次介绍的是如何在Amazon SageMaker上…

【qt】TCP客户端如何断开连接?

disconnectFromHost() 来关闭套接字,断开连接. 当我们关闭窗口时,也需要断开连接. 需要重写关闭事件 如果当前的套接字状态是连接上的,我们就可以来断开连接. 运行结果:

SSM框架学习笔记(仅供参考)

&#xff08;当前笔记简陋&#xff0c;仅供参考&#xff09; 第一节课&#xff1a; &#xff08;1&#xff09;讲述了Spring框架&#xff0c;常用jar包&#xff0c;以及框架中各个文件的作用 &#xff08;2&#xff09;演示了一个入门程序 &#xff08;3&#xff09;解释了…

前端项目本地的node_modules直接上传到服务器上无法直接使用(node-sasa模块报错)

跑 jekins任务的服务器不能连接外网下载依赖包&#xff0c;就将本地下载的 node_modules直接上传到服务器上&#xff0c;但是运行时node-sass模块报错了ERROR in Missing binding /root/component/node_modules/node-sass/vendor/linux-x64-48/binding.node >> 报错信息类…

不会编程怎么办?量化交易不会编程可以使用吗?

量化交易使用计算机模型程序代替人工进行交易&#xff0c;一般需要投资者自己编写程序建模&#xff0c;然后回测无误之后再进行实盘交易&#xff0c;那么不会编程的投资者能使用量化软件进行量化交易吗&#xff1f; 不会编程使用量化软件有两种方法 一种是请人代写代码&#x…