当前位置: 首页 > news >正文

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

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

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

相关文章:

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

最近开始研究微信小游戏&#xff0c;有兴趣的 可以关注一下 公众号&#xff0c; 记录一些心路历程和源代码。 定义一个 Water class 1. **定义接口和枚举**&#xff1a; - WaterInfo 接口定义了水的颜色、高度等信息。 - PourAction 枚举定义了水的倒动状态&#xff0c;…...

【链表】算法题(一) ---- 力扣 / 牛客

一、移除链表元素 移除链表中值为val的元素&#xff0c;并返回新的头节点 思路&#xff1a; 题目上这样说&#xff0c;我们就可以创建一个新的链表&#xff0c;将值不为val的节点&#xff0c;尾插到新的链表当中&#xff0c;最后返回新链表的头节点。 typedef struct ListNo…...

Linux系统之部署盖楼小游戏

Linux系统之部署盖楼小游戏 一、小游戏介绍1.1 小游戏简介1.2 小游戏玩法基本介绍1.3 项目预览二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍2.3 版本要求三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本四、安装node.js4.1 安装nvm4.2 查看nvm版本4.3 安装…...

“金山-讯飞”杯2024年武汉理工大学程序设计竞赛 A. Mobiusp败走***(思维题-点双连通分量、连通性)

题目 思路来源 官方题解 题解 手玩发现&#xff0c;能换的话&#xff0c;当且仅当.和1在一个环里&#xff0c;而这就是点双连通分量 所以最优策略是先把.换到(x,y)的位置&#xff0c;然后判断.和1在不在一个环里 也就是&#xff1a; 1. 判断删掉1时&#xff0c;.和(x,y)联…...

【机器翻译】基于术语词典干预的机器翻译挑战赛

