当前位置:   article > 正文

Unity制作消消乐游戏_unity 消除

unity 消除

文章目录

目录

文章目录

前言

一、游戏预制体

1.预制体介绍

2.普通类型预制体

二、动画和音效

三、主游戏界面

四、消除玩法

1.确定行列

2.生成具体游戏对象

3.使两个对象能进行交换

4.满足条件进行消除

五、道具制作

六、登录、商店、选关等界面

1.登录界面

2.选关界面

3.商店界面

七、连接服务器实现登录购买等功能

1.使用WebSocket插件连接服务器

2.实现登录功能

3.实现商店购买功能

八、使用json配置游戏关卡

九、广告SDK接入

1.SDK配置

2.权限申请

3.获取广告权限

4.初始化

5.获取广告

6.播放广告

十、打包安卓APK

总结



前言

使用Unity制作一款消消乐,能让开发者了解Unity UI组件的使用,C#编写代码的能力得到提升。


一、游戏预制体

1.预制体介绍

预制体包含几种消除类型的枚举,枚举里面一个字段代表一种消除对象的种类。

  1. public enum SweetsType
  2. {
  3. EMPTY,
  4. NORMAL,
  5. BARRIER,
  6. ROW_CLEAR,
  7. COLUMN_CLEAR,
  8. RAINBOWCANDY,
  9. WALL,
  10. COUNT
  11. }

简单介绍一下,有空类型、普通类型、障碍类型、行消除、列消除和同类型消除。

经常使用的是普通类型消除,这是我们用到的主要交互的游戏预制体。

2.普通类型预制体

在此预制体里面也有一个枚举类型的ColorType作为普通预制体的颜色区分,每种颜色对应一种图片。

  1. public enum ColorType
  2. {
  3. YELLOW,
  4. PURPLE,
  5. RED,
  6. BLUE,
  7. GREEN,
  8. PINK,
  9. ANY,
  10. COUNT
  11. }
  12. public struct ColorSprite
  13. {
  14. public ColorType color;
  15. public Sprite sprite;
  16. }
  17. public ColorSprite[] ColorSprites;

预制体分类后,需要对预制体添加可移动,可按颜色区分,可消除的游戏逻辑。

  1. //移动
  2. public void Move(int newX, int newY, float time)
  3. {
  4. if (moveCoroutine != null)
  5. {
  6. StopCoroutine(moveCoroutine);
  7. }
  8. moveCoroutine = MoveCoroutine(newX, newY, time);
  9. StartCoroutine(moveCoroutine);
  10. }
  11. //设置颜色类型
  12. public void SetColor(ColorType newColor)
  13. {
  14. color = newColor;
  15. if (colorSpriteDict.ContainsKey(newColor))
  16. {
  17. sprite.sprite = colorSpriteDict[newColor];
  18. }
  19. }
  20. //消除
  21. public virtual void Clear()
  22. {
  23. isClearing = true;
  24. GameObject newSweet = Instantiate(gameObject,
  25. sweet.gameManager.CorrectPosition(sweet.X, sweet.Y), Quaternion.identity);
  26. newSweet.GetComponent<ClearSweet>().PlayClearAnimator();
  27. Destroy(gameObject);
  28. }

二、动画和音效

当游戏对象被拖拽至能匹配的位置,被删除时就要播放销毁动画和销毁音效。

使用Animation动画控制器,改变图片的Scale和透明度,以此达到销毁的效果。

当游戏对象匹配成功后,开启销毁函数,使用协程待销毁动画播放完毕之后才开始销毁物体。

在销毁之前,会播放销毁音效和增加玩家的得分。

  1. private IEnumerator ClearCoroutine()
  2. {
  3. Animator animator = GetComponent<Animator>();
  4. if(animator != null )
  5. {
  6. animator.Play(clearAnimation.name);
  7. GameManager.Instance.PlayerScore++;
  8. AudioSource.PlayClipAtPoint(DestoryAudio,transform.position);
  9. yield return new WaitForSeconds(clearAnimation.length);
  10. Destroy(gameObject);
  11. }
  12. }

三、主游戏界面

此部分开始涉及到ui 组件的使用,使用Text,Button,Image等基础组件拼接UI元素,使用Scroll Rect制作道具滑动列表。

Scroll Rect组件使用是要注意一下其结构,根物体挂载Scroll Rect组件,

其子物体viewport挂载一个Mask组件作为限制显示区域的遮罩,

content物体需挂载Grid Layout Group组件限制道具物体的间距以及排列方式。

四、消除玩法

1.确定行列

  1. xColumn = lastLevelConfig.xColumn;
  2. yRow = lastLevelConfig.yRow;

