一:逻辑梳理
要实现的功能
游戏操作
在规定时间内,垃圾通过拖拽进入正确垃圾桶的容器,垃圾在这里消失,飞入第二个屏上对应垃圾桶的位置并实现加分和加时间的效果,垃圾拖拽进入不正确的垃圾桶,垃圾会返回到原来的位置,同时,相应的时间也会减少
胜利和失败的条件:
胜利:垃圾全部分类完毕,并且时间不为零,游戏胜利,
失败:时间为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中代码的添加和修改文章来源:https://www.toymoban.com/news/detail-435506.html
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);
}
}
五:package下载
垃圾分类小游戏_unity3d - Unity3D模型_U3D_免费下载-爱给网文章来源地址https://www.toymoban.com/news/detail-435506.html
到了这里,关于Unity学习记录:制作双屏垃圾分类小游戏的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!