Notifications
Article
教程 | 使用Unity制作2D动作游戏
Published 6 months ago
338
0
无论项目使用3D环境还是2D环境,在Unity中构建战斗系统的过程并不简单。本文将由开发者Yusufbek Alimatov介绍实现Unity 2D动作游戏的基础功能,你可以通过本文介绍的方法开发出更为复杂的战斗系统。

资源项目

本文中工程项目代码和素材文件下载地址:
https://github.com/youbek/2DMeleeCombat
我们将使用到Asset Store资源商店中以下的免费资源。
  • Knight Sprite Sheet
  • Ninja Sprite Sheet
  • 2D Game Starter Assets
我们将通过以下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教程,尽在Unity官方中文论坛(UnityChina.cn)!
Tags:教程
Unity China
576
Comments