Unity学习记录:制作双屏垃圾分类小游戏

一:逻辑梳理

要实现的功能

游戏操作

在规定时间内,垃圾通过拖拽进入正确垃圾桶的容器,垃圾在这里消失,飞入第二个屏上对应垃圾桶的位置并实现加分和加时间的效果,垃圾拖拽进入不正确的垃圾桶,垃圾会返回到原来的位置,同时,相应的时间也会减少

胜利和失败的条件:

胜利:垃圾全部分类完毕,并且时间不为零,游戏胜利,

失败:时间为0,垃圾未分类结束,游戏失败

游戏结束后,可以点击屏幕可以重新进入游戏,重新进入游戏时,垃圾的排列会发生变化

游戏代码梳理

需要一个每个垃圾自己的类,涉及垃圾自己的属性,都放在这个类里面,例如:垃圾的拖拽,整个游戏中,只有垃圾需要拖拽的功能,这个是只属于垃圾的功能,就放在这个类里面

每个垃圾桶自己的类,涉及垃圾桶的属性,都放在这里面,例如:垃圾进入和离开垃圾桶的碰撞器

垃圾和垃圾桶之间交互的管理类,例如:垃圾和垃圾桶之间分类正确加分,错误减分,垃圾的布局

游戏胜利和失败等都是在这个类中

双屏显示的类

二:效果展示

三:UI布局和代码书写

1.双屏的设置

新建两个canvas,分别命名:MainCanvas和ViceCanvas,新建一个摄像机,名为:ViceCamera

设置MainCamera和MainCanvas属性,如图所示

 ViceCamera和ViceCanvas的Target Display设置为DisPlay2,把ViceCamera上面的Audio Listence删掉,一个场景中只需要一个Audio Listence

新建一个C#脚本,名为:SplitScreen,编写代码

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

/// <summary>
/// 分屏代码
/// </summary>
public class SplitScreen : MonoBehaviour
{
    void Start()
    {
        if (Display.displays.Length > 1)
            Display.displays[1].Activate();
        if (Display.displays.Length > 2)
            Display.displays[2].Activate();
    }
}

二:垃圾的设置

在MainCanvas下面新建一个空物体:GarbagePanel ,铺满全屏,管理类的代码放在这个上面,一直都显示

在GarbagePanel下面新建一个空物体:root,铺满全屏,作为垃圾分类主界面的容器,所有的图片文字视频都放在这里面

在root下

新建图片:bg,铺满全屏,把背景图片拖进去,

新建空物体:garbageItemContent,铺满全屏,用来存放所有垃圾,把所有的垃圾都拖进来,作为他的子物体,每个垃圾都添加BoxCollide2Dr和Rigibody2D,手动调节碰撞盒的大小,(这里设置的是100*100),将Rigibody2D的重力值大小设为0(防止垃圾往下坠落)设置垃圾的标签,不同的垃圾设置不同的标签recycle,dry,wet,hamful

 新建C#脚本:GarbageItem,运用鼠标拖拽的三个接口实现垃圾拖拽的操作

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

public class GarbageItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    Vector2 offest;         //鼠标的偏移量
    Vector2 startPos;       //垃圾初始位置
    Vector2 oldPos;         //鼠标拖拽结束之后的位置

    RectTransform rect;     //垃圾的RectTransform(当垃圾消失(垃圾的setactive为false)时,垃圾的RectTransform为空)

    //组件初始化
    void InitCompoment()
    {
        if (rect==null)
        {
            rect = GetComponent<RectTransform>();
        }
    }

    //数据初始化
    void InitData(Vector2 pos)
    {
        //组件初始化
        InitCompoment();

        //位置初始化
        startPos = pos;
        RestoreOriginPos();

        //TODO:与垃圾桶的关系

    }

    //位置修改
    void RestoreOriginPos()
    {
        //获取初始位置
        startPos = rect.anchoredPosition;
    }

    //鼠标开始拖拽
    public void OnBeginDrag(PointerEventData eventData)
    {
        //拖拽过程中的偏移量
        offest = eventData.position - (Vector2)transform.position;
    }

    //鼠标拖拽过程
    public void OnDrag(PointerEventData eventData)
    {
        //垃圾的位置限制,不能拖出屏幕外
        Vector2 pos = eventData.position - offest;
        transform.position = new Vector3(Mathf.Clamp(pos.x, 0, Screen.width), Mathf.Clamp(pos.y, 0, Screen.width), 0);

        //拖拽完毕后的位置
        oldPos = eventData.position - offest;
    }

    //拖拽结束
    public void OnEndDrag(PointerEventData eventData)
    {
        //TODO:完成加分,加时间等操作,需要与垃圾桶,和管理者交互
    }
}

 

 但是,我们只是做到了垃圾的拖拽,但是与垃圾桶的交互和游戏的逻辑制作,后面会做这些工作