2.生成具体游戏对象

  1. sweets = new GameSweet[xColumn, yRow];
  2. for (int x = 0; x < xColumn; x++)
  3. {
  4. for (int y = 0; y < yRow; y++)
  5. {
  6. //生成墙体
  7. if (IsExistXY(lastLevelConfig.wallPos, x, y))
  8. {
  9. CreateNewSweet(x, y, SweetsType.WALL);
  10. continue;
  11. }
  12. CreateNewSweet(x, y, SweetsType.EMPTY);
  13. }
  14. }
  15. //生成墙体
  16. for (int i = 0; i < lastLevelConfig.wallPos.Length; i++)
  17. {
  18. int x = (int)lastLevelConfig.wallPos[i].x;
  19. int y = (int)lastLevelConfig.wallPos[i].y;
  20. Destroy(sweets[x, y].gameObject);
  21. CreateNewSweet(x, y, SweetsType.WALL);
  22. }
  23. //生成具体对象
  24. for (int i = 0; i < lastLevelConfig.sweetInfos.Length; i++)
  25. {
  26. SweetInfo sweetInfo = lastLevelConfig.sweetInfos[i];
  27. ColorType color = sweetInfo.color;
  28. SweetsType type = sweetInfo.type ;
  29. int x = (int)sweetInfo.sweetPos.x;
  30. int y = (int)sweetInfo.sweetPos.y;
  31. DestoryAndCreateSweet(x, y, type, color);
  32. }
  33. //填充物品
  34. public IEnumerator AllFill()
  35. {
  36. isMoving = false;
  37. bool needRefill = true;
  38. while (needRefill)
  39. {
  40. yield return new WaitForSeconds(fillTime);
  41. while (Fill())
  42. {
  43. yield return new WaitForSeconds(fillTime);
  44. }
  45. needRefill = ClearAllMatchedSweet();
  46. }
  47. CheckGameWin();
  48. isMoving = false;
  49. }
  50. //分布填充
  51. public bool Fill()
  52. {
  53. bool filledNotFinished = false;//判断本次填充是否完成
  54. for (int y = yRow - 2; y >= 0; y--)
  55. {
  56. for (int x = 0; x < xColumn; x++)
  57. {
  58. GameSweet sweet = sweets[x, y];//得到当前元素位置的甜品对象
  59. if (sweet.CanMove())//如果无法移动,则无法往下填充
  60. {
  61. GameSweet sweetBelow = sweets[x, y + 1];
  62. if (sweetBelow.Type == SweetsType.EMPTY) //垂直填充
  63. {
  64. Destroy(sweetBelow.gameObject);
  65. sweet.MovedComponent.Move(x, y + 1, fillTime);
  66. sweets[x, y + 1] = sweet;
  67. CreateNewSweet(x, y, SweetsType.EMPTY);
  68. filledNotFinished = true;
  69. }
  70. else //斜向填充
  71. {
  72. for (int down = -1; down <= 1; down++)
  73. {
  74. if (down != 0)
  75. {
  76. int downX = x + down;
  77. if (downX >= 0 && downX < xColumn)
  78. {
  79. GameSweet downSweet = sweets[downX, y + 1];
  80. if (downSweet.Type == SweetsType.EMPTY)
  81. {
  82. bool canfill = true;//用来判断垂直填充是否可以满足填充要求
  83. for (int aboveY = y; aboveY >= 0; aboveY--)
  84. {
  85. GameSweet sweetAbove = sweets[downX, aboveY];
  86. if (sweetAbove.CanMove())
  87. {
  88. break;
  89. }
  90. else if (!sweetAbove.CanMove() && sweetAbove.Type != SweetsType.EMPTY)
  91. {
  92. canfill = false;
  93. break;
  94. }
  95. }
  96. if (!canfill)
  97. {
  98. Destroy(downSweet.gameObject);
  99. sweet.MovedComponent.Move(downX, y + 1, fillTime);
  100. sweets[downX, y + 1] = sweet;
  101. CreateNewSweet(x, y, SweetsType.EMPTY);
  102. filledNotFinished = true;
  103. break;
  104. }
  105. }
  106. }
  107. }
  108. }
  109. }
  110. }
  111. }
  112. }
  113. //最上排的特殊情况
  114. for (int x = 0; x < xColumn; x++)
  115. {
  116. GameSweet sweet = sweets[x, 0];
  117. if (sweet.Type == SweetsType.EMPTY)
  118. {
  119. GameObject newSweet = Instantiate(sweetPrefabDict[SweetsType.NORMAL], CorrectPosition(x, -1), Quaternion.identity);
  120. newSweet.transform.parent = transform;
  121. sweets[x, 0] = newSweet.GetComponent<GameSweet>();
  122. sweets[x, 0].Init(x, -1, this, SweetsType.NORMAL);
  123. sweets[x, 0].MovedComponent.Move(x, 0, fillTime);
  124. sweets[x, 0].ColoredComponent.SetColor((ColorSweet.ColorType)Random.Range(0, sweets[x, 0].ColoredComponent.NumColors));
  125. filledNotFinished = true;
  126. }
  127. }
  128. return filledNotFinished;
  129. }

