Unity项目 - 吃豆人Pacman

时间:2022-12-14 19:22:11

项目展示

Unity项目 - 吃豆人Pacman

Unity项目 - 吃豆人Pacman

Github项目地址:Pacman

试玩下载:Pacman 吃豆人 提取码brkv

涉及知识

  • 切片制作 Animations
  • 状态机设置,any state切换,重写状态机
  • 按键读取进行整数距离的刚体移动
  • 用射线检测碰撞性
  • 渲染顺序问题
  • 单、多路径的实现
  • 协程延时
  • Button 按键功能

准备工作

Pixels Per Unit:多少像素相当于Unity一个单位,迷宫Maze大小232x256,

Pivot:设置贴图的零点,Bettom Left左下角

物理化:墙,import package->custom package,导入已经设置好碰撞体的墙

pacman切图,动画片段:Sprite Mode->multiple,Pixels Per Unit=8,进行Sprite Editor,显示其窗口。选择Slice切片,Type为Grid By Cell Count,切割参数3行4列,Apply后可在Pacman下面看到切割好的12张照片。

动作制作:12张照片每3张为一个动作,分别是右,左,上,下,每次将3张拖入Hierarchy面板,保存在Animations文档下,各自命名。可在Project面板Animations文件夹下包含4个动画文件,说明每次保存的3张图片生成一个动画,还包含4个动画机(但只需要设置一个。其余可删除)

初始状态机设置

状态机:在主角Pacman内添加Animator组件,添加上述留下的动画机,打开Animator页面,看到4个组件,初始情况为:当游戏物体进入状态机,默认状态转变为PacmanRight;后拖拽其他3个状态到状态机页面

分析主角移动:仅仅能横x纵y向移动,当持续按住某方向键位,速度为每0.3s移动 1 Unit

**

Any State:**切换连接4个状态,点击连线可看到说明:无论在任何状态只要达到连线内条件,即转变状态到所指对象状态

Any State 切换条件:在Parameters内添加float型DirX,DirY值用来判断(持续按键产生的是浮点数)。例如PacmanRight的判断,添加DirY,当DirY>0.1(浮点数不精确性质,留一定范围)。并且取消状态机Settings内Can Transition To(考虑到帧数问题,和重复播放初始动作问题),其次2D动画将融合调0

其他:可以调节动画的speed以调节播放该动作的速度

吃豆人 Pacman

吃豆人的实体化

  • 加碰撞器,circle collider,添加rigdbody2D,设置重力0

吃豆人的移动方法

  1. 修改transform瞬移,修改坐标,多用在生成位置
  2. rigidbody2D移动,物理移动,推荐使用

具体实现移动过程:

  1. 调用 Vector2.MoveTowards(transform.position,dest,speed) ,使得返回起始点到目标点的中间值,另设 temp 接收这个值;再对刚体进行移动操作GetComponent<Rigidbody2D>().MovePosition(temp);
    • Vector2.MoveTowards(transform.position,dest,speed):表示以 浮点数型 speed 的速度,从起点 transform.position 移动到终点 dest ,返回的值为两点坐标的中间值
  2. 初始时 transform.position = test ,故不会移动,因此需要按键检测以改变 dest 的值:
    • Input.GetKey(KeyCode.UpArrow) 或者 Input.GetKey(KeyCode.W) 实现读取键位
    • 然后赋值 dest:(Vector2)transform.position + Vector2.up;

实现吃豆人每次单位移动(Vector2)transform.position + Vector2.up ,表示 当前坐标position 加 向上一个单位量;每次读取键盘方向信息,将当前坐标 + 某方向单位量 = 目的地位置

产生问题1:此时移动会造成吃豆人旋转问题

原因:Pacman 与墙壁碰撞时Z轴坐标改变造成旋转

解决:冻结Z轴 Rigdibody2D->Constraints->Freeze Rotation Z

产生问题2:卡槽间移动容易卡住,非规则移动