文章目录 一、赛题链接二、安装库1.spacy2.torch_text 三、数据预处理赛题数据类定义 TranslationDataset批量处理函数 collate_fn 四、编码器和解码器Encoder 类Decoder 类Seq2Seq 类注意事项 五、主函数1. load_terminology_dictionary(dict_file)2. train(model, iterator, …...

推荐系统:从协同过滤到深度学习

目录 一、协同过滤&#xff08;Collaborative Filtering, CF&#xff09;1. 基于用户的协同过滤2. 基于物品的协同过滤 二、深度学习在推荐系统中的应用1. 深度学习模型的优势2. 深度学习在推荐系统中的应用实例 三、总结与展望 推荐系统是现代信息处理和传播中不可或缺的技术&…...

记录些Spring+题集(1)

接口防刷机制 接口被刷指的是同一接口被频繁调用&#xff0c;可能是由于以下原因导致&#xff1a; 恶意攻击&#xff1a;攻击者利用自动化脚本或工具对接口进行大量请求&#xff0c;以消耗系统资源、拖慢系统响应速度或达到其他恶意目的。误操作或程序错误&#xff1a;某些情…...

SpringBoot 解决 getSession().getAttribute() 在负载均衡环境下无法获取session的问题

在Spring Boot中&#xff0c;使用getSession().getAttribute()方法时遇到在负载均衡环境下无法正确获取session属性的问题&#xff0c;通常是由于session属性存储在单个服务器的内存中&#xff0c;而负载均衡会导致用户的请求被分配到不同的服务器上&#xff0c;因此无法找到在…...

Jmeter常用组件及执行顺序

一 常用组件 1.线程组 Thread Group 线程组是一系列线程的集合&#xff0c;每一个线程代表着一个正在使用应用程序的用户。在 jmeter 中&#xff0c;每个线程意味着模拟一个真实用户向服务器发起请求。 在 jmeter 中&#xff0c;线程组组件运行用户设置线程数量、初始化方式等…...

PTrade常见问题系列10

get_ashares获取list为空。 get_Ashares函数目前都是向行情服务器进行获取的 如果请求数过多&#xff0c;应答返回偶现为空现象&#xff0c; 后续版本内进行优化从服务器缓存内取&#xff0c;需求单号&#xff1a;202303213922&#xff0c;于PTradeQT1.0V202202.01.023内发布…...

数据结构(4.4)——求next数组

next数组的作用:当模式串的第j个字符失配时&#xff0c;从模式串的第next[j]的继续往后匹配 求模式串的next数组(手算) next[1] 任何模式串都一样&#xff0c;第一个字符不匹配时&#xff0c;只能匹配下一个子串&#xff0c;因此&#xff0c;往后&#xff0c;next[1]都无脑写…...

《mysql篇》--JDBC编程

JDBC是什么 JDBC就是Java DataBase Connectivity的缩写&#xff0c;翻译过来就很好理解了&#xff0c;就是java连接数据库。所以顾名思义&#xff0c;JDBC就是一种用于执行SQL语句的JavaApl&#xff0c;是Java中的数据库连接规范。为了可以方便的用Java连接各种数据库&#xff…...

android studio 怎么下载 buildTool

在Android Studio中下载Build Tools&#xff0c;通常可以通过Android Studio内置的SDK Manager来完成。以下是详细的步骤&#xff1a; 一、通过Android Studio的SDK Manager下载Build Tools 启动Android Studio&#xff1a;首先&#xff0c;确保你已经安装了Android Studio&am…...

copy 和 mutableCopy 有点乱

字符串的拷贝操作 对 string literal (字符串字面量) 执行 copy 要打印指针指向对象的地址和指针本身的地址&#xff0c;可以使用 %p 格式符来输出指针地址。以下代码&#xff0c;展示了 originalString 和 copiedString 的指针地址和指向对象的地址&#xff1a; NSString *…...

sqlalchemy通过查询参数生成query

sqlalchemy通过查询参数生成query 在SQLAlchemy中,可以使用查询参数来动态生成查询。这通常通过使用.filter()方法和Python的比较运算符来实现。以下是一个简单的示例,展示如何使用查询参数生成查询: 假设我们有一个名为User的模型(表),它具有id、username和email字段。…...

【JavaScript 算法】二分查找:快速定位目标元素

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、算法原理二、算法实现三、应用场景四、优化与扩展五、总结 二分查找&#xff08;Binary Search&#xff09;是一种高效的查找算法&#xff0c;适用于在有序数组中快速定位目标元素。相比于线性查找&#xff0c;二分查找…...

论文研读:ViT-V-Net—用于无监督3D医学图像配准的Vision Transformer

目录 摘要 介绍 方法 VIT-V-Net体系结构 损失函数 图像相似性度量 变形场正则化 结果与讨论 摘要 在过去的十年里&#xff0c;卷积神经网络(ConvNets)在各种医学成像应用中占据了主导地位并取得了最先进的性能。然而&#xff0c;由于缺乏对图像中远程空间关系的理解&a…...

C++入门到进阶(图文详解,持续更新中)

C入门到进阶&#xff08;图文详解&#xff0c;持续更新中&#xff09; 详解C入门知识到进阶&#xff0c;配合图观看易于理解记录 文章目录 目录 C入门到进阶&#xff08;图文详解&#xff0c;持续更新中&#xff09; 文章目录 前言 一、数据 &#xff08;一&#xff09;数据类…...

【React Hooks原理 - useRef】

概述 在Function Component项目中当我们需要操作dom的时候&#xff0c;第一时间想到的就是使用useRef这个Hook来绑定dom。但是这个仅仅是使用这个Hook而已&#xff0c;为了更好的学习React Hooks内部实现原理&#xff0c;知其所以然。所以本文根据源码从useRef的基础使用场景一…...

MVC之 IHttpModule管道模型《二》

》》》注意&#xff1a;在http请求的处理过程中&#xff0c;只能调用一个HttpHandler&#xff0c;但可以调用多个HttpModule。 HTTP Modules ASP.NET请求处理过程是基于管道模型的&#xff0c;这个管道模型是由多个HttpModule和HttpHandler组成&#xff0c;当请求到达HttpMod…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

认识CMake并使用CMake构建自己的第一个项目

1.CMake的作用和优势 跨平台支持&#xff1a;CMake支持多种操作系统和编译器&#xff0c;使用同一份构建配置可以在不同的环境中使用 简化配置&#xff1a;通过CMakeLists.txt文件&#xff0c;用户可以定义项目结构、依赖项、编译选项等&#xff0c;无需手动编写复杂的构建脚本…...

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)

注&#xff1a;文章末尾网盘链接中自取成品使用演示视频、项目源码、项目文档 所用硬件&#xff1a;STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …...

PostgreSQL 与 SQL 基础:为 Fast API 打下数据基础

在构建任何动态、数据驱动的Web API时&#xff0c;一个稳定高效的数据存储方案是不可或缺的。对于使用Python FastAPI的开发者来说&#xff0c;深入理解关系型数据库的工作原理、掌握SQL这门与数据库“对话”的语言&#xff0c;以及学会如何在Python中操作数据库&#xff0c;是…...

【Java基础】​​向上转型(Upcasting)和向下转型(Downcasting)

在面向对象编程中&#xff0c;转型&#xff08;Casting&#xff09; 是指改变对象的引用类型&#xff0c;主要涉及 继承关系 和 多态。 向上转型&#xff08;Upcasting&#xff09; ⬆️ 定义 将 子类对象 赋值给 父类引用&#xff08;自动完成&#xff0c;无需强制转换&…...

JVM——对象模型:JVM对象的内部机制和存在方式是怎样的?

引入 在Java的编程宇宙中&#xff0c;“Everything is object”是最核心的哲学纲领。当我们写下new Book()这样简单的代码时&#xff0c;JVM正在幕后构建一个复杂而精妙的“数据实体”——对象。这个看似普通的对象&#xff0c;实则是JVM内存管理、类型系统和多态机制的基石。…...