用unity写一个2D类的拼图游戏

前几天接了一个拼图项目刚好现在写完了,拿出来分享,拼图不难,我也是看了一个官方案例写的,因为当我们写图片跟随鼠标的时候,鼠标已经有一个图片了,这个图片会遮挡射线,然后就无法判断当前拼图块在哪里,话不多说,上菜

1、新建总控脚本LevelManager

public enum State
{
        None,
        mousedown,
        mousemove,
        mouseup,
}
public PuzzleDivision PuzzleDivision;
public State state;
private void Awake()
{
        ins = this;
        Random.InitState((int)System.DateTime.Now.Ticks);
}
void Start()
{
        state = State.None;
}

2、新建PuzzleDivision脚本,用于初始化拼图块的克隆和选区

public class PuzzleDivision : MonoBehaviour
{
    public int size = 3;
    public List<Grid1> grids = new List<Grid1>();
    public Grid gridPrefab;
    public Grid1 gridPrefab1;
public void Init(Transform tra)
    {
        for (int i = 1; i <= size; i++)
            {
            for (int j = 1; j <= size; j++)
            {
                if ((i - 1) * size + j > grids.Count)
                {
                    Grid1 grids2 = Instantiate(gridPrefab1, gridLayoutGroup.transform);
                    grids2.transform.name = "Start" + i;
                    grids2.id = i;
                    grids2.size = size;
                    grids.Add(grids2);
                    Grid grid = Instantiate(gridPrefab, tra);
                    LevelManager.ins.grids.Add(grid);
                }
                else
                {
                    grids[i].gameObject.SetActive(true);
                    LevelManager.ins.grids[i].gameObject.SetActive(true);
                }
                grids[(i - 1) * size + j - 1].SetInf(this, size, new Vector2(i, j), Texture2D);
                LevelManager.ins.grids[(i - 1) * size + j - 1].SetInf(this, size, new Vector2(i, j), Texture2D);
            }
        }
        if (grids.Count > size * size)
        {
            for (int i = size * size; i < grids.Count; i++)
            {
                grids[i].gameObject.SetActive(false);
            }
        }
        for (int i = 0; i < grids.Count; i++)
        {
            grids[i].transform.GetComponent<RawImage>().color = new Color32(255, 255, 255, 27);
        }
        if (LevelManager.ins.grids.Count > size * size)
        {
            for (int i = size * size; i < LevelManager.ins.grids.Count; i++)
            {
                LevelManager.ins.grids[i].gameObject.SetActive(false);
            }
        }
}
}

分割图片只有RawImage才能使用,所以这里用的是RawImage,并且注意Grid1是上半部分的图

Grid是下半部分,也就是可以互动的图,如图

下面是Grid的脚本:

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

