Unity plyGame插件技能模块分析

来源:互联网 发布:本地系统 网络受限 编辑:程序博客网 时间:2024/06/05 00:33

plyGame 插件

plyGame 是一款Unity游戏引擎的视觉游戏开发工具。它可以让开发者不必编程就可以创建游戏原型,同时仍然允许以脚本方式来与系统API进行交互。

plyGame 出于易用性的考虑,提供了用来创建砍杀类RPG游戏的组件和编辑器。

这里仅分析其技能模块。

Skill 技能模块

Basic 基本信息

  • Execution Time 执行时间,这段时间内用来播放动作等,不能释放新技能
  • Cooldown Time 冷却时间,下一次能使用的间隔时间
  • Perform while move 是否可以移动的时候释放技能
  • Force Stop 是否强制停止移动来释放技能,若未设置,则移动时候不能释放技能,当Perform while move未设置时有效
  • Can be queued 是否允许放到队列,否则的话,只有当前没有使用其他技能的时候才会生效
    • Auto Queue 自动放到队列,仅当Can be queued选项开启时有效
  • Actor must face target 必须朝向目标

Activation 技能激活类型

  • User 主动技能
  • Passive 被动技能

Delivery 技能生效类型

Instant 立即生效

如近战、治疗、远程非投射类型技能

  • Direction/ Location 技能执行的方向和位置
    • Actor Direction 角色当前朝向(方向技能)
    • Mouse Direction 鼠标所指的方向(方向技能)
    • Selected Direction 所选中目标的方向(方向技能)
    • At Click Position 所点击的位置(位置技能)
    • At Selected 目标被选中才能使用(位置技能)
    • Mouse Over 鼠标之下必须有目标(位置技能)
    • Camera Forward 摄像机的前进方向(方向技能)
    • Camera Direction 摄像机的XZ平面(方向技能)
    • Cross hairOver 通过射线来选择目标(位置技能)
  • Hit Height % 击中的高度偏移,0在脚底,50在中间,100在头顶
  • Hit Delay 击中事件延迟,若为0,则一收集到目标就触发击中事件
  • Max Targets Select 最大的目标收集数量
  • Max Distance from self 技能影响的最大距离(米)
  • Targteting Range 方向技能收集目标的角度,位置技能收集目标的半径

Projectile 投射生效

创建投射物来击中目标

  • Prefab(s) 投射物预制
  • Move Method 投射物的方向和移动方法
    • ActorFaceDirection 笔直地从角色发射出去
    • AngledFromEach 成角度的发射,不止一个投射物,如多重箭
      • Random Angle 是否设置随机角度,若否的话,则根据Targeting Range来设置角度
    • DirectToTarget 投射物寻找到目标,然后朝着目标移动
      • Follow 是否跟随,若不设置,则投射物只会移动到投射物创建时目标的位置
      • Hit Height % 击中的高度偏移,0在脚底,50在中间,100在头顶
  • Max Projectiles 最大投射物的创建数量
  • Create Delay 创建第一个投射物的延迟,使用这个来跟动画同步
  • Between Create Delay 创建下一个投射物的延迟
  • Use Vector offset 投射物创建时的偏移
    • Create at Offset 相对角色本身的坐标偏移
    • Create at Tagged 角色的虚拟体,以此虚拟体坐标来创建
  • Move Speed 投射物的移动速度
  • Collision Ray Width 射线碰撞检测宽度,若为0,则为直线检测,否则为球形检测
  • Fizzle Distance 失败的距离,投射物最大的飞行距离。DirectToTarget移动方法的话,则没有这个选项
  • Max Live Time 最大生存时间,通常使用这个选项或者Fizzle Distance选项来控制失败
  • Trigger secondary on Fizzle 失败的时候,是否要触发二次效果
    • … only if obstacle 是否仅当由于碰撞到障碍物导致失败,才触发二次效果
  • Destroy Projectile on Hit 击中的时候销毁投射物,通常开启这个选项,除非像镭射光那种,穿过目标,并且对其后的目标也造成伤害,只有当时间到达或者距离到达的时候才会销毁投射物
  • Prevent Projectile UpDown 锁定投射物的Y轴
  • Actor Direction 技能执行的方向和位置
    • Actor Direction 角色当前朝向(方向技能)
    • Mouse Direction 鼠标所指的方向(方向技能)
    • Selected Direction 所选中目标的方向(方向技能)
    • At Click Position 所点击的位置(位置技能)
    • At Selected 目标被选中才能使用(位置技能)
    • Mouse Over 鼠标之下必须有目标(位置技能)
    • Camera Forward 摄像机的前进方向(方向技能)
    • Camera Direction 摄像机的XZ平面(方向技能)
    • Cross hairOver 通过射线来选择目标(位置技能)
  • Max Distance from self 技能影响的最大距离(米)
  • Targteting Range 方向技能收集目标的角度,位置技能收集目标的半径

