Unity开发2D类银河恶魔城游戏学习笔记
Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进
Unity教程(十)Tile Palette搭建平台关卡
Unity教程(十一)相机
Unity教程(十二)视差背景
Unity教程(十三)敌人状态机
Unity教程(十四)敌人空闲和移动的实现
Unity教程(十五)敌人战斗状态的实现
Unity教程(十六)敌人攻击状态的实现
Unity教程(十七)敌人战斗状态的完善
Unity教程(十八)战斗系统 攻击逻辑
Unity教程(十九)战斗系统 受击反馈
如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录
文章目录
- Unity开发2D类银河恶魔城游戏学习笔记
- 前言
- 一、概述
- 二、攻击逻辑的实现
- (1)攻击检测
- (2)伤害函数
- (2)攻击触发器
- 三、解决碰撞问题
- 总结 完整代码
- Entity.cs
- PlayerAnimationTriggers.cs
- Enemy_SkeletonAnimationTriggers.cs
前言
本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。
本节实现战斗系统的攻击逻辑部分。
Udemy课程地址
对应视频:
Attack Logic
Collider’s collision exception
一、概述
本节我们先实现战斗的逻辑,即在攻击动画播放过程中找到攻击对象并造成伤害,这里伤害我们先用控制台信息代表,后续再进行具体实现。
二、攻击逻辑的实现
(1)攻击检测
在实体Entity类中添加攻击检测和攻击距离两个变量,并在OnDrawGizmos函数中绘制出攻击范围。
Gizmos函数详情见Unity官方手册
Gizmos可以绘制图形的图形如下表:
函数 | 功能 |
---|---|
DrawCube | 使用 center 和 size 绘制一个实心盒体。 |
DrawFrustum | 绘制一个摄像机视锥体,并且将当前设置的 Gizmos.matrix 用于其位置和旋转。 |
DrawGUITexture | 在该场景中绘制一个纹理。 |
DrawIcon | 在 Scene 视图中的某个位置绘制一个图标。 |
DrawLine | 绘制一条从 from 开始到 to 的线。 |
DrawMesh | 绘制一个网格。 |
DrawRay | 绘制一条从 from 开始到 from + direction 的射线。 |
DrawSphere | 使用 center 和 radius 绘制一个实心球体。 |
DrawWireCube | 使用 center 和 size 绘制一个线框盒体。 |
DrawWireMesh | 绘制一个线框网格。 |
DrawWireSphere | 使用 center 和 radius 绘制一个线框球体。 |
Entity类中添加代码如下:
添加变量:
[Header("Collision Info")]
public Transform attackCheck;
public float attackCheckRadius;
[SerializeField] protected Transform groundCheck;
[SerializeField] protected float groundCheckDistance;
[SerializeField] protected Transform wallCheck;
[SerializeField] protected float wallCheckDistance;
[SerializeField] protected LayerMask whatIsGround;
绘制攻击范围:
protected virtual void OnDrawGizmos()
{
Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));
Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance, wallCheck.position.y));
Gizmos.DrawWireSphere(attackCheck.position, attackCheckRadius);
}
在Player下创建空物体attackCheck用于攻击检测。
将空物体attackCheck拖入变量,并给attackCheckRadius设置合适的值。
移动attackCheck到Player身前合适位置,圆圈绘制出的就是Player的攻击范围。
同理,设置小怪的攻击检测。
(2)伤害函数
在Entity中添加伤害函数Damage(),实现受击后的反馈。目前不进行具体实现,先输出一条调试信息代替。
public virtual void Damage()
{
Debug.Log(gameObject.name + " was damaged");
}
(2)攻击触发器
我们先以Player的攻击为例。
在玩家触发器PlayerAnimationTriggers类中,添加攻击触发器attackTrigger函数。
每次攻击时,要记录所有在攻击范围内的敌人。然后每个攻击范围内的敌人将调用自己的Damage函数实现受伤的反馈。
检测攻击范围内的敌人调用Physics2D.OverlapCircleAll函数,它会返回圆内的所有碰撞体。
Collider2D[] OverlapCircleAll (Vector2 point, float radius, int layerMask, float minDepth, float maxDepth);
参数表如下
参数 | 介绍 |
---|---|
point | 圆形的中心。 |
radius | 圆形的半径。 |
layerMask | 筛选器,用于检查仅在指定层上的对象。 |
minDepth | 仅包括 Z 坐标(深度)大于或等于该值的对象。 |
maxDepth | 仅包括 Z 坐标(深度)小于或等于该值的对象。 |
在PlayerAnimationTriggers中添加代码:
private void AttackTrigger()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);
foreach(var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
hit.GetComponent<Enemy>().Damage();
}
}
在触发攻击伤害的帧添加事件调用attackTrigger
如需添加事件帧的详细介绍请见
Unity教程(八)角色基本攻击的实现
可以看到,在玩家攻击时,控制台输出对骷髅产生伤害
敌人的攻击触发器实现与玩家一样,代码如下:
private void AttackTrigger()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(enemy.attackCheck.position, enemy.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Player>() != null)
hit.GetComponent<Player>().Damage();
}
}
同样设置事件帧
三、解决碰撞问题
现在攻击时,因为两者之间存在碰撞,骷髅会推着玩家走,而玩家甚至可以在骷髅头上走,这看起来很不对劲。
在制作游戏时,有些会采用碰撞伤害,有些则只有攻击时造成伤害。在这里我们采用第二种,让玩家可以穿过敌人,只在被攻击击中时受到伤害。
解决方式很简单,只需要调整层次之间的碰撞即可。
首先必须确保玩家和敌人处于各自层级。
调整层次碰撞矩阵,将Player和Enemy层次间的碰撞勾掉
Edit -> Project Settings -> Physics 2D -> Layer Collision Matrix
目前存在一个问题,在骷髅与玩家重合时会不停碰撞导致骷髅抽搐,这个问题会在后续解决。
总结 完整代码
Entity.cs
添加攻击检测变量和绘制函数,添加伤害函数。
//Entity:实体类
using System.Collections;
using System.Collections.Generic;
using Unity.IO.LowLevel.Unsafe;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
public class Entity : MonoBehaviour
{
[Header("Flip Info")]
protected bool facingRight = true;
public int facingDir { get; private set; } = 1;
[Header("Collision Info")]
public Transform attackCheck;
public float attackCheckRadius;
[SerializeField] protected Transform groundCheck;
[SerializeField] protected float groundCheckDistance;
[SerializeField] protected Transform wallCheck;
[SerializeField] protected float wallCheckDistance;
[SerializeField] protected LayerMask whatIsGround;
#region 组件
public Rigidbody2D rb { get; private set; }
public Animator anim { get; private set; }
#endregion
protected virtual void Awake()
{
}
//获取组件
protected virtual void Start()
{
rb= GetComponent<Rigidbody2D>();
anim= GetComponentInChildren<Animator>();
}
// 更新
protected virtual void Update()
{
}
public virtual void Damage()
{
Debug.Log(gameObject.name + " was damaged");
}
#region 速度设置
//速度置零
public void ZeroVelocity() => rb.velocity = new Vector2(0, 0);
//设置速度
public void SetVelocity(float _xVelocity, float _yVelocity)
{
rb.velocity = new Vector2(_xVelocity, _yVelocity);
FlipController(_xVelocity);
}
#endregion
#region 翻转
//翻转实现
public virtual void Flip()
{
facingDir = -1 * facingDir;
facingRight = !facingRight;
transform.Rotate(0, 180, 0);
}
//翻转控制
public virtual void FlipController(float _x)
{
if (_x > 0 && !facingRight)
Flip();
else if (_x < 0 && facingRight)
Flip();
}
#endregion
#region 碰撞
//碰撞检测
public virtual bool isGroundDetected() => Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);
public virtual bool isWallDetected() => Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround);
//绘制碰撞检测
protected virtual void OnDrawGizmos()
{
Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));
Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance, wallCheck.position.y));
Gizmos.DrawWireSphere(attackCheck.position, attackCheckRadius);
}
#endregion
}
PlayerAnimationTriggers.cs
添加攻击触发器
//PlayerAnimationTriggers:触发器组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAnimationTriggers : MonoBehaviour
{
private Player player => GetComponentInParent<Player>();
private void AnimationTrigger()
{
player.AnimationTrigger();
}
private void AttackTrigger()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);
foreach(var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
hit.GetComponent<Enemy>().Damage();
}
}
}
Enemy_SkeletonAnimationTriggers.cs
//Enemy_SkeletonAnimationTriggers:触发器组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy_SkeletonAnimationTriggers : MonoBehaviour
{
private Enemy_Skeleton enemy => GetComponentInParent<Enemy_Skeleton>();
private void AnimationTrigger()
{
enemy.AnimationTrigger();
}
private void AttackTrigger()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(enemy.attackCheck.position, enemy.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Player>() != null)
hit.GetComponent<Player>().Damage();
}
}
}