赞
踩
目录
使用Unity制作一款消消乐,能让开发者了解Unity UI组件的使用,C#编写代码的能力得到提升。
预制体包含几种消除类型的枚举,枚举里面一个字段代表一种消除对象的种类。
- public enum SweetsType
- {
- EMPTY,
- NORMAL,
- BARRIER,
- ROW_CLEAR,
- COLUMN_CLEAR,
- RAINBOWCANDY,
- WALL,
- COUNT
- }
简单介绍一下,有空类型、普通类型、障碍类型、行消除、列消除和同类型消除。
经常使用的是普通类型消除,这是我们用到的主要交互的游戏预制体。
在此预制体里面也有一个枚举类型的ColorType作为普通预制体的颜色区分,每种颜色对应一种图片。
- public enum ColorType
- {
- YELLOW,
- PURPLE,
- RED,
- BLUE,
- GREEN,
- PINK,
- ANY,
- COUNT
- }
-
- public struct ColorSprite
- {
- public ColorType color;
- public Sprite sprite;
- }
-
- public ColorSprite[] ColorSprites;

预制体分类后,需要对预制体添加可移动,可按颜色区分,可消除的游戏逻辑。
- //移动
- public void Move(int newX, int newY, float time)
- {
- if (moveCoroutine != null)
- {
- StopCoroutine(moveCoroutine);
- }
-
- moveCoroutine = MoveCoroutine(newX, newY, time);
- StartCoroutine(moveCoroutine);
- }
-
- //设置颜色类型
- public void SetColor(ColorType newColor)
- {
- color = newColor;
- if (colorSpriteDict.ContainsKey(newColor))
- {
- sprite.sprite = colorSpriteDict[newColor];
- }
- }
-
- //消除
- public virtual void Clear()
- {
- isClearing = true;
- GameObject newSweet = Instantiate(gameObject,
- sweet.gameManager.CorrectPosition(sweet.X, sweet.Y), Quaternion.identity);
- newSweet.GetComponent<ClearSweet>().PlayClearAnimator();
- Destroy(gameObject);
- }

