redhat9i 发表于 2023-2-12 09:32

用U3D开发消灭星星(Pop Star)

某大学计算机专业的学生,接触u3d四个月了,正巧赶上学校Java有一个开发PopStar的实验课,寻思着用unity做一做。同时把这个小工程当做自己这几个月学习的一个小测试,真正的独立去开发一个小游戏,找到自己的弱点和不足之处。此外,写这个博客记录一下自己unity3d的学习。各位u3d大佬手下留情,如果能指点一二那就更好了。

一,场景的搭建
1.准备工作:
          1)工程创建为2D,接着各种文件夹的创建;
          2)两个场景——游戏场景,菜单界面(涉及到皮肤的更换);
          3)素材资源的导入;
          4)素材的处理,主要是分割图片。
          5)导入Dotween动画插件,方便实现星星的缓动效果。(这里应该是有点大材小用了,主要是想偷个懒   :)   )


2.搭建菜单场景
          1)面子工程:历史最高分,最大关卡的显示(逻辑后面再实现)。


          2)主要功能:游戏的开始与退出的,皮肤切换,分数和关卡的保存。
          3)创建空物体GameManager挂载脚本GameManager01实现:a.menu场景->game场景的切换和游戏的退出;
                                                                                                            b.添加场景的序列号;
                                                                                                            c.记录玩家选择的皮肤;
                                                                                                            d.为Button添加鼠标点击事件;代码如下:
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class GameManager01 : MonoBehaviour
{

    public Toggle toggle1;//取得皮肤1的Toggle组件

    /**
   * 游戏退出
   */
    public void OnClickExit()
    {
      #if UNITY_EDITOR
      UnityEditor.EditorApplication.isPlaying = false;
      #else
      Application.Quit();
      #endif
    }

    /**
   * 开始游戏
   */
    public void OnClickStart()
    {
      //开始游戏之前保存用户选择的皮肤
      if (toggle1.isOn)
      {
            PlayerPrefs.SetInt("SkinMode",0 );//皮肤设置为1
      }
      else
      {
            PlayerPrefs.SetInt("SkinMode", 1);//皮肤设置为2
      }

      print(PlayerPrefs.GetInt("SkinMode"));//输出,检测功能是否实现

      SceneManager.LoadScene(1);
    }
}

3.搭建游戏场景
          1)创建空物体StarGrid用于存放所有的星星,把其当做所有星星的父物体,统一进行管理
          2)新建Image,source image选中为红色星星,添加Star脚本,并将其他四个星星都作成prefab,并为不同的星星添加tag,代码如下:
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;

public class Star : MonoBehaviour
{

    public int row = 0;//行值
    public int col = 0;//列值

    private void Start()
    {
      SetPos();
    }

    /**
   * 根据行列值对星星的位置进行操作
   */
    public void SetPos()
    {
      transform.DOLocalMove(new Vector3(-258 + col*52 + 22.5f , 258 -row*52 - 22.5f , 0),0.5f);
    }

    /**
   * 通过行列值判断行星是否存在List中
   */
    public bool IsExist(List<Star> starList,int row,int col)
    {
      for (int i = 0; i < starList.Count; i++)
      {
            Star star = starList;
            if (star.row == row && star.col == col)
            {
                return true;
            }
      }

      return false;
    }

    /**
   * 清除单个星星的方法
   */
    public void Clear()
    {
      //TODO 星星清除的粒子特效
      this.transform.DOScale(new Vector3(0.01f, 0.01f, 0.01f), 0.3f);//借用了Dotween动画插件
      Destroy(this);//销毁
    }

}
          3)添加空物体GameManager挂载GameManager02脚本用于实现:
               a.星星矩阵的实例化;
