U3D 案例练习

  UGUI 实现摇杆控制3D世界物体移动

实现效果:使用UGUI实现类似于 王者荣耀,或其他的RPG类型的游戏中的屏幕摇杆功能

step1:搭建UI界面,摇杆的样式,一大一小两个圆盘的图案,将小圆设置为大圆的子物体。将摇杆放置于屏幕左下角。如图:

    

step2:实现小圆在大圆上拖拽,并获得小圆相对于初始位置拖拽的方向,脚本实现
由于在这里要将小圆被拖拽移动的数据传递出去,给3D世界的游戏物体Cube,所以可将摇杆的脚本写成单例脚本,既方便传值,又保证数据不会发生冲突。在由于要不停的在两个脚本之间传值,使用委托

//定义委托,用来将摇杆的Image的移动方向传递给对应的Cube
public delegate void YGImagechangeDirectionDelegate (Vector3 dir);
//声明委托对象
public YGImagechangeDirectionDelegate ygImgDele;

//定义记录摇杆初始位置的字段
Vector3 startPos;
//移动的方向,摇杆产生的数据
Vector3 moveDir;
//判断当前是否有拖拽
bool isDrag = false;

//单例脚本
public static YGImageScript Instance;
void Awake ()
{
Instance = this;
}

void Update ()
{ 由于摇杆的值在实时的发生变化,实时传值,方法在Update内实现
if (isDrag) {
//调用委托实现摇杆数据传递给Cube移动
ygImgDele (moveDir);
}
}

//开始拖拽
public void BeginDragAction (BaseEventData sender)
{当鼠标开始拖拽时,记下摇杆的初始位置;并将isDrag设为True
isDrag = true;
startPos = transform.position;
}
//拖拽中
public void DragAction (BaseEventData sender)
{当用户拖拽摇杆的时候,要想实现摇杆的位置随着鼠标点的拖拽移动,并且不超出背景图片(大圆),要首先判断鼠标点位置和摇杆起始位置(即同心圆圆心)之间的距离,保证摇杆不会超出背景范围
if (Vector3.Distance (startPos, Input.mousePosition) < 50f) {
transform.position = Input.mousePosition;
} else {
//当鼠标拖出大圆时,获取鼠标和初始位置之间的方向,根据方向移动小球位置
Vector3 dir = (Input.mousePosition - startPos).normalized;
得到从起始位置到鼠标点位置方向的一条标准化向量
transform.position = dir * 50f + startPos;
}
//将摇杆移动的方向传递出去
moveDir = (Input.mousePosition - startPos).normalized;
}

//结束拖拽
public void EndDragAction (BaseEventData sender)
{结束拖拽将摇杆归回起始位置
transform.position = startPos;
isDrag = false;
}

  

step3:Cube接收摇杆的传值,运动

float hor, ver = 0;
//实现让Cube移动的方法
void Start ()
{
将Cube移动的方法传递给委托对象绑定起来
当摇杆移动产生方向时,调用委托对象时就会执行让Cube移动的方法
YGImageScript.Instance.ygImgDele += MoveToPosition;
}
void MoveToPosition (Vector3 pos)
{
hor = pos.x; ver = pos.y;
Vector3 dir = new Vector3 (hor, 0, ver);
transform.position += dir * Time.deltaTime * 5f;
transform.forward = dir;
}


UGUI实现背包系统
实现效果:在背包中拖拽背包内的物体,拖入空的背包格子内放进去,拖入已有物品的格子内两个物品交换位置。

实现上述效果,要实现当鼠标拖拽图片时,图片随着鼠标点移动,放下时设置新的父物体,如果是交换位置,要和交换的物品交换父物体

public void OnBeginDrag (PointerEventData eventData)
{
//1.记录原始父物体
originParent = transform.parent;
//2.设置当前Item拖拽的临时父物体
transform.SetParent (tempParent);
//3.关闭当前Item身上的射线检测,因为要检测的是Item下方的UI控件,来决定当前Item落在哪一个格子里
GetComponent<Image> ().raycastTarget = false;
}
public void OnDrag (PointerEventData eventData)
{
//让图片跟随鼠标点移动
transform.position = Input.mousePosition;
}
public void OnEndDrag (PointerEventData eventData)
{
//拖拽结束
if (eventData.pointerEnter == null) {
transform.SetParent (originParent);
transform.localPosition = Vector3.zero;
} else if (eventData.pointerEnter.tag == "Cell" && eventData.pointerEnter.transform.childCount == 0) {
//表示当前Item落在了空的装备栏上,直接将该装备栏设置为装备的父物体
transform.SetParent (eventData.pointerEnter.transform);
//保证当前拖拽的装备图片和装备栏对齐
transform.localPosition = Vector3.zero;
} else if (eventData.pointerEnter.tag == "Item") {
//如果当前装备落在的已经有装备的装备栏上,实现当前装备和将要落下的装备交换装备栏
transform.SetParent (eventData.pointerEnter.transform.parent);
eventData.pointerEnter.transform.SetParent (originParent);
transform.localPosition = Vector3.zero;
eventData.pointerEnter.transform.localPosition = Vector3.zero;
} else {
transform.SetParent (originParent);
transform.localPosition = Vector3.zero;
}
//打开事件监测
GetComponent<Image> ().raycastTarget = true;
}
#endregion