3.使两个对象能进行交换

  1. // 交换两个甜品的方法
  2. private void ExchangeSweets(GameSweet sweet1, GameSweet sweet2)
  3. {
  4. if (sweet1.CanMove() && sweet2.CanMove())
  5. {
  6. sweets[sweet1.X, sweet1.Y] = sweet2;
  7. sweets[sweet2.X, sweet2.Y] = sweet1;
  8. if (MatchSweets(sweet1, sweet2.X, sweet2.Y) != null ||
  9. MatchSweets(sweet2, sweet1.X, sweet1.Y) != null || sweet1.Type == SweetsType.RAINBOWCANDY || sweet2.Type == SweetsType.RAINBOWCANDY || itemUseStatus[4])
  10. {
  11. int tempX = sweet1.X;
  12. int tempY = sweet1.Y;
  13. sweet1.MovedComponent.Move(sweet2.X, sweet2.Y, fillTime);
  14. sweet2.MovedComponent.Move(tempX, tempY, fillTime);
  15. if (sweet1.Type == SweetsType.RAINBOWCANDY && sweet1.CanClear() && sweet2.CanClear())
  16. {
  17. ClearColorSweet clearColor = sweet1.GetComponent<ClearColorSweet>();
  18. if (clearColor != null)
  19. {
  20. clearColor.ClearColor = sweet2.ColoredComponent.Color;
  21. }
  22. ClearSweet(sweet1.X, sweet1.Y);
  23. }
  24. if (sweet2.Type == SweetsType.RAINBOWCANDY && sweet1.CanClear() && sweet2.CanClear())
  25. {
  26. ClearColorSweet clearColor = sweet2.GetComponent<ClearColorSweet>();
  27. if (clearColor != null)
  28. {
  29. clearColor.ClearColor = sweet1.ColoredComponent.Color;
  30. }
  31. ClearSweet(sweet2.X, sweet2.Y);
  32. }
  33. ClearAllMatchedSweet();
  34. StartCoroutine(AllFill());
  35. pressedSweet = null;
  36. enteredSweet = null;
  37. itemUseStatus[4] = false;
  38. gameStep--;
  39. }
  40. else
  41. {
  42. sweets[sweet1.X, sweet1.Y] = sweet1;
  43. sweets[sweet2.X, sweet2.Y] = sweet2;
  44. }
  45. }
  46. }

4.满足条件进行消除