public class Grid: MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    Action Action;
    private PuzzleDivision game;
    private RawImage image;
    private Image bj;
    public Vector3 movepos;
    public float rotesize;
    public int id;
    public int size;
    public bool cheng = false;
    Vector3 pos;
    private void Awake()
    {
        Input.multiTouchEnabled = false;
        image = GetComponent<RawImage>();
        bj = GetComponent<Image>();
    }
    /// <summary>
    /// 设置当前Grid的信息
    /// </summary>
    /// <param name="size">n*n</param>
    /// <param name="id">Grid的ID</param>
    public void SetInf(PuzzleDivision game, int size, Vector2 id, Texture2D sp)
    {
        image.texture = sp;
        this.game = game;
        float width = 1f / size;

        gameObject.name = $"Grid_{id.x}_{id.y}";
        image.uvRect = new Rect(
            (id.x - 1) * width,
            (id.y - 1) * width,
            width,
            width
        );
        switch (size)
        {
            case 3:
                transform.GetComponent<RectTransform>().sizeDelta = new Vector2(394, 395);
                break;
            case 4:
                transform.GetComponent<RectTransform>().sizeDelta = new Vector2(298, 299);
                break;
            default:
                break;
        }
    }
    public void OnBeginDrag(PointerEventData eventData)//开始拖拽
    {
        LevelManager.ins.name = transform.name;
        transform.GetComponent<CanvasGroup>().blocksRaycasts = false;
        eventData.pointerDrag.GetComponent<RectTransform>().transform.DORotate(new Vector3(0,0,0),0.01f);
        LevelManager.ins.state = LevelManager.State.mousedown;
    }

    public void OnDrag(PointerEventData eventData)//拖拽中
    {
        //throw new System.NotImplementedException();
        transform.position = Input.mousePosition;
        eventData.pointerDrag.transform.GetComponent<RawImage>().transform.SetAsLastSibling();
        LevelManager.ins.state = LevelManager.State.mousemove;
    }

    public void OnEndDrag(PointerEventData eventData)//停止拖拽
    {
        //throw new System.NotImplementedException();
        transform.GetComponent<CanvasGroup>().blocksRaycasts = true;
        Debug.Log(eventData?.pointerDrag.name);//当前物体
        //Debug.Log(eventData.pointerEnter.name);//鼠标抬起后的物体

        if (eventData.pointerEnter!=null)
        {
            if (eventData.pointerEnter.name == eventData.pointerDrag.name)
            {
                switch (size)
                {
                    case 3:
                        eventData.pointerDrag.transform.GetComponent<RectTransform>().sizeDelta = new Vector2(394, 395);
                        AudioManager.ins.audios(3);
                        break;
                    case 4:
                        eventData.pointerDrag.transform.GetComponent<RectTransform>().sizeDelta = new Vector2(298, 299);
                        AudioManager.ins.audios(3);
                        break;
                    default:
                        break;
                }
               
                pos = eventData.pointerEnter.transform.position;
                if (Input.GetMouseButtonUp(0))
                {
                    eventData.pointerDrag.transform.position = eventData.pointerEnter.transform.position;
                    if (Vector3.Distance(pos, transform.position) <= 0.01)
                    {
                        cheng = true;
                        transform.GetComponent<Grid>().enabled = false;
                        Debug.LogError("当前手指数量:" + Input.touchCount);
                    }
                }
                if (Input.touchCount<=1&&Input.touchCount>0)
                {
                    eventData.pointerDrag.transform.position = eventData.pointerEnter.transform.position;
                    if (Vector3.Distance(pos, transform.position) <= 0.01)
                    {
                        cheng = true;
                        transform.GetComponent<Grid>().enabled = false;
                        Debug.LogError("当前手指数量:"+Input.touchCount);
                    }
                }
                LevelManager.ins.sum += 1;
                
            }
            else
            {
                eventData?.pointerDrag.GetComponent<RectTransform>().transform.DOLocalMove(movepos, 0.25f);
                eventData?.pointerDrag.GetComponent<RectTransform>().transform.DORotate(new Vector3(0, 0, rotesize), 0.1f);
            }
        }
        LevelManager.ins.state = LevelManager.State.mouseup;
    }
    private void Update()
    {
        if (Input.touchCount>0)
        {
            if (Input.GetTouch(0).phase == TouchPhase.Ended)
            {
                LevelManager.ins.state = LevelManager.State.mouseup;
            }
        }
        Action = LevelManager.ins.state switch
        {
            LevelManager.State.None => new Action(Nonetest),
            LevelManager.State.mousedown => new Action(mousedowntest),
            LevelManager.State.mousemove => new Action(mousemovetest),
            LevelManager.State.mouseup => new Action(mouseuptest),
            _ => throw new ArgumentOutOfRangeException(nameof(LevelManager.state))
        };
        Action.Invoke();
    }
    void mouseuptest()
    {
        Debug.Log("输出鼠标抬起回调");
        if (!cheng)
        {
            transform.DOLocalMove(movepos, 0.25f);
        }
        LevelManager.ins.state = LevelManager.State.None;
    }
}

 因为是webGL的项目,面对的bug解决方案也不同,代码可能有点乱,大家将就看看,webGL不支持禁用多点触控,所以在这里用了比较复杂的判定,不然直接一句话的事,而且策划说在松手状态下,拼图块要回到原位,并且要旋转到原来的位置,我当时听到这个真是mmp了(小声bb一句,狗策划)

Grid中size是设置拼图分割的块数,输入3,就分割成3*3的,输入4就是4*4,输入多少都可以,你输入一百也没事(其实我也不知道有没有事)

SetInf()是分割方

OnBeginDrag()是开始拖拽方法,到这步有聪明的小明就要问了,那我这个拼图块也要占用射线,我应该怎么判定拼图块是否到对应的位置了呢,问的好,用transform.GetComponent<CanvasGroup>().blocksRaycasts = false;