摄像机跟随
为摄像机设定一个相对于目标物体的固定位置,让摄像机始终位于目标之外的某一个物体
//表示摄像机应该距离物体的水平距离
public float distanceAway = 5f;
//表示摄像机应该距离物体的垂直距离
public float distanceUp = 3f;
//摄像机应该在的目标位置
Vector3 targetPosition;
//摄像机要跟随的目标位置
Transform followTarget;
//摄像机移动的平滑系数
public float smooth = 1f;
void Start ()
{
followTarget = GameObject.Find ("Player").transform;
}
void Update ()
{
//得到摄像机要一定到的目标位置
targetPosition = followTarget.position + Vector3.up * distanceUp - followTarget.forward * distanceAway;
//设置摄像机的位置到这里
transform.position = Vector3.Lerp (transform.position, targetPosition, Time.deltaTime * smooth);
//相机始终看向目标物体
transform.LookAt (followTarget);
}

小地图
1.在层级视图中新建一个相机将相机设为艾希的子物体
2.在Project中创建一个Render Texture,拖入新建的摄像机对应的Render Texture中
3.在场景中创建一个Raw Image将Render Texture拖给它

4.创建一个Image,将RawImage拖成Image的子物体,为Image添加不同形状的材质,可改变地图显示的形状

UGUI实现关卡选择
实现效果:每一关卡都会点亮不同数量的星星,当最高关卡通关后会自动解锁下一关卡;使用鼠标点击任意关卡都能选中该关卡,选中效果是除了这一个关卡的红线框被点亮,其余的所有关卡的红线框都灭掉;每次通关场景切换回来后,都可以继续之前的关卡通关。

 

在场景的切换的时候,每次加载一个新的场景前一个场景的所有游戏物体都会被释放掉。为了实现在场景与场景之间传值,可使用单例类来存储每次通关后的数据,以便于切换场景后重新加载关卡数据。
step1:先写实现每一关卡中的功能方法
//1.设置星星的数量
    public void SetStarNumber (int num)
    {
        //a.隐藏锁
        locked.gameObject.SetActive (false);
        //b.显示星星背景
        stars.gameObject.SetActive (true);
        //c.设置星星的数量(注意先后顺序)
        GameObject child0 = stars.transform.GetChild (0).gameObject;
        GameObject child1 = stars.transform.GetChild (2).gameObject;
        GameObject child2 = stars.transform.GetChild (1).gameObject;
        //将这三个子物体存储在数组中
        GameObject[] childs = { child0, child1, child2 };
        //将三颗星星初始化为隐藏
        for (int i = 0; i < childs.Length; i++) {
            childs [i].SetActive (false);
        }
        //根据传进来数值显示星星
        for (int i = 0; i < num; i++) {
            childs [i].SetActive (true);
        }
    }
    //2.红线的关闭和开启
    public void SetRedLineImage (bool flag)
    {
        redLineImage.gameObject.SetActive (flag);
    }
    //3.每一个关卡上的数字
    public void SetPassNumText (int num)
    {
        passNum.text = num.ToString ();
    }
    //4.获取当前正在游戏的关卡数
    public int GetCurrentPassnum ()
    {
        return int.Parse (passNum.text);
    }
    //5.显示锁,隐藏星星,关闭红线,关闭星星背景
    public void SetLockedImg ()
    {
        //隐藏星星
        SetStarNumber (0);
        //显示锁
        locked.gameObject.SetActive (true);
        //关闭红线
        SetRedLineImage (false);
        //关闭背景
        stars.gameObject.SetActive (false);
    }
    //6.开启新关卡
    public void SetNewCellBtn ()
    {
        //没有星星等级
        SetStarNumber (0);
        // 显示红线
        SetRedLineImage (true);
        //修改当前关卡是谁
        LevelManger.Instance.curPlayingLevel = GetCurrentPassnum ();
    }
    //7.选中一个关卡时,关闭其他红线,显示自己的红线
    public void SelectCellBtnAction ()
    {
        //如果没有关卡被选中
        if (locked.gameObject.activeSelf) {
            return;
        }
        //通过遍历某一个Button的父物体,将所有的子物体的红线隐藏
        foreach (Transform cellBtn in transform.parent.GetComponentInChildren<Transform>()) {
            cellBtn.gameObject.GetComponent<CellButtonScripts> ().SetRedLineImage (false);
        }
        //将自己的红线打开
        SetRedLineImage (true);
        //修改当前关卡是谁
        LevelManger.Instance.curPlayingLevel = GetCurrentPassnum ();
    }

