学习目标:
最近在b站和Youtube上看到了很多好项目,这对已经了解一定程度的Untiy知识的小伙伴有很大帮助,今天在b站看到一个Up教学如何提高游戏人物打击感,今天就用文字解析的方式算是总结一下我学到的内容吧。
视频地址:使用Unity实现动作游戏的打击感_哔哩哔哩_bilibili本期视频使用资源:https://legnops.itch.io/red-hood-characterhttps://szadiart.itch.io/pixel-fantasy-caveshttps://v-ktor.itch.io/pixelated-attackhit-animations项目链接:https://github.com/RedFF0000/AttackSense视频内https://www.bilibili.com/video/BV1fX4y1G7tv?spm_id_from=333.788.header_right.fav_list.click
Github项目:
GitHub - RedFF0000/AttackSenseContribute to RedFF0000/AttackSense development by creating an account on GitHub.https://github.com/RedFF0000/AttackSense
学习内容:
首先将我们需要的素材导入?,然后切割图像做好每一个动画,由于素材中有作者已经制作好的预设体,大伙可以更直接的看到作者设置的动画条件,同样人物需要Sprite Renderer调整它的Order in Layer大一点保证显示到最上面,Rigibody2D和BoxCollider2D也是少不了的。
??????
再来说动画条件,作者只用少量的参数Parameters控制了很多动画条件,Idle和Run用Horizontal,而涉及跳跃和降落用isGround和Vertical。
而对于攻击动画条件,作者用两个Trigger“LightAttack”和"HeavyAttack"来判断轻攻击还是重攻击,各分为ComboStep用ComboStep来判断第几段攻击
到Exit状态则设置1秒的Exit Time
代码部分:
? 用CharacterController.cs来实现人物的移动,跳跃以及攻击
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("补偿速度")]
public float lightSpeed;
public float heavySpeed;
[Header("打击感")]
public float shakeTime;
public int lightPause;
public float lightStrength;
public int heavyPause;
public float heavyStrength;
[Space]
public float interval = 2f;
private float timer;
private bool isAttack;
private string attackType;
private int comboStep;
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()
{
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);
}
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);
}
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;
}
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);
}
}
private void OnDrawGizmos()
{
Gizmos.DrawWireSphere(transform.position + new Vector3(check.x, check.y, 0), check.z);
}
}
在Player的Animation面板中需要为动画关键帧添加事件,在每个涉及Attack的动画中,在接近完成的时候添加一个事件,把我们写的public void AttackOver()添加上去
同时我们还需要两个脚本,一个是Enemy的,这里作者并没有给它血量让他只是一个沙包,有击退函数GetHit(),以及用AnimatorStateInfo检测动画播放的进度,当进度超过一定阈值的时候就会将isHit设置为false,同时为了增加敌人的受击感作者用另外的子对象的Animator
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public float speed;
private Vector2 direction;
private bool isHit;
private AnimatorStateInfo info;
private Animator animator;
private Animator hitAnimator;
new private Rigidbody2D rigidbody;
void Start()
{
animator = transform.GetComponent<Animator>();
hitAnimator = transform.GetChild(0).GetComponent<Animator>();
rigidbody = transform.GetComponent<Rigidbody2D>();
}
void Update()
{
info = animator.GetCurrentAnimatorStateInfo(0);
if (isHit)
{
rigidbody.velocity = direction * speed;
if (info.normalizedTime >= .6f)
isHit = false;
}
}
public void GetHit(Vector2 direction)
{
transform.localScale = new Vector3(-direction.x, 1, 1);
isHit = true;
this.direction = direction; ;
animator.SetTrigger("Hit");
hitAnimator.SetTrigger("Hit");
}
}
最后我们还想要实现一下让受攻击时顿帧以及摄像机抖动,在Main Camera上创建一个AttackTry
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AttackTry : MonoBehaviour
{
//单例模式,不被场景加载销毁
private static AttackTry instance;
public static AttackTry Instance
{
get
{
if (instance == null)
instance = GameObject.FindObjectOfType<AttackTry>();
return instance;
}
}
private bool isShake;
public void HitPause(int duration)
{
StartCoroutine(Pause(duration));
}
IEnumerator Pause(int duration)
{
float pauseTime = duration / 60f;
Time.timeScale = 0;
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;
var camera = Camera.main.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;
}
}
?再给人物设置好参数以及新建一个空对象来判断攻击范围
各个Tag:
最后在Project Setting的Physics 2D上早证玩家和敌人不会发生碰撞?
学习产出:
|