最终效果
文章目录
- 最终效果
- 前言
- 素材下载:
- 玩家移动跳跃控制
- 攻击动画配置
- 轻攻击
- 重攻击
- 攻击时禁止移动和攻击移动补偿
- 敌人击退和播放受击动画
- 受击特效
- 攻击停顿和屏幕震动
- 局部顿帧(补充)
- 参考
- 源码
- 完结
前言
注意本文为自己的学习记录笔记,主要是对游戏攻击 连击 轻重攻击和打击感进行探究,其中打击感实现一般依靠播放受击动画、击退、攻击特效、时停和屏幕震动反馈等来实现,如果你有其他的好方法也欢迎补充。
素材下载:
人物
https://legnops.itch.io/red-hood-character
敌人
https://jesse-m.itch.io/skeleton-pack
环境
https://szadiart.itch.io/pixel-fantasy-caves
攻击特效
https://v-ktor.itch.io/pixelated-attackhit-animations
玩家移动跳跃控制
public class PlayerController : MonoBehaviour
{
[Header("移动和跳跃参数")]
public float moveSpeed; // 移动速度
public float jumpForce; // 跳跃力量
new private Rigidbody2D rigidbody; // 刚体组件
private Animator animator; // 动画控制器
private float input; // 输入
private bool isGround; // 是否在地面上
[SerializeField] private LayerMask layer; // 地面碰撞层
[SerializeField] private Vector3 check; // 地面检测向量
void Start()
{
rigidbody = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
}
void Update()
{
input = Input.GetAxisRaw("Horizontal");
isGround = Physics2D.OverlapCircle(transform.position + new Vector3(check.x, check.y, 0), check.z, layer);
animator.SetFloat("Horizontal", rigidbody.velocity.x);
animator.SetFloat("Vertical", rigidbody.velocity.y);
animator.SetBool("isGround", isGround);
Move();
Attack();
}
void Move()
{
// 根据输入来移动角色
rigidbody.velocity = new Vector2(input * moveSpeed, rigidbody.velocity.y);
// 处理跳跃
if (Input.GetButtonDown("Jump") && isGround)
{
rigidbody.velocity = new Vector2(0, jumpForce);
animator.SetTrigger("Jump"); // 触发跳跃动画
}
// 根据水平速度方向更新角色朝向
if (rigidbody.velocity.x < 0)
transform.localScale = new Vector3(-1, 1, 1); // 向左
else if (rigidbody.velocity.x > 0)
transform.localScale = new Vector3(1, 1, 1); // 向右
}
private void OnDrawGizmos()
{
Gizmos.DrawWireSphere(transform.position + new Vector3(check.x, check.y, 0), check.z);
}
}
攻击动画配置
攻击动画分为轻攻击和重攻击
轻攻击
重攻击
[Header("攻击")]
public float interval = 2f; // 攻击间隔时间
private float timer; // 计时器
private bool isAttack; // 是否正在攻击
private string attackType; // 攻击类型
private int comboStep; // 连击步骤
void Update()
{
//...
Attack();
}
void Attack()
{
// 轻攻击输入检测
if (Input.GetKeyDown(KeyCode.Return) && !isAttack)
{
isAttack = true;
attackType = "Light";
comboStep++; // 连击步骤加一
if (comboStep > 3)
comboStep = 1; // 连击步骤循环
timer = interval; // 设置计时器
animator.SetTrigger("LightAttack"); // 触发轻攻击动画
animator.SetInteger("ComboStep", comboStep); // 设置连击步骤参数
}
// 重攻击输入检测
if (Input.GetKeyDown(KeyCode.RightShift) && !isAttack)
{
isAttack = true;
attackType = "Heavy";
comboStep++; // 连击步骤加一
if (comboStep > 3)
comboStep = 1; // 连击步骤循环
timer = interval; // 设置计时器
animator.SetTrigger("HeavyAttack"); // 触发重攻击动画
animator.SetInteger("ComboStep", comboStep); // 设置连击步骤参数
}
// 处理连击计时器
if (timer != 0)
{
timer -= Time.deltaTime;
if (timer <= 0)
{
timer = 0;
comboStep = 0; // 重置连击步骤
}
}
}
//攻击结束
public void AttackOver()
{
isAttack = false;
}
配置每个
攻击动画,在执行的位置执行攻击结束事件,通常攻击结束事件都不会放在动画的最后一帧,因为连击一般都存在预输入,也就是在上一个动画还未结束时就进行输入,这样能很好的提升combo的连贯性
效果
攻击时禁止移动和攻击移动补偿
攻击时我们不希望玩家还能移动,但是简单粗暴的禁止移动又会影响我们的攻击操作手感,在死亡细胞等游戏中,攻击时会朝前方以一个较小的速度移动,这样可以一定程度的补偿攻击时无法移动的缺陷
[Header("攻击补偿速度")]
public float lightSpeed; // 轻攻击速度
public float heavySpeed; // 重攻击速度
void Move()
{
// 如果玩家没有在攻击状态下,根据输入来移动角色
if (!isAttack)
rigidbody.velocity = new Vector2(input * moveSpeed, rigidbody.velocity.y);
else
{
// 如果正在攻击,则根据攻击类型设置速度
if (attackType == "Light")
rigidbody.velocity = new Vector2(transform.localScale.x * lightSpeed, rigidbody.velocity.y);
else if (attackType == "Heavy")
rigidbody.velocity = new Vector2(transform.localScale.x * heavySpeed, rigidbody.velocity.y);
}
// ...
}
配置
效果
敌人击退和播放受击动画
配置敌人受击动画
配置玩家每个动画的攻击范围
新增Enemy 敌人脚本,实现击退和受击动画
public class Enemy : MonoBehaviour
{
public float speed; // 敌人的移动速度
private Vector2 direction; // 受击时的移动方向
private bool isHit; // 是否正在受击状态
private AnimatorStateInfo info; // 动画状态信息
private Animator animator; // 敌人的主动画控制器
new private Rigidbody2D rigidbody; // 敌人的刚体组件
void Start()
{
// 获取组件的引用
animator = transform.GetComponent<Animator>();
rigidbody = transform.GetComponent<Rigidbody2D>();
}
void Update()
{
info = animator.GetCurrentAnimatorStateInfo(0); // 获取当前动画状态信息
if (isHit)
{
rigidbody.velocity = direction * speed; // 根据受击方向设置速度
if (info.normalizedTime >= .6f)
isHit = false; // 当动画播放超过60%时结束受击状态
}
}
// 外部调用,使敌人进入受击状态
public void GetHit(Vector2 direction)
{
transform.localScale = new Vector3(-direction.x, 1, 1); // 根据受击方向调整朝向
isHit = true; // 进入受击状态
this.direction = direction; // 设置受击方向
animator.SetTrigger("Hit"); // 播放主动画的受击动画状态
}
}
修改PlayerController,调用击退敌人
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Enemy"))
{
// 根据角色朝向确定敌人受击方向
if (transform.localScale.x > 0)
other.GetComponent<Enemy>().GetHit(Vector2.right); // 右侧受击
else if (transform.localScale.x < 0)
other.GetComponent<Enemy>().GetHit(Vector2.left); // 左侧受击
}
}
效果
受击特效
特效动画配置
修改Enemy
private Animator hitAnimator; // 敌人的受击特效动画控制器
hitAnimator = transform.GetChild(0).GetComponent<Animator>(); // 敌人的受击特效动画控制器在子对象中
// 外部调用,使敌人进入受击状态
public void GetHit(Vector2 direction)
{
//...
hitAnimator.SetTrigger("Hit"); // 播放受击特效动画
}
效果
攻击停顿和屏幕震动
新增AttackSense
public class AttackSense : MonoBehaviour
{
private static AttackSense instance;
public static AttackSense Instance
{
get
{
if (instance == null)
instance = FindObjectOfType<AttackSense>(); // 查找当前场景中的 AttackSense 实例
return instance;
}
}
private bool isShake; // 是否正在进行摄像机震动
// 暂停游戏一段时间
public void HitPause(int duration)
{
StartCoroutine(Pause(duration));
}
// 使用协程实现暂停功能
IEnumerator Pause(int duration)
{
float pauseTime = duration / 60f; // 将帧数转换为实际暂停时间
Time.timeScale = 0.2f; // 将游戏时间缩放设为0.2
yield return new WaitForSecondsRealtime(pauseTime); // 等待指定的暂停时间
Time.timeScale = 1; // 恢复游戏时间正常
}
// 触发摄像机震动效果
public void CameraShake(float duration, float strength)
{
if (!isShake) // 如果当前没有进行震动
StartCoroutine(Shake(duration, strength));
}
// 使用协程实现摄像机震动效果
IEnumerator Shake(float duration, float strength)
{
isShake = true; // 标记正在进行震动
Transform camera = Camera.main.transform; // 获取主摄像机的 Transform 组件
Vector3 startPosition = camera.position; // 记录摄像机震动前的初始位置
while (duration > 0)
{
// 将摄像机位置随机偏移一定范围来模拟震动效果
camera.position = Random.insideUnitSphere * strength + startPosition;
duration -= Time.deltaTime; // 每帧减去时间
yield return null; // 等待下一帧
}
camera.position = startPosition; // 震动结束后将摄像机位置恢复到初始位置
isShake = false; // 结束震动状态
}
}
修改PlayerController调用
[Header("打击感")]
public float shakeTime; // 摇晃时间
public int lightPause; // 轻攻击暂停时间
public float lightStrength; // 轻攻击相机震动强度
public int heavyPause; // 重攻击暂停时间
public float heavyStrength; // 重攻击相机震动强度
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Enemy"))
{
// 根据攻击类型处理打击感
if (attackType == "Light")
{
AttackSense.Instance.HitPause(lightPause); // 轻攻击暂停
AttackSense.Instance.CameraShake(shakeTime, lightStrength); // 相机震动
}
else if (attackType == "Heavy")
{
AttackSense.Instance.HitPause(heavyPause); // 重攻击暂停
AttackSense.Instance.CameraShake(shakeTime, heavyStrength); // 相机震动
}
// 根据角色朝向确定敌人击退方向
if (transform.localScale.x > 0)
other.GetComponent<Enemy>().GetHit(Vector2.right); // 右侧受击
else if (transform.localScale.x < 0)
other.GetComponent<Enemy>().GetHit(Vector2.left); // 左侧受击
}
}
配置参数
最终效果
局部顿帧(补充)
前面的顿帧这个思路其实不太好,可以看看有的游戏,他们不是所有物体都停顿的,一般是攻击者和受击者停顿,其他的不受影响。animator有个scale播放速度scale值应该可以实现这种效果,连续攻击可能用局部顿帧好一点,下面大概分享一下思路
public class AttackController : MonoBehaviour
{
public Animator animator;
public float slowMotionTimeScale = 0.5f; // 慢动作时的时间缩放值
public void PerformAttack()
{
StartCoroutine(AttackCoroutine());
}
IEnumerator AttackCoroutine()
{
// 播放攻击动画前先设置慢动作
animator.speed = slowMotionTimeScale;
// 等待攻击动画播放完成
yield return new WaitForSeconds(animator.GetCurrentAnimatorStateInfo(0).length / slowMotionTimeScale);
// 恢复正常时间缩放
animator.speed = 1f;
}
}
在这个示例中,PerformAttack 方法可以被调用来开始攻击动作。在攻击动作开始时,时间缩放被设置为 slowMotionTimeScale,然后通过 Coroutine 等待攻击动画播放完成后,再恢复为正常速度。
通过这种方法,你可以在游戏中实现局部的动画停顿效果,而不是整体减速,从而增强游戏的视觉冲击力和玩家的体验。
参考
https://www.bilibili.com/video/BV1fX4y1G7tv
源码
整理好我会放上来
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~