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

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、改进策略

首先我们整理一下拼图碎片随机散开的要求,即需求分析

  • 碎片之间彼此互不重叠
  • 碎片散开分布到整个画面上
  • 随机分散各个碎片

需求基本上就是这样。如果拼图碎片的数量有所增加,可能还需要追加一项"能够控制游戏的难易度"。

接下来我们对实现方法进行说明。首先简单熟悉一下整体流程

  1. 将拼图碎片分配到网格中
  2. 打乱拼图碎片的排列顺序
  3. 在网格内通过随机坐标调整碎片的位置
  4. 将整个拼图随机旋转一定角度

我们可以选择任意图片,将其分割成几块。这里我们选择一个"猫头鹰"图片,将其分割成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小游戏——迷你拼图

游戏展示 拼图演示 资源&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1BGeSmRCO_WZRUyl3MxefGw 提取码&#xff1a;0n4a 一、玩法介绍 排列拼图碎片&#xff0c;拼出最后的图案。可以点住碎片的任意位置拖动&#xff1b;点击"重来"按钮&#xff0c;可以…...

三 动手学深度学习v2 —— Softmax回归+损失函数+图片分类数据集

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

Stable Diffusion 使用教程

环境说明&#xff1a; stable diffusion version: v1.5.1python: 3.10.6torch: 2.0.1cu118xformers: N/Agradio: 3.32.0 1. 下载 webui 下载地址&#xff1a; GitHub stable-diffusion-webui 下载 根据自己的情况去下载&#xff1a; 最好是 N 卡&#xff1a;&#xff08;我的…...

在线考试系统springboot学生试卷问答管理java jsp源代码mysql

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

创建vue-cli(脚手架搭建)

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

【单调栈part02】| 503.下一个更大元素||、42.接雨水

&#x1f388;LeetCode503.下一个更大元素|| 链接&#xff1a;503.下一个更大元素|| 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按…...

Java——如何使用Stream替换掉List<Student>中符合要求的元素

使用Stream替换掉List中符合要求的元素 要使用Stream流替换掉List中符合特定条件的元素&#xff0c;您可以使用Stream的map()方法对每个元素进行映射&#xff0c;并使用collect()方法将映射后的元素收集到一个新的List中。 示例代码&#xff1a; import java.util.ArrayList; …...

gin 框架中的 gin.Context

〇、前言 Context 是 gin 中最重要的部分。 例如&#xff0c;它允许我们在中间件之间传递变量、管理流程、验证请求的 JSON 并呈现 JSON 响应。 Context 中封装了原生的 Go HTTP 请求和响应对象&#xff0c;同时还提供了一些方法&#xff0c;用于获取请求和响应的信息、设置响…...

新版chrome浏览器恢复下载的时候恢复底栏提示

近日&#xff0c;谷歌对其Chrome浏览器进行了更新&#xff0c;为所有桌面系统的Chrome浏览器增加了位于地址栏右侧的“下载”气泡&#xff0c;并同时取消了原有的底部下载栏。 谷歌表示&#xff0c;这次更新的目的是为了让用户更方便地与最近下载的文件进行交互。 然而&#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程序员&#xff0c;2024届电子信息研究生 目录 一、Linux安装软件&#xff1a; 1.yum安装 2.Linux和Windows文件互传 问题: 3.yum卸载软件 二、vim编辑器 1.命令模式 2.vim配置项说明 3.vim操作总结 一、Linux安装软件&#…...

ImageNet1000分类,英文原版,中文翻译版

在训练模型时&#xff0c;可以用imagenet中的图片进行分类学习&#xff0c;imagenet中分类介绍 一、官网网址 imagenet官网网址 1-398&#xff1a;动物 399-924&#xff1a;物品 925-1000&#xff1a;食物 二、官方英文版分类 n01440764 tench, Tinca tinca n01443537 gold…...

「Qt」常用事件介绍

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

小鱼深度产品测评之:阿里云容器服务器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的模型显著提高了探测器的性能&#xff0c;甚至优于经典的卷积模型。然而&#xff0c;在传统的编码器结构中&#xff0c;所有的标记都带来了冗余的计算负担。最近的稀疏化策略利用了信息标记的一个子集&#xff0c;通过稀疏编码器来降低注意力的复杂性&#xff0…...

2023 8-5

430. 扁平化多级双向链表 前序遍历(递归) 脖子左歪45度,多级链表变成了二叉树,输出先序即可。 前序遍历再将结果存放在双向链表中,通过将链表存入节点来改变原来的节点 /* // Definition for a Node. class Node { public:int val;Node* prev;Node* next;Node* child; }; *…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...