Custom projectile logic 自定义投射物逻辑

当一个投射物创建的时候,将会添加SkillProjectile脚本组件到投射物物体上,这个组件驱动投射物。当要自定义投射物逻辑的时候,派生重载SkillProjectile脚本。

Targeting Methods 目标搜集方法

  • Self 自身作为目标,如治疗技能;
  • Selected 所选择的目标;
  • Auto 根据技能范围来自动选择有效的目标;

Valid Target(s) 有效目标

  • Player 不管任何阵营;
  • Friendly Actor 友方阵营;
  • Neutral Actor 中立阵营;
  • Hostile Actor 敌对阵营;
  • RPG Object 任意对象;

ObstacleCheckMask 障碍物检测

检测技能释放者与目标之间是否有障碍物,使用 Layer 掩码来检测。

Secondary Hit 二次击中效果

当一个效果击中目标时,可以引起触发第二次效果。这第二次效果将会收集第一次效果半径周围的目标。

  • Max Secondary Targets 最大二次效果目标数量,若为0,则表示关闭这个功能
  • Obstacle Check 障碍物检测
    • Check height offset 障碍物检测的高度
  • Range 二次效果半径

Skill Event 技能事件

  • On Skill Activate 当技能被激活的时候
  • On Skill Effect 当技能效果被创建的时候,对投射技能来说是投射物创建的时候,对立即技能来说是技能被激活并且有效执行的时候
  • On Skill Hit 当技能效果击中或影响目标的时候
  • On Skill Secondary Hit 当技能效果由于二次效果而击中或影响目标的时候
  • On Skill Fizzle 当投射技能由于超过生存时间或超过最大距离导致失败的时候
  • On Validate 在技能被激活之前进行验证,返回True才能真正激活技能

技能流程

角色类 Actor

  • executingSkill 当前正在执行的技能
  • queuedSkill 队列里等待被执行的技能