当游戏对象被拖拽至能匹配的位置,被删除时就要播放销毁动画和销毁音效。
使用Animation动画控制器,改变图片的Scale和透明度,以此达到销毁的效果。
当游戏对象匹配成功后,开启销毁函数,使用协程待销毁动画播放完毕之后才开始销毁物体。
在销毁之前,会播放销毁音效和增加玩家的得分。
- private IEnumerator ClearCoroutine()
- {
- Animator animator = GetComponent<Animator>();
- if(animator != null )
- {
- animator.Play(clearAnimation.name);
- GameManager.Instance.PlayerScore++;
- AudioSource.PlayClipAtPoint(DestoryAudio,transform.position);
- yield return new WaitForSeconds(clearAnimation.length);
- Destroy(gameObject);
- }
- }
此部分开始涉及到ui 组件的使用,使用Text,Button,Image等基础组件拼接UI元素,使用Scroll Rect制作道具滑动列表。
Scroll Rect组件使用是要注意一下其结构,根物体挂载Scroll Rect组件,
其子物体viewport挂载一个Mask组件作为限制显示区域的遮罩,
content物体需挂载Grid Layout Group组件限制道具物体的间距以及排列方式。
- xColumn = lastLevelConfig.xColumn;
- yRow = lastLevelConfig.yRow;
- sweets = new GameSweet[xColumn, yRow];
- for (int x = 0; x < xColumn; x++)
- {
- for (int y = 0; y < yRow; y++)
- {
- //生成墙体
- if (IsExistXY(lastLevelConfig.wallPos, x, y))
- {
- CreateNewSweet(x, y, SweetsType.WALL);
- continue;
- }
- CreateNewSweet(x, y, SweetsType.EMPTY);
- }
- }
- //生成墙体
- for (int i = 0; i < lastLevelConfig.wallPos.Length; i++)
- {
- int x = (int)lastLevelConfig.wallPos[i].x;
- int y = (int)lastLevelConfig.wallPos[i].y;
- Destroy(sweets[x, y].gameObject);
- CreateNewSweet(x, y, SweetsType.WALL);
- }
-
- //生成具体对象
- for (int i = 0; i < lastLevelConfig.sweetInfos.Length; i++)
- {
- SweetInfo sweetInfo = lastLevelConfig.sweetInfos[i];
- ColorType color = sweetInfo.color;
- SweetsType type = sweetInfo.type ;
- int x = (int)sweetInfo.sweetPos.x;
- int y = (int)sweetInfo.sweetPos.y;
- DestoryAndCreateSweet(x, y, type, color);
- }
- //填充物品
- public IEnumerator AllFill()
- {
- isMoving = false;
- bool needRefill = true;
- while (needRefill)
- {
- yield return new WaitForSeconds(fillTime);
- while (Fill())
- {
- yield return new WaitForSeconds(fillTime);
- }
- needRefill = ClearAllMatchedSweet();
- }
- CheckGameWin();
- isMoving = false;
- }
- //分布填充
- public bool Fill()
- {
- bool filledNotFinished = false;//判断本次填充是否完成
- for (int y = yRow - 2; y >= 0; y--)
- {
- for (int x = 0; x < xColumn; x++)
- {
- GameSweet sweet = sweets[x, y];//得到当前元素位置的甜品对象
-
- if (sweet.CanMove())//如果无法移动,则无法往下填充
- {
- GameSweet sweetBelow = sweets[x, y + 1];
-
- if (sweetBelow.Type == SweetsType.EMPTY) //垂直填充
- {
- Destroy(sweetBelow.gameObject);
- sweet.MovedComponent.Move(x, y + 1, fillTime);
- sweets[x, y + 1] = sweet;
- CreateNewSweet(x, y, SweetsType.EMPTY);
- filledNotFinished = true;
- }
- else //斜向填充
- {
- for (int down = -1; down <= 1; down++)
- {
- if (down != 0)
- {
- int downX = x + down;
- if (downX >= 0 && downX < xColumn)
- {
- GameSweet downSweet = sweets[downX, y + 1];
- if (downSweet.Type == SweetsType.EMPTY)
- {
- bool canfill = true;//用来判断垂直填充是否可以满足填充要求
- for (int aboveY = y; aboveY >= 0; aboveY--)
- {
- GameSweet sweetAbove = sweets[downX, aboveY];
- if (sweetAbove.CanMove())
- {
- break;
- }
- else if (!sweetAbove.CanMove() && sweetAbove.Type != SweetsType.EMPTY)
- {
- canfill = false;
- break;
- }
- }
-
- if (!canfill)
- {
- Destroy(downSweet.gameObject);
- sweet.MovedComponent.Move(downX, y + 1, fillTime);
- sweets[downX, y + 1] = sweet;
- CreateNewSweet(x, y, SweetsType.EMPTY);
- filledNotFinished = true;
- break;
- }
- }
- }
- }
- }
- }
- }
- }
- }
- //最上排的特殊情况
- for (int x = 0; x < xColumn; x++)
- {
- GameSweet sweet = sweets[x, 0];
- if (sweet.Type == SweetsType.EMPTY)
- {
- GameObject newSweet = Instantiate(sweetPrefabDict[SweetsType.NORMAL], CorrectPosition(x, -1), Quaternion.identity);
- newSweet.transform.parent = transform;
- sweets[x, 0] = newSweet.GetComponent<GameSweet>();
- sweets[x, 0].Init(x, -1, this, SweetsType.NORMAL);
- sweets[x, 0].MovedComponent.Move(x, 0, fillTime);
- sweets[x, 0].ColoredComponent.SetColor((ColorSweet.ColorType)Random.Range(0, sweets[x, 0].ColoredComponent.NumColors));
- filledNotFinished = true;
- }
- }
- return filledNotFinished;
- }

