Unity小游戏——迷你拼图
游戏展示
拼图演示
资源:
链接:https://pan.baidu.com/s/1BGeSmRCO_WZRUyl3MxefGw
提取码:0n4a
一、玩法介绍
排列拼图碎片,拼出最后的图案。可以点住碎片的任意位置拖动;点击"重来"按钮,可以回到最初状态重新开始。
二、流畅的拖拽操作
有很多电脑游戏的原型来自于现实世界中的玩具,拼图游戏就是其中的一个代表。
本文我们介绍的拼图游戏虽然是一款玩法比较简单的游戏,不过这并不意味着开发也非常简单。
相对于其他游戏通过操作键盘或移动鼠标来控制角色的运动方向,拼图游戏通过鼠标的拖拽直接移动拼图的碎片。
游戏的核心在于流畅的拖拽操作。
除了拖拽操作外,我们也可以借此机会思考一下诸如"当碎片移动到正确的位置附近时会被吸附到正确的位置"等触屏游戏的小细节。
三、点住碎片的任意位置拖动
Unity可以很容易判断出"某个对象受到了点击",不过如果要实现自然流畅的操作,我们仍需下功夫。这里,为了让鼠标的拖拽操作更急接近"用手指摁住移动"的效果,我们需要考虑一下如何才能点住碎片的任意位置拖动。
1、透视变换和逆透视变换
鼠标的光标位于屏幕上时,其位置坐标位于二维坐标系内。而拼图碎片位于3D空间内,所以其位置坐标自然有三个维度。为了比较鼠标光标和拼图碎片的位置,必须将它们放入相同的坐标系。因此,我们使用逆透视变换的方法,将鼠标光标的坐标变换至三维坐标系
2、被点击处即为光标的位置
通过逆透视变换将鼠标光标和拼图碎片的坐标统一到相同的坐标系后,我们就该尝试通过拖拽使拼图移动了,只需要在点击按键的瞬间,将鼠标光标的坐标复制到拼图碎片的坐标即可。这种方法确实非常简单,不过它有个缺点:鼠标光标总是显示在拼图碎片的中心。
在本游戏中,拼图碎片被点击的位置并不影响游戏的玩法。不过,对于某些游戏而言,点击位置的不同可能会改变角色的朝向,或者是游戏角色以光标为中心摆动,这些情况下在何处点击就变得很重要了。
而且,即使不影响游戏的核心玩法,点击的瞬间拼图碎片会突然移动一下这种体验也很糟糕。尽管有些时候这种机制可能会更好,但是为了应对不同的要求,我们还是需要掌握如何能点住碎片的任意处拖动。
在本游戏中,碎片的点击判断都是通过Unity的网格碰撞器实现的。网格碰撞器采用网格进行碰撞检测,点击拼图碎片的任何部位都将发生碰撞。对于玩家来说点击碎片的哪个位置都可以,这反映到程序中就是"不用关心碎片的何处受到了点击"。
点击的瞬间,鼠标光标不一定位于碎片的中心。两者的坐标存在一定的差距,我们将这种坐标的差距称为偏移。
之前我们把光标的坐标原原本本地复制到碎片坐标时,因为两个坐标值相同所以差距为0,这种坐标差的急剧变化正是导致拼图碎片突然移动的原因。
知道了坐标偏移值的变化是问题所在后,我们来考虑如何固定这个偏移值。首先,要在鼠标点击拼图碎片的瞬间,也就是开始拖动的时候,计算出鼠标光标和碎片中心的坐标差,得到的值就是偏移值。
偏移=碎片的位置-鼠标光标的位置
拖动的过程中则与之相反,用鼠标光标的位置加上偏移值就可以得到碎片的位置
碎片的位置=鼠标光标位置+偏移值
这样一来,鼠标光标距离碎片中心总是保持一定的距离,这样就保证了鼠标点击瞬间的位置就是碎片被拖拽的位置。
下面来看看实际的代码
private void begin_dragging(){do {// 将光标坐标变换为3D空间内的世界坐标Vector3 world_position;if(!this.unproject_mouse_position(out world_position, Input.mousePosition)) {break;}if(PieceControl.IS_ENABLE_GRAB_OFFSET) {// 求出偏移值(点击位置距离碎片的中心有多远)this.grab_offset = this.transform.position - world_position;}} while(false);}
private void do_dragging(){do {// 将光标坐标变换为3D空间内的世界坐标Vector3 world_position;if(!this.unproject_mouse_position(out world_position, Input.mousePosition)) {break;}// 加上光标坐标(3D)的偏移值,计算出碎片的中心坐标this.transform.position = world_position + this.grab_offset;} while(false);}
四、打乱拼图碎片
商店里售卖纸质拼图游戏时一般会将拼图碎片打乱顺序后放入包装盒中。虽然也有些是已经拼好的状态,不过玩家在开始游戏之前还是要将各碎片的顺序打乱。
有很多事情都是"人类做起来很简单,计算机处理起来却很难",比如将拼图碎片全部打乱这件事就是一个例子。
Unity提供了取得随机数的方法,不过单纯使用该方法似乎并不能达到打乱碎片顺序的目的。
这里我们不妨来分析一下如何随机打乱各拼图碎片的顺序。
1、设置拼图碎片的坐标为随机数
最简单的随机打乱拼图碎片的方法是,直接将随机数代入各个碎片的坐标。只要控制好随机数的范围,就能让各个拼图碎片随机分布在画面上。
但这种方式的弊端也很明显,就是有很多拼图碎片可能叠在一起。虽然这样也未尝不可,不过可以的话最好还是将各碎片均匀分散开。如果很多碎片重叠在一起,就可能导致下面的碎片被覆盖而无法看见。
2、改进策略
首先我们整理一下拼图碎片随机散开的要求,即需求分析
- 碎片之间彼此互不重叠
- 碎片散开分布到整个画面上
- 随机分散各个碎片
需求基本上就是这样。如果拼图碎片的数量有所增加,可能还需要追加一项"能够控制游戏的难易度"。
接下来我们对实现方法进行说明。首先简单熟悉一下整体流程
- 将拼图碎片分配到网格中
- 打乱拼图碎片的排列顺序
- 在网格内通过随机坐标调整碎片的位置
- 将整个拼图随机旋转一定角度
我们可以选择任意图片,将其分割成几块。这里我们选择一个"猫头鹰"图片,将其分割成8块。
首先,将所有的拼图碎片从左上角开始依次放入网格中。
该网格的行数和列数相同,并且网格总数达于拼图碎片数量。"猫头鹰"拼图碎片数量为8,我们就搞一个3✖️3的网格,空出来的格子不用理会。根据碎片数量的不同,有时候剩余的格子会比较多,这种情况下可以调整网格的行数和列数。
所有网格块都为正方形,且都应当能确保能够容纳下拼图碎片。另外,因为后续步骤在网格内移动拼图碎片,所以还需要在确保整体网格不溢出画面的前提下适当放大网格的尺寸。
之所以想这样把拼图碎片纺织到网格中,是为了避免出现碎片之间彼此重叠的状况。
接下来随机打乱个碎片的排列位置
在第一个步骤中,我们将碎片从左上角开始依次放入了网格中。而第二个步骤就是打乱各个碎片的排列顺序。利用随机数选出两个网格,案后交换其中的碎片空白的网格也可以参与交换。
做到这里,前面我们做出的需求分析中,"碎片之间彼此互不重叠","碎片分散于整个画面"和"随机分散各个碎片"就已经基本得到了实现。不过从程序实际情况来看,很容易发现拼图碎片被规则地排列在了网格上。我们得想办法让这种随机分散的效果更真实。
在第三个步骤中,我们让拼图碎片在网格中随机移动。
最初的步骤中增加网格尺寸的用意就在于为这里的碎片移动做准备。如果网格的尺寸太小,将无法移动碎片,反之如果太大,则会令碎片之间过于松散。我们需要结合拼图碎片的大小和画面整体的尺寸,调整网格尺寸为最佳值,
最后,为了不让玩家看出碎片排列的规律性,稍微将拼图网格整体旋转一定的角度
虽然旋转了整体的网格,但是需要保持拼图碎片自身的角度不变。
下面,我们结合实际代码来看看这一流程
private void shuffle_pieces(){#if true// 将碎片按照网格顺序排列int[] piece_index = new int[this.shuffle_grid_num*this.shuffle_grid_num];for(int i = 0;i < piece_index.Length;i++) {if(i < this.all_pieces.Length) {piece_index[i] = i;} else {piece_index[i] = -1;}}// 随机选取两个碎片,交换位置for(int i = 0;i < piece_index.Length - 1;i++) {int j = Random.Range(i + 1, piece_index.Length);int temp = piece_index[j];piece_index[j] = piece_index[i];piece_index[i] = temp;}// 通过位置的索引变换为实际的坐标来进行配置Vector3 pitch;pitch = this.shuffle_zone.size/(float)this.shuffle_grid_num;for(int i = 0;i < piece_index.Length;i++) {if(piece_index[i] < 0) {continue;}PieceControl piece = this.all_pieces[piece_index[i]];Vector3 position = piece.finished_position;int ix = i%this.shuffle_grid_num;int iz = i/this.shuffle_grid_num;position.x = ix*pitch.x;position.z = iz*pitch.z;position.x += this.shuffle_zone.center.x - pitch.x*(this.shuffle_grid_num/2.0f - 0.5f);position.z += this.shuffle_zone.center.z - pitch.z*(this.shuffle_grid_num/2.0f - 0.5f);position.y = piece.finished_position.y;piece.start_position = position;}// 逐步(网格的格子内)随机移动位置Vector3 offset_cycle = pitch/2.0f;Vector3 offset_add = pitch/5.0f;Vector3 offset = Vector3.zero;for(int i = 0;i < piece_index.Length;i++) {if(piece_index[i] < 0) {continue;}PieceControl piece = this.all_pieces[piece_index[i]];Vector3 position = piece.start_position;position.x += offset.x;position.z += offset.z;piece.start_position = position;//offset.x += offset_add.x;if(offset.x > offset_cycle.x/2.0f) {offset.x -= offset_cycle.x;}offset.z += offset_add.z;if(offset.z > offset_cycle.z/2.0f) {offset.z -= offset_cycle.z;}}// 使全体旋转foreach(PieceControl piece in this.all_pieces) {Vector3 position = piece.start_position;position -= this.shuffle_zone.center;position = Quaternion.AngleAxis(this.pazzle_rotation, Vector3.up)*position;position += this.shuffle_zone.center;piece.start_position = position;}this.pazzle_rotation += 90;#else// 简单地使用随机数来决定坐标时的情况foreach(PieceControl piece in this.all_pieces) {Vector3 position;Bounds piece_bounds = piece.GetBounds(Vector3.zero);position.x = Random.Range(this.shuffle_zone.min.x - piece_bounds.min.x, this.shuffle_zone.max.x - piece_bounds.max.x);position.z = Random.Range(this.shuffle_zone.min.z - piece_bounds.min.z, this.shuffle_zone.max.z - piece_bounds.max.z);position.y = piece.finished_position.y;piece.start_position = position;}#endif}
相关文章:
Unity小游戏——迷你拼图
游戏展示 拼图演示 资源: 链接:https://pan.baidu.com/s/1BGeSmRCO_WZRUyl3MxefGw 提取码:0n4a 一、玩法介绍 排列拼图碎片,拼出最后的图案。可以点住碎片的任意位置拖动;点击"重来"按钮,可以…...

三 动手学深度学习v2 —— Softmax回归+损失函数+图片分类数据集
三 动手学深度学习v2 —— Softmax回归损失函数图片分类数据集 目录: softmax回归损失函数 1. softmax回归 回归vs分类: 回归估计一个连续值分类预测一个离散类别 从回归到多类分类 回归 单连续数值输出自然区间R跟真实值的误差作为损失 分类 通常多个输出输出i是预测为第…...

Stable Diffusion 使用教程
环境说明: stable diffusion version: v1.5.1python: 3.10.6torch: 2.0.1cu118xformers: N/Agradio: 3.32.0 1. 下载 webui 下载地址: GitHub stable-diffusion-webui 下载 根据自己的情况去下载: 最好是 N 卡:(我的…...

在线考试系统springboot学生试卷问答管理java jsp源代码mysql
本项目为前几天收费帮学妹做的一个项目,Java EE JSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。 一、项目描述 在线考试系统springboot 系统有2权限:管理…...

创建vue-cli(脚手架搭建)
目录 功能 需要的环境 使用HbuilderX快速搭建一个vue-cli项目 组件路由 element-ui vue-cli 官方提供的一个脚手架,用于快速生成一个 vue 的项目模板;预先定义 好的目录结构及基础代码,就好比咱们在创建 Maven 项目时可以选择创建一个 骨…...

【单调栈part02】| 503.下一个更大元素||、42.接雨水
🎈LeetCode503.下一个更大元素|| 链接:503.下一个更大元素|| 给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按…...
Java——如何使用Stream替换掉List<Student>中符合要求的元素
使用Stream替换掉List中符合要求的元素 要使用Stream流替换掉List中符合特定条件的元素,您可以使用Stream的map()方法对每个元素进行映射,并使用collect()方法将映射后的元素收集到一个新的List中。 示例代码: import java.util.ArrayList; …...
gin 框架中的 gin.Context
〇、前言 Context 是 gin 中最重要的部分。 例如,它允许我们在中间件之间传递变量、管理流程、验证请求的 JSON 并呈现 JSON 响应。 Context 中封装了原生的 Go HTTP 请求和响应对象,同时还提供了一些方法,用于获取请求和响应的信息、设置响…...

新版chrome浏览器恢复下载的时候恢复底栏提示
近日,谷歌对其Chrome浏览器进行了更新,为所有桌面系统的Chrome浏览器增加了位于地址栏右侧的“下载”气泡,并同时取消了原有的底部下载栏。 谷歌表示,这次更新的目的是为了让用户更方便地与最近下载的文件进行交互。 然而&#x…...
ModuleNotFoundError: No module named ‘lsb_release‘
Ubuntu 重装python版本导致的问题 $ lsb_release -a # 使用命令查看报错详情 Traceback (most recent call last):File "/usr/bin/lsb_release", line 25, in <module> # 这个路径很重要import lsb_release ModuleNotFoundError: No module named lsb_re…...

2021-03-03 Multisim 14.0 电池充电防止反接保护
R2R3当作充电线电阻看,也可设置这2个电阻导线电阻,电阻取值依据充电电流范围确定,由于电池存在电压因此可以用光耦检测,发光二极管当作继电器看,可采用继电器自锁,当下次再次反接的话另一个继电器同样,2个继电器相互控制.本电路可验证极性变化时2路检测的变化,图中S1为模拟电池…...
【AI】《动手学-深度学习-PyTorch版》笔记(八):线性回归
AI学习目录汇总 1、线性模型 线性函数如下: y ^ = w 1 x 1 + . . . + w d x d...

uniapp 持续获取定位(登录状态下才获取)(不采用定时器)(任意页面都可监听定位改变)
基于上次文章做了优化和改良,保证在登录状态下才获取定位信息 uniapp 小程序实时且持续获取定位信息(全局设置一次)(单页面监听定位改变)(不采用定时器)_uniapp小程序定位_前端小胡兔的博客-CSDN博客本篇文章实现了uniapp 微信小程序实时获取定位信息,小程序打开即可持续获取定…...

【Linux】Linux工具
Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员,2024届电子信息研究生 目录 一、Linux安装软件: 1.yum安装 2.Linux和Windows文件互传 问题: 3.yum卸载软件 二、vim编辑器 1.命令模式 2.vim配置项说明 3.vim操作总结 一、Linux安装软件&#…...
ImageNet1000分类,英文原版,中文翻译版
在训练模型时,可以用imagenet中的图片进行分类学习,imagenet中分类介绍 一、官网网址 imagenet官网网址 1-398:动物 399-924:物品 925-1000:食物 二、官方英文版分类 n01440764 tench, Tinca tinca n01443537 gold…...

「Qt」常用事件介绍
🔔 在开始本文的学习之前,笔者希望读者已经阅读过《「Qt」事件概念》这篇文章了。本文会在上篇文章的基础上,进一步介绍 Qt 中一些比较常用的事件。 0、引言 当我们想要让控件收到某个事件时做一些操作,通常都需要重写相应的事件处…...

小鱼深度产品测评之:阿里云容器服务器ASK,一款不需购买节点,即可直接部署容器应用。
容器服务器ASK测评 1、引言2、帮助文档3、集群3.1集群列表3.1.1 详情3.1.1.1概览 tab3.1.1.2基本信息 tab3.1.1.4集群资源 tab3.1.1.5 集群日志 tab3.1.1.6 集群任务 tab 3.1.2 应用管理3.1.2.1 详情3.1.2.2 详情3.1.2.3 伸缩3.1.2.4 监控 3.1.3 查看日志3.1.3.1 集群日志3.1.3…...

RK3588平台开发系列讲解(文件系统篇)什么是 VFS
文章目录 一、什么是 VFS二、VFS 数据结构2.1、超级块结构2.2、目录结构2.3、文件索引结点2.4、打开的文件2.5、四大对象结构的关系沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 今天我们一起来瞧一瞧 Linux 是如何管理文件,也验证一下 Linux 那句口号:一切皆为文…...

Less is More: Focus Attention for Efficient DETR
摘要 类似detr的模型显著提高了探测器的性能,甚至优于经典的卷积模型。然而,在传统的编码器结构中,所有的标记都带来了冗余的计算负担。最近的稀疏化策略利用了信息标记的一个子集,通过稀疏编码器来降低注意力的复杂性࿰…...

2023 8-5
430. 扁平化多级双向链表 前序遍历(递归) 脖子左歪45度,多级链表变成了二叉树,输出先序即可。 前序遍历再将结果存放在双向链表中,通过将链表存入节点来改变原来的节点 /* // Definition for a Node. class Node { public:int val;Node* prev;Node* next;Node* child; }; *…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
《Offer来了:Java面试核心知识点精讲》大纲
文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

表单设计器拖拽对象时添加属性
背景:因为项目需要。自写设计器。遇到的坑在此记录 使用的拖拽组件时vuedraggable。下面放上局部示例截图。 坑1。draggable标签在拖拽时可以获取到被拖拽的对象属性定义 要使用 :clone, 而不是clone。我想应该是因为draggable标签比较特。另外在使用**:clone时要将…...