// 角色触发执行技能public virtual void QueueSkillForExecution(Skill skill){    QueueSkillForExecution(skill, false, null, Vector3.zero, Vector3.zero);}// 将一个技能放到队列里面准备执行public virtual void QueueSkillForExecution(Skill skill, bool ifNoOtherQueued,     Targetable forceSelectedObject, Vector3 forceSelectedPosition, Vector3 forceMousePosition){    if (skill == null) return;    if (ifNoOtherQueued && queuedSkill != null) return;    // 技能不能放到队列里面    if (skill.canBeQueued == false)    {    // 那么检查当前是否有正在执行的技能        if (executingSkill != null)        {            // 有其他技能正在执行,那么就失败            if (executingSkill.IsExecuting())            {                ClearQueuedSkill();                return;            }        }        // 技能还在冷却        if (skill.CoolingDown()) return;        // 不能放到队列,因为当前角色还在移动        if (skill.forceStop == false && skill.mayPerformWhileMove == false)        {            if (character.Velocity().magnitude >= 0.01f) return;        }    }    // 不确定有其他队列技能    if (false == ifNoOtherQueued)    {        // ...... 省略,根据技能方向和位置        // 求得queuedSkill_SelectedObject、queuedSkill_MousePosition        if (queuedSkill_SelectedObject != null)        {            queuedSkill_TargetPosition = queuedSkill_SelectedObject.transform.position;        }        else        {            queuedSkill_TargetPosition = queuedSkill_MousePosition;        }        queuedSkill = skill;    }    else    {        queuedSkill = skill;        queuedSkill_SelectedObject = forceSelectedObject;        queuedSkill_MousePosition = forceMousePosition;        queuedSkill_TargetPosition = forceSelectedPosition;    }}protected void LateUpdate(){    // ... 检测是否死亡等    // 当前已经有正在执行的技能    if (executingSkill != null)    {        // 技能执行完毕        if (false == executingSkill.IsExecuting())        {            // 重新开启角色移动            character.hint_DoNotMove = false;            executingSkill = null;        }    }    else    {        // 有等待被执行的技能        if (queuedSkill != null)        {            // 技能的选中目标和坐标            if (queuedSkill_SelectedObject != null)            {                queuedSkill_TargetPosition = queuedSkill_SelectedObject.transform.position;            }            // 是否可以执行技能的判断            if (CanPerformQueuedSkill())            {                // 如果不能在移动的时候执行技能,那么禁止角色移动                if (false == queuedSkill.mayPerformWhileMove)                {                    character.hint_DoNotMove = true;                    character.Stop();                }                executingSkill = queuedSkill;                queuedSkill = null;                // 真正执行技能                executingSkill.Execute(queuedSkill_SelectedObject,                     queuedSkill_TargetPosition, queuedSkill_MousePosition);                if (character.IsPlayer())                {                    if (executingSkill.targetLocation == Skill.TargetLocation.MouseOver                         || executingSkill.targetLocation == Skill.TargetLocation.CrosshairOver)                    {                        character.SelectTarget(queuedSkill_SelectedObject);                    }                }            }        }    }}// 是否可以执行技能的判断private bool CanPerformQueuedSkill(){    // 如果技能不能在移动的时候执行,或者不能强制停止移动    if (false == queuedSkill.mayPerformWhileMove && false == queuedSkill.forceStop)    {    // 那么就检查当前是否在移动,是的话就不允许技能执行        if (character.Velocity().magnitude >= 0.01f)        {            ClearQueuedSkill();            return false;        }    }    // 技能是否不在CD冷却时间内    if (queuedSkill.IsReady())    {        // 检查目标是否在技能范围内        if (false == queuedSkill.DistanceAcceptable(character._tr.position,             queuedSkill_TargetPosition, queuedSkill_MousePosition,             out targetPositionForSkill, queuedSkill_SelectedObject))        {            // 如果不在范围内,那么走到范围内,再进行执行技能            // 如果走不到,则丢弃这个技能不再执行            if (false == character.RequestMoveTo(targetPositionForSkill, true))            {                queuedSkill = null;                return false;            }        }        else        {            // 如果必须朝向目标,那么检查角色是否面向目标            if (queuedSkill.actorMustFaceTarget                 && false == queuedSkill.FacingAcceptable(character._tr.forward,                     queuedSkill_TargetPosition, queuedSkill_MousePosition, out targetDirectionForSkill))            {                // 如果没有朝向目标,则转动方向                // 如果转动失败,则丢弃这个技能不再执行                if (false == character.RequestFaceDirection(targetDirectionForSkill,                     queuedSkill.executionTimeout > 0.1f ? queuedSkill.executionTimeout : 0.1f))                {                    queuedSkill = null;                    return false;                }            }            else            {                return true;            }        }    }    return false;}

技能类 Skill

// 执行技能public virtual void Execute(Targetable selectedObject, Vector3 selectedPosition, Vector3 mousePosition){    // 调用验证事件    if (eventHandler != null)    {        if (eventHandler.OnValidateSkill(owner, this) == false) return;    }    gameObject.SetActive(true);    // 开始计时执行时间和冷却时间    executeTimer = executionTimeout;    cooldownTimer = cooldownTimeout;    // 调用激活事件    if (eventHandler != null)    {        eventHandler.OnActivate(owner, this, selectedPosition, mousePosition);        owner.gameObject.BroadcastMessage("OnUsesSkill", this, SendMessageOptions.DontRequireReceiver);    }    // 允许放到队列,并且自动放到队列有效的时候,就会在当前没有队列技能的时候放到队列里面    if (canBeQueued && autoQueue)         owner.QueueSkillForExecution(this, true, selectedObject, selectedPosition, mousePosition);    // 有效的目标    if (validTargetsMask != 0)    {        // 立即生效技能        if (deliveryMethod == DeliveryMethod.Instant)             ExecuteInstant(selectedObject, selectedPosition, mousePosition);        // 投射技能        else if (deliveryMethod == DeliveryMethod.Projectile)             ExecuteProjectile(selectedObject, selectedPosition, mousePosition);    }}

