Unity2D 井字棋

Unity版本2022.3

场景布置

在这里插入图片描述
其中可以通过给Board对象添加Grid Layout Group,然后设置每个子物体所占宽高快速排整齐。用完删掉。每个落子的方格ChessBox都是一个Button。

在这里插入图片描述

根据Board的宽高除以三即可。

然后隐藏按钮,通过设置alpha值实现。

在这里插入图片描述

将ChessBox的Alpha值设置为1,如果是0-1的格式,设置为0.1即可,后续在代码里控制alpha值,让落子可以被看到。

看心情摆一摆就好了,然后是写代码放引用。

代码编写

棋子种类

public enum Chess 
{None,X,O,
}

落子的九宫格

每个落子的方格都是一个Button,实现被点击的效果。可以加标识符拒绝再次点击,或者关闭button的交互。都行。

using UnityEngine;
using UnityEngine.UI;public class ChessBox : MonoBehaviour
{public Button currentButton;    // 当前脚本所在的按钮的引用public Image currentImage;      // 当前脚本所在的图片的引用public Chess chess;             // 当前格子的棋子public bool isChessed;          // 当前格子是否落子// Start is called before the first frame updatevoid Start(){currentButton = GetComponent<Button>();currentButton.onClick.AddListener(DrawChess);currentImage = GetComponent<Image>();isChessed = false;}
}

其中CurrentButton和CurrentImage是预防需要反复getComponent写的,实际可以去掉,然后代码中用到的位置都用GetComponent。此处两个字段是引用,而非值类型,所以可以直接修改这两个字段实现修改组件内部的属性,避免使用Sprite sprite来控制Image的Sprite。如果通过保存Image的Sprite作为字段,此处sprite是值类型,不是引用,对它的修改不会应用到Image组件的Sprite参数。

Sprite sprite;void Start(){sprite = GetComponent<Image>().sprite;}

这样后续修改sprite是无效的。

然后实现其中的落子函数,通过isChessed作为标识符判断是否可以落子,或者直接关闭按钮的交互,因为它是按钮。因为原本按钮是被设置为几乎不可见,所以落子后要重新设置alpha值,让他可见,R,G,B,A参数范围是0-1。

// 落子
public void DrawChess()
{// 落子或游戏结束,直接返回,不处理if (isChessed || GameManager.Instance.isGameOver) return;if(GameManager.Instance.isPlayerTurn){currentImage.sprite = GameManager.Instance.playerChessSprite;currentImage.color = new Color(1,1,1,1);chess = GameManager.Instance.playerChess;}else{currentImage.sprite = GameManager.Instance.aiChessSprite;currentImage.color = new Color(1, 1, 1, 1);chess = GameManager.Instance.aiChess;}isChessed = true;// 不用isChessed的话// currentButton.interactable = false;Board board = GetComponentInParent<Board>();board.OnChessBoxClicked(this);
}

落子后要让棋盘知道,所以这里使用的 GetComponentInParent<Board>()的方式。也可以给每个格子都像CurentButton那样保存对棋盘的引用。

这里暂时到这里。

棋盘

然后是棋盘的代码,判断游戏的进展之类的,还有重开。

实现格子落子时执行的棋盘的函数。

using TMPro;
using UnityEngine;
using UnityEngine.UI;public class Board : MonoBehaviour
{// 九个落子的格子public ChessBox[] chessBoxes = new ChessBox[9];// 获胜文本框public TextMeshProUGUI text_Winner;// 胜者public string winner;// 重开按钮public Button restartButton;// 落子数量public int chessCount;void Start(){chessCount = 0;chessBoxes = GetComponentsInChildren<ChessBox>();restartButton.onClick.AddListener(ReStart);}// 落子时调用public void OnChessBoxClicked(ChessBox chessBox){chessCount++;if (CheckIfWin()){GameOver();return;}else if( chessCount == 9)   // 没赢但是满了{winner = "Nobody";GameOver();return;}GameManager.Instance.SwitchTurn();}
}

此处的pulic字段其实都应使用private,毕竟其他地方也不用,然后通过其他方式赋初值,写在Awake函数中,或者序列化私有字段。

就像这样。

[SerializeField]private TextMeshProUGUI text_Winner;

这样可以在Unity中通过拖拽赋值,但是其他脚本访问不到。

每次落子后都要判断谁赢了或者是不是平局。

井字棋的获胜方式只有八种,即三行三列两个对角。
所以判断八种情况下的棋子有哪一种情况是相同的棋子,并且必须是其中一方的棋子,要避开初始情况下棋子全是None。