- // 交换两个甜品的方法
- private void ExchangeSweets(GameSweet sweet1, GameSweet sweet2)
- {
- if (sweet1.CanMove() && sweet2.CanMove())
- {
- sweets[sweet1.X, sweet1.Y] = sweet2;
- sweets[sweet2.X, sweet2.Y] = sweet1;
- if (MatchSweets(sweet1, sweet2.X, sweet2.Y) != null ||
- MatchSweets(sweet2, sweet1.X, sweet1.Y) != null || sweet1.Type == SweetsType.RAINBOWCANDY || sweet2.Type == SweetsType.RAINBOWCANDY || itemUseStatus[4])
- {
- int tempX = sweet1.X;
- int tempY = sweet1.Y;
-
- sweet1.MovedComponent.Move(sweet2.X, sweet2.Y, fillTime);
- sweet2.MovedComponent.Move(tempX, tempY, fillTime);
-
- if (sweet1.Type == SweetsType.RAINBOWCANDY && sweet1.CanClear() && sweet2.CanClear())
- {
- ClearColorSweet clearColor = sweet1.GetComponent<ClearColorSweet>();
- if (clearColor != null)
- {
- clearColor.ClearColor = sweet2.ColoredComponent.Color;
-
- }
- ClearSweet(sweet1.X, sweet1.Y);
-
- }
- if (sweet2.Type == SweetsType.RAINBOWCANDY && sweet1.CanClear() && sweet2.CanClear())
- {
- ClearColorSweet clearColor = sweet2.GetComponent<ClearColorSweet>();
- if (clearColor != null)
- {
- clearColor.ClearColor = sweet1.ColoredComponent.Color;
-
- }
- ClearSweet(sweet2.X, sweet2.Y);
-
- }
-
- ClearAllMatchedSweet();
- StartCoroutine(AllFill());
-
- pressedSweet = null;
- enteredSweet = null;
-
- itemUseStatus[4] = false;
- gameStep--;
- }
- else
- {
- sweets[sweet1.X, sweet1.Y] = sweet1;
- sweets[sweet2.X, sweet2.Y] = sweet2;
- }
-
- }
- }