投射技能

角色当前朝向投射
Vector3 forward = owner.transform.forward;// ...... 根据目标方向和位置类型调整forward// 最终坐标点Vector3 tl = owner.transform.position + (forward * maxFlightDistance);tl.y += projectileCreateOffset.y;float waitTime = projectileCreateDelay;for (int i = 0; i < maxEffects; i++){    CreateProjectile(i, waitTime, forward, tl, null, Vector3.zero);    waitTime += projectileCreateDelayBetween;}
成角度的发射
Vector3 forward = owner.transform.forward;// ...... 根据目标方向和位置类型调整forward// 最大投射只有1个的情况if (maxEffects == 1){    Vector3 tl = owner.transform.position + (forward * maxFlightDistance);    tl.y += projectileCreateOffset.y;    CreateProjectile(0, 0f, forward, tl, null, Vector3.zero);}else{    // 每个投射所占的角度    float angleUnit = targetingAngle / (maxEffects - 1);    float maxAngle = (targetingAngle / 2);    float minAngle = -maxAngle;    float angle = minAngle;    float waitTime = projectileCreateDelay;    // 投射偏移    Vector3 pos = owner.transform.position;    pos += (owner.transform.right * projectileCreateOffset.x);    pos += (owner.transform.up * projectileCreateOffset.y);    pos += (owner.transform.forward * projectileCreateOffset.z);    for (int i = 0; i < maxEffects; i++)    {        // 随机角度        if (moveMethod_b_opt) angle = Random.Range(minAngle, maxAngle);        Vector3 tl = pos + (Quaternion.AngleAxis(+angle, Vector3.up) * forward * maxFlightDistance);        CreateProjectile(i, waitTime, forward, tl, null, Vector3.zero);        waitTime += projectileCreateDelayBetween;        angle += angleUnit;    }}
直达目标投射
// 收集目标List<Targetable> targets = CollectTargetsBasedOnTargetingLocation(selectedObject, selectedPosition, mousePosition);float waitTime = projectileCreateDelay;if (targets.Count > 0){    int projectilesleft = maxEffects;    while (projectilesleft > 0)    {        // 击中的高度比例偏移        float hitOffs = (float)hitHeightPercentage / 100f;        for (int i = 0; i < targets.Count; i++)        {            if (targets[i] == null) continue;            // 计算击中高度            float y = 1f;            Collider col = targets[i].GetComponent<Collider>();            if (col != null) y = col.bounds.size.y;            Vector3 followOffset = new Vector3(0f, y * hitOffs, 0f);            // 跟随目标            if (moveMethod_b_opt)            {                CreateProjectile(i, waitTime, owner.transform.forward, targets[i].transform.position, targets[i].transform, followOffset);            }            else            {                Vector3 pos = targets[i].transform.position + followOffset;                CreateProjectile(i, waitTime, owner.transform.forward, pos, null, followOffset);            }            waitTime += projectileCreateDelayBetween;            projectilesleft--;            if (projectilesleft <= 0) break;        }    }}else{    // 没有目标的话,随机投射    int leftToCreate = maxEffects;    {        Vector3 forward = owner.transform.forward;        float maxAngle = (targetingAngle / 2);        float minAngle = -maxAngle;        float angle = minAngle;        Vector3 pos = owner.transform.position;        pos += (owner.transform.right * projectileCreateOffset.x);        pos += (owner.transform.up * projectileCreateOffset.y);        pos += (owner.transform.forward * projectileCreateOffset.z);        for (int i = 0; i < leftToCreate; i++)        {            angle = Random.Range(minAngle, maxAngle);            Vector3 tl = pos + (Quaternion.AngleAxis(+angle, Vector3.up) * forward * maxFlightDistance);            CreateProjectile(i, waitTime, forward, tl, null, Vector3.zero);            waitTime += projectileCreateDelayBetween;        }    }}
SkillProjectile