三:垃圾桶的设置

新建一个空物体:TrashCanRoot,放在屏幕上方,调整大小

在TrashCanRoot的下面新建四个图片,代表四种类型的垃圾桶,每个垃圾筒上都添加BoxCollider2D和Rigidbody2D组件,在BoxCollider2D中勾选IsTrigger,设置Rigidbody2D的重力值为0,并为每个垃圾桶添加标签,如图所示

 新建C#脚本:Trash

在GarbageItem中添加垃圾桶的引用,并实现封装

在数据初始化中添加:Trash = null;

在鼠标拖拽结束的代码中,添加

 编辑Trash脚本

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

/// <summary>
/// 垃圾桶
/// </summary>
public class Trash : MonoBehaviour
{
    #region   垃圾进入垃圾桶的碰撞器
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.transform.GetComponent<GarbageItem>() != null)
            collision.transform.GetComponent<GarbageItem>().Trash = this;
    }
    #endregion

    #region 物体离开垃圾桶的碰撞器
    private void OnTriggerExit2D(Collider2D collision)
    {
        //离开后设置垃圾桶为空
        if (collision.transform.GetComponent<GarbageItem>() != null)
            collision.transform.GetComponent<GarbageItem>().Trash = null;
    }
    #endregion

}

副屏上面的界面

四:游戏管理者

新建四个空物体,挂载Audio Source组件,把对应的音效文件拖入Clip中,如图

编写脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using UnityEngine.Audio;
using Interface;
using DG.Tweening;

/// <summary>
/// 垃圾分类面板脚本
/// </summary>
public class GarbagePanel : MonoBehaviour, BubbleContent
{
    public bool isGameOver;  //游戏是否结束 true结束 false未结束
    int createNumber = 0;    //随机次数 为了创建垃圾item的不同位置

    int sumScore = 0;        //总分数(由垃圾item容器子物体数量决定)
    int timer = 300;         //时间

    public int score = 0;           //分数

    GarbageItem currentItem; //当前正在拖拽的垃圾item

    Tweener addTimerTweener; //加减时间动画

    [Header("游戏最大持续时间")]
    public int maxTimer = 180;

    [Header("退出按钮")]
    [SerializeField] Button quitBtn;

    [Header("正确、错误、游戏胜利、游戏失败音效")]
    [SerializeField] AudioSource correntAudio;
    [SerializeField] AudioSource errorAudio;
    [SerializeField] AudioSource victoryAudio;
    [SerializeField] AudioSource defeatedAudio;

    [Header("游戏胜利/失败Text,2屏时间、分数、加时间Text,点击屏幕刷新游戏按钮")]
    [SerializeField] Text gameOverTxt;
    public Text timeTxt;
    public Text scoreTxt;
    [SerializeField] Button refreshBtn;

    [Header("2屏加时间Text,加减分颜色")]
    [SerializeField] Color addColor;
    [SerializeField] Color subtractColor;
    [SerializeField] Text addTimerTxt;

    [Header("加分星星动画预设,星星容器")]
    [SerializeField] Transform starPrefab;
    [SerializeField] Transform starContent;
    List<Transform> starPool; //星星对象池
    List<Transform> moveStarList; //正在播放的星星特效

    [Header("2屏的垃圾桶位置")]
    public Transform twoSceneTrashPos;

    [Header("1屏 所有垃圾item存放容器")]
    public Transform garbageItemContent;

    [Header("垃圾分类root,2屏垃圾分类界面")]
    [SerializeField] GameObject root;
    [SerializeField] GameObject twoSceneRoot;

    List<GarbageItem> moveGarbageList;                  //正在dotween移动的垃圾item
    List<Vector2> garbageItemPosRotList;                //垃圾item的所有位置
    Dictionary<string, Transform> twoSceneTrashPosDic;  //2屏的垃圾桶位置字典
    public Dictionary<string, Transform> TwoSceneTrashPosDic { get => twoSceneTrashPosDic; set => twoSceneTrashPosDic = value; } //封装 GarbageItem调用
    public List<GarbageItem> MoveGarbageList { get => moveGarbageList; }
    public GarbageItem CurrentItem { get => currentItem; set => currentItem = value; }

    //单例
    static GarbagePanel instance;
    public static GarbagePanel GetInstance()
    {
        return instance;
    }
    void Awake()
    {
        instance = this;
    }

