目录
数值伤害
在人物动画里attack2作为暴击的动画,条件的设置和之前一样。
玩家的冷却时间更新:
在character stats里设置伤害数值
判断攻击的方式使用帧事件的方式实现
判断是否暴击的话在攻击之前就要产生判断 所以在attackevent里面实现
hit函数 真是件调用的函数
hit
在特定的时间添加关键帧,并设定函数:
此时即可实现攻击时实现暴击的效果。
接下来实现怪物的暴击,
当我们想在slime的动画里添加关键帧时发是只读
、
将文件夹里的动画使用ctrl+d复制一份,然后在动画机里替换即可。
之后就可以被编辑了,添加关键帧:
接下来即可实现怪物攻击调用的代码。
守卫状态
接下来补充守卫状态的代码:
用sqr也可以计算距离
接下来即可实现让怪物脱战时回到原来的状态,但是出问题在于回去时脸朝外:
记录下初始的旋转四元数
然后这个可以实现缓慢转动然后
接下来即可实现回去后仍然朝向原来的地方了。
case EnemyStates.GUARD:
isChase = false;
if (transform.position != guardPos)
{
isWalk = true;
agent.destination = guardPos;
if (Vector3.SqrMagnitude(guardPos - transform.position) < agent.stoppingDistance)
{
isWalk = false;
transform.rotation = Quaternion.Lerp(transform.rotation, guardRotation, 0.01f);
}
}
break;
死亡状态
接下来添加受击:
添加一个死亡层级,并把权重设置为1,并建立基础空状态
导入动画并设定条件
(死亡动画由anystate进入)
在死亡动画勾选中为防止其重复播放动画,取消勾选:
给玩家添加一样的死亡层级:
在代码中添加一些设定
(注意此处加else 否则会再次进入chase状态)
受击的动画触发条件则在此处实现:
之后即可实现怪物被杀并播放死亡动画,但是此处存在一个问题,怪物被杀到消失之前仍然可以点击攻击,这是因为我们是通过鼠标事件点击是否有collider,因此怪物死亡时需要立马关掉collider即可。
人物的死亡与此同理设定即可。
注意到每次加载时需要重新设置血量。
case EnemyStates.DEAD:
collider.enabled = false;
//agent.enabled = false;
agent.radius = 0;
Destroy(gameObject, 2f);
break;
泛型单例模式
创建一个gamemanager,会变成齿轮的样子
此处public是为了此后所有的数据都通过gamemanager进行访问,这样可以方便集中管理数据。
那我们什么时候进行赋值呢,我们这里用观察者模式,反向注册的方法,让player在生成的时候告诉gamemanager我是playerstats。
写完了这个方法,我们希望在player中进行调用。
如果我们希望在player生成的时候就使用,那我们应该将gamemanager像mousemanager一样使用单例模式。这样可以直接使用,就不需要创建并赋值。
但是,在这个游戏中会有很多的manager,我们都希望它是单例模式然后进行访问,每一个都像mousemanager一样去写的话会很麻烦,因此此处使用泛型的单例模式。
如果将所有的manager都继承自这个泛型单例,就可以省下很多的步骤。
举个例子,此处使用单例,并且在后面加约束,约束是T就是那样的单例。
(此处涉及到的相关 知识是泛型约束的概念,所谓的泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。那么怎么给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。)
具体可以查看这个文章的第五点:
C#泛型详解 - .NET开发菜鸟 - 博客园 (cnblogs.com)
用这个返回当前的单例模式是否已生成。
可以用这个在之后的订阅注册中判断是否已经生成。这个bool值的变量是通常的泛型模式中会用到的。
注意到上面有一个destroy函数,有一个函数在destroy调用时也会一起调用,就是onDestroy,如果一个场景中有多个单例模式,我们需要将它销毁。
如果当前这个事例被销毁就将它设置为空。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static T instance;
public static T Instance
{
get { return instance; }
}
protected virtual void Awake()
{
if (instance != null) Destroy(gameObject);
else instance = (T)this;
}
public static bool IsInitialized
{
get { return instance != null; }
}
protected virtual void OnDestroy()
{
if (instance == this)
{
instance = null;
}
}
}
于是此处就可以在此将之前的mouseManager继承为单例:
然后这两个就可以不需要了
并且此时也会警告:
此处有一点,通常来说,我们不希望在场景转换的时候manager被消除掉,因此我们需要在awake中写一些东西。
(base.Awake是算数)
这个方法可以实现跨场景保留。
同理这里gamemanager也可以继承
然后就可以在playercontroller中直接使用并注册,将信息传递进去。
然后接下来就可以实现一个广播的效果,如果player死了,就执行一个广播,通知所有敌人玩家死了。
接口实现观察者模式的订阅和广播
有关观察者的详解在此:
(172条消息) 学过的设计模式_晴夏。的博客-CSDN博客
定义一个观察者接口:
所有的观察者都需要继承该接口,并实现该接口,这样才可以作为观察者。
接下来在enemy controller中继承:
继承之后需要实现接口
以后会有多个敌人,都会实现接口。
我们创建一个列表,收集所有实现加载了这个接口的函数方法,那么就代表它是一个敌人,需要订阅游戏结束。
加入列表和移出列表:
在enemycontroller中的启用和销毁调用该代码:
这里有一点需要注意,当人物消失时,
所以结束游戏时会出现这样的报错:
解决方法:
怎么实现广播呢?
让接口执行end notify的函数,
然后当死亡时执行该方法:
在怪物的win条件中增加
但是光这样不行,因为即使此时敌人的动画被设定为了win,但此时enemy的update函数仍然会继续进行,切换状态到攻击状态。
因此需要设定一个变量:
因此此处加入判断条件:
但是这样子仍然不行,因为此时处于attack layer中
而attack layer的动画会覆盖掉上面的动画:
所以此处还得是这个动画:
制作更多的敌人
本节内容:
-解决复制出来的敌人公用同一个数据的问题
复制模板数据然后复制即可
在代码中补充这个,
-解决 Player 死亡后仍然可以移动的问题
-使用 Animator override controller 制作 乌龟 敌人
对于动画器使用override创建的controller,可以直接覆盖动画即可。
对乌龟的数据设定如下:
-添加 兽人 和 石头人 的素材简单摆放
下载兽人和石头人素材
对于兽人的动画,创建动画器时不使用override而是直接复制,因为使用override,如果修改会对原版也产生修改,所以使用复制的版本。
可以修改兽人和石头人材质,修改其金属质感和光滑程度(光滑会决定反光程度)
将attack02的动画的条件设置为true
创建脚本和一些数据
让它继承enemycontroller动画
然后添加帧事件
在attack01中添加这个帧事件:
attack02同理:
将enemycontroller中的attacktarget改成protected,以便于继承。
attck02有两个函数需要执行,除了上面的攻击,此处还实现一个推开的函数:
击飞:
修改一下数据
attakforce改为15
接下来实现玩家被击飞时实现眩晕的效果:
在玩家这里添加dizzy眩晕动画:
在kick off里添加眩晕的代码
实测发现动画播放速度太慢,则调整播放动画速度:
此处还有个问题,处于眩晕状态时,人物依然可以移动。我们希望被攻击的时候不会发生移动,
所以此处选中hit状态,添加add behaviour。
添加animation behaviour文件夹。
把该脚本放进去。
动画状态机的脚本与普通脚本不同,继承的是stateMachineBehaviour。
我们在动画进入时,设定agent的isStopped为真代表无法移动,结束时再将其设定为可以移动。
除此之外,物体正在移动时,isStopped也必须设置为true,这是因为
每当我们点击去往某个地方时,agent的isStopped都是false,意味着此时将会变得可以移动。
所以我们需要保证动画运行时候agent也是不能移动的,才这样:
这样就实现了眩晕时候无法再次移动。
此处有另外一个问题,当敌人攻击玩家时,如果玩家移动敌人会跟着移动,那么就会出现敌人边攻击边移动。
实现这个办法只需要在attack的动画中添加之前那个无法移动的behaviour即可。
前面设置的史莱姆同理(史莱姆设置之后乌龟不需要再次设置)。
接下来,当玩家杀死敌人时会出现一个报错。 这是因为敌人死的时候这个命令还在执行,
这是因为死亡时我们设置了:
回忆一下此处我们设置它为false是因为不希望死了之后还挡住人,那只需要设置半径为0即可。
这样子就可以消除bug。
此处补充一个,人物旋转时 相机可能就不会跟着旋转,设定下面这个选项可以使得游戏运行时相机始终看着人物前方。
兽人代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Grunt : EnemyController
{
[Header("Basic Setting")]
public float kickForce = 1f;
public void KickOff()
{
Debug.Log("执行推开函数");
if (attackTarget)
{
Debug.Log("目标存在,推开");
if(attackTarget.GetComponent<NavMeshAgent>().isStopped == true)
{
Debug.Log("玩家此时agent静止");
}
transform.LookAt(attackTarget.transform);
Vector3 direction = attackTarget.transform.position - transform.position;
direction.Normalize();
attackTarget.GetComponent<NavMeshAgent>().isStopped = true;
attackTarget.GetComponent<NavMeshAgent>().velocity = kickForce*direction;
attackTarget.GetComponent<Animator>().SetTrigger("Dizzy");
}
}
}
扩展方法
我们希望实现玩家在敌人的前方120度的范围内攻击才会受到伤害,否则不会受到伤害。
方法如下: 判断两个向量的点积如果大于0.5,则说明在范围内。
此处虽然可以不使用拓展方法,但是为了使用这个用法,所以使用下面的写法:
扩展方法就是在现有的类中去延展方法:
后面那个则是所需的参数
使用起来方法如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Grunt : EnemyController
{
[Header("Basic Setting")]
public float kickForce = 1f;
public void KickOff()
{
Debug.Log("执行推开函数");
if (attackTarget)
{
Debug.Log("目标存在,推开");
if(attackTarget.GetComponent<NavMeshAgent>().isStopped == true)
{
Debug.Log("玩家此时agent静止");
}
transform.LookAt(attackTarget.transform);
Vector3 direction = attackTarget.transform.position - transform.position;
direction.Normalize();
attackTarget.GetComponent<NavMeshAgent>().isStopped = true;
attackTarget.GetComponent<NavMeshAgent>().velocity = kickForce*direction;
attackTarget.GetComponent<Animator>().SetTrigger("Dizzy");
}
}
}
设置石头人boss
创建一个石头人的动画器,源自重载的兽人animator。
再写一个代码,继承自enemycontroller。为了子类能使用这个变量,将其设定为protected
设定数据:
最终代码如下:
kick off会存在一个问题,玩家静止时无法改变其速度,也就是无法击退,要将auto braking 的勾选关掉。
接下来对之前代码产生的一些小问题进行修复:
回顾一下我们是怎么让玩家进行攻击的?:
当点击时会触发attack事件,然后执行移动,当敌人在玩家的攻击范围以外时,就会将敌人所在位置设置为目标点。当敌人进入玩家攻击范围时,设置玩家停止。(但是此时玩家的目标点仍然是敌人的position。)
此处修改停止距离,
设计一个变量获取初始时的停止距离:
然后此处修改停止距离为武器半径距离。
但是平常的时候我们希望停止距离是玩家的半径。
接下来修复另外一个问题,当人物受伤或者玩家受伤动画时,此时仍然可以触发攻击效果。
在gethit的behaviour里面添加
stop agent 的状态机代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class StopAgent : StateMachineBehaviour
{
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<NavMeshAgent>().isStopped = true;
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<NavMeshAgent>().isStopped = true;
}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<NavMeshAgent>().isStopped = false;
}
// OnStateMove is called right after Animator.OnAnimatorMove()
//override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that processes and affects root motion
//}
// OnStateIK is called right after Animator.OnAnimatorIK()
//override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that sets up animation IK (inverse kinematics)
//}
}
为石头人boss添加丢石头
将石头预制件导入,添加代码碰撞体
石头代码:
将石头作为prefab
在石头人这里添加变量
并将手的位置赋值进去,以及rock预制体
记得补充石头人的攻击范围还有扔石头的力:
当手举到最高点时候添加rock函数:
帧事件中的扔石头:
注意,石头要添加mesh collider,并且还要勾选上convex才行:
接下来即可实现扔石头。
但是有一个问题,石头人开始做丢石头的动作时,如果这个时候人物远离怪物的距离,则虽然会进行丢石头的动画,但是不会产生石头。
这是因为上面这里:
如果attackTarget为空,则岩石也无法生成。
所以加一句话:
这样即使玩家此时走出范围依旧有效。
除此之外,此处还有一个攻击范围的问题,我们希望离玩家远时用skii,近时用attack的话,那我们要保证同时只有一个条件为true。
如下图所示 ,添加条件保证同时只有一个条件成立。
还有一个问题,注意到只有玩家处于敌人视野时才会进入攻击状态,因此如果视野为10,但是丢石头的skil range为20,那么此时是没有用的。
因此如果想扩大石头人的攻击范围,得同时修改石头人的视野范围:
使用石头进行反击
接下来添加一些石头的状态:
石头需要产生伤害,但是注意到之前的take damage用的函数参数是两个states,此处重载一个:
然后在石头函数里写一个碰撞函数:
记得对石头状态初始化:
这样就可以实现石头的击退合受伤效果。
但是有个问题在于玩家被击晕后会自动回到原来的destiantion。修改此处即可,将动画状态机的exit函数注释掉即可,这样就不会自动回去。
另一方面,石头可以用来反击石头人。
思路就是可以反推回石头对石头人造成伤害,当石头触碰到玩家或者速度小于1时,则将石头状态改为hitnothing,即可以反推的状态。
在玩家点击时如果点击的是石头则将其传入event中,然后让石头反推回去。
具体的用法就是:
先给石头添加Attackable标签
在鼠标点击事件里添加一个控制函数,如果点击的是可攻击类的标签则将其传回event事件。
状态改为public
在player的hit中修改,如果触碰的是可攻击的物体则调用石头进行攻击。
并且当石头状态为hitNothing时才能反击。(虽然这个条件可以删除)
并且当攻击了石头后,石头自动变为hitEnemy状态。
接下来设定石头什么时候进入hitnothing的状态呢?有两种,一个是没碰到任何事物
一个是通过速度来判断:
可以暂停下来通过后面这个按钮逐帧播放进行调试:
逐帧判断之后发现石头刚生成的一瞬间速度是为0的。然后初始时设立状态为one,这样防止刚开始时就将其状态设置为hitNothing。
同理,当player刚赋予岩石速度时,就要设置成Vector3.one,以防止其进入hitNothing状态。
除此之外,此处有个问题,在于玩家会穿过石头,但是玩家有了agent,不能直接添加rigidbody,否则会产生冲突,因此此时使用isKinematic的rigidbody,防止其受力的效果。(添加了rigidbody后就可以和石头产生碰撞效果。)
这样就可以实现产生石头和石头的反击。
接下来实现石头消失后的粒子效果:
关闭循环,设定持续时间,设定重力效果,以及随时间出来多少个
实现碰撞:
设定粒子形状:
调整大小:
调整碎片炸裂角度:
然后在rock消失前产生碎片即可。
最终效果:
代码
石头人代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Golem : EnemyController
{
[Header("Skill")]
public float kickForce = 20;
public GameObject rockPrefab;
public Transform handPos;
public bool agentIsStopped;
public void ThrowRock()
{
if (attackTarget == null) attackTarget = FindObjectOfType<PlayerController>().gameObject;
if (attackTarget != null)
{
var rock = Instantiate(rockPrefab,handPos.position,Quaternion.identity);
rock.GetComponent<Rock>().target = attackTarget;
}
}
public void KickOff()
{
Debug.Log("执行推开函数");
if (attackTarget)
{
Debug.Log("目标存在,推开");
//if (attackTarget.GetComponent<NavMeshAgent>().isStopped == true)
//{
//}
agentIsStopped = attackTarget.GetComponent<NavMeshAgent>().isStopped;
transform.LookAt(attackTarget.transform);
Vector3 direction = attackTarget.transform.position - transform.position;
direction.Normalize();
attackTarget.GetComponent<NavMeshAgent>().velocity = kickForce * direction;
Vector3 velocity = attackTarget.GetComponent<NavMeshAgent>().velocity;
Debug.Log("kickForce:" + kickForce);
Debug.Log("direction:" + direction.x+" "+direction.y+" "+direction.z);
Debug.Log("velocity:" + velocity.x + " " + velocity.y + " " + velocity.z);
//attackTarget.GetComponent<NavMeshAgent>().isStopped = true;
attackTarget.GetComponent<Animator>().SetTrigger("Dizzy");
}
}
}
石头代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Rock : MonoBehaviour
{
public enum RockStates { HitPlayer,HitEnemy,HitNothing};
Rigidbody rb;
[Header("Basic Settings")]
public float force=10;
public GameObject target;
private Vector3 direction;
public RockStates rockStates;
int rockDamage = 10;
public GameObject breakPatrical;
private void Start()
{
rb = GetComponent<Rigidbody>();
rockStates = RockStates.HitPlayer;
FlyToTarget();
rb.velocity = Vector3.one;
}
private void FixedUpdate()
{
if (rb.velocity.sqrMagnitude < 1)
{
rockStates = RockStates.HitNothing;
}
Debug.Log("rockStates:" + rockStates);
}
public void FlyToTarget()
{
direction = (target.transform.position - transform.position + Vector3.up).normalized;
rb.AddForce(direction * force,ForceMode.Impulse);
}
private void OnCollisionEnter(Collision collision)
{
switch (rockStates)
{
case RockStates.HitPlayer:
if (collision.gameObject.CompareTag("Player"))
{
collision.gameObject.GetComponent<NavMeshAgent>().isStopped = true;
collision.gameObject.GetComponent<NavMeshAgent>().velocity = direction * force;
collision.gameObject.GetComponent<Animator>().SetTrigger("Dizzy");
collision.gameObject.GetComponent<CharacterStats>().TakeDamage(rockDamage,collision.gameObject.GetComponent<CharacterStats>());
rockStates = RockStates.HitNothing;
}
break;
case RockStates.HitEnemy:
if (collision.gameObject.GetComponent<Golem>())
{
var otherStats=collision.gameObject.GetComponent<CharacterStats>();
otherStats.TakeDamage(rockDamage, otherStats);
Instantiate(breakPatrical, transform.position, Quaternion.identity);
Destroy(gameObject);
}
break;
}
}
}
CharaStats:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterStats : MonoBehaviour
{
public CharacterData_SO characterData;
public CharacterData_SO templateData;
#region Read from Data_SO
private void Awake()
{
if (templateData != null) characterData = Instantiate(templateData);
}
public int MaxHealth//为间接可以将其写在一个片段里
{
get { if (characterData != null) return characterData.maxHealth; else return 0; }
set { characterData.maxHealth = value; }
}
public int CurrentHealth//为间接可以将其写在一个片段里
{
get { if (characterData != null) return characterData.currentHealth; else return 0; }
set { characterData.currentHealth = value; }
}
public int BaseDefence//为间接可以将其写在一个片段里
{
get { if (characterData != null) return characterData.baseDefence; else return 0; }
set { characterData.baseDefence = value; }
}
public int CurrentDefence//为间接可以将其写在一个片段里
{
get { if (characterData != null) return characterData.currentDefence; else return 0; }
set { characterData.currentDefence = value; }
}
#endregion
public AttackData_SO attackData;
[HideInInspector]
public bool isCritical;
public void TakeDamage(CharacterStats attacker,CharacterStats defener)
{
int damage = Mathf.Max(attacker.CurrentDamage() - defener.CurrentDefence,0);
//Debug.Log("受到" + damage + "伤害!");//受到多少伤害
CurrentHealth = Mathf.Max(CurrentHealth - damage,0);
if (attacker.isCritical)
{
defener.GetComponent<Animator>().SetTrigger("Hit");
}
}
public void TakeDamage(int damage,CharacterStats defener)
{
int currentDamage = Mathf.Max(damage - defener.CurrentDefence, 0);
CurrentHealth = Mathf.Max(CurrentHealth - currentDamage, 0);
}
private int CurrentDamage()
{
float coreDamage = UnityEngine.Random.Range(attackData.minDamage, attackData.maxDamage);
if (isCritical)
{
coreDamage *= attackData.criticalMultipler;
//Debug.Log("暴击!" + coreDamage);
}
else
{
//Debug.Log("没暴击!" + coreDamage);
}
return (int)coreDamage;
}
}
playercontroller:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class PlayerController : MonoBehaviour
{
private NavMeshAgent agent;
private Animator animator;
private GameObject attackTarget;
private float lastAttackTime = 0f;
private CharacterStats characterStats;
private bool isDead;
// Start is called before the first frame update
void Awake()
{
agent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
characterStats = GetComponent<CharacterStats>();
}
void Start()
{
MouseManager.Instance.OnMouseClicked += MoveToTarget;
MouseManager.Instance.OnEnemyClicked += EventAttack;
characterStats.CurrentHealth = characterStats.MaxHealth;
GameManager.Instance.RigisterPlayer(characterStats);//将信息注册进去
}
void Update()
{
lastAttackTime -= Time.deltaTime;
isDead = characterStats.CurrentHealth == 0;
SwitchAnimation();
if (isDead) GameManager.Instance.NotifyObservers();
Vector3 des = agent.destination;
//Debug.Log("des:" + des.x + " " + des.y + " " + des.z);
}
private void SwitchAnimation()
{
animator.SetFloat("Speed", agent.velocity.sqrMagnitude);
animator.SetBool("Death", isDead);
}
void MoveToTarget(Vector3 target)
{
StopAllCoroutines();//使得人物在走向目标的过程中也可以通过点击去往其他地方,打断操作
if (isDead) return;
agent.isStopped = false;//加上这条语句,就解决了人物一旦攻击之后就无法行动的问题
agent.destination = target;
}
void EventAttack(GameObject target)
{
if (isDead) return;
if (target != null)
{
attackTarget = target;
characterStats.isCritical = UnityEngine.Random.value < characterStats.attackData.criticalChance;
StartCoroutine(MoveToAttackTarget());
}
}
IEnumerator MoveToAttackTarget()
{
//为防止这次点击后下次agent无法行动了,在这次点击的开头使用复原
agent.isStopped = false;
transform.LookAt(attackTarget.transform);
while (Vector3.Distance(transform.position, attackTarget.transform.position) > characterStats.attackData.attackRange)
{
agent.destination = attackTarget.transform.position;
yield return null;
}
//当到达指定地点时,命令agent停止
agent.isStopped = true;
//攻击具有cd时间
if (lastAttackTime < 0)
{
animator.SetBool("Critical", characterStats.isCritical);
animator.SetTrigger("Attack");
lastAttackTime =characterStats.attackData.coolDown;
}
}
void Hit()
{
if (attackTarget.CompareTag("Attackable"))
{
if (attackTarget.GetComponent<Rock>() && attackTarget.GetComponent<Rock>().rockStates == Rock.RockStates.HitNothing)
{
attackTarget.GetComponent<Rigidbody>().velocity = Vector3.one;
attackTarget.GetComponent<Rock>().rockStates = Rock.RockStates.HitEnemy;//一旦发动攻击则将状态设置为hitEnemy
attackTarget.GetComponent<Rigidbody>().AddForce(transform.forward * 20, ForceMode.Impulse);
}
}
else {
var targetStats = attackTarget.GetComponent<CharacterStats>();
targetStats.TakeDamage(characterStats, targetStats);
}
}
}