解决,这个是用于禁用当前的射线交互的,简单点说,射线可以直接穿过当前拼图块,这样就可以用来检测了,这个是官方的做法,我记得哪个插件里有示例,我也是根据那个示例来的,这个:

UI Samples

OnDrag()是拖拽中的方法

OnEndDrag()是停止拖拽的方法

我们是拼图游戏,所有图块都是在松手后判定,所以写在OnEndDrag()中的东西会多一点,在OnEndDrag()中可以写你自己的判定逻辑,当然,也可以在其他方法里写这个就不多赘述,Update()中主要是判定屏幕中是否有手指,至于这个switch则是C#的新语法糖

注意!switch语法糖有时候在unity中可能会报错,因为mono对C#的支持不太好,还没更新到最新的语法糖

Action = LevelManager.ins.state switch
        {
            LevelManager.State.None => new Action(Nonetest),
            LevelManager.State.mousedown => new Action(mousedowntest),
            LevelManager.State.mousemove => new Action(mousemovetest),
            LevelManager.State.mouseup => new Action(mouseuptest),
            _ => throw new ArgumentOutOfRangeException(nameof(LevelManager.state))
        };

至于mouseuptest()则是松手后拼图块的归位

Grid1脚本如下:

using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class Grid1 : MonoBehaviour, IDropHandler, IPointerEnterHandler, IPointerExitHandler
{
    private PuzzleDivision game;
    private RawImage image;
    [SerializeField] Image bj;
    public int id;
    public int size;
    Tween tween;
    private void Awake()
    {
        image = GetComponent<RawImage>();
    }
    /// <summary>
    /// 设置当前Grid1的信息
    /// </summary>
    /// <param name="size">n*n</param>
    /// <param name="id">Grid��ID</param>
    public void SetInf(PuzzleDivision game, int size, Vector2 id,Texture2D sp)
    {
        image.texture = sp;
        this.game = game;
        float width = 1f / size;

        gameObject.name = $"Grid_{id.x}_{id.y}";
        image.uvRect = new Rect(
            (id.x - 1) * width,
            (id.y - 1) * width,
            width,
            width
        );
        switch (size)
        {
            case 3:
                bj.transform.GetComponent<RectTransform>().sizeDelta = new Vector2(394 - 10, 395 - 10);
                break;
            case 4:
                bj.transform.GetComponent<RectTransform>().sizeDelta = new Vector2(298 - 10, 299 - 10);
                break;
            default:
                break;
        }
    }
    private void Update()
    {
        switch (LevelManager.ins.state)
        {
            case LevelManager.State.None:
                HIde();
                break;
            case LevelManager.State.mousedown:
                break;
            case LevelManager.State.mousemove:
                break;
            case LevelManager.State.mouseup:
                break;
            default:
                break;
        }
    }
    public void Show()
    {
        bj.DOFade(1, 0.01f);
        //bj.gameObject.SetActive(true);
        tween = bj.DOFade(0, 0.25f).SetLoops(-1, LoopType.Yoyo);
        AudioManager.ins.audios(2);
    }
    public void HIde()
    {
        tween.Kill();
        bj.DOFade(1, 0.01f);
    }
    public void OnDrop(PointerEventData eventData)//鼠标抬起
    {
        Debug.Log("鼠标抬起");
        HIde();
    }

    public void OnPointerEnter(PointerEventData eventData)//鼠标移入
    {
        Debug.Log("鼠标移入");
        if (LevelManager.ins.state == LevelManager.State.mousemove)
        {
            Show();
        }
    }

    public void OnPointerExit(PointerEventData eventData)//鼠标移出
    {
        //throw new System.NotImplementedException();

        if (LevelManager.ins.state == LevelManager.State.mousemove)
        {
            HIde();
        }
        Debug.Log("鼠标移出");
    }
}

Grid1就简单多了,Grid1主要用于上半部分的对比图展示,所以要判定鼠标是否移入、移出这种,这里就不做赘述,我们接着往下走

将Grid1和Grid做成预设,面板上是这样的:

 

 

我这个套了个Image,是因为策划说要增加虚线,表示当前拖拽状态,我也是无语了

下面给你们看看示例 

猜你喜欢

转载自blog.csdn.net/k253316/article/details/128594205