    void Start()
    {
        starPool = new List<Transform>();
        moveStarList = new List<Transform>();
        moveGarbageList = new List<GarbageItem>();

        //DoTween动画
        addTimerTweener = addTimerTxt.DOFade(0, 2f);
        addTimerTweener.SetAutoKill(false);
        addTimerTweener.Pause();

        //默认颜色透明
        addTimerTxt.color = new Color32(0, 0, 0, 0);

        //初始化所有垃圾的位置
        garbageItemPosRotList = new List<Vector2>();
        foreach (Transform item in garbageItemContent)
        {
            garbageItemPosRotList.Add(item.GetComponent<RectTransform>().anchoredPosition);
        }

        //2垃圾桶位置字典初始化
        twoSceneTrashPosDic = new Dictionary<string, Transform>();
        foreach (Transform item in twoSceneTrashPos)
        {
            twoSceneTrashPosDic.Add(item.tag, item);
        }

        //按钮监听
        quitBtn.onClick.AddListener(ReturnMainPanel);
        refreshBtn.onClick.AddListener(RefreshGame);
    }



    #region 游戏刷新
    IEnumerator timerIE; //时间倒数
    void RefreshGame()
    {
        //游戏未结束
        isGameOver = false;

        //不为空 先停止协程,然后在开启,防止冲突
        if (timerIE != null)
            StopCoroutine(timerIE);

        //音效停止
        StopAllAudio();

        //隐藏刷新,游戏结束提示按钮
        refreshBtn.gameObject.SetActive(false);
        gameOverTxt.gameObject.SetActive(false);

        //回收所有加分星星
        RecycleAllStar();

        //刷新垃圾item布局
        RefreshGarbage();

        //总分数、时间、分数 初始化
        sumScore = garbageItemContent.childCount;
        score = 0;
        timer = maxTimer;
        scoreTxt.text = "分数:" + score.ToString();
        timeTxt.text = "时间:" + timer.ToString();

        //开启协程
        timerIE = GameTime();
        StartCoroutine(timerIE);

    }
    #endregion

    #region 刷新垃圾item布局
    public void RefreshGarbage()
    {
        int count = garbageItemContent.childCount;

        //创建次数超过垃圾最大数量时为0
        if (createNumber > count - 1)
            createNumber = 0;

        for (int i = 0; i < count; i++)
        {
            int index = i + createNumber;

            //当下标超出最后一位时为0
            if (index > count - 1)
                index = Mathf.Abs(count - index);

            //位置初始化
            garbageItemContent.GetChild(i).GetComponent<GarbageItem>().InitData(garbageItemPosRotList[index]);
        }

        //创建数量加1
        createNumber++;
    }

    #endregion

    #region 游戏时间
    IEnumerator GameTime()
    {
        while (timer > 0)
        {
            yield return new WaitForSeconds(1f);

            //当游戏时间结束前,游戏胜利可以将timer等于-1
            timer--;
            if (timer<=0)
                timer = 0;

            timeTxt.text = "时间:" + timer;

        }

        //停止所有正在移动的垃圾item和星星动画
        StopAllGarbageItemAndStar();

        //游戏结束判断
        if (score >= sumScore)
        {
            Debug.Log("时间结束:" + score + " child:" + sumScore);
            GameVictory();  //游戏胜利
        }
        else
            GameDefeated(); //游戏失败

    }

    /// <summary>
    /// 游戏胜利
    /// </summary>
    void GameVictory()
    {
        //游戏结束
        isGameOver = true;

        //停止所有正在移动的垃圾item和星星动画
        StopAllGarbageItemAndStar();

        VictoryAudio(); //胜利音效

        //刷新按钮,游戏结束Txt显示
        refreshBtn.gameObject.SetActive(true);
        gameOverTxt.gameObject.SetActive(true);

        gameOverTxt.text = "游戏胜利";
    }

    /// <summary>
    /// 游戏失败
    /// </summary>
    void GameDefeated()
    {
        //游戏结束
        isGameOver = true;

        //停止所有正在移动的垃圾item和星星动画
        StopAllGarbageItemAndStar();

        DefeatedAudio();//失败音效

        //刷新按钮,游戏结束Txt显示
        refreshBtn.gameObject.SetActive(true);
        gameOverTxt.gameObject.SetActive(true);

        gameOverTxt.text = "游戏失败";

    }
    #endregion