i.先判断对象是否满足匹配方式(三个以上的相同类型对象同行或者同列)

  1. //匹配方法
  2. public List<GameSweet> MatchSweets(GameSweet sweet, int newX, int newY)
  3. {
  4. if (sweet.CanColor())
  5. {
  6. ColorSweet.ColorType color = sweet.ColoredComponent.Color;
  7. List<GameSweet> matchRowSweets = new List<GameSweet>();
  8. List<GameSweet> matchLineSweets = new List<GameSweet>();
  9. List<GameSweet> finishedMathchingSweets = new List<GameSweet>();
  10. //行匹配
  11. matchRowSweets.Add(sweet);
  12. //i = 0代表:当前交换目标的左边,i=1代表往右
  13. for (int i = 0; i <= 1; i++)
  14. {
  15. for (int xDistance = 1; xDistance < xColumn; xDistance++)
  16. {
  17. int x;//实际要判断的目标
  18. if (i == 0)
  19. {
  20. x = newX - xDistance;//向左边遍历
  21. }
  22. else
  23. {
  24. x = newX + xDistance;//向右遍历
  25. }
  26. if (x < 0 || x >= xColumn)//边界
  27. {
  28. break;
  29. }
  30. if (sweets[x, newY].CanColor() && sweets[x, newY].ColoredComponent.Color == color)//相邻的颜色与基准颜色相等
  31. {
  32. matchRowSweets.Add(sweets[x, newY]);//将符合条件的甜品放入匹配队列
  33. }
  34. else
  35. {
  36. break;
  37. }
  38. }
  39. }
  40. if (matchRowSweets.Count >= 3)
  41. {
  42. for (int i = 0; i < matchRowSweets.Count; i++)
  43. {
  44. finishedMathchingSweets.Add(matchRowSweets[i]);
  45. }
  46. }
  47. //L T型匹配
  48. //检查当前行遍历列表中的元素是否大于3
  49. if (matchRowSweets.Count >= 3)
  50. {
  51. for (int i = 0; i < matchRowSweets.Count; i++)
  52. {
  53. //循环将满足条件的甜品放入
  54. //finishedMathchingSweets.Add(matchRowSweets[i]);
  55. //在行匹配列表满足匹配的情况下,每个元素进行列遍历
  56. //0代表上方,1代表下方
  57. for (int j = 0; j <= 1; j++)
  58. {
  59. //根据基准位置依次往上遍历的距离
  60. for (int yDistance = 1; yDistance < yRow; yDistance++)
  61. {
  62. int y;
  63. if (j == 0)
  64. {
  65. y = newY - yDistance;
  66. }
  67. else
  68. {
  69. y = newY + yDistance;
  70. }
  71. if (y < 0 || y >= yRow)
  72. {
  73. break;
  74. }
  75. if (sweets[matchRowSweets[i].X, y].CanColor() && sweets[matchRowSweets[i].X, y].ColoredComponent.Color == color)
  76. {
  77. matchLineSweets.Add(sweets[matchRowSweets[i].X, y]);
  78. }
  79. else
  80. {
  81. break;
  82. }
  83. }
  84. }
  85. if (matchLineSweets.Count < 2)
  86. {
  87. matchLineSweets.Clear();
  88. }
  89. else
  90. {
  91. for (int j = 0; j < matchLineSweets.Count; j++)
  92. {
  93. finishedMathchingSweets.Add(matchLineSweets[j]);
  94. }
  95. break;
  96. }
  97. }
  98. }
  99. if (finishedMathchingSweets.Count >= 3)
  100. {
  101. return finishedMathchingSweets;
  102. }
  103. matchRowSweets.Clear();
  104. matchLineSweets.Clear();
  105. //列匹配
  106. matchLineSweets.Add(sweet);
  107. //i = 0代表:当前交换目标的左边,i=1代表往右
  108. for (int i = 0; i <= 1; i++)
  109. {
  110. for (int yDistance = 1; yDistance < yRow; yDistance++)
  111. {
  112. int y;//实际要判断的目标
  113. if (i == 0)
  114. {
  115. y = newY - yDistance;//向左边遍历
  116. }
  117. else
  118. {
  119. y = newY + yDistance;//向右遍历
  120. }
  121. if (y < 0 || y >= yRow)//边界
  122. {
  123. break;
  124. }
  125. if (sweets[newX, y].CanColor() && sweets[newX, y].ColoredComponent.Color == color)//相邻的颜色与基准颜色相等
  126. {
  127. matchLineSweets.Add(sweets[newX, y]);//将符合条件的甜品放入匹配队列
  128. }
  129. else
  130. {
  131. break;
  132. }
  133. }
  134. }
  135. if (matchLineSweets.Count >= 3)
  136. {
  137. for (int i = 0; i < matchLineSweets.Count; i++)
  138. {
  139. finishedMathchingSweets.Add(matchLineSweets[i]);//不一样
  140. }
  141. }
  142. //
  143. //L T型匹配
  144. //检查当前行遍历列表中的元素是否大于3
  145. if (matchLineSweets.Count >= 3)
  146. {
  147. for (int i = 0; i < matchLineSweets.Count; i++)
  148. {
  149. //循环将满足条件的甜品放入
  150. //finishedMathchingSweets.Add(matchRowSweets[i]);
  151. //在行匹配列表满足匹配的情况下,每个元素进行列遍历
  152. //0代表上方,1代表下方
  153. for (int j = 0; j <= 1; j++)
  154. {
  155. //根据基准位置依次往上遍历的距离
  156. for (int xDistance = 1; xDistance < xColumn; xDistance++)
  157. {
  158. int x;
  159. if (j == 0)
  160. {
  161. x = newY - xDistance;
  162. }
  163. else
  164. {
  165. x = newY + xDistance;
  166. }
  167. if (x < 0 || x >= xColumn)
  168. {
  169. break;
  170. }
  171. if (sweets[x, matchLineSweets[i].Y].CanColor() && sweets[x, matchLineSweets[i].Y].ColoredComponent.Color == color)
  172. {
  173. matchRowSweets.Add(sweets[x, matchLineSweets[i].Y]);
  174. }
  175. else
  176. {
  177. break;
  178. }
  179. }
  180. }
  181. if (matchRowSweets.Count < 2)
  182. {
  183. matchRowSweets.Clear();
  184. }
  185. else
  186. {
  187. for (int j = 0; j < matchRowSweets.Count; j++)
  188. {
  189. finishedMathchingSweets.Add(matchRowSweets[j]);
  190. }
  191. break;
  192. }
  193. }
  194. }
  195. if (finishedMathchingSweets.Count >= 3)
  196. {
  197. return finishedMathchingSweets;
  198. }
  199. }
  200. //全部都不满足
  201. return null;
  202. }