原因:问题在于按键过程时刻改变dest ,造成 temp = Vector2.MoveTowards()的时刻改变

解决:判断当上一个dest抵达时才读取新的键位if ((Vector2)transform.position == dest)

产生问题3:首次测试按键移动发现撞墙后就不可再移动

原因:抵达墙边时,键盘读取的dest到了墙以外,if判断永远无法transform.position==dest,无法在键盘读取

解决:检测目的地合法性

//检查目的地是否合法 dir方向值(上述的Vector.XXX)
private bool Valid(Vector2 dir)
{
//pos 存储当前位置(墙内的合法位置)
Vector2 pos = transform.position; //从 当前值pos+方向值dir 的位置发射一条射线到Pacman 当前的位置pos
RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos); //射线打到的碰撞器 是否等于 吃豆人的碰撞器:
//若射线从墙中心(不合法位置)射出,hit.collider为墙的,不等于Pacman的,返回fault
//若射线从路面(合法位置)射出,hit.collider等于Pacman的,返回true
return (hit.collider == GetComponent<Collider2D>());
}

状态机的切换

实现不同动作状态机的切换:

  • 获取移动的方向:Vector2 dir = dest - (Vector2)transform.position;
  • 把获取到的方向设置到状态机:GetComponent<Animator>().SetFloat("DirX", dir.x);

    Unity项目 - 吃豆人Pacman

2D游戏Z轴问题:若在不同层级碰撞功能失效,若在同一层级,则存在渲染顺序问题(谁覆盖谁)

渲染顺序问题:Sprite Renderer->Order in Layer

  • 小的先渲染,大的后渲染:迷宫Maze 0,豆子pacdot 1,敌人 2~5,Pacman 6
  • 小的被覆盖,大的覆盖:先渲染的存在于底层,后渲染的位于上层(类似ppt中的图层)

    Unity项目 - 吃豆人Pacman

    Unity项目 - 吃豆人Pacman

豆子及敌人的创建、移动

豆子:

  1. Pacdot 即为豆子图标,拖入页面内创建对象
  2. 对其添加碰撞器 Box Collider 2D,设置为触发器
  3. 对所有Pacdot添加脚本 Pacdot.cs
    • 脚本内创建碰撞检测OntriggerEnter2D(Collider2D collision) 函数用来检测触发 Pacdot 的物体是否为Pacman ,是则销毁Pacdot