// 检查是否获胜
public bool CheckIfWin()
{return ChessMatch(0, 1, 2) || ChessMatch(3, 4, 5) || ChessMatch(6, 7, 8) ||ChessMatch(0, 3, 6) || ChessMatch(1, 4, 7) || ChessMatch(2, 5, 8) ||ChessMatch(0, 4, 8) || ChessMatch(2, 4, 6);
}
// 棋子匹配检查,是否三连子
bool ChessMatch(int i, int j, int k)
{if(chessBoxes[i].chess == chessBoxes[j].chess && chessBoxes[j].chess == chessBoxes[k].chess ){if(chessBoxes[k].chess == GameManager.Instance.playerChess){winner = "player";return true;}else if(chessBoxes[k].chess == GameManager.Instance.aiChess){winner = "ai";return true;}}return false;
}

接下来是落子后如果游戏结束,显示文本和不允许落子即可。

// 结束游戏,显示胜者
public void GameOver()
{text_Winner.text = $"{winner} is win!";GameManager.Instance.isGameOver = true;for (int i = 0; i < chessBoxes.Length; i++){// 禁止所有格子落子chessBoxes[i].isChessed = true;// 不用isChessed的话// chessBoxes[i].GetComponent<Button>().interactable = false;// chessBoxes[i].currentButton.interactable = false;}
}

重新开始

让所有格子重置,所以棋盘上写:

    // 清空棋盘状态,将上局最后一手设置为后手public void ReStart(){GameManager.Instance.isGameOver = false;for (int i = 0; i < chessBoxes.Length; i++){chessBoxes[i].ReStart();}text_Winner.text = "gaming";GameManager.Instance.SwitchTurn();}

重置状态,alpha值要设置的小一些,不然会显示null的sprite是纯白的,会挡住背景,但不要设置为0,如果是0就点不到了。格子按钮上写:

    public void ReStart(){isChessed = false;chess = Chess.None;currentImage.sprite = null;// 不用isChessed的话// GetComponent<Button>().interactable = true;currentImage.color = new Color(1, 1, 1, 0.01f);}

GameManager

存一堆到处用的东西?大概吧。

public class GameManager : MonoBehaviour
{public bool isPlayerTurn;   // 是否是玩家回合public Chess playerChess;   // 玩家棋子public Chess aiChess;       // ai棋子public Sprite playerChessSprite;    // 玩家的棋子精灵public Sprite aiChessSprite;        // ai的棋子精灵public bool isGameOver;             // 游戏是否已结束public Board board;     // 棋盘引用public static GameManager Instance { get; private set; }private void Awake(){// 不切换场景,这样就够了if (Instance == null){Instance = this;}// 初始化状态isPlayerTurn = true;isGameOver = false;board = FindObjectOfType<Board>();}

在Inspector中给了Board引用后,其中的 FindObjectOfType<Board>()可以删除。

切换回合,很简单,如果不写AI行为,那AI其实算是P2。

// 切换回合
public void SwitchTurn()
{isPlayerTurn = !isPlayerTurn;// 如果是 AI 的回合,调用 AI 落子逻辑if (!isPlayerTurn && !isGameOver){AIPlay();}
}

AI行为–基于规则的AI

由于井字棋很简单,所以AI的逻辑也很简单,如果玩家能赢,那就堵,如果AI能赢,那就填,不然随便放,也可以加一步,如果AI先手,即场上全空,那优先抢中间。

    // 获取没落子的空位置private ChessBox[] GetEmptyChessBoxes(){// 获取所有 ChessBoxChessBox[] allBoxes = FindObjectsOfType<ChessBox>();// 过滤出空的 ChessBoxList<ChessBox> emptyBoxes = new List<ChessBox>();foreach (ChessBox box in allBoxes){if (box.chess == Chess.None){emptyBoxes.Add(box);}}return emptyBoxes.ToArray();}// AI行为private void AIPlay(){ChessBox[] emptyBoxes = GetEmptyChessBoxes();//if(emptyBoxes.Length == 9)//{//    Board board = FindObjectOfType<Board>();//    board.chessBoxes[4].DrawChess();//    return;//}if (emptyBoxes.Length > 0){// 检查是否有可以立即获胜的位置ChessBox winningBox = FindWinningBox(aiChess, emptyBoxes);if (winningBox != null){winningBox.DrawChess();return;}// 检查玩家是否有即将获胜的位置ChessBox blockingBox = FindWinningBox(playerChess, emptyBoxes);if (blockingBox != null){blockingBox.DrawChess();return;}// 随机选择一个空的位置int randomIndex = Random.Range(0, emptyBoxes.Length);ChessBox selectedBox = emptyBoxes[randomIndex];selectedBox.DrawChess();}}// 寻找可以获胜的格子private ChessBox FindWinningBox(Chess targetChess, ChessBox[] emptyBoxes){foreach (ChessBox box in emptyBoxes){// 模拟落子box.chess = targetChess;// 检查是否获胜if (board.CheckIfWin()){// 恢复 ChessBox 状态box.chess = Chess.None;return box;}// 恢复 ChessBox 状态box.chess = Chess.None;}return null;}

最后

在每次重开后,切换回合是为了避免上一局最后是玩家,那么玩家落子后就是AI了,而没有写AI先手。

可以改为重置为玩家回合,或者给Restart里写如果是AI那就执行一次AIplay。

直接执行切换回合,不仅使上一局最后一手为后手,还同时检测了是谁,如果是AI那就落子,如果是玩家那就玩家落子,同时做到了开局可以是玩家也可以是AI。

其实反正小东西,无脑public的好处是写得少,还能直接在Inspector里看情况,如果真需要调用,不用回来重新改private为public。

最后一点截图,场景中的引用

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

参考链接

Unity tutorial - How to make Tic Tac Toe game [ Part 1 ]

Unity tutorial - How to make Tic Tac Toe game [ Part 2 ]

仓库链接

MapleInori/JingZiQi: 学习记录 (github.com)

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

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

相关文章

专题三搜索插入位置

1.题目 题目分析&#xff1a; 给一个目标值&#xff0c;然后要在排序的整数数组中&#xff0c;找到跟目标值一样的&#xff0c;如果没有就把这个值插入进去&#xff0c;然后返回插入后的下标。 2.算法原理 根据题目的时间复杂度可以知道要用二分&#xff0c;开始划分区域&…

正式进入linux 1.0

切记&#xff1a;在Linux中空格很重要 回车键也很重要&#xff0c;不要按两次回车键 ls是显示当前所有文件夹 具体解释&#xff1a; 前面的东西是用户名 后面的是设备名&#xff08;计算机名&#xff09; 这是因为linux允许不同用户在终端下进行操作&#xff0c;这么做可以…

分页查询的实现

目录 前言 一.问题描述 二.后端实现步骤 2.1配置PageHelper插件 ①导入依赖 ②在application.yml配置文件中添加相关配置 2.2编写一个入门的程序&#xff0c;体验分页过程 2.3定义一个vo&#xff0c;用来收集分页后的所有信息 2.4修改serviceImpl层的代码 2.5动态设…

16003. orin camera 相机驱动源码 imx477分析记录

文章目录 1 背景2 原理图2.1 CAM_MUX_SEL 4 lane 选通2.2 J21 和 J20 原理图3 驱动源码及设备树3.1 子设备树 tegra234-p3768-camera-rbpcv3-imx477.dtsi3.2 顶层设备树 tegra234-camera-rbpcv3-imx477.dtsi3.2.1 tegra-capture-vi 视频输入子系统节点配置.3.2.2 host1x 主机控…

无标签数据增强+高效注意力GAN:基于CARLA的夜间车辆检测精度跃升

目录 一、摘要 二、引言 三、框架 四、方法 生成合成夜间数据 昼夜图像风格转换 针对夜间图像的无标签数据增强技术 五、Coovally AI模型训练与应用平台 六、实验 数据 图像风格转换 夜间车辆检测和分类 结论 论文题目&#xff1a;ENHANCING NIGHTTIME VEHICLE D…

开源工具利器:Mermaid助力知识图谱可视化与分享

在现代 web 开发中&#xff0c;可视化工具对于展示流程、结构和数据关系至关重要。Mermaid 是一款强大的 JavaScript 工具&#xff0c;它使用基于 Markdown 的语法来呈现可定制的图表、图表和可视化。对于展示流程、结构和数据关系至关重要。通过简单的文本描述&#xff0c;你可…

C++算法学习2:二分算法精讲

一、实数二分法回顾 1.1问题背景 在1~2的范围内找到一个x&#xff0c;使得式子5x2 -9x 1 的绝对值<10-9&#xff08;即无限接近0&#xff09; 要求&#xff1a;x精确到小数点后9位。 换句话说也就是求&#xff1a;就是求方程 5x2- 9x 1 0 在1~2内的近似解 1.2怎么找到…

手写一个简易版的tomcat

Tomcat 是一个广泛使用的开源 Servlet 容器&#xff0c;用于运行 Java Web 应用程序。深入理解 Tomcat 的工作原理对于 Java 开发者来说是非常有价值的。本文将带领大家手动实现一个简易版的 Tomcat&#xff0c;通过这个过程&#xff0c;我们可以更清晰地了解 Tomcat 是如何处理…

object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别

object.assign和扩展运算法是深拷贝还是浅拷贝&#xff0c;两者区别 1. 浅拷贝的本质2. Object.assign 和扩展运算符的区别‌3. 具体场景对比‌合并多个对象‌‌复制数组‌‌处理默认值‌ ‌4. 如何实现深拷贝&#xff1f;JSON.parse(JSON.stringify(obj))‌‌递归深拷贝函数第…

X-CLIP和X-FLORENCE论文解读

1.研究背景 尽管已有研究探索了如何将语言-图像模型迁移到其他下游任务&#xff08;如点云理解和密集预测&#xff09;&#xff0c;但视频识别领域的迁移和适应性研究还不够充分。例如&#xff0c;ActionCLIP提出了一种“预训练、提示和微调”的框架用于动作识别&#xff0c;但…

微信小程序刷题逻辑实现:技术揭秘与实践分享

页面展示&#xff1a; 概述 在当今数字化学习的浪潮中&#xff0c;微信小程序以其便捷性和实用性&#xff0c;成为了众多学习者刷题备考的得力工具。今天&#xff0c;我们就来深入剖析一个微信小程序刷题功能的实现逻辑&#xff0c;从代码层面揭开其神秘面纱。 小程序界面布局…

Android UI 组件系列(二):Button 进阶用法

引言 在上一篇博客中&#xff0c;我们介绍了 Button 的基本用法和常见属性&#xff0c;掌握了 Button 的基础知识。然而&#xff0c;在实际开发中&#xff0c;Button 远不止于简单的点击功能&#xff0c;它还可以支持不同的变体、丰富的自定义样式&#xff0c;以及更灵活的状态…

【云馨AI-大模型】RAGFlow功能预览:Dify接入外部知识库RAGFlow指南

介绍 Dify介绍 开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力&#xff0c;轻松构建和运营生成式 AI 原生应用。比 LangChain 更易用。官网&#xff1a;https://dify.ai/zh RAGFlow介绍 RAGFlow 是一款基于深度文档理解构建的…

Redis超高并发分key实现

Redis扛并发的能力是非常强的&#xff0c;所以高并发场景下经常会使用Redis&#xff0c;但是Redis单分片的写入瓶颈在2w左右&#xff0c;读瓶颈在10w左右&#xff0c;如果在超高并发下即使是集群部署Redis&#xff0c;单分片的Redis也是有可能扛不住的&#xff0c;如下图所示&a…

缓存使用的具体场景有哪些?缓存的一致性问题如何解决?缓存使用常见问题有哪些?

缓存使用场景、一致性及常见问题解析 一、缓存的核心使用场景 1. 高频读、低频写场景 典型场景&#xff1a;商品详情页、新闻资讯、用户基本信息。特点&#xff1a;数据更新频率低&#xff0c;但访问量极高。策略&#xff1a; Cache-Aside&#xff08;旁路缓存&#xff09;&a…

HTML5(Web前端开发笔记第一期)

p.s.这是萌新自己自学总结的笔记&#xff0c;如果想学习得更透彻的话还是请去看大佬的讲解 目录 三件套标签标题标签段落标签文本格式化标签图像标签超链接标签锚点链接默认链接地址 音频标签视频标签 HTML基本骨架综合案例->个人简介列表表格表单input标签单选框radio上传…

ubuntu22.04 关于挂在设备为nfts文件格式无法创建软连接的问题

最近遇到情况&#xff0c;解压工程报错&#xff0c;无法创建软连接 但是盘内还有130G空间&#xff0c;明显不是空间问题&#xff0c;查找之后发现是移动硬盘的文件格式是NTFS&#xff0c;在ubuntu上不好兼容&#xff0c;于是报错。 开贴记录解决方案。 1.确定文件格式 使用命…

深度解读DeepSeek部署使用安全(48页PPT)(文末有下载方式)

深度解读DeepSeek&#xff1a;部署、使用与安全 详细资料请看本解读文章的最后内容。 引言 DeepSeek作为一款先进的人工智能模型&#xff0c;其部署、使用与安全性是用户最为关注的三大核心问题。本文将从本地化部署、使用方法与技巧、以及安全性三个方面&#xff0c;对Deep…

RK3568 Android13 源码编译

提示&#xff1a;RK3568 Android13 源码编译 脚本&#xff0c;源码编译管理方式优化 文章目录 获取源码设置屏幕配置确认屏幕修改源码的设备树 修改线程数整体编译Android固件配置JDK java 环境 source javaenv.sh使能编译 build/envsetup.sh lunch topeet_rk3568-userdebug整体…

【CentOS】搭建Radius服务器

目录 背景简介&#xff1a;Radius是什么&#xff1f;Radius服务器验证原理搭建Radius服务器环境信息yum在线安装配置FreeRADIUS相关文件clients.conf文件users文件重启服务 验证 参考链接 背景 在项目中需要用到Radius服务器作为数据库代理用户的外部验证服务器&#xff0c;做…