ii.对于满足条件的游戏对象存入匹配数组中,并且进行消除

  1. //清除方法
  2. public bool ClearSweet(int x, int y)
  3. {
  4. if (sweets[x, y].CanClear() && !sweets[x, y].ClearedComponent.IsClearing)
  5. {
  6. hasCleanSweetType[sweets[x, y].Type]++;
  7. if(sweets[x, y].CanColor())
  8. hasCleanSweetColor[sweets[x, y].ColoredComponent.Color]++;
  9. sweets[x, y].ClearedComponent.Clear();//调用清除协程
  10. CreateNewSweet(x, y, SweetsType.EMPTY);
  11. ClearBarrier(x, y);
  12. return true;
  13. }
  14. return false;
  15. }
  1. //消除匹配条件的对象
  2. private bool ClearAllMatchedSweet()
  3. {
  4. bool neewRefill = false;
  5. for (int y = 0; y < yRow; y++)
  6. {
  7. for (int x = 0; x < xColumn; x++)
  8. {
  9. if (sweets[x, y].CanClear())
  10. {
  11. List<GameSweet> matchList = MatchSweets(sweets[x, y], x, y);
  12. if (matchList != null)
  13. {
  14. SweetsType specialSweetsType = SweetsType.COUNT;//是否需要产生特殊对象
  15. //随机对象的位置
  16. GameSweet randomSweet = matchList[Random.Range(0, matchList.Count)];
  17. int specialSweetX = randomSweet.X;
  18. int specialSweetY = randomSweet.Y;
  19. //消除的对象数量为4才生成对象
  20. if (matchList.Count == 4)
  21. {
  22. //int Random.Range(int min,int max)
  23. //这个得到的是int类型的随机数,需要注意的是,随机数的取值范围包括min,但不包括max;
  24. specialSweetsType = (SweetsType)Random.Range((int)SweetsType.ROW_CLEAR, (int)SweetsType.COLUMN_CLEAR + 1);
  25. }
  26. //5个就产生消除同类型的对象
  27. else if (matchList.Count >= 5)
  28. {
  29. specialSweetsType = SweetsType.RAINBOWCANDY;
  30. }
  31. for (int i = 0; i < matchList.Count; i++)
  32. {
  33. if (ClearSweet(matchList[i].X, matchList[i].Y))
  34. {
  35. neewRefill = true;
  36. }
  37. }
  38. if (specialSweetsType != SweetsType.COUNT)
  39. {
  40. Destroy(sweets[specialSweetX, specialSweetY].gameObject);
  41. GameSweet newSweet = CreateNewSweet(specialSweetX, specialSweetY, specialSweetsType);
  42. if (specialSweetsType == SweetsType.ROW_CLEAR || specialSweetsType == SweetsType.COLUMN_CLEAR && newSweet.CanColor() && matchList[0].CanColor())
  43. {
  44. newSweet.ColoredComponent.SetColor(matchList[0].ColoredComponent.Color);
  45. }
  46. //特殊类型的产生
  47. else if (specialSweetsType == SweetsType.RAINBOWCANDY && newSweet.CanColor())
  48. {
  49. newSweet.ColoredComponent.SetColor(ColorSweet.ColorType.ANY);
  50. }
  51. }
  52. }
  53. }
  54. }
  55. }
  56. return neewRefill;
  57. }

五、道具制作

消消乐游戏中一些常见的道具,例如“增加步数”、“增加倒计时”、“改变物体类型”、“强制交换”、“炸弹消除部分区域”、“刷新所有物体”等。此部分的道具都是使用了,Button组件来绑定对应的函数事件来触发道具效果。

  1. //加5步
  2. public void AddFiveExtraMoves()
  3. {
  4. if (playerData.items[0] <= 0)
  5. {
  6. if (countdownCoroutine != null)
  7. {
  8. StopCoroutine(countdownCoroutine);
  9. }
  10. countdownCoroutine = StartCoroutine(CountdownCoroutine());
  11. return;
  12. }
  13. Array.Clear(itemUseStatus, 0, itemUseStatus.Length);
  14. gameStep += 5;
  15. //点击使用道具,发送扣除道具
  16. socketConnector.SendUseItem(1);
  17. Debug.Log("扣除道具1");
  18. }
  1. //石化猫咪
  2. public void MakeCatStoned()
  3. {
  4. if (playerData.items[1] <= 0)
  5. {
  6. if (countdownCoroutine != null)
  7. {
  8. StopCoroutine(countdownCoroutine);
  9. }
  10. countdownCoroutine = StartCoroutine(CountdownCoroutine());
  11. return;
  12. }
  13. Array.Clear(itemUseStatus, 0, itemUseStatus.Length);
  14. itemUseStatus[5] = true;
  15. socketConnector.SendUseItem(2);
  16. Debug.Log("扣除道具2");
  17. }
  1. //范围炸弹
  2. public void BombInRange()
  3. {
  4. if (playerData.items[4] <= 0)
  5. {
  6. if (countdownCoroutine != null)
  7. {
  8. StopCoroutine(countdownCoroutine);
  9. }
  10. countdownCoroutine = StartCoroutine(CountdownCoroutine());
  11. return;
  12. }
  13. Array.Clear(itemUseStatus, 0, itemUseStatus.Length);
  14. itemUseStatus[6] = true;
  15. socketConnector.SendUseItem(5);
  16. Debug.Log("扣除道具5");
  17. }

六、登录、商店、选关等界面

1.登录界面

ui元素加上动画控制器,控制图片旋转,位移,缩放,不透明等变化。输入账号密码即可登录登录。