i.先判断对象是否满足匹配方式(三个以上的相同类型对象同行或者同列)
- //匹配方法
- public List<GameSweet> MatchSweets(GameSweet sweet, int newX, int newY)
- {
- if (sweet.CanColor())
- {
- ColorSweet.ColorType color = sweet.ColoredComponent.Color;
- List<GameSweet> matchRowSweets = new List<GameSweet>();
- List<GameSweet> matchLineSweets = new List<GameSweet>();
- List<GameSweet> finishedMathchingSweets = new List<GameSweet>();
-
- //行匹配
- matchRowSweets.Add(sweet);
- //i = 0代表:当前交换目标的左边,i=1代表往右
- for (int i = 0; i <= 1; i++)
- {
- for (int xDistance = 1; xDistance < xColumn; xDistance++)
- {
- int x;//实际要判断的目标
- if (i == 0)
- {
- x = newX - xDistance;//向左边遍历
- }
- else
- {
- x = newX + xDistance;//向右遍历
- }
- if (x < 0 || x >= xColumn)//边界
- {
- break;
- }
- if (sweets[x, newY].CanColor() && sweets[x, newY].ColoredComponent.Color == color)//相邻的颜色与基准颜色相等
- {
- matchRowSweets.Add(sweets[x, newY]);//将符合条件的甜品放入匹配队列
- }
- else
- {
- break;
- }
- }
- }
-
- if (matchRowSweets.Count >= 3)
- {
- for (int i = 0; i < matchRowSweets.Count; i++)
- {
- finishedMathchingSweets.Add(matchRowSweets[i]);
- }
- }
-
- //L T型匹配
- //检查当前行遍历列表中的元素是否大于3
- if (matchRowSweets.Count >= 3)
- {
- for (int i = 0; i < matchRowSweets.Count; i++)
- {
- //循环将满足条件的甜品放入
- //finishedMathchingSweets.Add(matchRowSweets[i]);
-
- //在行匹配列表满足匹配的情况下,每个元素进行列遍历
- //0代表上方,1代表下方
- for (int j = 0; j <= 1; j++)
- {
- //根据基准位置依次往上遍历的距离
- for (int yDistance = 1; yDistance < yRow; yDistance++)
- {
- int y;
- if (j == 0)
- {
- y = newY - yDistance;
- }
- else
- {
- y = newY + yDistance;
- }
- if (y < 0 || y >= yRow)
- {
- break;
- }
-
- if (sweets[matchRowSweets[i].X, y].CanColor() && sweets[matchRowSweets[i].X, y].ColoredComponent.Color == color)
- {
- matchLineSweets.Add(sweets[matchRowSweets[i].X, y]);
- }
- else
- {
- break;
- }
- }
-
- }
- if (matchLineSweets.Count < 2)
- {
- matchLineSweets.Clear();
-
- }
- else
- {
- for (int j = 0; j < matchLineSweets.Count; j++)
- {
- finishedMathchingSweets.Add(matchLineSweets[j]);
- }
- break;
- }
-
- }
-
- }
-
- if (finishedMathchingSweets.Count >= 3)
- {
- return finishedMathchingSweets;
- }
-
- matchRowSweets.Clear();
- matchLineSweets.Clear();
-
- //列匹配
- matchLineSweets.Add(sweet);
-
- //i = 0代表:当前交换目标的左边,i=1代表往右
- for (int i = 0; i <= 1; i++)
- {
- for (int yDistance = 1; yDistance < yRow; yDistance++)
- {
- int y;//实际要判断的目标
- if (i == 0)
- {
- y = newY - yDistance;//向左边遍历
- }
- else
- {
- y = newY + yDistance;//向右遍历
- }
- if (y < 0 || y >= yRow)//边界
- {
- break;
- }
- if (sweets[newX, y].CanColor() && sweets[newX, y].ColoredComponent.Color == color)//相邻的颜色与基准颜色相等
- {
- matchLineSweets.Add(sweets[newX, y]);//将符合条件的甜品放入匹配队列
- }
- else
- {
- break;
- }
- }
- }
-
- if (matchLineSweets.Count >= 3)
- {
- for (int i = 0; i < matchLineSweets.Count; i++)
- {
- finishedMathchingSweets.Add(matchLineSweets[i]);//不一样
- }
- }
- //
- //L T型匹配
- //检查当前行遍历列表中的元素是否大于3
- if (matchLineSweets.Count >= 3)
- {
- for (int i = 0; i < matchLineSweets.Count; i++)
- {
- //循环将满足条件的甜品放入
- //finishedMathchingSweets.Add(matchRowSweets[i]);
-
- //在行匹配列表满足匹配的情况下,每个元素进行列遍历
- //0代表上方,1代表下方
- for (int j = 0; j <= 1; j++)
- {
- //根据基准位置依次往上遍历的距离
- for (int xDistance = 1; xDistance < xColumn; xDistance++)
- {
- int x;
- if (j == 0)
- {
- x = newY - xDistance;
- }
- else
- {
- x = newY + xDistance;
- }
- if (x < 0 || x >= xColumn)
- {
- break;
- }
-
- if (sweets[x, matchLineSweets[i].Y].CanColor() && sweets[x, matchLineSweets[i].Y].ColoredComponent.Color == color)
- {
- matchRowSweets.Add(sweets[x, matchLineSweets[i].Y]);
- }
- else
- {
- break;
- }
- }
-
- }
- if (matchRowSweets.Count < 2)
- {
- matchRowSweets.Clear();
-
- }
- else
- {
- for (int j = 0; j < matchRowSweets.Count; j++)
- {
- finishedMathchingSweets.Add(matchRowSweets[j]);
- }
- break;
- }
-
- }
-
- }
-
- if (finishedMathchingSweets.Count >= 3)
- {
- return finishedMathchingSweets;
- }
-
- }
- //全部都不满足
- return null;
- }

ii.对于满足条件的游戏对象存入匹配数组中,并且进行消除
- //清除方法
- public bool ClearSweet(int x, int y)
- {
- if (sweets[x, y].CanClear() && !sweets[x, y].ClearedComponent.IsClearing)
- {
- hasCleanSweetType[sweets[x, y].Type]++;
- if(sweets[x, y].CanColor())
- hasCleanSweetColor[sweets[x, y].ColoredComponent.Color]++;
-
- sweets[x, y].ClearedComponent.Clear();//调用清除协程
- CreateNewSweet(x, y, SweetsType.EMPTY);
-
- ClearBarrier(x, y);
- return true;
- }
-
- return false;
-
- }

