教程 | 使用Unity制作2D动作游戏
无论项目使用3D环境还是2D环境,在Unity中构建战斗系统的过程并不简单。本文将由开发者Yusufbek Alimatov介绍实现Unity 2D动作游戏的基础功能,你可以通过本文介绍的方法开发出更为复杂的战斗系统。
资源项目
本文中工程项目代码和素材文件下载地址:
https://github.com/youbek/2DMeleeCombat
我们将使用到Asset Store资源商店中以下的免费资源。
Knight Sprite Sheet
https://assetstore.unity.com/packages/2d/characters/knight-sprite-sheet-free-93897
Ninja Sprite Sheet
https://assetstore.unity.com/packages/2d/characters/ninja-sprite-sheet-free-93901
2D Game Starter Assets
https://assetstore.unity.com/packages/2d/environments/2d-game-starter-assets-24626
我们将通过以下4个步骤实现项目:
创建Unity 2D项目
从Assets Store资源商店下载并导入资源
编写控制玩家移动的C#脚本并制作动画
实现玩家的战斗过程
最终得效果如下图所示。
创建Unity 2D项目
首先在Unity中创建新项目。打开Unity编辑器,单击New按钮并命名项目,设置项目的保存位置,然后选择模板,不同的模板带有Unity提供的相应基础资源。
本项目我们将使用2D模板。
导入Asset Store资源商店中的资源
在本文的项目中,我们将使用Asset Store资源商店中的免费资源来构建环境,敌人和玩家。
Knight Sprite Sheet制作玩家
Ninja Sprite Sheet制作敌人
2D Game Starter Assets用来构建环境
下载并导入资源后,请使用2D Game Starter Assets资源包按下图效果构造场景。
我们为环境部分设置了新的排序图层“env”,以避免角色被环境部分挡住。
添加一个空白游戏对象来保存地面部分。然后向该游戏对象添加Edge Collider 2D组件,并按照下图的效果进行修改。
我们添加Edge Collider 2D组件到地面游戏对象,以便使角色和地面发生碰撞。
编写控制玩家移动脚本并设置动画
本文的项目中,我们使用Knight Sprite Sheet资源制作玩家。
从Assets Store资源商店下载并导入资源后,我们会在Assets文件夹内看到Knight Files文件夹。打开Knight Files > Body > Knight,将Knight_idle_01从项目窗口拖到场景中。
我们可能无法在场景看见精灵,但精灵就在场景中,这是因为精灵位于默认的排序图层,而环境背景的排序图层为env,角色被环境背景挡住了。
解决方法:只要为角色设置另一个排序图层即可,我们将该排序图层命名为“Char”。
我们将角色的游戏对象名称从Knight_idle_01改为Player。现在我们给角色添加物理,使角色受重力影响,这样角色就不会像上图一样停留在空中。
在层级窗口中选中角色,单击Add Component,搜索并添加RigidBody2D组件和BoxCollider2D组件。添加组件后,角色会对物理效果产生反应。
效果不错,但是玩家对象的Box Collider区域比所需大小稍微大一些,所以要调整该区域的大小。 我们将Box Collider组件的Offset改为(0.03, -0.46),Size改为(0.74, 1.48)。
完成保存场景后,在Unity编辑器中创建Scripts和Animations文件夹,然后在Animations文件夹中创建PlayerAnimation和EnemyAnimation文件夹。
打开PlayerAnimation文件夹,在空白位置单击右键,选择Create > Animator Controller,创建Animator Controller文件并命名为PlayerAnimController。单击右键,选择Create > Animation创建三个动画,将它们分别命名为“Idle”,“Running”和“Attacking”。
我们给玩家对象附加Animator Controller组件,打开Animator Controller,将创建的动画拖到PlayerAnimController。
在Animator窗口添加isAttacking、isRunning、isIdle三个布尔值参数,它们分别代表:攻击、奔跑、空闲,最后我们在状态之间加入过渡。
当isAttacking为true时,Idle转为Attacking
当isAttacking为false且isIdle为true时,Attacking转为Idle
当isRunning为true且isIdle为false时,Idle转为Running
当isRunning为false且isIdle为true时,Running转为Idle
当isAttacking为true时,Running转为Attacking
当isAttacking为false且isRunning为true时,Attacking转为Running
除了Attacking转为Idle和Attacking转为Running,其它过渡状态都要取消勾选Has Exit Time。
最后,打开Assets > Knight Files > Knight PNG文件夹,通过使用其中的精灵图集制作奔跑,空闲和攻击动画,制作方法如下图所示。
上图是空闲动画的制作过程,你可以使用精灵图集,通过上图步骤做出奔跑和攻击动画。制作好攻击动画后,我们要添加一个点,用来决定近战武器是否对敌人造成伤害。
在Player游戏对象中,创建一个空白游戏对象并命名为DamageMaker。默认情况下,该对象处于不活动状态。
在攻击动画中,我们要将该对象位置按照剑的位置移动,在动画的第4帧,取消勾选DamageMaker,将其位置调整到剑的边缘。
现在返回Assets文件夹并打开Scripts文件夹,在空白位置单击右键,选择Create > C# Script,新建C#脚本并命名为PlayerMovement。我们将PlayerMovement脚本附加到Player游戏对象上,然后打开该脚本。
我们将根据以下输入来移动角色。
按键“A”用来向左移动
按键“D”用来向右移动
鼠标左键用来进行攻击
PlayerMovement脚本包含玩家的血量和攻击力信息,我们首先向PlayerMovement脚本添加变量。
public class PlayerMovement:MonoBehaviour {
#region PUBLIC_VAR
#endregion
#region PRIVATE_VAR
[SerializeField] private Transform attackPoint;
[SerializeField] private float attackRadius;
[SerializeField] private float movementSpeed;
[SerializeField] private int damage;
#endregion
下面是以上代码的解释:
#region关键字可以让我们在使用Visual Studio的大纲显示功能时,指定想要折叠或展开的代码块。
Transform attackPoint会检查攻击时敌人的特定点。
Animator anim用于通过代码控制动画。
有些变量会从Player游戏对象获取组件信息,例如:本项目中的anim变量。在Start函数中编写以下代码。
anim = GetComponent <Animator>();
我们还要添加一些代码,使角色在玩家按下“A”和“D”键时移动。我们在Update函数中编写以下代码。
// 检查水平轴
float movement = Input.GetAxis("Horizontal") * Time.deltaTime * movementSpeed;
// 如果movement不等于0,这表示玩家按下了A或D,所以停止空闲动画,否则停止奔跑动画
if (movement != 0 && anim.GetCurrentAnimatorStateInfo(0).IsName("Attacking") == false)
{
anim.SetBool("isRunning", true);
anim.SetBool("isIdle", false);
// 如果movement值大于0,这表示玩家对象向右移动,所以将玩家对象向右转并进行移动
if (movement > 0)
{
transform.localScale = new Vector3(1, 1, 1);
transform.Translate(transform.right * movement);
}
else if (movement < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
transform.Translate(transform.right * movement);
}
} else if(movement == 0 && anim.GetCurrentAnimatorStateInfo(0).IsName("Attacking") == false)
{
anim.SetBool("isRunning", false);
anim.SetBool("isIdle", true);
}
我们通过Input.GetAxis方法检查输入,然后通过变量检查玩家是否移动,是否正在播放攻击动画。
如果movement变量不等于0,而且没有播放攻击动画,这表示玩家调用了Input.GetAxis,从而知道玩家按下了和movement相关的按键,而且玩家没有处于攻击状态。因此在这种情况下要播放奔跑动画,否则播放空闲动画。
运行游戏前,在检视窗口将Player对象的movement speed值由0改为3,运行游戏后的效果如下图所示。
实现玩家的战斗过程
下面我们要实现近战攻击过程,代码如下所示。
// 如果点击了鼠标左键,播放攻击动画if (Input.GetMouseButtonDown(0))
{
anim.SetBool("isAttacking", true);
} else
{
anim.SetBool("isAttacking", false);
}
//检查attackPoint和damagemaker是否处于活动状态if(attackPoint.gameObject.active == true && damageMade == false)
{
damageMade = true;
Collider2D[] hittedObjects = Physics2D.OverlapCircleAll(attackPoint.position, attackRadius);
if(hittedObjects.Length > 0)
{
for(int i = 0; i < hittedObjects.Length; i++)
{
if(hittedObjects[i].gameObject != gameObject)
{
EnemyMovement enemy = hittedObjects[i].gameObject.GetComponent<EnemyMovement>();
if (enemy != null){
enemy.health -= damage;
}
}
}
}
} else if(attackPoint.gameObject.active == false && damageMade == true)
{
damageMade = false;
}
在上面的代码中,我们首先会检查是否按下了鼠标左键,如果按下了左键,播放攻击动画;如果没有按下左键,则停止播放攻击动画。
同时,我们会检查attackPoint是否处于活动状态,如果attackPoint处于活动状态,这表示角色可以通过该点伤害敌人。为了在attackRadius变量设定的半径内通过该点造成伤害,我们使用了Physics2D.OverlapCircle来获取所有带有碰撞体的对象。
然后我们会检查对象是否拥有EnemyMovement脚本,从而确定对象是否为敌人,如果带有该脚本,则对象为敌人,然后使敌人血量减去玩家伤害量。
小结
按照本文中的4个步骤,我们就实现了一个简单的2D动作游戏,希望大家学以致用,使用本文介绍的方法开发出更为复杂的战斗系统。
友情提示:Unity 2D开发挑战赛在12月18日星期二即将截至,希望参与本次挑战赛的开发者,一定要抓紧最后的时间,提交你所开发的 游戏,我们期待有更多精彩的Unity 2D作品。
更多Unity教程,尽在Unity官方中文论坛(UnityChina.cn)!
推荐阅读
官方活动
Unity 2D开发挑战赛(最后2天)
参加Unity 2D开发挑战赛,向全世界展示你在Unity 2D方面的创意实践。[了解详情...]
活动地址:
https://connect.unity.com/challenges/2dchallenge
Unity面向全球的学生推出-Unity全球学生开发挑战赛,寻找全世界最具创意,展现自我的学生开发者团队。[了解详情...]
活动地址:
https://connect.unity.com/challenges/gsc2018
截至12月19日,Unity的新订阅用户在获得最高价值2800元的资源插件圣诞礼包的同时,我们将邀请活动期间所有的订阅用户参与Unity的圣诞派对,共庆年终圣诞季!
点击“阅读原文”访问Unity官方中文论坛