private List<Star> CreateStarList()
    {
      List <Star> StarList = null;

      for (int i = 0; i < MAX_ROW; i++)
      {
            for (int j = 0; j < MAX_COL; j++)
            {
                GameObject gameobject = Instantiate(GetRandomPrefab(), Vector3.zero, Quaternion.identity);//实例化随机颜色的星星
                gameobject.transform.SetParent(starGrid);//把星星的父物体设置为starGrid
                gameobject.transform.localScale = new Vector3(1,1,1);//把局部的scale保持为原来一致,防止相机渲染导致大小的变化
                Star star = gameobject.GetComponent<Star>();//取得当前星星的引用
                star.row = i;//设置行值
                star.col = j;//设置列值
                star.SetPos();//位置的变化
                StarList.Add(star);//把星星添加到集合中
            }
      }

      return StarList;
    }

    /**
   * 随机获得五个星星prefab中的一个
   */
    private GameObject GetRandomPrefab()
    {
      int r = Random.Range(0, 5);
      GameObject prefab = redStarPrefab;
      switch (r)
      {
            case 0:
                prefab = redStarPrefab;
                break;
            case 1:
                prefab = blueStarPrefab;
                break;
            case 2:
                prefab = yellowStarPrefab;
                break;
            case 3:
                prefab = purpleStarPrefab;
                break;
            case 4:
                prefab = greenStarPrefab;
                break;
      }

      return prefab;
    }
               b.星星的消除,创建空物体StarManager挂载StarManager脚本用于实现星星的消除和位置的移动;
               实现获得待星星消除的方法,该方法在Star的鼠标点击事件里面进行调用。
               涉及了递归调用,递归死循环已经让我绝望了无数次了。
               具体的算法也不进行介绍了,还算比较简单
public static void FindStar(List<Star> currentStarList, Star star, List<Star> clearedStars)
    {
      int row = star.row;
      int col = star.col;

      Star tStar = null;

      if (col - 1 >= 0)
      {
            tStar = Star.LookupStar(currentStarList, row, col - 1);
            if (tStar != null && !Star.IsExisted(clearedStars, tStar))
            {
                if (tStar.tag == star.tag)
                {
                  clearedStars.Add(tStar);
                  FindStar(currentStarList, tStar, clearedStars);
                }
            }
      }
      if (col + 1 < GameManager02.MAX_COL)
      {
            tStar = Star.LookupStar(currentStarList, row, col + 1);
            if (tStar != null && !Star.IsExisted(clearedStars, tStar))
            {
                if (tStar.tag == star.tag)
                {
                  clearedStars.Add(tStar);
                  FindStar(currentStarList, tStar, clearedStars);
                }
            }
      }
      if (row - 1 >= 0)
      {
            tStar = Star.LookupStar(currentStarList, row - 1, col);
            if (tStar != null && !Star.IsExisted(clearedStars, tStar))
            {
                if (tStar.tag == star.tag)
                {
                  clearedStars.Add(tStar);
                  FindStar(currentStarList, tStar, clearedStars);
                }
            }
      }
      if (row + 1 < GameManager02.MAX_ROW)
      {
            tStar = Star.LookupStar(currentStarList, row + 1, col);
            if (tStar != null && !Star.IsExisted(clearedStars, tStar))
            {
                if (tStar.tag == star.tag)
                {
                  clearedStars.Add(tStar);
                  FindStar(currentStarList, tStar, clearedStars);
                }
            }
      }
    }
   
               c.皮肤的切换,更换图片的Sprite就好了




   /**
   *修改皮肤的方法
   */
    private void ChangeSkin()
    {
      if (PlayerPrefs.GetInt("SkinMode") == 0)
      {
            background.GetComponent<Image>().sprite = skinSprites;

            redStarPrefab.GetComponent<Image>().sprite = starSprites;
            blueStarPrefab.GetComponent<Image>().sprite = starSprites;
            yellowStarPrefab.GetComponent<Image>().sprite = starSprites;
            purpleStarPrefab.GetComponent<Image>().sprite = starSprites;
            greenStarPrefab.GetComponent<Image>().sprite = starSprites;
      }

      if (PlayerPrefs.GetInt("SkinMode") == 1)
      {
            background.GetComponent<Image>().sprite = skinSprites;

            redStarPrefab.GetComponent<Image>().sprite = starSprites;
            blueStarPrefab.GetComponent<Image>().sprite = starSprites;
            yellowStarPrefab.GetComponent<Image>().sprite = starSprites;
            purpleStarPrefab.GetComponent<Image>().sprite = starSprites;
            greenStarPrefab.GetComponent<Image>().sprite = starSprites;
      }
    }
               d.星星消除后的位置移动
                         上下的移动,核心算法就不介绍了,简单易懂。
   /**
   * 更新上下,并且返回空列的col的集合,以便进行左右的移动
   */
    public static List<int> UpdateUD()
    {
      List<int> nullColumn = new List<int>();

      for (int i = 0; i < GameManager02.MAX_COL; i++)
      {
            int moveStep = 0;
            for (int j = GameManager02.MAX_ROW - 1; j >= 0; j--)
            {

                Star star = Star.LookupStar(StarManager.starList, j, i);
                if (star == null)
                {
                  moveStep++;
                  continue;
                }
                else
                {
                  if (moveStep == 0)
                  {
                        continue;
                  }
                  else
                  {
                        star.row += moveStep;

                        star.SetPos();
                  }
                }
            }

            if (moveStep == GameManager02.MAX_ROW)
            {
                nullColumn.Add(i);
            }
      }

      if (nullColumn != null)
      {
            for (int i = 0; i < nullColumn.Count; i++)
            {
                print(nullColumn + " ");
            }

      }

      return nullColumn;
    }
                         左右的移动,基于上下移动