- //消除匹配条件的对象
- private bool ClearAllMatchedSweet()
- {
- bool neewRefill = false;
-
- for (int y = 0; y < yRow; y++)
- {
- for (int x = 0; x < xColumn; x++)
- {
- if (sweets[x, y].CanClear())
- {
- List<GameSweet> matchList = MatchSweets(sweets[x, y], x, y);
-
- if (matchList != null)
- {
- SweetsType specialSweetsType = SweetsType.COUNT;//是否需要产生特殊对象
- //随机对象的位置
- GameSweet randomSweet = matchList[Random.Range(0, matchList.Count)];
- int specialSweetX = randomSweet.X;
- int specialSweetY = randomSweet.Y;
-
- //消除的对象数量为4才生成对象
- if (matchList.Count == 4)
- {
- //int Random.Range(int min,int max)
- //这个得到的是int类型的随机数,需要注意的是,随机数的取值范围包括min,但不包括max;
- specialSweetsType = (SweetsType)Random.Range((int)SweetsType.ROW_CLEAR, (int)SweetsType.COLUMN_CLEAR + 1);
- }
- //5个就产生消除同类型的对象
- else if (matchList.Count >= 5)
- {
- specialSweetsType = SweetsType.RAINBOWCANDY;
- }
- for (int i = 0; i < matchList.Count; i++)
- {
- if (ClearSweet(matchList[i].X, matchList[i].Y))
- {
- neewRefill = true;
- }
- }
-
- if (specialSweetsType != SweetsType.COUNT)
- {
- Destroy(sweets[specialSweetX, specialSweetY].gameObject);
- GameSweet newSweet = CreateNewSweet(specialSweetX, specialSweetY, specialSweetsType);
- if (specialSweetsType == SweetsType.ROW_CLEAR || specialSweetsType == SweetsType.COLUMN_CLEAR && newSweet.CanColor() && matchList[0].CanColor())
- {
- newSweet.ColoredComponent.SetColor(matchList[0].ColoredComponent.Color);
- }
- //特殊类型的产生
- else if (specialSweetsType == SweetsType.RAINBOWCANDY && newSweet.CanColor())
- {
- newSweet.ColoredComponent.SetColor(ColorSweet.ColorType.ANY);
- }
- }
-
- }
- }
- }
- }
- return neewRefill;
-
- }
-

消消乐游戏中一些常见的道具,例如“增加步数”、“增加倒计时”、“改变物体类型”、“强制交换”、“炸弹消除部分区域”、“刷新所有物体”等。此部分的道具都是使用了,Button组件来绑定对应的函数事件来触发道具效果。
- //加5步
- public void AddFiveExtraMoves()
- {
- if (playerData.items[0] <= 0)
- {
- if (countdownCoroutine != null)
- {
- StopCoroutine(countdownCoroutine);
- }
-
- countdownCoroutine = StartCoroutine(CountdownCoroutine());
- return;
- }
- Array.Clear(itemUseStatus, 0, itemUseStatus.Length);
- gameStep += 5;
- //点击使用道具,发送扣除道具
- socketConnector.SendUseItem(1);
- Debug.Log("扣除道具1");
- }

- //石化猫咪
- public void MakeCatStoned()
- {
- if (playerData.items[1] <= 0)
- {
- if (countdownCoroutine != null)
- {
- StopCoroutine(countdownCoroutine);
- }
-
- countdownCoroutine = StartCoroutine(CountdownCoroutine());
- return;
- }
- Array.Clear(itemUseStatus, 0, itemUseStatus.Length);
- itemUseStatus[5] = true;
- socketConnector.SendUseItem(2);
- Debug.Log("扣除道具2");
- }

- //范围炸弹
- public void BombInRange()
- {
- if (playerData.items[4] <= 0)
- {
- if (countdownCoroutine != null)
- {
- StopCoroutine(countdownCoroutine);
- }
-
- countdownCoroutine = StartCoroutine(CountdownCoroutine());
- return;
- }
- Array.Clear(itemUseStatus, 0, itemUseStatus.Length);
- itemUseStatus[6] = true;
- socketConnector.SendUseItem(5);
- Debug.Log("扣除道具5");
- }

