Unity实现杀戮尖塔出牌效果( 三. 贝塞尔曲线引导箭头绘制,卡牌使用效果制作)

三. 卡牌使用效果,塞贝尔曲线引导箭头绘制

1. 攻击类型卡牌

①拖拽超过一定高度之后卡牌会移动到手牌中心位置
②出现攻击引导箭头 (塞贝尔曲线)
③成功指向目标怪物后打出

2. 技能能力类型卡牌

①可自由拖动
②脱离手牌高度后打出

核心代码展示

这里只展示此效果核心代码内容,重复代码不做赘述,上期(二.鼠标指向卡牌时,卡牌强调动画)完整版传送门

1.绘制贝塞尔曲线引导箭头(此脚本可作为独立通用脚本使用,制作其他贝塞尔曲线也可引用)
在这里插入图片描述
完整版本代码

public class ArrowEffectManager : MonoBehaviour  
{  
    public GameObject reticleBlock;  
    public GameObject reticleArrow;  
    public int MaxCount = 18;  
    public Vector3 startPoint; // 起始点  
    private Vector3 controlPoint1; // 控制点1  
    private Vector3 controlPoint2; // 控制点2  
    private Vector3 endPoint; // 结束点  
    private List<GameObject> ArrowItemList;  
    private List<SpriteRenderer> RendererItemList;  
    private Animator Arrow_anim;  
    private bool isSelect;  
  
    public bool IsSelect  
    {  
        get => isSelect;  
        set  
        {  
            if (isSelect != value)  
            {        
                isSelect = value;  
                if (value==true)  
                {
                      PlayAnim();  
                }         
            }       
         }   
     }  
    private void Awake()  
    {        InitData();  
    }  
    private void InitData()  
    {  
        ArrowItemList = new List<GameObject>();  
        RendererItemList = new List<SpriteRenderer>();  
        for (int i = 0; i < MaxCount; i++)  
        { 
            GameObject Arrow = (i == MaxCount - 1) ? reticleArrow : reticleBlock;  
            GameObject temp = Instantiate(Arrow, this.transform);  
            if (i == MaxCount - 1)  
            {    
                Arrow_anim = temp.GetComponent<Animator>();  
            }  
            ArrowItemList.Add(temp);  
            SpriteRenderer re = temp.transform.GetChild(0).GetComponent<SpriteRenderer>();  
            if (re != null)  
            {    
                  RendererItemList.Add(re);  
            }       
         }    
    }  
    public void Update()  
    {       
        Move();  
        DrawBezierCurve();  
    }  
    private void DrawBezierCurve()  
    {        
        for (int i = 0; i < ArrowItemList.Count; i++)  
        {            
            float t = i / (float) (ArrowItemList.Count - 1); // 参数 t 在 0 到 1 之间  
            Vector3 position =CalculateBezierPoint(t, startPoint, controlPoint1, controlPoint2, endPoint);  
            ArrowItemList[i].gameObject.SetActive(i != ArrowItemList.Count - 2);  
            ArrowItemList[i].transform.position = position;  
            ArrowItemList[i].transform.localScale = Vector3.one * (t / 2f) + Vector3.one * 0.3f;  
            if (i > 0)  
            {                
                float SignedAngle = Vector2.SignedAngle(Vector2.up,  
                ArrowItemList[i].transform.position - ArrowItemList[i - 1].transform.position);  
                Vector3 euler = new Vector3(0, 0, SignedAngle);  
                ArrowItemList[i].transform.rotation = Quaternion.Euler(euler);  
            }        
        }    
    }  
    private void OnDrawGizmos()  
    {        
     // Gizmos.color = Color.yellow;        
     // Gizmos.DrawLine(startPoint, controlPoint1);       
     // Gizmos.DrawSphere(startPoint, 0.1f);        
     // Gizmos.DrawSphere(controlPoint1, 0.1f);        
     // Gizmos.DrawLine(endPoint, controlPoint2);        
     // Gizmos.DrawSphere(endPoint, 0.1f);        
     // Gizmos.DrawSphere(controlPoint2, 0.1f);    
     }  
    public void Move()  
    {        
        Vector3 mousePosition = Input.mousePosition; // 获取鼠标位置  
        mousePosition.z = Camera.main.nearClipPlane; // 设置z坐标为摄像机近裁剪平面的位置  
        Vector3 worldPosition = Camera.main.ScreenToWorldPoint(mousePosition);  
        worldPosition.z = 2f;  
        endPoint = worldPosition;  
  
        controlPoint1 = (Vector2) startPoint + (worldPosition - startPoint) * new Vector2(-0.28f, 0.8f);  
        controlPoint2 = (Vector2) startPoint + (worldPosition - startPoint) * new Vector2(0.12f, 1.4f);  
        controlPoint1.z = startPoint.z;  
        controlPoint2.z = startPoint.z;  
    }  
    /// <summary>  
    /// 设置起始位置  
    /// </summary>  
    public void SetStartPos(Vector3 pos)  
    {        
       startPoint = pos;  
    }  
    /// <summary>  
    /// 设置颜色  
    /// </summary>  
    public void SetColor(Color color)  
    {        
        if (RendererItemList == null)  
        {            
            return;  
        }  
        for (int i = 0; i < RendererItemList.Count; i++)  
        {            
            RendererItemList[i].color = color;  
        }    
        }  
    /// <summary>  
    /// 设置颜色  
    /// </summary>  
    /// <param name="isSelect"></param>    
    public void SetColor(bool isSelect)  
    {        
        IsSelect = isSelect;  
        if (isSelect)  
        {            
           SetColor(Color.red);  
        }        
        else  
        {  
            SetColor(Color.white);  
        }    
        }  
    private void PlayAnim()  
    {        
       if (Arrow_anim == null)  
        {            
           return;  
        }        
        Arrow_anim.SetTrigger("select");  
    }          
/// <summary>  
    /// 获取贝塞尔曲线中间点  
    /// </summary>  
    /// <param name="t">参数 t 在 0 到 1 之间</param>  
    /// <param name="startPoint">起始点</param>  
    /// <param name="controlPoint1">起点控制点</param>  
    /// <param name="controlPoint2">终点控制点</param>  
    /// <param name="endPoint">终点</param>  
    /// <returns></returns>    
    public static Vector3 CalculateBezierPoint(float t, Vector3 startPoint, Vector3 controlPoint1, Vector3 controlPoint2, Vector3 endPoint)  
    {        
        float u = 1 - t;  
        float tt = t * t;  
        float uu = u * u;  
        float uuu = uu * u;  
        float ttt = tt * t;  
  
        Vector3 point = uuu * startPoint; // (1-t)^3 * P0  
        point += 3 * uu * t * controlPoint1; // 3 * (1-t)^2 * t * P1  
        point += 3 * u * tt * controlPoint2; // 3 * (1-t) * t^2 * P2  
        point += ttt * endPoint; // t^3 * P3  
  
        return point;  
    }}