step2 :通过代码控制在场景中创建关卡,并初始化。由于每一次加载场景,都会更新创建。所以在创建关卡时要获取到存储在单例类中关卡信息。
//关卡按钮的预制体
    public GameObject cellButtonPrefab;
    //用一个cell产生之后的父物体
    public Transform content;
    //设置游戏的总关卡数
    public int totalLevelNum = 12;

    void Start ()
    {
        for (int i = 1; i <= totalLevelNum; i++) {
            //获取到创建的游戏物体
            GameObject cellBtn = Instantiate (cellButtonPrefab);
            cellBtn.transform.SetParent (content);
            //设置关卡数
            cellBtn.GetComponent<CellButtonScripts> ().SetPassNumText (i);
            //设置没有开启的关卡
            cellBtn.GetComponent<CellButtonScripts> ().SetLockedImg ();
            //如果单例中包含当前关卡的数据,赋值
            if (LevelManger.Instance.scoreInfoDic.ContainsKey (i)) {
                //从单例类中去除对应关卡数据
                int num = LevelManger.Instance.scoreInfoDic [i];
                //设置给cellBtn
                cellBtn.GetComponent<CellButtonScripts> ().SetStarNumber (num);
            }
            //判断当前关卡是否是最高关卡,如果是,就解锁下一关
            if (LevelManger.Instance.maxPassLevel == i) {
                //解锁关卡
                cellBtn.GetComponent<CellButtonScripts> ().SetNewCellBtn ();
            }
        }
    }
    //点击开始游戏按钮的响应事件
    public void StartGameAction ()
    {
        //加载修改数据的场景
        SceneManager.LoadScene (1);
    }

step3:创建单例类保存每次重新加载的关卡数据,并实现两个场景之间的传值
单例类不需要继承于MonoBehaviour类,没有脚本的声明周期,不用挂载在游戏物体上。所以不用担心每次重新加载场景是被释放掉。
public class LevelManger
{
    //定义字典来存储每一管对应的星星数量
    public Dictionary<int,int> scoreInfoDic;
    //当前玩的是哪一关卡
    public int curPlayingLevel;
    //检测当前完了多少关
    public int maxPassLevel = 3;
a.私有化构造函数,使该类不能在外部被实例化
    private LevelManger ()
    {
        //初始化存储数据的字典
        scoreInfoDic = new Dictionary<int, int> ();
        scoreInfoDic [1] = 2;
        scoreInfoDic [2] = 3;

    }
b.私有化一个静态单例类的对象
    private static LevelManger level = null;
c.在一个公开的静态属性中实现单例类的初始化
    public static LevelManger Instance {
        get {
            if (level == null) {
                level = new LevelManger ();
            }
            return level;
        }
    }

step4:数据修改场景,完成关卡数据的修改,并返回上一场景
//分数的输入框
    public InputField scoreInputField;
    //实现返回上个场景的方法
    public void ReturnUISceneAction ()
    {
        //拿到当前正在玩的关卡
        int level = LevelManger.Instance.curPlayingLevel;
        int score = int.Parse (scoreInputField.text);
        if (score <= 0 || score > 3) {
            Debug.Log ("输入有误!");
            return;
        }

        //修改当前关卡对应的星星
        LevelManger.Instance.scoreInfoDic [level] = score;
        // 如果修改的关卡是当前存储的最大关卡,那么要将单例中最大关卡数加1
        if (LevelManger.Instance.maxPassLevel == level) {
            LevelManger.Instance.maxPassLevel++;
        }
        //返回之前的场景
        SceneManager.LoadScene (0);
    }

Application类:
//返回程序的数据文件所在的文件夹的相对路径,(只读)
        //Debug.Log (Application.dataPath);
        //返回当前程序运行的平台
        //Debug.Log (Application.platform.ToString ());
        //判断当前程序是否支持在后台运行
        //Debug.Log (Application.runInBackground.ToString ());
        //获取当前场景的索引值
        //Debug.Log (Application.loadedLevel.ToString ());
        //Debug.Log (Application.loadedLevelName.ToString ());
        Debug.Log (SceneManager.GetActiveScene ().buildIndex);
        Debug.Log (SceneManager.GetActiveScene ().name);
        Debug.Log (SceneManager.sceneCountInBuildSettings + "");
原文地址:https://www.cnblogs.com/zpy1993-09/p/11750322.html