2.选关界面

选关列表使用到的UI组件整体上与前文提及使用的方法相似,此处的关卡数量是依据本地的json数据,根据关卡数量来生成按钮数量。此时的金币数量和道具数量是依据服务器的socket发送回来的数据读取并显示。

3.商店界面

商店界面UI使用的组件也与前文提及的类似,客户端根据服务器发送的道具数量,来生成具体的物品数量,点击购买就会发送对应的物品id到服务器,待服务器确认数据后,发送消息回来客户端,客户端刷新道具数量。

七、连接服务器实现登录购买等功能

1.使用WebSocket插件连接服务器

具体参考GitHub - psygames/UnityWebSocket: :whale: The Best Unity WebSocket Plugin for All Platforms.

  1. // 命名空间
  2. using UnityWebSocket;
  3. // 创建实例
  4. string address = "ws://echo.websocket.org";
  5. WebSocket socket = new WebSocket(address);
  6. // 注册回调
  7. socket.OnOpen += OnOpen;
  8. socket.OnClose += OnClose;
  9. socket.OnMessage += OnMessage;
  10. socket.OnError += OnError;
  11. // 连接
  12. socket.ConnectAsync();
  13. // 发送 string 类型数据
  14. socket.SendAsync(str);
  15. // 或者 发送 byte[] 类型数据(建议使用)
  16. socket.SendAsync(bytes);
  17. // 关闭连接
  18. socket.CloseAsync();

2.实现登录功能

发送登录信息

  1. public void LoginSend(string username,string password)
  2. {
  3. Socket_Send("login|" + username + "|"+password);
  4. }

接收服务器返回的信息

  1. private void Socket_OnMessage(object sender, MessageEventArgs e)
  2. {
  3. Debug.Log(string.Format("Receive: {0}", e.Data));
  4. gameManager = FindObjectOfType<GameManager>();
  5. selectLevel = FindObjectOfType<SelectLevel>();
  6. string[] args = e.Data.Split('|');//则根据‘|’分割成字符串数组
  7. switch (args[0])
  8. {
  9. case "login":
  10. if (args[1] == "1")
  11. {
  12. //登录成功的代码
  13. SceneManager.LoadScene(2);
  14. Debug.Log("login successful");
  15. }
  16. break;
  17. }
  18. }

连接成功后服务器会校验账号密码是否正确,正确则返回确认信息并跳转到第二个关卡,注意关卡跳转期间要保WebSocket脚本不要被销毁,使用DontDestroyOnLoad()函数将脚本保护起来

3.实现商店购买功能

商店的物品使用金币来购买,客户端需要发送增加金币的函数

  1. public void SendAddGold(int amount)
  2. {
  3. Socket_Send("addgold|" + amount);
  4. }

金币数量满足购买条件后,发送购买协议到服务器

  1. public void AddItemSend(string[] str)
  2. {
  3. string tempstr = "additem";
  4. for (int i = 0; i < str.Length; i++)
  5. {
  6. tempstr += ( "|" + str[i]);
  7. }
  8. Socket_Send(tempstr);
  9. }

购买成功后,客户端接收数据,并刷新道具数量和金币数量

  1. case "item":
  2. for(int i = 0; i < playerData.items.Length;i++)
  3. {
  4. if (i + 1 >= args.Length) break;
  5. //可能有异常,留着以后增强代码健壮性
  6. playerData.items[i] = int.Parse(args[i + 1]);
  7. }
  8. if (gameManager != null && gameManager.IsGaming)
  9. {
  10. Debug.Log("刷新道具数据");
  11. gameManager.SetItems();
  12. }
  13. if (selectLevel != null && selectLevel.IsRunning)
  14. {
  15. Debug.Log("刷新道具数据");
  16. selectLevel.SetItems();
  17. }
  18. break;
  19. case "gold":
  20. playerData.gold = int.Parse(args[1]);
  21. if (selectLevel != null && selectLevel.IsRunning)
  22. {
  23. Debug.Log("刷新金币");
  24. selectLevel.SetGold();
  25. }
  26. break;

八、使用json配置游戏关卡

使游戏数据能够存入json

  1. public void SaveConfig(string filePath)
  2. {
  3. string json = JsonUtility.ToJson(gameConfig, true);
  4. System.IO.File.WriteAllText(filePath, json);
  5. }
  6. // 保存配置到文件
  7. string configFilePath = Path.Combine(Application.streamingAssetsPath, "config.json");
  8. SaveConfig(configFilePath);