    #region 加分(星星效果)
    /// <summary>
    /// 加分
    /// </summary>
    /// <param name="pos">垃圾item消失的位置</param>
    public void AddScore(Vector3 pos)
    {
        //获取星星动画,修改位置
        Transform star = GetStar();
        star.position = pos;
        star.gameObject.SetActive(true);

        //星星正在播放 添加到列表
        moveStarList.Add(star);
    }

    //获取星星
    Transform GetStar()
    {
        Transform star;
        //判断对象池是否存在item
        if (starPool.Count > 0)
        {
            star = starPool[0];
            starPool.Remove(star);
        }
        else
            star = Instantiate(starPrefab, starContent);

        return star;
    }

    //星星回收(动画播放完毕后,回收星星到对象池中)由动画关键帧调用
    public void RecycleStar(Transform star)
    {
        /*回收*/

        //星星播放结束 从列表移除
        moveStarList.Remove(star);

        star.gameObject.SetActive(false);
        starPool.Add(star);

        加分
        //score++;
        //scoreTxt.text = "分数:" + score.ToString();

        加时间
        //ADDTimer();

         播放音效
        //CorrentAudio();

        //判断所有垃圾分类完成
        if (score >= sumScore)
        {
            Debug.Log("加分结束胜利:" + score + " child:" + sumScore);

            //停止时间协程
            StopCoroutine(timerIE);

            GameVictory();  //游戏胜利
        }
    }

    /// <summary>
    /// 回收所有加分星星
    /// </summary>
    void RecycleAllStar()
    {
        for (int i = 0; i < starContent.childCount; i++)
        {
            Transform star = starContent.GetChild(i);
            star.gameObject.SetActive(false);

            //对象池中不存在此星星时,回收
            if (starPool.IndexOf(star) == -1)
                starPool.Add(star);
        }
    }
    #endregion

    #region 加减游戏时间

    /// <summary>
    /// 游戏时间+2秒
    /// </summary>
    public void ADDTimer()
    {
        addTimerTxt.text = "时间+2";
        addTimerTxt.color = addColor;
        addTimerTweener.Restart();

        timer += 2;
        if (timer >= maxTimer)
            timer = maxTimer;

        timeTxt.text = "时间:" + timer.ToString();
    }
    /// <summary>
    /// 游戏时间-2秒
    /// </summary>
    public void SubtractTimer()
    {
        addTimerTxt.text = "时间-2";
        addTimerTxt.color = subtractColor;
        addTimerTweener.Restart();

        timer -= 2;
        if (timer <= 0)
        {
            timer = 0;
            //游戏失败
            GameDefeated();
        }

        timeTxt.text = "时间:" + timer.ToString();


    }
    #endregion

    #region 隐藏并停止所有动画及效果

    /// <summary>
    /// 隐藏并停止所有垃圾动画效果、星星效果、正在拖拽的物体
    /// 防止游戏结束时,
    /// </summary>
    void StopAllGarbageItemAndStar()
    {
        if (currentItem != null)
        {
            currentItem.RestoreOriginPos();
            currentItem = null;
        }

        //循环所有在移动的item
        for (int i = 0; i < MoveGarbageList.Count; i++)
            MoveGarbageList[i].StopDoTween(); //停止dotween动画

        for (int i = 0; i < moveStarList.Count; i++)
        {
            //隐藏星星
            moveStarList[i].gameObject.SetActive(false);
            //添加到对象池中
            starPool.Add(moveStarList[i]);
        }
    }
    #endregion

    #region 音效播放

    /// <summary>
    /// 正确
    /// </summary>
    public void CorrentAudio()
    {
        correntAudio.Play();
    }
    /// <summary>
    /// 错误
    /// </summary>
    public void ErrorAudio()
    {
        errorAudio.Play();
    }

    /// <summary>
    /// 胜利
    /// </summary>
    public void VictoryAudio()
    {
        victoryAudio.Play();
    }

    /// <summary>
    /// 失败
    /// </summary>
    public void DefeatedAudio()
    {
        defeatedAudio.Play();
    }

    /// <summary>
    /// 停止所有音效
    /// </summary>
    public void StopAllAudio()
    {
        correntAudio.Stop();
        errorAudio.Stop();
        victoryAudio.Stop();
        defeatedAudio.Stop();
    }
    #endregion

}

GarbageItem中代码的添加和修改

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using DG.Tweening;

