最终效果
文章目录
- 最终效果
- 前言
- 素材下载:
- 玩家移动跳跃控制
- 攻击动画配置
- 轻攻击
- 重攻击
- 攻击时禁止移动和攻击移动补偿
- 敌人击退和播放受击动画
- 受击特效
- 攻击停顿和屏幕震动
- 局部顿帧(补充)
- 参考
- 源码
- 完结
前言
注意本文为自己的学习记录笔记,主要是对游戏攻击 连击 轻重攻击和打击感进行探究,其中打击感实现一般依靠播放受击动画、击退、攻击特效、时停和屏幕震动反馈等来实现,如果你有其他的好方法也欢迎补充。
素材下载:
人物
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.2yield 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是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~