读取json,这里有两种方法,一种是System.IO.File.ReadAllText(filePath),另一种是UnityWebRequest.Get(filePath))。使用第一种ReadAllText方法在unity的编辑器模式下运行能正常读取到json文件,当资源被打包成APK后就不能读取到json文件,使用第二种UnityWebRequest.Get(filePath))方法,需要将json文件放置在StreamingAssets文件夹下,在此文件夹的资源不会被压缩,能正常读取到。

  1. public void LoadConfig(string filePath)
  2. {
  3. StartCoroutine(LoadFromStreamingAssets(filePath, (json) =>
  4. {
  5. gameConfig = JsonUtility.FromJson<GameConfig>(json);
  6. Debug.Log("配置加载成功");
  7. }, (error) =>
  8. {
  9. Debug.LogError("配置加载失败:" + error);
  10. }));
  11. }
  12. public IEnumerator LoadFromStreamingAssets(string filePath, Action<string> successCallback, Action<string> failCallback)
  13. {
  14. using (UnityWebRequest www = UnityWebRequest.Get(filePath))
  15. {
  16. yield return www.SendWebRequest();
  17. if (www.result != UnityWebRequest.Result.Success)
  18. {
  19. failCallback?.Invoke(www.error);
  20. }
  21. else
  22. {
  23. successCallback?.Invoke(www.downloadHandler.text);
  24. }
  25. }
  26. }
  27. //public void LoadConfig(string filePath)
  28. //{
  29. // if (System.IO.File.Exists(filePath))
  30. // {
  31. // string json = System.IO.File.ReadAllText(filePath);
  32. // gameConfig = JsonUtility.FromJson<GameConfig>(json);
  33. // Debug.Log("配置加载成功");
  34. // }
  35. // else
  36. // {
  37. // Debug.LogError("配置文件不存在");
  38. // }
  39. //}

json部分关卡数据

  1. {
  2. "xColumn": 5,
  3. "yRow": 5,
  4. "cookiePos": [],
  5. "wallPos": [],
  6. "cookieBlood": [],
  7. "levelGoalBySweetType": [
  8. {
  9. "x": 1.0,
  10. "y": 70.0
  11. },
  12. {
  13. "x": 2.0,
  14. "y": 3.0
  15. }
  16. ],
  17. "levelGoalByColor": [],
  18. "sweetInfos": [
  19. {
  20. "type": 3,
  21. "color": 0,
  22. "sweetPos": {
  23. "x": 1.0,
  24. "y": 2.0
  25. }
  26. },
  27. {
  28. "type": 4,
  29. "color": 0,
  30. "sweetPos": {
  31. "x": 1.0,
  32. "y": 3.0
  33. }
  34. }
  35. ]
  36. },

九、广告SDK接入

这里选着接入TapTap的SDK,详情可以查看TapTap的开发者文档。

TapADN SDK 接入指南 | TapTap 开发者文档

1.SDK配置

  • TapADN SDK 从 3.16.3.10 版本开始更新了 glide 的依赖,glide 版本从 4.0.0 更新到了 4.9.0。
  • 3.16.3.17 加入 Android Dependencies 文件(位于 TapAD/Editor/TapAdDependencies.xml) 方便接入 EDM4U(https://github.com/googlesamples/unity-jar-resolver) 的项目方解决 Android 依赖。
  1. dependencies {
  2. implementation fileTree(dir: 'libs', include: ['*.jar'])
  3. // 加入的依赖库-开始
  4. implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
  5. implementation 'io.reactivex.rxjava2:rxjava:2.0.1'
  6. implementation 'com.squareup.okhttp3:okhttp:3.12.1'
  7. implementation "com.android.support:appcompat-v7:28.0.0"
  8. implementation "com.android.support:support-annotations:28.0.0"
  9. implementation "com.android.support:support-v4:28.0.0"
  10. implementation "com.github.bumptech.glide:glide:4.9.0"
  11. implementation 'com.android.support:recyclerview-v7:28.0.0'
  12. // 加入的依赖库-结束
  13. // 下面这行是 Unity 的 mainTemplate.gradle 自带的,帮助定位插入位置
  14. // **DEPS**
  15. }

2.权限申请

无论 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

修改文件内容:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. package="com.unity3d.player"
  5. xmlns:tools="http://schemas.android.com/tools">
  6. <!-- TapAd 必须的权限-开始 -->
  7. <!-- TargetVersion 31 及以上 通过时,需要该权限) deviceName 和下面的 BLUETOOTEH 互斥-->
  8. <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
  9. <!-- 广告获取坐标(经度、纬度、精度半径(米)、获取时间 毫秒)精准推送 -->
  10. <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
  11. <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  12. <!-- IMEI 、序列号、MEID 、IMSI 、 ICCID 等信息。TargetSdkVersion 4 以及更高需要申请 -->
  13. <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
  14. <!-- TapAd 必须的权限-结束 -->
  15. <!-- TapAd 可选择权限-开始 -->
  16. <!-- 获取网络状态信息 -->
  17. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
  18. <!-- 获取安装应用列表 Android 11 及以上版本才需声明,Android 11 以下版本无需申请 -->
  19. <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
  20. <!-- (targetVersion 31 以下)deviceName 和上面的 BLUETOOTH_CONNECT 互斥-->
  21. <uses-permission android:name="android.permission.BLUETOOTH"/>
  22. <!-- 允许应用请求安装软件包 -->
  23. <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
  24. <!-- TapAd 可选择权限-结束 -->
  25. ...