ui元素加上动画控制器,控制图片旋转,位移,缩放,不透明等变化。输入账号密码即可登录登录。
选关列表使用到的UI组件整体上与前文提及使用的方法相似,此处的关卡数量是依据本地的json数据,根据关卡数量来生成按钮数量。此时的金币数量和道具数量是依据服务器的socket发送回来的数据读取并显示。
商店界面UI使用的组件也与前文提及的类似,客户端根据服务器发送的道具数量,来生成具体的物品数量,点击购买就会发送对应的物品id到服务器,待服务器确认数据后,发送消息回来客户端,客户端刷新道具数量。
具体参考GitHub - psygames/UnityWebSocket: :whale: The Best Unity WebSocket Plugin for All Platforms.
- // 命名空间
- using UnityWebSocket;
-
- // 创建实例
- string address = "ws://echo.websocket.org";
- WebSocket socket = new WebSocket(address);
-
- // 注册回调
- socket.OnOpen += OnOpen;
- socket.OnClose += OnClose;
- socket.OnMessage += OnMessage;
- socket.OnError += OnError;
-
- // 连接
- socket.ConnectAsync();
-
- // 发送 string 类型数据
- socket.SendAsync(str);
-
- // 或者 发送 byte[] 类型数据(建议使用)
- socket.SendAsync(bytes);
-
- // 关闭连接
- socket.CloseAsync();

发送登录信息
- public void LoginSend(string username,string password)
- {
- Socket_Send("login|" + username + "|"+password);
- }
接收服务器返回的信息
- private void Socket_OnMessage(object sender, MessageEventArgs e)
- {
- Debug.Log(string.Format("Receive: {0}", e.Data));
- gameManager = FindObjectOfType<GameManager>();
- selectLevel = FindObjectOfType<SelectLevel>();
- string[] args = e.Data.Split('|');//则根据‘|’分割成字符串数组
- switch (args[0])
- {
- case "login":
- if (args[1] == "1")
- {
- //登录成功的代码
- SceneManager.LoadScene(2);
- Debug.Log("login successful");
- }
- break;
- }
- }

连接成功后服务器会校验账号密码是否正确,正确则返回确认信息并跳转到第二个关卡,注意关卡跳转期间要保WebSocket脚本不要被销毁,使用DontDestroyOnLoad()函数将脚本保护起来
商店的物品使用金币来购买,客户端需要发送增加金币的函数
- public void SendAddGold(int amount)
- {
- Socket_Send("addgold|" + amount);
- }
金币数量满足购买条件后,发送购买协议到服务器
- public void AddItemSend(string[] str)
- {
- string tempstr = "additem";
- for (int i = 0; i < str.Length; i++)
- {
- tempstr += ( "|" + str[i]);
- }
- Socket_Send(tempstr);
- }
购买成功后,客户端接收数据,并刷新道具数量和金币数量
- case "item":
- for(int i = 0; i < playerData.items.Length;i++)
- {
- if (i + 1 >= args.Length) break;
- //可能有异常,留着以后增强代码健壮性
- playerData.items[i] = int.Parse(args[i + 1]);
- }
- if (gameManager != null && gameManager.IsGaming)
- {
- Debug.Log("刷新道具数据");
- gameManager.SetItems();
-
- }
- if (selectLevel != null && selectLevel.IsRunning)
- {
- Debug.Log("刷新道具数据");
- selectLevel.SetItems();
-
- }
- break;
- case "gold":
- playerData.gold = int.Parse(args[1]);
- if (selectLevel != null && selectLevel.IsRunning)
- {
- Debug.Log("刷新金币");
- selectLevel.SetGold();
-
- }
- break;