/**
   *更新左右,传入空列的集合
   */
    public static void UpdateLR(List<int> nullColumn)
    {
      int moveStep = 0;
      for (int i = 0; i < GameManager02.MAX_COL; i++)
      {
            if (nullColumn.Contains(i))
            {
                moveStep++;
            }
            else
            {
                if (moveStep == 0)
                {
                  continue;
                }
                else
                {
                  for (int j = GameManager02.MAX_ROW - 1; j >= 0; j--)
                  {
                        Star star = Star.LookupStar(StarManager.starList, j, i);
                        if (star != null)
                        {
                            star.col -= moveStep;

                            star.SetPos();
                        }

                  }
                }
            }
      }
    }
游戏逻辑的实现都在Star脚本里面完成,鼠标点击开始时调用函数。
到了这一步游戏的主要功能就差不多完成了。
二,分数计算的实现,以及存档的保存
          根据一次消除星星的个数进行计算分数:10*(Mathf.pow(2,number-2)),最高次定为8
         canvas下新建空物体ScoreManager用于挂载ScoreManager脚本进行分数的计算
public float ComputeScore(int number)
    {
      if (number<8)
      {
            return 10 * Mathf.Pow(2, number);
      }
      else
      {
            return 10 * Mathf.Pow(2, 8);
      }
    }
三,动画的制作和画面的优化
当当前分数>目标分数,通关成功,加在下一关;
反之闯关败,记录分数和关卡,更新最高分等信息并返回主菜单
主动画的制作要是面板显示的的渐入渐出
    /**
   * 根据剩余星星数,给予额外得分
   */
    public void GetBonusScore(List<Star> starList)
    {
      if (starList.Count>20)
      {
            return;
      }
      else
      {
            float bonusScore = 10 * (20 - starList.Count);
            score += bonusScore;
      }
      UpdateScoreText();
    }
   
    /**
   *根据关卡数计算目标分数
   */
    public void GetGoalScore(int number)
    {
      goalScore = 1000 * number + 2000 * (number / 3 + 1);
    }

    /**
   *更新UI
   */   
    public void UpdateScoreText()
    {
      scoreText.text = "当前分数:" + score;
      goalScoreText.text = "目标分数:" + goalScore;
      levelText.text = "当前关卡:" + level;
    }

    public void GetBounsUI(int number)
    {
      float bonusScore = 10 * (20 - number);
      bouns.gameObject.SetActive(true);
      bouns.text = "剩余:" + number + "额外得分" + bonusScore;
    }
5.音效的添加和粒子系统的完善。更改渲染模式为world space,手残党。粒子特效太硬核了,就演示了。
6.导出游戏,工程结束。


一小段演示视屏:


https://www.zhihu.com/video/1131908748255662080

pc8888888 发表于 2023-2-12 09:35

yukamu 发表于 2023-2-12 09:44

为啥劝退u3d?

Ilingis 发表于 2023-2-12 09:51

[赞][赞][赞]

unityloverz 发表于 2023-2-12 09:51

好!!!

闲鱼技术01 发表于 2023-2-12 09:57

哈哈,听起来好玩而已,没其他意思

kyuskoj 发表于 2023-2-12 10:04

楼主可以分享一下源代码吗?正好要学习制作消灭星星,有许多不懂的问题

unityloverz 发表于 2023-2-12 10:07

核心的代码基本上都在里面了

ChuanXin 发表于 2023-2-12 10:08

LookupStar是什么方法?

unityloverz 发表于 2023-2-12 10:16

同问 LookupStar是什么方法?
页: [1] 2
查看完整版本: 用U3D开发消灭星星(Pop Star)