3.获取广告权限

  1. using TapTap.TapAd
  2. TapAdSdk.RequestPermissionIfNecessary();

4.初始化

  1. using TapTap.TapAd
  2. TapAdConfig config = new TapAdConfig.Builder()
  3. .MediaId(your_media_id) // 必选参数,为 TapADN 注册的媒体 ID
  4. .MediaName(your_media_name) // 必选参数,为 TapADN 注册的媒体名称
  5. .MediaKey(your_media_key) // 必选参数,媒体密钥,可以在 TapADN 后台查看(用于传输数据的解密)
  6. .MediaVersion("1") // 必选参数,默认值 "1"
  7. .Channel(your__channel) // 必选参数,渠道
  8. .TapClientId(your_tap_client_id) // 可选参数,TapTap 开发者中心的游戏 Client ID
  9. .EnableDebugLog(false) // 可选参数,是否打开原生 debug 调试信息输出:true 打开、false 关闭。默认 false 关闭
  10. .Build();
  11. // CustomControllerWrapper 为实现了 TapTap.TapAd.ICustomController 的类
  12. // onInitedCallback 为可选回调函数,类型是 System.Action,
  13. TapAdSdk.Init(config, new CustomControllerWrapper(this), onInitedCallback);

5.获取广告

  1. using TapTap.TapAd
  2. TapSplashAd _tapSplashAd = null;
  3. if (TapAdSdk.IsInited == false)
  4. {
  5. Debug.Log("TapAd 需要先初始化!");
  6. return;
  7. }
  8. // 释放之前的广告
  9. if (_tapSplashAd != null)
  10. {
  11. _tapSplashAd.Dispose();
  12. _tapSplashAd = null;
  13. }
  14. int adId = YOUR_AD_ID;
  15. // create AdRequest
  16. var request = new TapAdRequest.Builder()
  17. .SpaceId(adId)
  18. .Build();
  19. _tapSplashAd = new TapSplashAd(request);
  20. // SplashAdLoadListener 为实现了 ISplashAdLoadListener 的类
  21. _tapSplashAd.SetLoadListener(new SplashAdLoadListener(this));
  22. _tapSplashAd.Load();
  23. // Splash 加载接口说明
  24. public interface ISplashAdLoadListener : ICommonLoadListener
  25. {
  26. // 当 Splash 加载完毕
  27. void OnSplashAdLoad(TapSplashAd ad);
  28. }
  29. // 通用加载接口说明
  30. public interface ICommonLoadListener
  31. {
  32. // 加载出错回调
  33. void OnError(int code, string message);
  34. }

6.播放广告

  1. using TapTap.TapAd
  2. if (TapAdSdk.IsInited == false)
  3. {
  4. Debug.Log("TapAd 需要先初始化!");
  5. return;
  6. }
  7. if (_tapSplashAd != null)
  8. {
  9. // SplashInteractionListener 为实现了 ISplashAdInteractionListener 的类
  10. _tapSplashAd.SetInteractionListener(new SplashInteractionListener(this));
  11. _tapSplashAd.Show();
  12. }
  13. else
  14. {
  15. Debug.LogErrorFormat($"[Unity::AD] 未加载好视频,无法播放!");
  16. }
  17. // Splash 播放回调接口说明
  18. public interface ISplashAdInteractionListener : ICommonInteractionListener
  19. {
  20. // 点击跳过
  21. void OnAdSkip(TapSplashAd ad);
  22. // 广告时间到
  23. void OnAdTimeOver(TapSplashAd ad);
  24. }
  25. // 通用播放回调接口说明
  26. public interface ICommonInteractionListener
  27. {
  28. }

广告播放完毕可以发放玩家奖励

  1. public void OnAdClose(TapRewardVideoAd ad)
  2. {
  3. example.ShowText("[Unity::AD] {ad.AdType} OnAdClose/n发送了13金币");
  4. Debug.Log("测试sdk结束输出,发送了13金币");
  5. example.socketConnector.SendAddGold(13);
  6. }

广告样例:

十、打包安卓APK

打开Build Setttings设置,平台选中Android,将需要打包的场景勾选上。

打开Player Settings,在Publishing Settings中点击Keystore Manager生成一个keystore。

设置好后选中你生成的keystore,并且输入密码。

接着配置好打包环境,选着Edit -- Preferences -- External Tools ,配置JDK,SDK,NDK等路径,勾选上方框使用官方推荐的即可。

以上设置完成后即可在Build Settings里面点击Build生成安卓APK安装包。


总结

以上就是简单的介绍了unity制作消消乐的流程,和一些游戏系统里面该有的模块制作。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/370793
推荐阅读
相关标签
  

闽ICP备14008679号