使游戏数据能够存入json
- public void SaveConfig(string filePath)
- {
- string json = JsonUtility.ToJson(gameConfig, true);
- System.IO.File.WriteAllText(filePath, json);
- }
- // 保存配置到文件
- string configFilePath = Path.Combine(Application.streamingAssetsPath, "config.json");
- SaveConfig(configFilePath);
读取json,这里有两种方法,一种是System.IO.File.ReadAllText(filePath),另一种是UnityWebRequest.Get(filePath))。使用第一种ReadAllText方法在unity的编辑器模式下运行能正常读取到json文件,当资源被打包成APK后就不能读取到json文件,使用第二种UnityWebRequest.Get(filePath))方法,需要将json文件放置在StreamingAssets文件夹下,在此文件夹的资源不会被压缩,能正常读取到。
- public void LoadConfig(string filePath)
- {
- StartCoroutine(LoadFromStreamingAssets(filePath, (json) =>
- {
- gameConfig = JsonUtility.FromJson<GameConfig>(json);
- Debug.Log("配置加载成功");
- }, (error) =>
- {
- Debug.LogError("配置加载失败:" + error);
- }));
- }
-
- public IEnumerator LoadFromStreamingAssets(string filePath, Action<string> successCallback, Action<string> failCallback)
- {
- using (UnityWebRequest www = UnityWebRequest.Get(filePath))
- {
- yield return www.SendWebRequest();
-
- if (www.result != UnityWebRequest.Result.Success)
- {
- failCallback?.Invoke(www.error);
- }
- else
- {
- successCallback?.Invoke(www.downloadHandler.text);
- }
- }
- }
-
- //public void LoadConfig(string filePath)
- //{
- // if (System.IO.File.Exists(filePath))
- // {
- // string json = System.IO.File.ReadAllText(filePath);
- // gameConfig = JsonUtility.FromJson<GameConfig>(json);
- // Debug.Log("配置加载成功");
- // }
- // else
- // {
- // Debug.LogError("配置文件不存在");
- // }
- //}

json部分关卡数据
- {
- "xColumn": 5,
- "yRow": 5,
- "cookiePos": [],
- "wallPos": [],
- "cookieBlood": [],
- "levelGoalBySweetType": [
- {
- "x": 1.0,
- "y": 70.0
- },
- {
- "x": 2.0,
- "y": 3.0
- }
- ],
- "levelGoalByColor": [],
- "sweetInfos": [
- {
- "type": 3,
- "color": 0,
- "sweetPos": {
- "x": 1.0,
- "y": 2.0
- }
- },
- {
- "type": 4,
- "color": 0,
- "sweetPos": {
- "x": 1.0,
- "y": 3.0
- }
- }
- ]
- },

这里选着接入TapTap的SDK,详情可以查看TapTap的开发者文档。
TapADN SDK 接入指南 | TapTap 开发者文档
- dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- // 加入的依赖库-开始
- implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
- implementation 'io.reactivex.rxjava2:rxjava:2.0.1'
- implementation 'com.squareup.okhttp3:okhttp:3.12.1'
- implementation "com.android.support:appcompat-v7:28.0.0"
- implementation "com.android.support:support-annotations:28.0.0"
- implementation "com.android.support:support-v4:28.0.0"
- implementation "com.github.bumptech.glide:glide:4.9.0"
- implementation 'com.android.support:recyclerview-v7:28.0.0'
- // 加入的依赖库-结束
- // 下面这行是 Unity 的 mainTemplate.gradle 自带的,帮助定位插入位置
- // **DEPS**
- }
无论 Unity 版本都需加入 Android 相关权限申请,在 Project Settings
-> Player
-> Android Tab
-> Publish Settings
-> Build
,勾选Custom Main Manifest
。
将以下更改应用于生成的这个文件: Assets/Plugins/Android/AndroidManifest.xml
如果存在,请移除文件顶部的以下注释:
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN
修改文件内容:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.unity3d.player"
- xmlns:tools="http://schemas.android.com/tools">
-
- <!-- TapAd 必须的权限-开始 -->
- <!-- TargetVersion 31 及以上 通过时,需要该权限) deviceName 和下面的 BLUETOOTEH 互斥-->
- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
- <!-- 广告获取坐标(经度、纬度、精度半径(米)、获取时间 毫秒)精准推送 -->
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- <!-- IMEI 、序列号、MEID 、IMSI 、 ICCID 等信息。TargetSdkVersion 4 以及更高需要申请 -->
- <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
- <!-- TapAd 必须的权限-结束 -->
-
- <!-- TapAd 可选择权限-开始 -->
- <!-- 获取网络状态信息 -->
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
- <!-- 获取安装应用列表 Android 11 及以上版本才需声明,Android 11 以下版本无需申请 -->
- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
- <!-- (targetVersion 31 以下)deviceName 和上面的 BLUETOOTH_CONNECT 互斥-->
- <uses-permission android:name="android.permission.BLUETOOTH"/>
- <!-- 允许应用请求安装软件包 -->
- <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
- <!-- TapAd 可选择权限-结束 -->
- ...