每个投射物都会挂上这个脚本,在Update事件里来控制投射物的移动和触发事件。

protected void Update(){    if (GameGlobal.Paused) return;    if (owner == null)    {        if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);        else Destroy(gameObject); // 拥有者无效的时候,则投射失败        return;    }    if (owner.owner.IsDead())    {        if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);        else Destroy(gameObject); // 角色死亡的时候,也投射失败        return;    }    // 跟踪直达目标的时候,目标坐标要一直更新    if (targetTr != null) targetLocation = targetTr.position + followOffset;    // 投射物当前的世界坐标    prevPos = _tr.position;    // 根据速度沿着目的地前进    _tr.position = Vector3.MoveTowards(_tr.position, targetLocation, Time.deltaTime * moveSpeed);    // 调整方向,对准目的地    Vector3 f = (targetLocation - _tr.position).normalized;    if (f != Vector3.zero) _tr.forward = f;    // 构建射线    ray.origin = prevPos;    ray.direction = _tr.position - prevPos;    distance = ray.direction.magnitude;    if (collisionRayWidth > 0.0f)    {        // 球形射线检测目标        if (Physics.SphereCast(ray, collisionRayWidth, out hit, distance, validTargetsLayerMask))        {            if (owner != null)            {                // 还未包含到击中列表                if (false == hitList.Contains(hit.transform.gameObject))                {                    // 尝试击中目标                    if (owner.AttemptHit(hit.transform.gameObject, hit.point, gameObject))                    {                        // 击中即销毁投射物的话                        if (destroyProjectileOnHit)                        {                            if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);                            else Destroy(gameObject);                            return;                        }                        else                        {                            hitList.Add(hit.transform.gameObject);                        }                    }                }            }            else            {    // 拥有者无效的时候销毁投射物                if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);                else Destroy(gameObject);                return;            }        }        // 检查是否碰到了障碍物        if (obstacleCheckMask != 0)        {            if (Physics.SphereCast(ray, collisionRayWidth, out hit, distance, obstacleCheckMask))            {                // 是的话,则投射失败                if (owner != null)                {                    owner.ExecuteFizzleEvent(_tr.position, true, gameObject);                    if (triggerSecondaryOnFizzle) owner.ExecuteSecondary(_tr.position, null, gameObject);                }                if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);                else Destroy(gameObject);                return;            }        }    }    else    {        // 检查射线        if (Physics.Raycast(ray, out hit, distance, validTargetsLayerMask))        {            if (owner != null)            {                if (false == hitList.Contains(hit.transform.gameObject))                {                    if (owner.AttemptHit(hit.transform.gameObject, hit.point, gameObject))                    {                        if (destroyProjectileOnHit)                        {                            if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);                            else Destroy(gameObject);                            return;                        }                        else                        {                            hitList.Add(hit.transform.gameObject);                        }                    }                }            }            else            {                if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);                else Destroy(gameObject);                return;            }        }        if (obstacleCheckMask != 0)        {            if (Physics.Raycast(ray, out hit, distance, obstacleCheckMask))            {                if (owner != null)                {                    owner.ExecuteFizzleEvent(_tr.position, true, gameObject);                    if (triggerSecondaryOnFizzle) owner.ExecuteSecondary(_tr.position, null, gameObject);                }                if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);                else Destroy(gameObject);                return;            }        }    }    // 当已经到达目的地的时候,或者时间已经到了,则投射失败    maxLiveTime -= Time.deltaTime;    if ((targetLocation - _tr.position).sqrMagnitude < 0.01f || maxLiveTime <= 0.0f)    {        if (owner != null)        {            owner.ExecuteFizzleEvent(_tr.position, false, gameObject);            if (triggerSecondaryOnFizzle && triggerSecondaryOnFizzleOnlyIfObstacle == false)            {                owner.ExecuteSecondary(_tr.position, null, gameObject);            }        }        if (Skill.SkillProjectilePool != null) Skill.SkillProjectilePool.Destroy(gameObject);        else Destroy(gameObject);    }}
原创粉丝点击