/// <summary>
/// 垃圾Item
/// </summary>
public class GarbageItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    Vector2 offset;           //偏移量
    Vector2 oldPos;           //鼠标拖拽结束之后的位置
    Vector2 originPos;        //物体原点位置

    RectTransform rect;

    Trash trash;              //垃圾桶
    public Trash Trash { get => trash; set => trash = value; }

    #region 组件初始化
    void ThisGetCompontent()
    {
        if (rect == null)
        {
            rect = GetComponent<RectTransform>();
        }
    }

    #endregion

    #region 数据初始化
    public void InitData(Vector2 pos)
    {
        //组件初始化
        ThisGetCompontent();

        //刷新后 垃圾桶为空
        Trash = null;

        //位置修改
        originPos = pos;
        RestoreOriginPos();

        //显示
        gameObject.SetActive(true);
    }

    #endregion

    //位置还原
    public void RestoreOriginPos() 
    {
        rect.anchoredPosition = originPos;
    }

    //开始拖拽
    public void OnBeginDrag(PointerEventData eventData)
    {
        GarbagePanel.GetInstance().CurrentItem = this;
        offset = eventData.position - (Vector2)transform.position;
    }

    //拖拽
    public void OnDrag(PointerEventData eventData)
    {
        //游戏结束 不执行操作
        if (GarbagePanel.GetInstance().isGameOver)
            return;

        Vector3 pos = eventData.position - offset;
        transform.position = new Vector3(Mathf.Clamp(pos.x, 0, Screen.width), Mathf.Clamp(pos.y, 0, Screen.height), 0);

        oldPos = eventData.position - offset;
    }

    //拖拽结束
    Tweener tweener;
    public void OnEndDrag(PointerEventData eventData)
    {
        //游戏结束 不执行操作
        if (GarbagePanel.GetInstance().isGameOver) 
            return;

        //垃圾桶不为空,代表垃圾进入了垃圾桶
        if (trash != null)
        {
            //判断垃圾与垃圾桶的tag是否相同
            if (trash.tag==transform.tag)
            {
                //Debug.Log("正确,执行位移,并加分");

                //加分
                GarbagePanel.GetInstance().score++;
                GarbagePanel.GetInstance().scoreTxt.text = "分数:" + GarbagePanel.GetInstance().score.ToString();

                //加时间
                GarbagePanel.GetInstance().ADDTimer();

                // 播放音效
                GarbagePanel.GetInstance(). CorrentAudio();

                //dotween动画正在移动,添加自己
                GarbagePanel.GetInstance().MoveGarbageList.Add(this);

                //修改垃圾item在2屏的垃圾桶位置
                rect.anchoredPosition = originPos;
                Debug.Log(rect);
                rect.SetParent(GarbagePanel.GetInstance().twoSceneTrashPos);
                rect.anchoredPosition = GarbagePanel.GetInstance().TwoSceneTrashPosDic[transform.tag].GetComponent<RectTransform>().anchoredPosition;

                tweener = rect.DOAnchorPos(new Vector2(rect.anchoredPosition.x, -14), 1f);
                tweener.OnComplete((TweenCallback)delegate
                {
                    //Debug.Log("消失");

                    //dotween移动结束,移除自己
                    GarbagePanel.GetInstance().MoveGarbageList.Remove(this);

                    //加分
                    GarbagePanel.GetInstance().AddScore(transform.position);

                    //修改为1屏的父物体
                    gameObject.SetActive(false);
                    rect.SetParent((Transform)GarbagePanel.GetInstance().garbageItemContent);

                });
            }
            else
            {
                //减时间
                GarbagePanel.GetInstance().SubtractTimer();

                //播放错误音效
                GarbagePanel.GetInstance().ErrorAudio();

                //垃圾回到原来的位置
                rect.anchoredPosition = originPos;
                //Debug.Log("错误");
            }

        }
        else
        {
            //垃圾回到原来的位置
            rect.anchoredPosition = originPos;
        }

        //拖拽结束 设置为空
        GarbagePanel.GetInstance().CurrentItem = null;
        //加分结束后,垃圾桶设置为空
        trash = null;
    }


    /// <summary>
    /// 停止dotween动画
    /// </summary>
    public void StopDoTween()
    {
        //dotween移动结束,移除自己
        GarbagePanel.GetInstance().MoveGarbageList.Remove(this);

        //停止并杀死动画,防止有残留
        tweener.Pause();
        tweener.Kill();

        //隐藏自己,并修改为1屏的父物体
        gameObject.SetActive(false);
        rect.SetParent(GarbagePanel.GetInstance().garbageItemContent);
    }

}

五 程序包链接

链接:https://pan.baidu.com/s/1KAaM0GXSB9JtDyHHvC7-Yg?pwd=jl98 
提取码:jl98

猜你喜欢

转载自blog.csdn.net/shijinlinaaa/article/details/126656350
今日推荐