- using TapTap.TapAd
- TapAdSdk.RequestPermissionIfNecessary();
- using TapTap.TapAd
-
- TapAdConfig config = new TapAdConfig.Builder()
- .MediaId(your_media_id) // 必选参数,为 TapADN 注册的媒体 ID
- .MediaName(your_media_name) // 必选参数,为 TapADN 注册的媒体名称
- .MediaKey(your_media_key) // 必选参数,媒体密钥,可以在 TapADN 后台查看(用于传输数据的解密)
- .MediaVersion("1") // 必选参数,默认值 "1"
- .Channel(your__channel) // 必选参数,渠道
- .TapClientId(your_tap_client_id) // 可选参数,TapTap 开发者中心的游戏 Client ID
- .EnableDebugLog(false) // 可选参数,是否打开原生 debug 调试信息输出:true 打开、false 关闭。默认 false 关闭
- .Build();
-
- // CustomControllerWrapper 为实现了 TapTap.TapAd.ICustomController 的类
- // onInitedCallback 为可选回调函数,类型是 System.Action,
- TapAdSdk.Init(config, new CustomControllerWrapper(this), onInitedCallback);
- using TapTap.TapAd
- TapSplashAd _tapSplashAd = null;
- if (TapAdSdk.IsInited == false)
- {
- Debug.Log("TapAd 需要先初始化!");
- return;
- }
- // 释放之前的广告
- if (_tapSplashAd != null)
- {
- _tapSplashAd.Dispose();
- _tapSplashAd = null;
- }
- int adId = YOUR_AD_ID;
- // create AdRequest
- var request = new TapAdRequest.Builder()
- .SpaceId(adId)
- .Build();
- _tapSplashAd = new TapSplashAd(request);
- // SplashAdLoadListener 为实现了 ISplashAdLoadListener 的类
- _tapSplashAd.SetLoadListener(new SplashAdLoadListener(this));
- _tapSplashAd.Load();
- // Splash 加载接口说明
- public interface ISplashAdLoadListener : ICommonLoadListener
- {
- // 当 Splash 加载完毕
- void OnSplashAdLoad(TapSplashAd ad);
- }
- // 通用加载接口说明
- public interface ICommonLoadListener
- {
- // 加载出错回调
- void OnError(int code, string message);
- }

- using TapTap.TapAd
- if (TapAdSdk.IsInited == false)
- {
- Debug.Log("TapAd 需要先初始化!");
- return;
- }
- if (_tapSplashAd != null)
- {
- // SplashInteractionListener 为实现了 ISplashAdInteractionListener 的类
- _tapSplashAd.SetInteractionListener(new SplashInteractionListener(this));
- _tapSplashAd.Show();
- }
- else
- {
- Debug.LogErrorFormat($"[Unity::AD] 未加载好视频,无法播放!");
- }
- // Splash 播放回调接口说明
- public interface ISplashAdInteractionListener : ICommonInteractionListener
- {
- // 点击跳过
- void OnAdSkip(TapSplashAd ad);
-
- // 广告时间到
- void OnAdTimeOver(TapSplashAd ad);
- }
- // 通用播放回调接口说明
- public interface ICommonInteractionListener
- {
-
- }

广告播放完毕可以发放玩家奖励
- public void OnAdClose(TapRewardVideoAd ad)
- {
- example.ShowText("[Unity::AD] {ad.AdType} OnAdClose/n发送了13金币");
- Debug.Log("测试sdk结束输出,发送了13金币");
- example.socketConnector.SendAddGold(13);
-
- }
广告样例:
打开Build Setttings设置,平台选中Android,将需要打包的场景勾选上。
打开Player Settings,在Publishing Settings中点击Keystore Manager生成一个keystore。
设置好后选中你生成的keystore,并且输入密码。
接着配置好打包环境,选着Edit -- Preferences -- External Tools ,配置JDK,SDK,NDK等路径,勾选上方框使用官方推荐的即可。
以上设置完成后即可在Build Settings里面点击Build生成安卓APK安装包。
以上就是简单的介绍了unity制作消消乐的流程,和一些游戏系统里面该有的模块制作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。