赞
踩
基本来源
https://noobtuts.com/unity/2d-pacman-game
http://forum.china.unity3d.com/thread-13546-1-1.html
前言
这篇教程讲解如何在Unity中制作《吃豆人》游戏。该游戏最初发布于1980年10月,并很快风靡全球直至现在。Unity也用到了其中的形象作为UI:
本文将使用Unity强大的2D功能,用简洁的代码来制做一个仿版《吃豆人》游戏。游戏会尽可能的简单,只关注迷宫、怪物、豆豆以及主角吃豆人。旨在发现实际开发中的问题并解决。
素材
游戏背景
吃豆人
敌人
只找到这一个。。。。。
豆子
[豆子:我在这里!!!]
game start
新建项目 将主摄像机上的背景设置为黑色
将上述游戏背景图片移动到Unity中并设置:
注意:Pixels Per Unit 值为8表示游戏世界的每个单位会填充8x8像素。我们会将该数值用于所有纹理。选择8是因为两个豆豆之间的距离是8像素,并且我们希望这是游戏中的一个单位。将锚点(Pivot)设为左上是便于后续使用。
物理特性
此时迷宫还只是一张图。不具有物理性质,所以吃豆人也无法在墙间游走。现在就为迷宫的每道墙都添加一个Collider。在检视面板中依次选择Add Component->Physics 2D->Box Collider 2D:我们希望每道墙都有单独的Collider。有两种办法可以实现。第一种是写个算法基于迷宫图片来生成Collider(求大神教我),第二种就是简单的手动添加。
在检视面板中点击Edit Collider 按钮,之后就可以在场景中使用绿色的点来调整Collider:
对迷宫的每道墙都重复以上步骤,依次选择Add Component->Physics 2D->Box Collider 2D,然后点击Edit Collider调整各Collider的形状直至与墙贴合。这里的诀窍就是设置Collider的大小和中心点时注意值都是1.25、1.5、1.75或2.00这样,而非1.24687或1.25788。
注意:如果后面吃豆人被困在迷宫或无法移动,就是因为迷宫的Collider没有完全设置好。
添加我们的主角吃豆人并设置:
每个方向的移动都需要一个动画:- 右- 左- 上- 下
将所有动画放在一张图中,一行就是一个方向的动画:
将Sprite Mode设为Multiple很重要,这会告诉Unity其中包含了多个吃豆人精灵。点击按钮打开Sprite Editor:
现在就让Unity对吃豆人精灵进行切片。选择Slice将Grid设为16x16,然后点击下面的Slice按钮:
精灵切割完成后关闭Sprite Editor。如果Unity提示Unapplied Import Settings,选择Apply。
现在项目视图中吃豆人精灵下有12个子精灵:
吃豆人动画
现在可以创建4个方向的动画了。再提一下,我们需要的动画如下:
右(切片0,1,2)
左(切片3,4,5)
上(切片6,7,8)
下(切片9,10,11)
先创建向右的动画。在项目视图中选中前三个切片精灵,然后将它们拖拽到场景中。
同时拖拽几个精灵到场景中,Unity就会知道我们想创建动画,并自动询问动画保存的位置。将动画命名为right.anim并保存到新建的Animation目录。Unity会自动在场景中加入游戏对象pacman_0,并在项目视图中新增两个文件:
第一个文件是动画状态机,用于设置动画速度和混合树等。第二个的动画文件。
对余下的动画重复以上步骤。
Unity为每个动画都新建了游戏对象,但我们这里只需要一个。选中其余三个并删除。
在项目视图中也一样。动画和状态机各有4个。同样删除其它多余的三个。
吃豆人动画状态机
现在有4个动画文件,但Unity并不知道要播放哪个动画。所以我们需要一个具有4种状态的动画状态机:
•右
•左
•上
•下
还要添加变换让Unity知道何时切换动画。
注:Unity会在处于right状态时重复播放right动画。它依靠Transition来判断何时切换状态。这些都是Unity自动完成的,我们要做的就是在后面的脚本中告知它吃豆人的移动方向。
在项目视图中双击pacman_0动画状态机:
现在可在Animator查看状态机:
状态机中已经包含right状态了,将项目视图中Animation文件夹下的其他三个anim文件拖拽到Animator中新建状态:
Mecanim最大的优点是它会完全自动管理动画状态。我们要做的就是告诉Mecanim注意吃豆人的移动方向。Mecanim会自动切换动画状态,不需我们操心。
创建参数
下面就来告诉Mecanim主意吃豆人的移动方向。移动方向参数类型是Vector2,下图展示了几种可能的移动方向:
本例中吃豆人只有4个移动方向。也就是说在动画状态机中仅需4种状态变换:
DirY>0则向上移动
DirY<0则向下移动
DirX>0则向右移动
DirX<0则向左移动
在Animator界面中选择Parameters标签页。点击右边的+添加一个Float类型的参数命名为DirX,再添加一个Float参数命名为DirY:
创建变换
我们希望Unity基于这些参数自动切换动画状态。变换就是用于实现这点的。例如,新建一个从left到right的变换,条件是DirX > 0。但要考虑到最完美的变换也会存在细微误差,因为浮点数的比较并不太完美,因此我们会使用DirX>0.1来判断。
从其它任何状态切换为right都会用到DirX > 0.1。所以借助Unity的Any State来节省一些工作量。
Any State几乎表示任何状态。所以创建一个从Any State到right的变换就相当于同时创建了left,up和down到right的变换。
右击Any State 选择Make Transition。然后将白色箭头拖拽到right状态:
点击白色箭头后在检视面板中设置变换的条件(即何时切换状态)为DirX > 0.1:
禁用Settings中的Can Transition To Self :
注:这样可以避免出现按下某个方向键时动画会一直重新开始的情况
因此,当吃豆人向右移动时(DirX > 0.1),状态机会切换到right状态播放向右移动的动画。
同样的步骤新建另外3个变换:
Any State到left,条件为DirX < -0.1
Any State 到up,条件为DirY > 0.1
Any State 到down,条件为 DirY < -0.1
现在Animator如下:
动画状态机就差不多完成了。还要进行最后一个调整,选中所有状态并在检视面板中修改Speed以便动画看起来不会太快:
点击Play就可以看到right动画:
吃豆人命名及摆放
在层次面板中选中pacman_0并重命名为pacman(右击选择重命名,或按F2)。
将其坐标设为 (14, 14) 这样就不会位于迷宫外了。
吃豆人物理
现在吃豆人也只是一张没有物理特性的图片,所以它不会移动。在吃豆人上新加一个Collider使其具有物理性质,然后周围的物体就可以与它进行碰撞而非直接穿过。
吃豆人可以到处移动。Rigidbody可用于处理重力、速度和其它推动它移动的力。物理世界所有可以移动的物体都需要一个Rigidbody组件。
在层次面板中选中pacman,依次点击Add Component->Physics 2D->Circle Collider 2D添加碰撞器,同样依次点击Add Component->Physics 2D->Rigidbody 2D添加Rigidbody。使用下图的设置:
注意:Gravity Scale设置为0,否则主角位置会受力的改变而自动改变。(产生BUG,运行时吃豆人位置自行改变)
现在吃豆人可以与周围的物体进行碰撞了。这也会导致所有绑定到pacman的脚本中的OnCollisionEnter2D函数被调用。
Movement脚本
现在有几种让吃豆人移动的方法。最简单的就是创建脚本,并检测键盘方向键操作,然后按照对应的方向移动吃豆人。但这种操作不好。
我们来试试复杂点的办法。玩家按下某个方向键时,吃豆人都在相应方向移动一个完整单位。豆豆也会按照一个单位的间隔距离来摆放,所以吃豆人每次需要移动一个完整单位。
在层次面板中选中pacman,依次点击Add Component->New Script新建C#脚本PacmanMove。将脚本放在项目视图中新建的Scripts文件夹下:
我们需要一个公共变量以便后面在检视面板中修改移动速度:
还需要一个方法来判断吃豆人是否可以朝某个方向前进,或者前面是否有墙。例如,如果想知道前面是否有墙,就从吃豆人前方一单位距离处发出射线到吃豆人,看看是否撞到东西。如果撞到的是吃豆人自身则什么都没有,否则就是有墙。
//判断前面是否有墙
bool validMove(Vector2 dir)
{
//获得自身位置
Vector2 pos = transform.position;
//发射射线从pos+dir到pos
RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos);
return (hit.collider == cor2D);
}
注:这里只是简单的从距离吃豆人一单位的点 (pos + dir)发射到吃豆人自身(pos)。
将目的地保存到变量,然后在FixedUpdate函数中一步步移动,加入输入控制,设置动画参数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move : MonoBehaviour {
//移动速度
public float speed = 0.4f;
Vector2 dest = Vector2.zero;
Collider2D cor2D;
void Start()
{
dest = transform.position;
cor2D = GetComponent<Collider2D>();
}
void FixedUpdate()
{
Vector2 p = Vector2.MoveTowards(transform.position, dest, speed);
GetComponent<Rigidbody2D>().MovePosition(p);
if ((Vector2)transform.position == dest) //BUG float值有差值 出现差值后不能移动了
{
if (Input.GetKey(KeyCode.UpArrow) && validMove(Vector2.up))
dest = (Vector2)transform.position + Vector2.up;
if (Input.GetKey(KeyCode.RightArrow) && validMove(Vector2.right))
dest = (Vector2)transform.position + Vector2.right;
if (Input.GetKey(KeyCode.DownArrow) && validMove(Vector2.down))
dest = (Vector2)transform.position + Vector2.down;
if (Input.GetKey(KeyCode.LeftArrow) && validMove(Vector2.left))
dest = (Vector2)transform.position + Vector2.left;
}
Vector2 dir = dest - (Vector2)transform.position; //bug 移动一会会旋转
//print("dir.x" + dir.x);
//print("dir.y" + dir.y);
GetComponent<Animator>().SetFloat("X", dir.x);
GetComponent<Animator>().SetFloat("Y", dir.y);
}
//判断前面是否有墙
bool validMove(Vector2 dir)
{
//获得自身位置
Vector2 pos = transform.position;
//发射射线从pos+dir到pos
RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos);
return (hit.collider == cor2D);
}
}
注:使用GetComponent获取pacman的Rigidbody组件用于移动(永远不要用transform.position来移动带有Rigidbody的游戏对象)。
到此吃豆人的移动就完成了。保存脚本后点击Play可以看到移动动画也正常了:
保证吃豆人在最上层
在制作豆豆之前,要先保证吃豆人在所有物体上层。这是2D游戏,所以这里没有3D游戏中的Z值。也就是说Unity按照它自己的想法来绘制物体。我们要确保吃豆人绘制值所有物体上层。
有两种办法。改变Sprite Renderer的Sorting Layer 属性,或改变Order in Layer 属性。对于有很多对象的游戏来说使用Sorting Layer 更合适,这里我们只要将Order in Layer 改为1就好:
注:Unity会按从低到高的顺序来绘制对象。所以将吃豆人的Order in Layer设为1后,Unity会先绘制Order为0的迷宫、豆豆等。这样吃豆人就可以永远在最上层。
豆豆
吃豆人吃的就是豆豆。就像现在的水果和食物。首先导入一张2x2像素的图作为豆豆并设置参数。
然后将它拖拽到场景。我们希望在吃豆人走过豆豆时收到通知,所以在检视面板中依次点击 Add Component->Physics 2D->Box Collider 2D并选中Is Trigger:
注:启用IsTrigger的Collider只会接收碰撞信息,不与其它物体进行物理碰撞。
不论何时在吃豆人或怪物走过豆豆时Unity都会自动调用OnTriggerEnter2D函数。
依次点击Add Component->New Script新建C#脚本Pacdot,并将其放到Scripts文件夹下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Pacdot : MonoBehaviour {
void OnTriggerEnter2D(Collider2D co2)
{
if (co2.name == "pacman")
{
Destroy(gameObject);
}
}
}
重复添加豆豆并摆放后结果如下:
怪物
与吃豆人一样,怪物也有四个方向的动画:前后左右。
• 右
• 左
• 上
• 下
导入怪物图片并设置参数:
创建动画
与上面吃豆人一样将Sprite Mode设为Multiple,在Sprite Editor 中以16x16的Grid切割怪物精灵:
点击Apply后关闭Sprite Editor 。与上面吃豆人一样,将切片拖拽到场景中创建动画。
首先将切片0~1拖拽到场景创建向右移动到动画 right.anim,保存在BlinkyAnimation文件夹下。
重复以上步骤:
2~3是left
4~5是up
6~7是down
之后清除多余的内容
与吃豆人一样设置动画状态机
还是与之前吃豆人一样的步骤和设置。拖拽动画、创建参数并设置变换:
从Any State到right的变换,条件是DirX > 0.1
从Any State到left的变换,条件是DirX < -0.1
从Any State到up的变换,条件是DirY > 0.1
从Any State到down的变换,条件是DirY < -0.1
最终的动画状态机如下:
在检视面板中设置blinky坐标让其显示在迷宫中间:
怪物物理
怪物也需具有物理特性。同样在检视面板中依次点击Add Component->Physics 2D->Circle Collider 2D并设置以下属性:
注:启用Is Trigger是因为怪物可以穿过物体。
怪物也可在迷宫中移动,所以要添加Rigidbody。依次点击Add Component->Physics 2D->Rigidbody 2D:
设置Order in Layer为1
怪物移动
这里我们不清楚原作的AI机制,只是其中一些专注于四处移动,而另一些则专注于跟着吃豆人,或者还有些专门负责出现在吃豆人面前。
本教程中我们选用最简单的AI:在迷宫中四处移动。新建一个按路径点移动的脚本,让怪物按照路径移动。听起来很简单,但确实能很好的解决AI问题。路径越复杂,万件就越难躲避怪物。
新建C#脚本GhostMove,放在Scripts文件夹下。然后双击打开它:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GhostMove : MonoBehaviour {
//添加一个公共的Transform数组,以便在检视面板中设置路径点:
public Transform[] waypoints;
//索引变量来记录怪物当前走向第几个路径点:
int cur = 0;
//移动速度
public float speed = 0.3f;
void FixedUpdate()
{
//判断当前位置是否不等于路经点,不等于就继续移动等于就转向下一个路径点
if (transform.position != waypoints[cur].position)
{
Vector2 p = Vector2.MoveTowards(transform.position, waypoints[cur].position, speed);
GetComponent<Rigidbody2D>().MovePosition(p);
}
else cur = (cur + 1) % waypoints.Length;
Vector2 dir = waypoints[cur].position - transform.position;
GetComponent<Animator>().SetFloat("X", dir.x);
GetComponent<Animator>().SetFloat("Y", dir.y);
}
//碰到吃豆人就销毁它
void OnTriggerEnter2D(Collider2D co2)
{
if(co2.name=="吃豆人")
{
Destroy(co2.gameObject);
}
}
}
注:可以通过waypoints[cur]来访问当前路径点。
添加路径点
为怪物添加一些路径点。新建游戏对象,重命名为Blinky_Waypoint0并为其设置Gizmo便于在场景中观察:
注:Gizmo只是一个可视化的辅助工具,运行时不可见。
将其复制多个,自行选择摆放位置。
因为移动脚本会在到达最后点路径点之后从第一个冲心开始,所以我们要设置循环。
在层次面板中选中blinky,将路径点依次拖拽到GhostMove的Waypoints变量:
点击Play可以看到怪物按照路径点移动:
也可以设置复杂的路径并设置多个怪物。
最终效果图:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。