出牌整体效果实现:

1. 选中卡牌拖拽后卡牌本体暂时隐藏(gameObject.SetActive(false))
2. 展示此卡牌的克隆体,克隆体鼠标跟随
3. 非攻击类型卡牌:拖拽中无位置限制,拖拽结束超过基础高度视为打出,出牌成功后隐藏克隆卡牌,销毁卡牌本体,执行使用成功指令。拖拽结束如果没有超过基础高度,隐藏克隆卡牌,卡牌本体隐藏状态解除
4. 攻击类型卡牌:拖拽超过基础高度克隆体位移至手牌居中位置,生成塞贝尔曲线引导箭头,箭头指向目标后,箭头变色,播放选中动画。如果选中目标隐藏克隆卡牌,销毁卡牌本体,执行使用成功指令。否则,隐藏克隆卡牌,卡牌本体隐藏状态解除

在这里插入图片描述

【核心代码片段】

//class CardManager

 /// <summary>
 /// 设置卡牌使用特效
 /// </summary>
 public void CardUseEffect()
 {
     //如果当前没有选中卡牌,隐藏所有效果,并返回
     if (nowTaskItem == null)
     {
         temporaryCard.gameObject.SetActive(false);
         lineEffect.gameObject.SetActive(false);
         return;
     }

     temporaryCard.gameObject.SetActive(true);
     // 获取鼠标位置
     Vector3 mousePosition = Input.mousePosition;
     // 设置z坐标为摄像机近裁剪平面的位置
     mousePosition.z = Camera.main.nearClipPlane;
     //将屏幕上的鼠标位置转换为世界坐标系中的位置。
     Vector3 worldPosition = Camera.main.ScreenToWorldPoint(mousePosition);
     worldPosition.z = 5f;
     Vector3 centPos = new Vector3(0, -2.9f, 4);
     bool isWaitAttack = false;
     if (nowTaskItem.attackType == ECardAttackType.Single)
     {
         if (worldPosition.y > -2.4f)
         {
             //攻击类型卡牌,拖拽超过基础高度标记为等待攻击 
             isWaitAttack = true;
         }
     }

     //如果是等待攻击状态将克隆卡牌移动至centPos否则跟随鼠标移动
     temporaryCard.gameObject.transform.position = Vector3.Lerp(temporaryCard.gameObject.transform.position,
         isWaitAttack ? centPos : worldPosition, Time.deltaTime * 15);
     //设置塞贝尔曲线起始点
     lineEffect.SetStartPos(isWaitAttack ? centPos : worldPosition);
     //设置攻击引导箭头颜色
     lineEffect.SetColor(nowSelectPlayer != null);
     //攻击引导箭头显示隐藏控制
     lineEffect.gameObject.SetActive(isWaitAttack);
 }