敌人的创建:

  1. 重复切图,合成动作,设置图层,安放位置
  2. 关于状态机,采用 重写状态机
    • 在 Animation文档内 create->Animator Override Controller,设置状态机参照 Controller 为 Pacman的,可看到 Original 为 Pacman内的动作,Override 内的就设置为每个敌人的不同动作(注意,删除原有的状态机使得物体的 Animator 内 Controller 找不到状态机组件,此时需要将重写后的状态机设置到它身上
  3. 再设置 Rigidbody 和 CircleCollider(注意此处要设置为触发器Trriger而不是碰撞器,范围0.8

    Unity项目 - 吃豆人Pacman

    Unity项目 - 吃豆人Pacman

敌人的移动(单路径):

  1. 创建与豆子坐标一致的、始末位置同点的闭合路径(用空物体作路径点即可),统一储存于 way 结构内作为一条路径
  2. 编写 EnemyMove.cs
    • 创建循环队列保存所有路径点 Transform[] WayPoint ,及index 标记敌人在前往哪个路径点的途中;
    • 创建 FixedeUpdata() ,判断:若怪物没抵达目标位置,则从当前位置持续移动直到抵达(同Pacman的移动,但没有输入检测),若抵达,则index++,前往下一个点;此外动画状态的检测、切换也同Pacman的;
    • 设置触发检测:当检测到触发的物体是 Pacman ,则销毁 Pacman
  3. 在Unity页面将全部路径点拖拽到怪物脚本的Way Point处实现赋值数组

    Unity项目 - 吃豆人Pacman

敌人的移动(多路径):高级的方法是AI,但本例采用多路线随机分配实现

  1. 先创建对象 wayPointsGo 用来接收为预制体的路线 Way
  2. 创建 wayPoints ,在 start() 内调用foreach方法,将 wayPointGo 内的组件取出到 t,将 t.position 坐标顺序添加到wayPoints 表内(还需修改FixedUpdate()函数内: wayPoints[index] 此时为表,存储的是坐标,无需再.position,后续的wayPoints.Length也改为了wayPoints.Count),由此实现了一条路径;
  3. 修改EnemyMove.cs,游戏对象 wayPointsGo 改为数组形式 实现存储多路线
  4. 根据前面路径Way预制体的制作方法,再次制作多条路径
private void LoadApath(GameObject go)
{
//将wayPointsGo数组内某一路径的子物体(路径点)的Transform组件取出,依次将其position赋值到Ways表中
//修改为多路径后随机从5条路径走
foreach (Transform t in wayPointsGo[Random.Range(0, 4)].transform)
{
wayPoints.Add(t.position);
}
}
  • 创建LoadApath函数:首先清除上调路径遗留再List中的信息,后foreach()加载路径到List内,而后再Start内每次调用随机,传入随机一条路径。

产生问题1:不同怪物出门都与 Blinky红色敌人 同一点问题

原因:因为做预制体way1,way2,...,wayn 时,路径始末两点坐标都是 Blinky红色敌人 上方3个单位,所以其他敌人起始移动就会先进行穿墙到那个点

解决:修改 EnemyMove.cs ,创建一个坐标变量 startPos 用在存放每个敌人路径的始末位置点,在 Start() 函数内初始化设置 satrtPos 为怪物起始坐标+向上3个单位;再后续 foreach() 内插入该点到List表头,及在List末尾添加该点,但注意每次要调用LoadApath()函数要清除上一次路径信息

//清空List内前次路径的信息
wayPoints.Clear(); //添加首末路径点到List内
wayPoints.Insert(0, startPos);
wayPoints.Add(startPos);

产生问题2:即便随机路径下不同怪物选到同路径问题

原因:每个敌人进行 Random.Range(0,n) 随机分配路径时可能抽到一样的随机数

解决:添加 GameManager.cs ,调用如下代码

public class GameManager : MonoBehaviour
{
private static GameManager _instance;
public static GameManager Instance
{
get
{
return _instance;
}
} public List<int> usingIndex = new List<int>();
public List<int> rawIndex = new List<int> { 0, 1, 2, 3}; private void Awake()
{
_instance = this;
int tempCount = rawIndex.Count; for (int i = 0; i < tempCount; i++)
{
int tempIndex = Random.Range(0, rawIndex.Count);
usingIndex.Add(rawIndex[tempIndex]);
rawIndex.RemoveAt(tempIndex);
}
}
} //再修改EnemyMove,Start内
LoadApath(wayPointsGo[GameManager.Instance.usingIndex[GetComponent<SpriteRenderer>().sortingOrder - 2]]);

超级豆子

超级豆子的生成:

  • GameManager.cs内:
    1. 创建pacdotGos,foreach()存储所有豆子;
    2. 生成超级豆子:CreateSuperPacdot()
    3. 创建布尔变量isSuperPacman(初始为false);
  • Pacdot.cs内:
    • 修改碰撞触发判定:if(是超吃豆人状态){} else 被敌人消灭
  • EnemyMove.cs内:
    • 修改碰撞触发判定:if(碰到的是超级吃豆人){} else 消灭吃豆人

超级豆子带来的超级吃豆人状态:敌人静止,且可以被吃掉

  • Pacman的超级状态:OnEatSuperPacdot()
    • GameManager.cs 内添加布尔变量 isSuperPacman 判定是否超级状态
    • 当吃到超级豆子后启用该函数,变更状态标记 isSuperPacman = true
    • 启用静止敌人函数 FreeEnemy()(下有说明)
    • 实现保持超级状态4s:协程延时 StartCoroutine(Recover()); (下面说明)
    • 4s时间结束后取消状态(同在协程函数Recover()内进行)
  • 敌人静止: FreezeEnemy()
    • Blinky.GetComponent<EnemyMove>().enabled = false; 禁用怪物移动脚本的update方法
    • Blinky.GetComponent<SpriteRenderer>().color = new Color(0.7f, 0.7f, 0.7f, 0.7f); 敌人图标变暗淡
  • 敌人被吃掉:
    • 在敌人移动脚本 EnemyMove.cs 内检测,若碰撞检测到的 Pacman 是超级状态 GameManager.Instance.isSuperPacman 则自身回到出生点

协程延时:

  • 协程的作用:当 启动 OnEatSuperPacdot() 变更为超级状态状态时,启用协程函数 StartCoroutine(Recover()) ,表示该协程函数与 OnEateSuperPacdot() 同时启用,并行运行
  • 实现功能:在吃到超级豆子瞬间即开始计时,计时完后取消敌人静止状态Dis_FreezeEnemy() 及恢复吃豆人状态 isSuperPacman = false
  • 延时代码:yield return new WaitForSeconds(4f);

吃豆过程概述:

  • 开局一段时间后生成超级豆子
  • 豆子被吃掉后,从表内移除该豆子,销毁对象,延时10s后准备下一个超级的生成,与此同时改变吃豆人状态isSuperPacman=true(两过程不干涉,并行执行)
  • 若在超级吃豆人状态期间吃到敌人,则敌人位置回归到初始
  • 持续超级吃豆人状态4s后取消敌人的冻结,并调用状态恢复函数

UI设计

Start 与 Exit 图标:

  1. 创建 UI->Canvas,作为UI工作区域;
  2. 添加image作为logo;创建空物体命名StartPanel,包含2个 UI->text,start和exit ,修改字体,调整位置
  3. 创建空物体命名GamePanel,包含3个UI->text,remain,eaten,修改字体,score
  4. 倒计时321动画:同理将素材文件Start切片,设置动作,修改每个动作间隔时长为1s(Animation->Samples)
  5. GameManager.cs 内持有UI面板及动画、音乐

建立 Button 按键跳转:

  1. Start、Exit 两UI添加 Button(script)组件,设置 Target Graphic->Start,Exit
  2. GameManager.cs内添加 OnStartButton()OnExitButton()对接按键脚本,如下图代码:
  3. 启用Button功能:对UI图标添加 On Click->GameManager->OnStartButton 启用该函数
  4. 其他:按键或者其他UI都必须存在于 画布Canvas 内;画布Canvas下一级是 画板Panel ;在下一级就是各类UI组件

    Unity项目 - 吃豆人Pacman
//当点击 Start
public void OnStartButton()
{
//与点击开始按钮后同步进行的函数
StartCoroutine(PlayStartCountDown()); //Start声音,声音源在原点位置
AudioSource.PlayClipAtPoint(startClip, Vector3.zero); //隐藏开始按钮的页面
startPanel.SetActive(false);
} //点击Exit后退出游戏
public void OnExitButton()
{
Application.Quit();
}

其他

网页跳转:

调用 Application.OpenURL("https://www.cnblogs.com/SouthBegonia/"); 即可实现,可用在Button 也可用在其他触发时间

Unity项目 - 吃豆人Pacman的更多相关文章

  1. &lbrack;代码&rsqb;解析nodejs的require,吃豆人的故事

    最近在项目中需要对nodejs的require关键字做解析,并且替换require里的路径.一开始我希望nodejs既然作为脚本语言,内核提供一个官方的parser库应该是一个稳定可靠又灵活的渠道,然 ...

  2. 利用纯css写三角形,弧度箭头,吃豆人,气泡。放大镜,标签的源码

    1. 向上三角形

  3. Fzu2124 - 吃豆人 BFS

    Description 吃豆人是一款非常经典的游戏,游戏中玩家控制吃豆人在地图上吃光所有豆子,并且避免被怪物抓住. 这道题没有怪物,将游戏的画面分成n*m的格子,每格地形可能为空地或者障碍物,吃豆人可 ...

  4. TurnipBit开发板DIY呼吸的吃豆人教程实例

    转载请以链接形式注明文章来源(MicroPythonQQ技术交流群:157816561,公众号:MicroPython玩家汇) 0x00前言 吃豆人是耳熟能详的可爱形象,如今我们的TurnipBit也 ...

  5. FZU 2124 吃豆人 bfs

    题目链接:吃豆人 比赛的时候写的bfs,纠结要不要有vis数组设置已被访问,没有的话死循环,有的话就不一定是最优解了.[此时先到的不一定就是时间最短的.]于是换dfs,WA. 赛后写了个炒鸡聪明的df ...

  6. FZU 2124 FOJ 2124 吃豆人【BFS】

     Problem 2124 吃豆人 Accept: 134    Submit: 575 Time Limit: 1000 mSec    Memory Limit : 32768 KB  Probl ...

  7. css吃豆人动画

    一. Css吃豆人动画 1. 上半圆:两个div,内部一个圆div,外部设置宽高截取半圆 外部div动画:animation: 动画样式 1s(时长) ease(动画先低速后快速) infinite( ...

  8. Pac-Man 吃豆人

    发售年份 1980 平台 街机 开发商 南梦宫(Namco) 类型 迷宫 https://www.youtube.com/watch?v=dScq4P5gn4A

  9. 【C语言】用C语言输出一个吃豆人

    大圆盘减去扇形和小圆盘: #include <math.h> #include <stdio.h> int main() { double x, y; ; y >= -; ...

随机推荐

  1. SeGue 多控制器跨界面传递数据原理

    多控制器跨界面传递数据原理

  2. python之6-3嵌套函数

    1. 嵌套函数 子函数可以继承父函数的变量 父函数返回子函数 子函数返回结果 看例子如下:结果是一个字符串fun1+fun2 #!/usr/bin/env python # coding=utf-8 ...

  3. hdu 畅通工程再续

    http://acm.hdu.edu.cn/showproblem.php?pid=1875 #include <cstdio> #include <cstring> #inc ...

  4. PHP openssl函数库

    php openssl 函数库中.提供了大量的函数.但是我们一般用的最多的,就是 openssl_encrypt string openssl_encrypt ( string $data , str ...

  5. asp&period;net core 系列 7 Razor框架路由&lpar;上&rpar;

    一.概述 在上二篇中,主要是介绍了asp.net core mvc中路由的使用,这篇继续介绍路由在ASP.NET Core Razor中的使用.Razor Pages应该使用默认的传统路由,从应用程序 ...

  6. 输入二进制数转换成十进制数(在cin&gt&semi;&gt&semi;和cin&period;get&lpar;&rpar;上掉的坑)

    题目:输入一个二进制数转换成十进制. 题目分析:书上说,在一般情况下,c++的键盘输入可以识别是十进制数.八进制数和十六进制数,因此输入的二进制数据要作为字符处理.(其实我觉得数字也没问题吧). 正确 ...

  7. Galera Cluster——一种新型的高一致性MySQL集群架构

    原文链接:https://www.sohu.com/a/147032902_505779,最近被分配定位mysql的问题,学习下. 1. 何谓Galera Cluster 何谓Galera Clust ...

  8. 2017-12-15python全栈9期第二天第七节之运算符

    #!/user/bin/python# -*- coding:utf-8 -*-print(3>4 or 4<3 and 1==1)print(1<2 and 3 <4 or ...

  9. 在vim中注释多行

    使用查找替换的方法 在linux中,文本每一行的起始标志是^,结束标志为$,因此使用vim搜索^并替换为^#即可. :10,20s/^/#/g 表示将10-20行添加注释,同理取消注释为: :10,2 ...

  10. Leetcode--136&period; Single Number&lpar;easy&rpar;

    Given a non-empty array of integers, every element appears twice except for one. Find that single on ...