新增枚举

public enum ECardAttackType
{
    Skill,
    Single,
    Power
}

CardManager 当前阶段完整版本代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CardManager : MonoBehaviour
{
    /// <summary>
    /// 卡牌起始位置
    /// </summary>
    public Vector3 rootPos = new Vector3(0, -33.5f, 20);

    /// <summary>
    /// 卡牌对象
    /// </summary>
    public GameObject cardItem;

    /// <summary>
    /// 扇形半径
    /// </summary>
    public float size = 30f;

    /// <summary>
    /// 卡牌出现最大位置
    /// </summary>
    private float minPos = 1.415f;

    /// <summary>
    /// 卡牌出现最小位置
    /// </summary>
    private float maxPos = 1.73f;

    /// <summary>
    /// 手牌列表
    /// </summary>
    private List<CardItem> cardList;

    /// <summary>
    /// 偶数手牌位置列表
    /// </summary>
    private List<float> rotPos_EvenNumber;
    /// <summary>
    /// 奇数手牌位置列表
    /// </summary>
    private List<float> rotPos_OddNumber;

    /// <summary>
    /// 最大手牌数量
    /// </summary>
    private int CardMaxCount = 8;

    private CardItem nowSelectItem;

    /// <summary>
    /// 当前鼠标指向的卡牌
    /// </summary>
    public CardItem NowSelectItem
    {
        get => nowSelectItem;
        set
        {
            if (nowSelectItem != value)
            {
                nowSelectItem = value;
                RefreshSelectItem(nowSelectItem);
            }
        }
    }

    public GameObject temporaryCard;

    /// <summary>
    /// 当前点击选中的卡牌
    /// </summary>
    private CardItem nowTaskItem;

    public ArrowEffectManager lineEffect;

    /// <summary>
    /// 当前选中敌人
    /// </summary>
    private GameObject nowSelectPlayer;

    private Vector3 temporaryCardStartPos;


    void Start()
    {
        InitCard();
    }

    /// <summary>
    /// 数据初始化
    /// </summary>
    public void InitCard()
    {
        int EvenNumber = CardMaxCount % 2 == 0 ? CardMaxCount : CardMaxCount - 1;
        int OddNumber = CardMaxCount % 2 == 0 ? CardMaxCount-1 : CardMaxCount;
        rotPos_EvenNumber=InitRotPos(EvenNumber);
        rotPos_OddNumber=InitRotPos(OddNumber);
    }

    /// <summary>
    /// 初始化位置
    /// </summary>
    /// <param name="count"></param>
    /// <param name="interval"></param>
    /// <returns></returns>
    public List<float> InitRotPos(int count)
    {
        List<float> rotPos = new List<float>();
        float interval = (maxPos - minPos) / count;
        for (int i = 0; i < count; i++)
        {
            float nowPos = maxPos - interval * i;
            rotPos.Add(nowPos);
        }

        return rotPos;
    }

    // Update is called once per frame
    void Update()
    {
        TaskItemDetection();
        RefereshCard();
        SelectItemDetection();
        CardUseEffect();
    }

    /// <summary>
    /// 添加卡牌
    /// </summary>
    public void AddCard()
    {
        if (cardList == null)
        {
            cardList = new List<CardItem>();
        }

        if (cardList.Count >= CardMaxCount)
        {
            Debug.Log("手牌数量上限");
            return;
        }

        GameObject item = Instantiate(cardItem, this.transform);
        CardItem text = item.GetComponent<CardItem>();
        text.RefreshData(rootPos, 0, 0, 0);
        cardList.Add(text);
    }

    /// <summary>
    /// 手牌状态刷新
    /// </summary>
    public void RefereshCard()
    {
        if (cardList == null)
        {
            return;
        }

        int TaskIndex = 0;
        //得到当前选中的卡牌下标
        bool isTaskIndex = GetTaskIndex(out TaskIndex);

        List<float> rotPos;
        int strtCount = 0;
        float interval;
        if (cardList.Count%2==0)
        {
            rotPos = rotPos_EvenNumber;
            strtCount= rotPos_EvenNumber.Count / 2 - cardList.Count / 2;
        }
        else
        {
            rotPos = rotPos_OddNumber;
            strtCount= (rotPos_OddNumber.Count+1) / 2 - (cardList.Count+1) / 2;
        }
        for (int i = 0; i < cardList.Count; i++)
        {
            float shifting = 0;
            float indexNowNumber = 0.0042f;
            float Difference = TaskIndex - i;
            float absDifference = Difference > 0 ? 4 - Difference : 4 + Difference;
            if (absDifference < 0)
            {
                absDifference = 0;
            }

            if (isTaskIndex && TaskIndex != i)
            {
                shifting = (TaskIndex > i) ? indexNowNumber * absDifference : -indexNowNumber * absDifference;
            }

            cardList[i].RefreshData(rootPos, rotPos[strtCount+i] + shifting, size, i);
        }
    }

    /// <summary>
    /// 销毁卡牌
    /// </summary>
    public void RemoveCard()
    {
        if (cardList == null)
        {
            return;
        }

        CardItem item = cardList[cardList.Count - 1];
        cardList.Remove(item);
        Destroy(item.gameObject);
    }

    /// <summary>
    /// 销毁卡牌
    /// </summary>
    /// <param name="item"></param>
    public void RemoveCard(CardItem item)
    {
        if (cardList == null)
        {
            return;
        }

        cardList.Remove(item);
        Destroy(item.gameObject);
    }

    private Vector3 oldmousePosition;


    /// <summary>
    /// 玩家操作检测
    /// </summary>
    public void TaskItemDetection()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            AddCard();
        }

        if (Input.GetMouseButtonDown(0))
        {
            if (nowTaskItem != null)
            {
                nowTaskItem.gameObject.SetActive(true);
                nowTaskItem = null;
            }

            temporaryCardStartPos = temporaryCard.transform.position;
            SelectCard();
        }

        if (Input.GetMouseButton(0))
        {
            SelectEnemy();
        }

        if (Input.GetMouseButtonUp(0))
        {
            if (nowTaskItem != null)
            {
                if (IsDestoryCard())
                {
                    RemoveCard(nowTaskItem);
                }
                else
                {
                    nowTaskItem.gameObject.SetActive(true);
                    nowTaskItem = null;
                }

                nowSelectPlayer = null;
            }
        }
    }

    /// <summary>
    /// 是否需要销毁卡牌
    /// </summary>
    /// <returns></returns>
    public bool IsDestoryCard()
    {
        if (nowSelectPlayer != null)
        {
            return true;
        }

        float dis = temporaryCardStartPos.y - temporaryCard.transform.position.y;
        float absDis = dis > 0 ? dis : -dis;
        return absDis > 2.6f;
    }


    /// <summary>
    /// 选中卡牌检测
    /// </summary>
    public void SelectItemDetection()
    {
        if (oldmousePosition == Input.mousePosition)
        {
            return;
        }

        oldmousePosition = Input.mousePosition;
        // 从鼠标位置创建一条射线
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        LayerMask layerMask = LayerMask.GetMask("Card");
        // 检测射线是否与物体相交
        if (Physics.Raycast(ray, out hit, 1000, layerMask))
        {
            if (hit.collider.gameObject != null)
            {
                NowSelectItem = hit.collider.gameObject.GetComponent<CardItem>();

                return;
            }
        }

        NowSelectItem = null;
    }

    /// <summary>
    /// 刷新当前选中的卡牌
    /// </summary>
    /// <param name="selectItem"></param>
    public void RefreshSelectItem(CardItem selectItem)
    {
        if (cardList == null)
        {
            return;
        }

        for (int i = 0; i < cardList.Count; i++)
        {
            cardList[i].isSelect = cardList[i] == selectItem;
            if (cardList[i] == selectItem)
            {
                temporaryCard.gameObject.transform.position = cardList[i].gameObject.transform.position;
            }
        }
    }

    /// <summary>
    /// 得到当前选中的卡牌下标
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public bool GetTaskIndex(out int index)
    {
        index = 0;
        for (int i = 0; i < cardList.Count; i++)
        {
            if (cardList[i].isSelect)
            {
                index = i;
                return true;
            }
        }

        return false;
    }


    /// <summary>
    /// 选中卡牌
    /// </summary>
    public void SelectCard()
    {
        // 从鼠标位置创建一条射线
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        LayerMask layerMask = LayerMask.GetMask("Card");
        // 检测射线是否与物体相交
        if (Physics.Raycast(ray, out hit, 1000, layerMask))
        {
            if (hit.collider.gameObject != null)
            {
                nowTaskItem = hit.collider.gameObject.GetComponent<CardItem>();
                nowTaskItem.gameObject.SetActive(false);
            }
        }
    }

    /// <summary>
    /// 获取当前选中的对象
    /// </summary>
    /// <param name="layerName"></param>
    /// <returns></returns>
    public GameObject GetSelectPlayer(string layerName)
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        LayerMask layerMask = LayerMask.GetMask(layerName);
        // 检测射线是否与物体相交
        if (Physics.Raycast(ray, out hit, 1000, layerMask))
        {
            if (hit.collider.gameObject != null)
            {
                return hit.collider.gameObject;
            }
        }

        return null;
    }

    /// <summary>
    /// 选中对象
    /// </summary>
    public void SelectEnemy()
    {
        if (nowTaskItem == null)
        {
            nowSelectPlayer = null;
            return;
        }


        ECardAttackType etype = nowTaskItem.attackType;
        switch (etype)
        {
            case ECardAttackType.Power:
                break;
            case ECardAttackType.Single:
                nowSelectPlayer = GetSelectPlayer("Enemy");
                break;
            case ECardAttackType.Skill:
                break;
        }
    }

    /// <summary>
    /// 设置卡牌使用特效
    /// </summary>
    public void CardUseEffect()
    {
        //如果当前没有选中卡牌,隐藏所有效果,并返回
        if (nowTaskItem == null)
        {
            temporaryCard.gameObject.SetActive(false);
            lineEffect.gameObject.SetActive(false);
            return;
        }

        temporaryCard.gameObject.SetActive(true);
        // 获取鼠标位置
        Vector3 mousePosition = Input.mousePosition;
        // 设置z坐标为摄像机近裁剪平面的位置
        mousePosition.z = Camera.main.nearClipPlane;
        //将屏幕上的鼠标位置转换为世界坐标系中的位置。
        Vector3 worldPosition = Camera.main.ScreenToWorldPoint(mousePosition);
        worldPosition.z = 5f;
        Vector3 centPos = new Vector3(0, -2.9f, 4);
        bool isWaitAttack = false;
        if (nowTaskItem.attackType == ECardAttackType.Single)
        {
            if (worldPosition.y > -2.4f)
            {
                //攻击类型卡牌,拖拽超过基础高度标记为等待攻击 
                isWaitAttack = true;
            }
        }

        //如果是等待攻击状态将克隆卡牌移动至centPos否则跟随鼠标移动
        temporaryCard.gameObject.transform.position = Vector3.Lerp(temporaryCard.gameObject.transform.position,
            isWaitAttack ? centPos : worldPosition, Time.deltaTime * 15);
        //设置塞贝尔曲线起始点
        lineEffect.SetStartPos(isWaitAttack ? centPos : worldPosition);
        //设置攻击引导箭头颜色
        lineEffect.SetColor(nowSelectPlayer != null);
        //攻击引导箭头显示隐藏控制
        lineEffect.gameObject.SetActive(isWaitAttack);
    }
}

附件箭头素材
在这里插入图片描述
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值