LOADING

- 系统启动中

少前2方糖喵咖馆复刻

前言

  • 记录一下复刻过程,有机会后续做个 3d 的出来。

  • 素材都是直接扒的游戏的,网上有人做出来解密脚本了,直接解密用 AssetStudio 导出就行了。

  • 少扒了几个素材,后期用 ps 抠一下。

  • 先写大致思路,全部实现后填充细节。

过程

前期素材搭建

  • 底座,一切都没有的时候。

  • 基本摆好

  • 这里的水果一开始想全部摆好的,但是发现橙子的不太一样,只能是用常规的方法,一个显示另一个隐藏。

描边效果实现

  • 描边,少前的描边居然是素材实现的,通过脚本控制显示即可。

  • 一开始用的 OnPointerEnter 和 OnPointerExit 实现(虽然 0 引用,脚本上还提示被拦截,但实际上系统会自己调用,这就是 unity 自带的接口),但是部分素材有交叉,有些小 bug,只能是用常规的 Update 每帧检测鼠标悬停物体了,性能方面有些影响。

  • 补充一下,UI 的射线检测可以直接用 raycast,不需要 collider(虽然有 collider 2d),但 3d 射线检测才需要。

  • UI 射线检测一定要注意遮挡关系(没用的关掉它的 image 下的光线投射目标),可以新开简单场景测试脚本。代码如下

点击动效实现

  • 明天再做,正在思考要不要塞到描边里,毕竟已经在描边脚本中获取了那些可被点击的物体了。

  • 塞描边里了。

  • 实现如下:

  • 同样的建表拿物体,只不过不是悬停而是点击

  • 播放逻辑,用的关启动画状态机实现(手动关启动画会重置,代码控制不会重置,所以需要 Fanimator.Play(“Click Animation”, -1, 0f); animator.Play(“Click Animation”, -1, 0f); 实现每次都从头开始)

  • 用协程控制播放,因为要传参,所以不用 invoke() 应该没拼错吧

  • 由于是统一动画,所以对缩放做了些妥协,用父级控制缩放,虽然有点麻烦,但是有效。

  • 最后挂载界面,发现如果手动删除组里数据,会导致显示出错,实际没事,运行就好了。

  • 最后效果展示(左下动画应该是上下震动,后面独立改一下)

游戏核心提示框实现

  • 核心提示框,有一些小的动效,游戏的核心玩法在这里体现,所以核心脚本挂载在这里。

  • 判定的话涉及恢复和点击物体的判定—-这里是早期的简单写法

  • 忘了截图记录了,只记录了每天的最后成果,所以有时间直接贴代码分析吧。

每天成果

代码解释

描边和点击动画

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Linq;

public class outline : MonoBehaviour
{
    [System.Serializable]
    public class ObjectOutlinePair
    {
        public GameObject targetObject; // UI 图像
        public GameObject outlineObject; // 轮廓图像
    }
    public Animator AClick; // 点击时播放的动画的状态机(这里好像不是单纯的animator controller)
    public Animator BClick;

    public List<ObjectOutlinePair> objectOutlineList = new List<ObjectOutlinePair>();
    private Dictionary<GameObject, GameObject> outlineDictionary = new Dictionary<GameObject, GameObject>();

    private GraphicRaycaster raycaster;
    private EventSystem eventSystem;

    private GameObject lastHoveredObject;
    private GameObject currentlyDisplayedOutline;

    private Coroutine currentAnimationCoroutine;

    string[] excludedNames = { "S1", "S2", "S3", "S4", "SS1", "SS2", "SS3", "SS4" };

    void Start()
    {
        // 获取必要的组件
        raycaster = GetComponent<GraphicRaycaster>();
        eventSystem = GetComponent<EventSystem>();
        if (raycaster == null) raycaster = FindObjectOfType<GraphicRaycaster>();
        if (eventSystem == null) eventSystem = FindObjectOfType<EventSystem>();

        // 初始化字典
        foreach (var pair in objectOutlineList)
        {
            if (pair.targetObject != null && pair.outlineObject != null)
            {
                outlineDictionary[pair.targetObject] = pair.outlineObject;
                pair.outlineObject.SetActive(false); // 初始时隐藏所有轮廓

                // 确保目标对象有Raycast Target启用
                Image img = pair.targetObject.GetComponent<Image>();
                if (img != null) img.raycastTarget = true;
            }

            // 添加Animator组件(如果还没有)
            if (AClick != null && pair.outlineObject.GetComponent<Animator>() == null)
            {
                pair.outlineObject.AddComponent<Animator>();

                Addanimation(pair.outlineObject);

/*                Debug.Log("已为" + pair.outlineObject.name + "添加动画控制器");*/
            }

            // 添加Animator组件(如果还没有)
            if (AClick != null && pair.targetObject.GetComponent<Animator>() == null)
            {
                pair.targetObject.AddComponent<Animator>();

                //复制参考的状态机的值.
                Addanimation(pair.targetObject);

/*                Debug.Log("已为" + pair.targetObject.name + "添加动画控制器");*/
            }

            pair.targetObject.GetComponent<Animator>().enabled = false;
            pair.outlineObject.GetComponent<Animator>().enabled = false;
        }
    }

    void Update()
    {
        // 每帧检测鼠标悬停
        CheckHover();

        // 检测鼠标点击
        if (Input.GetMouseButtonDown(0)) // 0 表示左键点击
        {
            CheckClick();
        }
    }

    void CheckHover()
    {
        // 创建指针事件数据
        PointerEventData pointerData = new PointerEventData(eventSystem);
        pointerData.position = Input.mousePosition;

        // 射线检测结果列表
        List<RaycastResult> results = new List<RaycastResult>();

        // 执行射线检测
        raycaster.Raycast(pointerData, results);

        GameObject currentlyHoveredObject = null;

        // 查找第一个在字典中的对象
        foreach (var result in results)
        {
            if (outlineDictionary.ContainsKey(result.gameObject))
            {
                currentlyHoveredObject = result.gameObject;
                break;
            }
        }

        // 如果悬停的对象发生了变化
        if (currentlyHoveredObject != lastHoveredObject)
        {
            // 隐藏之前显示的轮廓
            if (currentlyDisplayedOutline != null)
            {
                currentlyDisplayedOutline.SetActive(false);
                //确保每次都是以(111)开始点击动画的变化,还有协程意外终止导致的状态机没有关闭
                currentlyDisplayedOutline.transform.localScale = new Vector3(1f, 1f, 1f);
                currentlyDisplayedOutline.GetComponent<Animator>().enabled=false;

                currentlyDisplayedOutline = null;
            }

            // 如果悬停在新对象上,显示其轮廓
            if (currentlyHoveredObject != null && outlineDictionary.TryGetValue(currentlyHoveredObject, out GameObject outlineToShow))
            {
                outlineToShow.SetActive(true);
                currentlyDisplayedOutline = outlineToShow;
                Debug.Log("鼠标悬停在: " + currentlyHoveredObject.name);
            }

            // 更新最后悬停的对象
            lastHoveredObject = currentlyHoveredObject;
        }
    }

    void CheckClick()
    {
        // 创建指针事件数据
        PointerEventData pointerData = new PointerEventData(eventSystem);
        pointerData.position = Input.mousePosition;

        // 射线检测结果列表
        List<RaycastResult> results = new List<RaycastResult>();

        // 执行射线检测
        raycaster.Raycast(pointerData, results);

        GameObject currentlyClickedObject = null;

        // 查找第一个在字典中的对象
        foreach (var result in results)
        {
            if (outlineDictionary.ContainsKey(result.gameObject))
            {
                currentlyClickedObject = result.gameObject;
                break;
            }
        }

        // 获取对应的ObjectOutlinePair
        if (outlineDictionary.TryGetValue(currentlyClickedObject, out GameObject ClickToShow))
        {
            // 播放点击动画
            PlayClickAnimation(currentlyClickedObject,ClickToShow);
            Debug.Log("点击了: " + currentlyClickedObject.name);
        }
    }

    void PlayClickAnimation(GameObject currentlyClickedObject,GameObject ClickToShow)
    {
        Animator Fanimator = currentlyClickedObject.GetComponent<Animator>();
        Animator animator = ClickToShow.GetComponent<Animator>();
        if (animator != null)
        {
            // 播放动画
            Fanimator.enabled = true;
            animator.enabled = true;
            Fanimator.Play("Click Animation", -1, 0f);
            animator.Play("Click Animation", -1, 0f);

            if (currentAnimationCoroutine != null)
            {
                StopCoroutine(currentAnimationCoroutine);

                Debug.Log("已结束上一个协程");
                // 重置前一个动画对象的状态
            }

            Debug.Log(currentAnimationCoroutine);
            // 启动协程实现延迟调用
            currentAnimationCoroutine = StartCoroutine(DisableAnimatorsAfterDelay(animator.GetCurrentAnimatorStateInfo(0).length, Fanimator, animator));
        }
        else
        {
            Debug.LogWarning($"物体 {ClickToShow.name} 没有Animator组件,无法播放动画");
        }
    }

    // 协程方法接收参数
    private IEnumerator DisableAnimatorsAfterDelay(float delay, Animator anim1, Animator anim2)
    {
        yield return null;

        yield return new WaitForSeconds(delay);

        anim1.enabled = false;
        anim2.enabled = false;

        currentAnimationCoroutine = null;

        Debug.Log("动画长度" + delay + "动画播放完毕,已禁用Animator组件");
    }

    void Addanimation(GameObject selectedobject)
    {

        if (excludedNames.Contains(selectedobject.name) && BClick != null)
        {
            selectedobject.GetComponent<Animator>().runtimeAnimatorController = BClick.runtimeAnimatorController;
        }
        else
        {
            selectedobject.GetComponent<Animator>().runtimeAnimatorController = AClick.runtimeAnimatorController;
            Debug.Log("特殊动画BClick未部署");
        }


    }
}
  • 效果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 void Start()
    {
        // 获取必要的组件
        raycaster = GetComponent<GraphicRaycaster>();
        eventSystem = GetComponent<EventSystem>();
        if (raycaster == null) raycaster = FindObjectOfType<GraphicRaycaster>();
        if (eventSystem == null) eventSystem = FindObjectOfType<EventSystem>();

        // 初始化字典
        foreach (var pair in objectOutlineList)
        {
            if (pair.targetObject != null && pair.outlineObject != null)
            {
                outlineDictionary[pair.targetObject] = pair.outlineObject;
                pair.outlineObject.SetActive(false); // 初始时隐藏所有轮廓

                // 确保目标对象有Raycast Target启用
                Image img = pair.targetObject.GetComponent<Image>();
                if (img != null) img.raycastTarget = true;
            }

            // 添加Animator组件(如果还没有)
            if (AClick != null && pair.outlineObject.GetComponent<Animator>() == null)
            {
                pair.outlineObject.AddComponent<Animator>();

                Addanimation(pair.outlineObject);

/*                Debug.Log("已为" + pair.outlineObject.name + "添加动画控制器");*/
            }

            // 添加Animator组件(如果还没有)
            if (AClick != null && pair.targetObject.GetComponent<Animator>() == null)
            {
                pair.targetObject.AddComponent<Animator>();

                //复制参考的状态机的值.
                Addanimation(pair.targetObject);

/*                Debug.Log("已为" + pair.targetObject.name + "添加动画控制器");*/
            }

            pair.targetObject.GetComponent<Animator>().enabled = false;
            pair.outlineObject.GetComponent<Animator>().enabled = false;
        }
    }
  • 首先开头的定义变量不解释

  • 该部分为初始化 Ui 的射线检测部件,顺便把物体和对应的轮廓初始化,以及实现点击弹性动画。

  • 思路是轮廓用图片的显示与否来实现,点击弹性动画由单一的缩放值控制,复制一个 animator 组件给所有物体实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
void CheckHover()
    {
        // 创建指针事件数据
        PointerEventData pointerData = new PointerEventData(eventSystem);
        pointerData.position = Input.mousePosition;

        // 射线检测结果列表
        List<RaycastResult> results = new List<RaycastResult>();

        // 执行射线检测
        raycaster.Raycast(pointerData, results);

        GameObject currentlyHoveredObject = null;

        // 查找第一个在字典中的对象
        foreach (var result in results)
        {
            if (outlineDictionary.ContainsKey(result.gameObject))
            {
                currentlyHoveredObject = result.gameObject;
                break;
            }
        }

        // 如果悬停的对象发生了变化
        if (currentlyHoveredObject != lastHoveredObject)
        {
            // 隐藏之前显示的轮廓
            if (currentlyDisplayedOutline != null)
            {
                currentlyDisplayedOutline.SetActive(false);
                //确保每次都是以(111)开始点击动画的变化,还有协程意外终止导致的状态机没有关闭
                currentlyDisplayedOutline.transform.localScale = new Vector3(1f, 1f, 1f);
                currentlyDisplayedOutline.GetComponent<Animator>().enabled=false;

                currentlyDisplayedOutline = null;
            }

            // 如果悬停在新对象上,显示其轮廓
            if (currentlyHoveredObject != null && outlineDictionary.TryGetValue(currentlyHoveredObject, out GameObject outlineToShow))
            {
                outlineToShow.SetActive(true);
                currentlyDisplayedOutline = outlineToShow;
                Debug.Log("鼠标悬停在: " + currentlyHoveredObject.name);
            }

            // 更新最后悬停的对象
            lastHoveredObject = currentlyHoveredObject;
        }
    }
  • update 不解释,只是检测下是否点击

  • 该部分为鼠标悬停检测,主要是获取鼠标位置,然后执行射线检测,找到第一个在轮廓字典中的物体,然后显示其轮廓,并隐藏之前显示的轮廓。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 void CheckClick()
    {
        // 创建指针事件数据
        PointerEventData pointerData = new PointerEventData(eventSystem);
        pointerData.position = Input.mousePosition;

        // 射线检测结果列表
        List<RaycastResult> results = new List<RaycastResult>();

        // 执行射线检测
        raycaster.Raycast(pointerData, results);

        GameObject currentlyClickedObject = null;

        // 查找第一个在字典中的对象
        foreach (var result in results)
        {
            if (outlineDictionary.ContainsKey(result.gameObject))
            {
                currentlyClickedObject = result.gameObject;
                break;
            }
        }

        // 获取对应的ObjectOutlinePair
        if (outlineDictionary.TryGetValue(currentlyClickedObject, out GameObject ClickToShow))
        {
            // 播放点击动画
            PlayClickAnimation(currentlyClickedObject,ClickToShow);
            Debug.Log("点击了: " + currentlyClickedObject.name);
        }
    }
  • 其实是悬停的翻版,因为没有再回头优化,现在看这玩意都多余分出来。

  • 点击动画的实现,主要是获取点击的物体,然后获取对应的轮廓,然后播放动画,这里的动画是由 animator 组件控制的,所以需要先添加 animator 组件,然后复制参考的状态机的值,然后播放动画,最后启动一个协程实现延迟调用,等待动画播放完毕,然后禁用 animator 组件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void PlayClickAnimation(GameObject currentlyClickedObject,GameObject ClickToShow)
    {
        Animator Fanimator = currentlyClickedObject.GetComponent<Animator>();
        Animator animator = ClickToShow.GetComponent<Animator>();
        if (animator != null)
        {
            // 播放动画
            Fanimator.enabled = true;
            animator.enabled = true;
            Fanimator.Play("Click Animation", -1, 0f);
            animator.Play("Click Animation", -1, 0f);

            if (currentAnimationCoroutine != null)
            {
                StopCoroutine(currentAnimationCoroutine);

                Debug.Log("已结束上一个协程");
                // 重置前一个动画对象的状态
            }

            Debug.Log(currentAnimationCoroutine);
            // 启动协程实现延迟调用
            currentAnimationCoroutine = StartCoroutine(DisableAnimatorsAfterDelay(animator.GetCurrentAnimatorStateInfo(0).length, Fanimator, animator));
        }
        else
        {
            Debug.LogWarning($"物体 {ClickToShow.name} 没有Animator组件,无法播放动画");
        }
    }
  • 播放动画真正实现的地方,获取本体和轮廓的 animator 组件,由于运行起来之后播放动画状态在启用和禁用之间不会重置,所以要手动在播放动画前设置初始状态,最后通过协程方式激活

  • 剩下的比较简单,不解释了。

游戏核心玩法

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using System.Linq;
using System.Collections;

public class ImageCombinationDisplay : MonoBehaviour
{
    [Header("可点击物体")]
    [Tooltip("按顺序排列可点击物体序号")]
    public GameObject[] targetObject; // UI 图像

    [Header("图片资源")]
    [Tooltip("按顺序分配对应的图片")]
    public Sprite[] numberSprites = new Sprite[8];

    [Header("UI图片组件")]
    [Tooltip("用于显示组合图片的5个UI Image组件")]
    public Image[] displayImages = new Image[5];

    [Header("预设组合")]
    [Tooltip("预设的五位数组合列表")]
    public string[] presetCombinations = new string[] { "0,1,10,10" };

    [Header("预设蛋糕组合")]
    [Tooltip("预设的蛋糕组合列表")]
    public string[] cakeCombinations = new string[] { "17,18,19" };

    [Header("提示用物体")]
    [Tooltip("按顺序排列控制提示")]
    public GameObject[] tipObject; // 提示

    [Header("动态提示用指针")]
    [Tooltip("按顺序排列控制提示")]
    public GameObject[] DtipObject; // 提示

    [Header("动态提示用物体")]
    [Tooltip("按顺序排列控制提示")]
    public GameObject[] DDtipObject; // 提示

    [Header("三次判断提示用物体")]
    [Tooltip("三次判断控制提示")]
    public GameObject[] threeObject; // 提示

    [Header("三次判断加载提示用物体")]
    [Tooltip("三次判断加载提示")]
    public GameObject[] threeloadObject; // 提示

    [Header("测试用")]
    [Tooltip("在编辑器中测试的组合字符串")]
    public string testCombination = "21356";

    // UI射线检测必需的组件
    private GraphicRaycaster raycaster;
    private EventSystem eventSystem;

    // 存储解析后的数字数组
    private int[] currentDigitArray;

    //特殊三次判定的物品序号
    private int[] excludedObject = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 19};
    //点击帮他人恢复次数
    private int[] OtherexcludedObject = { 22,23,24,25,26,27,28};
    //点击自身恢复次数
    private int[] SpecialexcludedObject = { 1,2,3,4,5,17,18,19};

    // 存储每个物品的剩余点击次数的字典
    private Dictionary<int, int> clickCounts = new Dictionary<int, int>();

    // 记录玩家点击的顺序
    private List<int> playerInputSequence = new List<int>();

    // 当前点击的次数
    private int currentClickedIndex = 0;

    // 当前点击的正确数
    private int currentExpectedIndex = 0;

    //当前点击的错误数
    private int currentErrorIndex = 0;

    // 记录上一次是否执行了蛋糕组合函数
    private bool lastWasCakeCombination = false;

    //记录当前消逝协程动画数(协程具有延后性,像描边那里根据单一值跟踪其实没有用)
    private Dictionary<int, int> AnimateUICount = new Dictionary<int, int>();

    void Start()
    {

        // 初始化点击次数
        InitializeClickCounts();

        // 获取必要的组件(鼠标点击用的射线检测)
        raycaster = GetComponent<GraphicRaycaster>();
        eventSystem = GetComponent<EventSystem>();
        if (raycaster == null) raycaster = FindObjectOfType<GraphicRaycaster>();
        if (eventSystem == null) eventSystem = FindObjectOfType<EventSystem>();

        // 如果设置了测试组合,自动应用
        if (!string.IsNullOrEmpty(testCombination))
        {
            SetImageCombination(testCombination);
        }
        else
        {
            SelectRandomCombination();
        }
    }

    void Update()
    {
        // 检测鼠标点击
        if (Input.GetMouseButtonDown(0)) // 0 表示左键点击
        {
            CheckClick();
        }

        //我也不想用每帧判断,会浪费性能,但是第一时间又没想好用哪个最简单实现蛋糕自动判断
        if(lastWasCakeCombination && currentExpectedIndex >= currentDigitArray.Length)
        {
            ProcessObjectClick(20);
        }
    }

    public void SelectRandomCombination()
    {
        if (presetCombinations == null || presetCombinations.Length == 0)
        {
            Debug.LogError("预设组合列表为空!");
            return;
        }

        // 随机选择一个组合
        int randomIndex = UnityEngine.Random.Range(0, presetCombinations.Length);
        string selectedCombination = presetCombinations[randomIndex];

        // 应用选中的组合
        SetImageCombination(selectedCombination);
    }

    public void SelectRandomcakeCombination()
    {
        if (cakeCombinations == null || cakeCombinations.Length == 0)
        {
            Debug.LogError("预设组合列表为空!");
            return;
        }

        // 随机选择一个组合
        int randomIndex = UnityEngine.Random.Range(0, cakeCombinations.Length);
        string selectedCombination = cakeCombinations[randomIndex];

        // 应用选中的组合
        SetImageCombination(selectedCombination);
    }

    /// <summary>
    /// 设置图片组合
    /// </summary>
    /// <param name="combination">5位数字字符串,每位数字0-7</param>
    public void SetImageCombination(string combination)
    {

        // 验证输入
        if (string.IsNullOrEmpty(combination))
        {
            Debug.LogError("组合不能为空!");
            return;
        }

        // 检查所有图片资源是否已分配
        for (int i = 0; i < numberSprites.Length; i++)
        {
            if (numberSprites[i] == null)
            {
                Debug.LogError($"数字 {i} 对应的图片未分配!");
                return;
            }
        }

        // 检查所有UI Image组件是否已分配
        for (int i = 0; i < displayImages.Length; i++)
        {
            if (displayImages[i] == null)
            {
                Debug.LogError($"显示图片 {i} 的UI组件未分配!");
                return;
            }
        }

        //应用前关掉全部材料图
        for (int i = 0; i < 5;i++)
        {
            displayImages[i].gameObject.SetActive(false);
        }


        // 解析组合字符串
        string[] digitStrings = combination.Split(',');
        currentDigitArray = new int[digitStrings.Length];

        for (int i = 0; i < digitStrings.Length; i++)
        {
            if (!int.TryParse(digitStrings[i], out currentDigitArray[i]))
            {
                Debug.LogError($"无法解析数字: {digitStrings[i]}");
                return;
            }
            Debug.Log($"当前解析数字: {currentDigitArray[i]}");
        }

        // 应用组合
        for (int i = 0; i < currentDigitArray.Length; i++)
        {

            displayImages[i].gameObject.SetActive(true);

            displayImages[i].sprite = numberSprites[currentDigitArray[i]];
        }

        Debug.Log($"成功设置图片组合: {combination}");

        // 重置游戏状态
        ResetGameState();
    }

    void CheckClick()
    {
        // 创建指针事件数据
        PointerEventData pointerData = new PointerEventData(eventSystem);
        pointerData.position = Input.mousePosition;

        // 射线检测结果列表
        List<RaycastResult> results = new List<RaycastResult>();

        // 执行射线检测
        raycaster.Raycast(pointerData, results);


        // 查找点击到的对象是否在targetObject数组中
        foreach (var result in results)
        {
            int objectIndex = Array.IndexOf(targetObject, result.gameObject);

            if (objectIndex >= 0) // 找到了点击的对象
            {
                Debug.Log($"点击了对象: {result.gameObject.name}, 索引: {objectIndex}");

                //独立的,如果是这两个功能更键直接在这里单独传-----因为下面传参是写在判断逻辑里,判断逻辑实现了0次数排除
                if (objectIndex == 20 || objectIndex == 21)
                {
                    //传点击序号给最终确认脚本
                    Finalconfirmation.TriggerObjectClicked(objectIndex);
                }

                // 处理点击逻辑
                ProcessObjectClick(objectIndex);
                break;
            }
        }
    }

    /// <summary>
    /// 处理对象点击逻辑
    /// </summary>
    /// <param name="objectIndex">被点击对象的索引</param>
    void ProcessObjectClick(int objectIndex)
    {
        // 特殊功能键处理
        if (objectIndex == 20) // 完成键
        {
            // 检查是否完成整个序列
            if (currentExpectedIndex >= currentDigitArray.Length)
            {
                Debug.Log("恭喜! 顺序完全正确!");

                // 隐藏所有动态提示
                for (int i = 0; i < DtipObject.Length; i++)
                {
                    if (i < DtipObject.Length) DtipObject[i].SetActive(false);
                    if (i < DDtipObject.Length) DDtipObject[i].SetActive(false);
                }

                // 下一个组合
                if (lastWasCakeCombination)
                {
                    // 上次执行了蛋糕组合,本次必须执行普通组合
                    SelectRandomCombination();
                    lastWasCakeCombination = false;
                }
                else
                {
                    // 50%概率随机选择
                    float randomValue = UnityEngine.Random.value; // 生成0-1之间的随机数

                    if (randomValue < 0.5f)
                    {
                        // 50%概率执行蛋糕组合
                        SelectRandomcakeCombination();
                        lastWasCakeCombination = true;

                        Debug.Log("此次为蛋糕组合");
                    }
                    else
                    {
                        // 50%概率执行普通组合
                        SelectRandomCombination();
                        lastWasCakeCombination = false;
                    }
                }
            }
            else
            {
                Debug.Log("尚未完成当前组合!");
            }
            return;
        }

        if (objectIndex == 21) // 重置键
        {
            ResetGameState();
            return;
        }

        // 记录玩家点击
        playerInputSequence.Add(objectIndex);

        // 追踪点击次数
        currentClickedIndex++;

        if (excludedObject.Contains(objectIndex) || OtherexcludedObject.Contains(objectIndex))
        {
            Debug.Log("当前点击为特殊物品,序号" + objectIndex);

            ParticularJudgment(objectIndex);
        }
        else
        {
            Judgment(objectIndex);
        }

        Debug.Log($"当前点击次数为{currentClickedIndex},当前正确次数为{currentExpectedIndex},当前错误次数为{currentErrorIndex}");

    }

    // 初始化点击次数---用于追踪消逝动画执行次数和三次判定
    void InitializeClickCounts()
    {
        foreach (int id in excludedObject)
        {
            clickCounts[id] = 3; // 初始3次点击机会
        }
        for (int i = 15; i < 20; i++)
        {
            AnimateUICount[i] = 0;//初始每个都0个协程动画
        }
        //咖啡液
        AnimateUICount[10] = 0;

    }

    // 加载动画---点击他人恢复次数
    public void StartSequence(int clickCountsobject,int loadobject,int n,bool YNthree)
    {
        // 激活threeloadObject并播放动画
        threeloadObject[loadobject].SetActive(true);

        // 获取动画组件并播放
        Animator animator = threeloadObject[loadobject].GetComponent<Animator>();
        if (animator != null)
        {
            animator.SetTrigger("Load Animation");

            // 启动协程等待动画完成
            StartCoroutine(WaitForAnimationFinish(animator,clickCountsobject,loadobject, n, YNthree));
        }
        else
        {
            Debug.LogError( threeloadObject[loadobject].name + "上没有Animator组件!");
        }
    }

    // 协程:等待动画播放完成----点击他人供应的
    IEnumerator WaitForAnimationFinish(Animator animator, int clickCountsobject, int loadobject, int n, bool YNthree)
    {
        // 等待动画开始播放
        yield return new WaitForEndOfFrame();

        // 获取当前播放的动画状态信息(层索引默认为0
        AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);

        // 等待动画播放完成(normalizedTime >= 1表示播放完毕)
        yield return new WaitWhile(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1);

        // 动画播放完成后执行的操作
        ExecuteAfterAnimation(clickCountsobject,loadobject, n, YNthree);
    }

    // 动画完成后执行:激活目标对象并禁用源对象
    void ExecuteAfterAnimation(int clickCountsobject, int loadobject, int n,bool YNthree)
    {
        //重置次数
        clickCounts[clickCountsobject] = 3;

        if (YNthree)
        {
            // 激活threeObject
            for (int i = 0; i < 3; i++)
            {
                int index = i + 3 * n;
                if (threeObject[index] != null)
                {
                    threeObject[index].SetActive(true);
                }
                else
                {
                    Debug.LogWarning($"threeObject[{index}] 未赋值!");
                }
            }
        }
        else
        {
            threeObject[2 + 3 * n].SetActive(true);
        }
        //靠他人恢复的重置
        if (clickCountsobject == 10 || clickCountsobject == 15 || clickCountsobject == 16)
        {
            //重置消逝协程动画数
            AnimateUICount[clickCountsobject] = 0;
        }

        // 禁用threeloadObject
        threeloadObject[loadobject].SetActive(false);
    }

    // 协程:等待动画播放完成----为有特殊的加载动画的物体服务(点击自身供应的)
    IEnumerator LittleWaitForAnimationFinish(Animator animator, int objectIndex)
    {
        // 等待动画开始播放
        yield return new WaitForEndOfFrame();

        // 获取当前播放的动画状态信息(层索引默认为0
        AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);

        // 等待动画播放完成(normalizedTime >= 1表示播放完毕)
        yield return new WaitWhile(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1);

        // 动画播放完成后执行的操作
        clickCounts[objectIndex] = 3;

        if (objectIndex < 6)
        {
            // 取消激活threeloadObject
            threeloadObject[objectIndex].SetActive(false);
        }
        else
        {
            //重置消逝协程动画数
            AnimateUICount[objectIndex] = 0;
        }

    }

    //提示版的正负判断逻辑
    void Judgment(int objectIndex)
    {
        // 更新动态提示
        UpdateDynamicTips();


        //传点击序号给最终确认脚本
        Finalconfirmation.TriggerObjectClicked(objectIndex);



        // 检查是否正确
        if (currentExpectedIndex < currentDigitArray.Length &&
            objectIndex == currentDigitArray[currentErrorIndex + currentExpectedIndex])
        {
            // 正确提示显示 - 确保不越界
            if (currentExpectedIndex < currentDigitArray.Length)
            {
                tipObject[currentErrorIndex + currentExpectedIndex].SetActive(true);
            }

            // 正确点击
            currentExpectedIndex++;
            Debug.Log($"正确! 当前进度: {currentExpectedIndex}/{currentDigitArray.Length}");

        }
        else
        {
            // 错误点击提示 - 确保不越界
            if (currentErrorIndex + currentExpectedIndex < currentDigitArray.Length)
            {
                tipObject[5 + currentErrorIndex + currentExpectedIndex].SetActive(true);
            }

            // 错误点击
            currentErrorIndex++;
            Debug.Log("点击错误显示位置为 " + (4 + currentClickedIndex - 1));

        }
    }

    //有加载动画的恢复次数逻辑
    void ParticularJudgment(int objectIndex)
    {
        //此处判断全是硬编码,没有换循环---器械
        if (objectIndex == 22 && AnimateUICount[objectIndex - 12] == 3)
        {
            //clickCountsloadObject3*n
            StartSequence(10,10,4, true);

            Debug.Log($" 咖啡液 点击次数已恢复");

            return;
        }

        if (objectIndex == 27 && AnimateUICount[objectIndex - 12] == 3)
        {
            //clickCountsloadObject3*n
            StartSequence(15, 11, 5, true);

            Debug.Log($" 芝士 点击次数已恢复");

            return;
        }

        if (objectIndex == 28 && AnimateUICount[objectIndex - 12] == 3)
        {
            //clickCountsloadObject3*n
            StartSequence(16, 0, 6, true);

            Debug.Log($" 坚果 点击次数已恢复");

            return;
        }

        //此处判断全是硬编码,没有换循环---水果
        if (objectIndex == 23)
        {
            //clickCountsloadObject2 + 3*n
            StartSequence(9, 9, 0, false);

            Debug.Log($" 蓝莓 点击次数已恢复");

            return;
        }

        if (objectIndex == 24)
        {
            //clickCountsloadObject2 + 3*n
            StartSequence(8, 8, 1, false);

            Debug.Log($" 橙子 点击次数已恢复");

            return;
        }

        if (objectIndex == 25)
        {
            //clickCountsloadObject2 + 3*n
            StartSequence(7, 7, 2, false);

            Debug.Log($" 草莓 点击次数已恢复");

            return;
        }

        if (objectIndex == 26)
        {
            //clickCountsloadObject2 + 3*n
            StartSequence(6, 6, 3, false);

            Debug.Log($" 薄荷 点击次数已恢复");

            return;
        }

        // 处理其他特殊物品----点击自身恢复次数
        if (SpecialexcludedObject.Contains(objectIndex))
        {
            if (clickCounts[objectIndex] == 0)
            {

                //提拉美苏 樱花蛋卷 草莓蛋糕 17 18 19
                //n用来追踪组
                int n = 7;

                for (int i = 17;i<20;i++)
                {
                    if (objectIndex == i && AnimateUICount[objectIndex] == 3)
                    {
                        for (int ii = 0; ii < 3; ii++)
                        {
                            threeObject[ii + 3 * n].SetActive(true);
                        }
                        StartCoroutine(LittleWaitForAnimationFinish(threeObject[3 * n].GetComponent<Animator>(), objectIndex));
                        n = 7;
                    }
                    n++;
                }
                //  牛奶  椰汁 巧克力 1 2 3 4 5
                for (int i = 1; i < 6; i++)
                {
                    if (objectIndex == i)
                    {
                        threeObject[53 - 4 * i].SetActive(true);
                        // 激活threeloadObject并播放动画 11 4 5 6 7
                        threeloadObject[objectIndex].SetActive(true);
                        // 启动特殊协程等待动画完成
                        StartCoroutine(LittleWaitForAnimationFinish(threeloadObject[objectIndex].GetComponent<Animator>(),objectIndex));
                    }

                }

                Debug.Log($"特殊物品 {objectIndex} 点击次数重置为 3");
            }
            else
            {
                if (currentErrorIndex + currentExpectedIndex != currentDigitArray.Length)
                {
                    clickCounts[objectIndex]--;

                    threeJudgement(objectIndex);

                    Judgment(objectIndex);

                    Debug.Log($"特殊物品 {objectIndex} 点击次数减1,剩余: {clickCounts[objectIndex]}");
                }
            }
        }
        else
        {
            // 如果不是SpecialexcludedObject或OtherexcludedObject中的特殊物品,属于普通的三次物品直接进行判定即可---那些水果
            if (currentErrorIndex + currentExpectedIndex != currentDigitArray.Length && !OtherexcludedObject.Contains(objectIndex))
            {

                if (clickCounts[objectIndex] > 0)
                {
                    clickCounts[objectIndex]--;

                    threeJudgement(objectIndex);

                    Judgment(objectIndex);

                    Debug.Log($"其他特殊物品 {objectIndex} 点击次数减1,剩余: {clickCounts[objectIndex]}");
                }
                else
                {
                        Debug.Log($"其他特殊物品 {objectIndex} 点击次数已为0,不再减少");
                }

            }

        }

    }

    //消逝动画赋予逻辑
    void threeJudgement(int objectIndex)
    {
        //咖啡液 排序有问题只能单领出来了---上面注册消逝协程动画数也是单领的
        if (objectIndex == 10)
        {
            //消逝协程动画数
            if (AnimateUICount[objectIndex] != 3)
            {
                StartCoroutine(AnimateUI(threeObject[clickCounts[objectIndex] + 12], objectIndex));
            }
        }

        //n用来追踪组
        int n = 0;

        //三个蛋糕和芝士,坚果
        for (int i = 15; i < 20; i++)
        {
            if (objectIndex == i)
            {
                //消逝协程动画数
                if (AnimateUICount[objectIndex] != 3)
                {
                    StartCoroutine(AnimateUI(threeObject[clickCounts[objectIndex] + 15 + 3 * n],objectIndex));
                }
            }
            n++;
        }

        //蓝莓
        if (objectIndex == 9)
        {
            if (clickCounts[objectIndex] != 0)
            {
                threeObject[clickCounts[objectIndex]].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1].SetActive(true);
            }
            else
            {
                threeObject[clickCounts[objectIndex]].SetActive(false);
            }
        }

        //橙子
        if (objectIndex == 8)
        {
            if (clickCounts[objectIndex] != 0)
            {
                threeObject[clickCounts[objectIndex] + 3].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1 + 3].SetActive(true);
            }
            else
            {
                threeObject[clickCounts[objectIndex] + 3].SetActive(false);
            }
        }

        //草莓
        if (objectIndex == 7)
        {
            if (clickCounts[objectIndex] != 0)
            {
                threeObject[clickCounts[objectIndex] + 6].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1 + 6].SetActive(true);
            }
            else
            {
                threeObject[clickCounts[objectIndex] + 6].SetActive(false);
            }
        }

        //薄荷
        if (objectIndex == 6)
        {
            if (clickCounts[objectIndex] != 0)
            {
                threeObject[clickCounts[objectIndex] + 9].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1 + 9].SetActive(true);
            }
            else
            {
                threeObject[clickCounts[objectIndex] + 9].SetActive(false);
            }
        }

        //巧克力
        if (objectIndex == 5)
        {
                threeObject[clickCounts[objectIndex] + 31].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1 + 31].SetActive(true);
        }

        //椰汁
        if (objectIndex == 4)
        {
            threeObject[clickCounts[objectIndex] + 35].SetActive(false);
            threeObject[clickCounts[objectIndex] - 1 + 35].SetActive(true);
        }

        //
        if (objectIndex == 3)
        {
            threeObject[clickCounts[objectIndex] + 39].SetActive(false);
            threeObject[clickCounts[objectIndex] - 1 + 39].SetActive(true);
        }

        //牛奶
        if (objectIndex == 2)
        {
            threeObject[clickCounts[objectIndex] + 43].SetActive(false);
            threeObject[clickCounts[objectIndex] - 1 + 43].SetActive(true);
        }

        //
        if (objectIndex == 1)
        {
            threeObject[clickCounts[objectIndex] + 47].SetActive(false);
            threeObject[clickCounts[objectIndex] - 1 + 47].SetActive(true);
        }

    }

    //协程:消逝特效动画
    IEnumerator AnimateUI(GameObject targetUI,int objectIndex)
    {

        // 获取RectTransform和CanvasGroup组件
        RectTransform rectTransform = targetUI.GetComponent<RectTransform>();
        CanvasGroup canvasGroup = targetUI.GetComponent<CanvasGroup>();
        //没有canvasgroup自动加
        if (canvasGroup == null)
        {
            canvasGroup = targetUI.AddComponent<CanvasGroup>();
        }

        // 记录初始值
        Vector2 startPosition = rectTransform.anchoredPosition;
        float startAlpha = canvasGroup.alpha;

        // 目标值
        Vector2 targetPosition = startPosition + new Vector2(0, 70f);
        float targetAlpha = 0f;

        // 动画持续时间(可根据需要调整)
        float duration = 0.35f;
        float elapsed = 0f;

        // 线性插值
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float t = elapsed / duration;

            rectTransform.anchoredPosition = Vector2.Lerp(startPosition, targetPosition, t);
            canvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, t);

            yield return null;
        }

        // 恢复初始值
        rectTransform.anchoredPosition = startPosition;
        canvasGroup.alpha = startAlpha;

        targetUI.SetActive(false);

        //消逝协程动画数自增----最高到3
        AnimateUICount[objectIndex]++;
    }

    /// <summary>
    /// 更新动态提示
    /// </summary>
    void UpdateDynamicTips()
    {
        // 显示当前步骤的动态提示 - 确保不越界
        int tipIndex = Mathf.Min(currentErrorIndex + currentExpectedIndex + 1, DtipObject.Length - 1);

        // 隐藏所有动态提示
        if (tipIndex != currentDigitArray.Length)
        {
            for (int i = 0; i < DtipObject.Length; i++)
            {
                if (i < DtipObject.Length) DtipObject[i].SetActive(false);
            }
            for (int i = 0; i < DDtipObject.Length; i++)
            {
                if (i < DDtipObject.Length) DDtipObject[i].SetActive(false);
            }
        }

        if (tipIndex >= 0 && tipIndex < DtipObject.Length && tipIndex < currentDigitArray.Length)
        {
            DtipObject[tipIndex].SetActive(true);
        }
        if (tipIndex >= 0 && tipIndex < DDtipObject.Length && tipIndex < currentDigitArray.Length)
        {
            DDtipObject[tipIndex].SetActive(true);
        }
    }

    /// <summary>
    /// 重置游戏状态
    /// </summary>
    private void ResetGameState()
    {
        // 玩家点击序号顺序其实没啥用(本来想用来校准用,不过直接用正确数确认也行)
        playerInputSequence.Clear();

        // 重置掉三个追踪数
        currentExpectedIndex = 0;
        currentErrorIndex = 0;
        currentClickedIndex = 0;

        // 重置正确与错误提示显示
        for (int i = 0; i < tipObject.Length; i++)
        {
            tipObject[i].SetActive(false);
        }

        // 全部重置指针,防止提前重置
        for (int i = 0; i < DtipObject.Length; i++)
        {
            if (i < DtipObject.Length) DtipObject[i].SetActive(false);
        }
        for (int i = 0; i < DDtipObject.Length; i++)
        {
            if (i < DDtipObject.Length) DDtipObject[i].SetActive(false);
        }

        // 初始指针需要被显示
        if (DtipObject.Length > 0) DtipObject[0].SetActive(true);
        if (DDtipObject.Length > 0) DDtipObject[0].SetActive(true);

    }
}
  • 这个就比较多了,我不想涉及太多脚本通信,能写一起的全写一起了,效果就是游戏做成的样子。由于缺少回头优化,所以很多地方还保留着我最开始的设想,比如多个 if 判断,还有大量的硬编码。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
public void SelectRandomCombination()
    {
        if (presetCombinations == null || presetCombinations.Length == 0)
        {
            Debug.LogError("预设组合列表为空!");
            return;
        }

        // 随机选择一个组合
        int randomIndex = UnityEngine.Random.Range(0, presetCombinations.Length);
        string selectedCombination = presetCombinations[randomIndex];

        // 应用选中的组合
        SetImageCombination(selectedCombination);
    }

    public void SelectRandomcakeCombination()
    {
        if (cakeCombinations == null || cakeCombinations.Length == 0)
        {
            Debug.LogError("预设组合列表为空!");
            return;
        }

        // 随机选择一个组合
        int randomIndex = UnityEngine.Random.Range(0, cakeCombinations.Length);
        string selectedCombination = cakeCombinations[randomIndex];

        // 应用选中的组合
        SetImageCombination(selectedCombination);
    }

    /// <summary>
    /// 设置图片组合
    /// </summary>
    /// <param name="combination">5位数字字符串,每位数字0-7</param>
    public void SetImageCombination(string combination)
    {

        // 验证输入
        if (string.IsNullOrEmpty(combination))
        {
            Debug.LogError("组合不能为空!");
            return;
        }

        // 检查所有图片资源是否已分配
        for (int i = 0; i < numberSprites.Length; i++)
        {
            if (numberSprites[i] == null)
            {
                Debug.LogError($"数字 {i} 对应的图片未分配!");
                return;
            }
        }

        // 检查所有UI Image组件是否已分配
        for (int i = 0; i < displayImages.Length; i++)
        {
            if (displayImages[i] == null)
            {
                Debug.LogError($"显示图片 {i} 的UI组件未分配!");
                return;
            }
        }

        //应用前关掉全部材料图
        for (int i = 0; i < 5;i++)
        {
            displayImages[i].gameObject.SetActive(false);
        }


        // 解析组合字符串
        string[] digitStrings = combination.Split(',');
        currentDigitArray = new int[digitStrings.Length];

        for (int i = 0; i < digitStrings.Length; i++)
        {
            if (!int.TryParse(digitStrings[i], out currentDigitArray[i]))
            {
                Debug.LogError($"无法解析数字: {digitStrings[i]}");
                return;
            }
            Debug.Log($"当前解析数字: {currentDigitArray[i]}");
        }

        // 应用组合
        for (int i = 0; i < currentDigitArray.Length; i++)
        {

            displayImages[i].gameObject.SetActive(true);

            displayImages[i].sprite = numberSprites[currentDigitArray[i]];
        }

        Debug.Log($"成功设置图片组合: {combination}");

        // 重置游戏状态
        ResetGameState();
    }
  • 提前设置好预设组合,随机选择预设组合,通过预设组合的数字对应图片资源,设置图片组合。

  • 提示版是一个重要的体现,他的组合并非固定,由于图片多于 10 个,起初我是用数组来表示,但是数组存在 010101 这样类似的情况会难以分辨,所以没法单纯的用数组来表示,所以我用了字符串来表示,然后通过英文逗号分割来解析。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    void CheckClick()
    {
        // 创建指针事件数据
        PointerEventData pointerData = new PointerEventData(eventSystem);
        pointerData.position = Input.mousePosition;

        // 射线检测结果列表
        List<RaycastResult> results = new List<RaycastResult>();

        // 执行射线检测
        raycaster.Raycast(pointerData, results);


        // 查找点击到的对象是否在targetObject数组中
        foreach (var result in results)
        {
            int objectIndex = Array.IndexOf(targetObject, result.gameObject);

            if (objectIndex >= 0) // 找到了点击的对象
            {
                Debug.Log($"点击了对象: {result.gameObject.name}, 索引: {objectIndex}");

                //独立的,如果是这两个功能更键直接在这里单独传-----因为下面传参是写在判断逻辑里,判断逻辑实现了0次数排除
                if (objectIndex == 20 || objectIndex == 21)
                {
                    //传点击序号给最终确认脚本
                    Finalconfirmation.TriggerObjectClicked(objectIndex);
                }

                // 处理点击逻辑
                ProcessObjectClick(objectIndex);
                break;
            }
        }
    }
  • 同样的射线检测,中间涉及一个脚本通讯,这里 objectIndex == 20 || objectIndex == 21 是确认和删除键。

  • 这一层实现点击判断,通过点击的对象在组中的序号来作为根基,去实现我们想要的其他操作,分辨出点击的是哪个物体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
 /// <summary>
    /// 处理对象点击逻辑
    /// </summary>
    /// <param name="objectIndex">被点击对象的索引</param>
    void ProcessObjectClick(int objectIndex)
    {
        // 特殊功能键处理
        if (objectIndex == 20) // 完成键
        {
            // 检查是否完成整个序列
            if (currentExpectedIndex >= currentDigitArray.Length)
            {
                Debug.Log("恭喜! 顺序完全正确!");

                // 隐藏所有动态提示
                for (int i = 0; i < DtipObject.Length; i++)
                {
                    if (i < DtipObject.Length) DtipObject[i].SetActive(false);
                    if (i < DDtipObject.Length) DDtipObject[i].SetActive(false);
                }

                // 下一个组合
                if (lastWasCakeCombination)
                {
                    // 上次执行了蛋糕组合,本次必须执行普通组合
                    SelectRandomCombination();
                    lastWasCakeCombination = false;
                }
                else
                {
                    // 50%概率随机选择
                    float randomValue = UnityEngine.Random.value; // 生成0-1之间的随机数

                    if (randomValue < 0.5f)
                    {
                        // 50%概率执行蛋糕组合
                        SelectRandomcakeCombination();
                        lastWasCakeCombination = true;

                        Debug.Log("此次为蛋糕组合");
                    }
                    else
                    {
                        // 50%概率执行普通组合
                        SelectRandomCombination();
                        lastWasCakeCombination = false;
                    }
                }
            }
            else
            {
                Debug.Log("尚未完成当前组合!");
            }
            return;
        }

        if (objectIndex == 21) // 重置键
        {
            ResetGameState();
            return;
        }

        // 记录玩家点击
        playerInputSequence.Add(objectIndex);

        // 追踪点击次数
        currentClickedIndex++;

        if (excludedObject.Contains(objectIndex) || OtherexcludedObject.Contains(objectIndex))
        {
            Debug.Log("当前点击为特殊物品,序号" + objectIndex);

            ParticularJudgment(objectIndex);
        }
        else
        {
            Judgment(objectIndex);
        }

        Debug.Log($"当前点击次数为{currentClickedIndex},当前正确次数为{currentExpectedIndex},当前错误次数为{currentErrorIndex}");

    }
  • 点击判断的初步判断层,这里首先处理是否正确完成组合,因为是点击完成键才进行完成检测的,并且插入蛋糕组合,用简单的随机去控制

  • // 记录玩家点击 playerInputSequence.Add(objectIndex); 本来是用来追踪正确与否的,后来改用了正确次数和错误次数两个变量来追踪,所以这个其实已经没用了,不过没删掉,万一有需要的地方呢。

  • 在此处通过判断序号实现特殊判断和普通判断的分割

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
 //提示版的正负判断逻辑
    void Judgment(int objectIndex)
    {
        // 更新动态提示
        UpdateDynamicTips();


        //传点击序号给最终确认脚本
        Finalconfirmation.TriggerObjectClicked(objectIndex);



        // 检查是否正确
        if (currentExpectedIndex < currentDigitArray.Length &&
            objectIndex == currentDigitArray[currentErrorIndex + currentExpectedIndex])
        {
            // 正确提示显示 - 确保不越界
            if (currentExpectedIndex < currentDigitArray.Length)
            {
                tipObject[currentErrorIndex + currentExpectedIndex].SetActive(true);
            }

            // 正确点击
            currentExpectedIndex++;
            Debug.Log($"正确! 当前进度: {currentExpectedIndex}/{currentDigitArray.Length}");

        }
        else
        {
            // 错误点击提示 - 确保不越界
            if (currentErrorIndex + currentExpectedIndex < currentDigitArray.Length)
            {
                tipObject[5 + currentErrorIndex + currentExpectedIndex].SetActive(true);
            }

            // 错误点击
            currentErrorIndex++;
            Debug.Log("点击错误显示位置为 " + (4 + currentClickedIndex - 1));

        }
    }

    //有加载动画的恢复次数逻辑
    void ParticularJudgment(int objectIndex)
    {
        //此处判断全是硬编码,没有换循环---器械
        if (objectIndex == 22 && AnimateUICount[objectIndex - 12] == 3)
        {
            //clickCountsloadObject3*n
            StartSequence(10,10,4, true);

            Debug.Log($" 咖啡液 点击次数已恢复");

            return;
        }

        if (objectIndex == 27 && AnimateUICount[objectIndex - 12] == 3)
        {
            //clickCountsloadObject3*n
            StartSequence(15, 11, 5, true);

            Debug.Log($" 芝士 点击次数已恢复");

            return;
        }

        if (objectIndex == 28 && AnimateUICount[objectIndex - 12] == 3)
        {
            //clickCountsloadObject3*n
            StartSequence(16, 0, 6, true);

            Debug.Log($" 坚果 点击次数已恢复");

            return;
        }

        //此处判断全是硬编码,没有换循环---水果
        if (objectIndex == 23)
        {
            //clickCountsloadObject2 + 3*n
            StartSequence(9, 9, 0, false);

            Debug.Log($" 蓝莓 点击次数已恢复");

            return;
        }

        if (objectIndex == 24)
        {
            //clickCountsloadObject2 + 3*n
            StartSequence(8, 8, 1, false);

            Debug.Log($" 橙子 点击次数已恢复");

            return;
        }

        if (objectIndex == 25)
        {
            //clickCountsloadObject2 + 3*n
            StartSequence(7, 7, 2, false);

            Debug.Log($" 草莓 点击次数已恢复");

            return;
        }

        if (objectIndex == 26)
        {
            //clickCountsloadObject2 + 3*n
            StartSequence(6, 6, 3, false);

            Debug.Log($" 薄荷 点击次数已恢复");

            return;
        }

        // 处理其他特殊物品----点击自身恢复次数
        if (SpecialexcludedObject.Contains(objectIndex))
        {
            if (clickCounts[objectIndex] == 0)
            {

                //提拉美苏 樱花蛋卷 草莓蛋糕 17 18 19
                //n用来追踪组
                int n = 7;

                for (int i = 17;i<20;i++)
                {
                    if (objectIndex == i && AnimateUICount[objectIndex] == 3)
                    {
                        for (int ii = 0; ii < 3; ii++)
                        {
                            threeObject[ii + 3 * n].SetActive(true);
                        }
                        StartCoroutine(LittleWaitForAnimationFinish(threeObject[3 * n].GetComponent<Animator>(), objectIndex));
                        n = 7;
                    }
                    n++;
                }
                //  牛奶  椰汁 巧克力 1 2 3 4 5
                for (int i = 1; i < 6; i++)
                {
                    if (objectIndex == i)
                    {
                        threeObject[53 - 4 * i].SetActive(true);
                        // 激活threeloadObject并播放动画 11 4 5 6 7
                        threeloadObject[objectIndex].SetActive(true);
                        // 启动特殊协程等待动画完成
                        StartCoroutine(LittleWaitForAnimationFinish(threeloadObject[objectIndex].GetComponent<Animator>(),objectIndex));
                    }

                }

                Debug.Log($"特殊物品 {objectIndex} 点击次数重置为 3");
            }
            else
            {
                if (currentErrorIndex + currentExpectedIndex != currentDigitArray.Length)
                {
                    clickCounts[objectIndex]--;

                    threeJudgement(objectIndex);

                    Judgment(objectIndex);

                    Debug.Log($"特殊物品 {objectIndex} 点击次数减1,剩余: {clickCounts[objectIndex]}");
                }
            }
        }
        else
        {
            // 如果不是SpecialexcludedObject或OtherexcludedObject中的特殊物品,属于普通的三次物品直接进行判定即可---那些水果
            if (currentErrorIndex + currentExpectedIndex != currentDigitArray.Length && !OtherexcludedObject.Contains(objectIndex))
            {

                if (clickCounts[objectIndex] > 0)
                {
                    clickCounts[objectIndex]--;

                    threeJudgement(objectIndex);

                    Judgment(objectIndex);

                    Debug.Log($"其他特殊物品 {objectIndex} 点击次数减1,剩余: {clickCounts[objectIndex]}");
                }
                else
                {
                        Debug.Log($"其他特殊物品 {objectIndex} 点击次数已为0,不再减少");
                }

            }

        }

    }
  • 普通判断和特殊判断,这里的逻辑比较复杂,首先物体分为

    1. 点击自己无限提供物体 冰 水 炼乳 焦糖 棉花糖 冰淇凌
    2. 点击自己提供三次物体 咖啡 芝士 坚果 蓝莓 橙子 草莓 薄荷 牛奶 茶 椰汁 巧克力
    3. 点击自己为他人恢复三次次数 咖啡 芝士 坚果 蓝莓 橙子 草莓 薄荷 对应的物体
    4. 点击自己为自己恢复三次次数 牛奶 茶 椰汁 巧克力
  • 普通判断就是只判断是否点击有效,点击正确

  • 特殊判断则将他人恢复和自身恢复物体区分开并调整对应关系,然后判断是否点击有效,点击正确,点击次数减 1,点击次数为 0 则不再减少,以至于恢复。

  • 由于还要涉及动画的不同,动画分为

    1. 有特定加载动画 牛奶 茶 椰汁 巧克力 水
    2. 无特定加载动画 咖啡 芝士 坚果 蓝莓 橙子 草莓
  • 这里其实全部都用等待假进度条恢复次数就好,不过我属于那种实现一种方法又想实现另一种方法的人,所以分成了两种判断。

  • 第一种需要等待液体的加载动画完成才能恢复次数,恢复判定

  • 第二种通过假进度条恢复次数,恢复判定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/// <summary>
    /// 更新动态提示
    /// </summary>
    void UpdateDynamicTips()
    {
        // 显示当前步骤的动态提示 - 确保不越界
        int tipIndex = Mathf.Min(currentErrorIndex + currentExpectedIndex + 1, DtipObject.Length - 1);

        // 隐藏所有动态提示
        if (tipIndex != currentDigitArray.Length)
        {
            for (int i = 0; i < DtipObject.Length; i++)
            {
                if (i < DtipObject.Length) DtipObject[i].SetActive(false);
            }
            for (int i = 0; i < DDtipObject.Length; i++)
            {
                if (i < DDtipObject.Length) DDtipObject[i].SetActive(false);
            }
        }

        if (tipIndex >= 0 && tipIndex < DtipObject.Length && tipIndex < currentDigitArray.Length)
        {
            DtipObject[tipIndex].SetActive(true);
        }
        if (tipIndex >= 0 && tipIndex < DDtipObject.Length && tipIndex < currentDigitArray.Length)
        {
            DDtipObject[tipIndex].SetActive(true);
        }
    }

    /// <summary>
    /// 重置游戏状态
    /// </summary>
    private void ResetGameState()
    {
        // 玩家点击序号顺序其实没啥用(本来想用来校准用,不过直接用正确数确认也行)
        playerInputSequence.Clear();

        // 重置掉三个追踪数
        currentExpectedIndex = 0;
        currentErrorIndex = 0;
        currentClickedIndex = 0;

        // 重置正确与错误提示显示
        for (int i = 0; i < tipObject.Length; i++)
        {
            tipObject[i].SetActive(false);
        }

        // 全部重置指针,防止提前重置
        for (int i = 0; i < DtipObject.Length; i++)
        {
            if (i < DtipObject.Length) DtipObject[i].SetActive(false);
        }
        for (int i = 0; i < DDtipObject.Length; i++)
        {
            if (i < DDtipObject.Length) DDtipObject[i].SetActive(false);
        }

        // 初始指针需要被显示
        if (DtipObject.Length > 0) DtipObject[0].SetActive(true);
        if (DDtipObject.Length > 0) DDtipObject[0].SetActive(true);

    }
}
  • 更新动态提示,重置游戏状态,重置三个追踪数,重置正确与错误提示显示,全部重置指针,防止提前重置,初始指针需要被显示。

  • 这里逻辑就很简单了

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
 //消逝动画赋予逻辑
    void threeJudgement(int objectIndex)
    {
        //咖啡液 排序有问题只能单领出来了---上面注册消逝协程动画数也是单领的
        if (objectIndex == 10)
        {
            //消逝协程动画数
            if (AnimateUICount[objectIndex] != 3)
            {
                StartCoroutine(AnimateUI(threeObject[clickCounts[objectIndex] + 12], objectIndex));
            }
        }

        //n用来追踪组
        int n = 0;

        //三个蛋糕和芝士,坚果
        for (int i = 15; i < 20; i++)
        {
            if (objectIndex == i)
            {
                //消逝协程动画数
                if (AnimateUICount[objectIndex] != 3)
                {
                    StartCoroutine(AnimateUI(threeObject[clickCounts[objectIndex] + 15 + 3 * n],objectIndex));
                }
            }
            n++;
        }

        //蓝莓
        if (objectIndex == 9)
        {
            if (clickCounts[objectIndex] != 0)
            {
                threeObject[clickCounts[objectIndex]].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1].SetActive(true);
            }
            else
            {
                threeObject[clickCounts[objectIndex]].SetActive(false);
            }
        }

        //橙子
        if (objectIndex == 8)
        {
            if (clickCounts[objectIndex] != 0)
            {
                threeObject[clickCounts[objectIndex] + 3].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1 + 3].SetActive(true);
            }
            else
            {
                threeObject[clickCounts[objectIndex] + 3].SetActive(false);
            }
        }

        //草莓
        if (objectIndex == 7)
        {
            if (clickCounts[objectIndex] != 0)
            {
                threeObject[clickCounts[objectIndex] + 6].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1 + 6].SetActive(true);
            }
            else
            {
                threeObject[clickCounts[objectIndex] + 6].SetActive(false);
            }
        }

        //薄荷
        if (objectIndex == 6)
        {
            if (clickCounts[objectIndex] != 0)
            {
                threeObject[clickCounts[objectIndex] + 9].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1 + 9].SetActive(true);
            }
            else
            {
                threeObject[clickCounts[objectIndex] + 9].SetActive(false);
            }
        }

        //巧克力
        if (objectIndex == 5)
        {
                threeObject[clickCounts[objectIndex] + 31].SetActive(false);
                threeObject[clickCounts[objectIndex] - 1 + 31].SetActive(true);
        }

        //椰汁
        if (objectIndex == 4)
        {
            threeObject[clickCounts[objectIndex] + 35].SetActive(false);
            threeObject[clickCounts[objectIndex] - 1 + 35].SetActive(true);
        }

        //茶
        if (objectIndex == 3)
        {
            threeObject[clickCounts[objectIndex] + 39].SetActive(false);
            threeObject[clickCounts[objectIndex] - 1 + 39].SetActive(true);
        }

        //牛奶
        if (objectIndex == 2)
        {
            threeObject[clickCounts[objectIndex] + 43].SetActive(false);
            threeObject[clickCounts[objectIndex] - 1 + 43].SetActive(true);
        }

        //水
        if (objectIndex == 1)
        {
            threeObject[clickCounts[objectIndex] + 47].SetActive(false);
            threeObject[clickCounts[objectIndex] - 1 + 47].SetActive(true);
        }

    }

    //协程:消逝特效动画
    IEnumerator AnimateUI(GameObject targetUI,int objectIndex)
    {

        // 获取RectTransform和CanvasGroup组件
        RectTransform rectTransform = targetUI.GetComponent<RectTransform>();
        CanvasGroup canvasGroup = targetUI.GetComponent<CanvasGroup>();
        //没有canvasgroup自动加
        if (canvasGroup == null)
        {
            canvasGroup = targetUI.AddComponent<CanvasGroup>();
        }

        // 记录初始值
        Vector2 startPosition = rectTransform.anchoredPosition;
        float startAlpha = canvasGroup.alpha;

        // 目标值
        Vector2 targetPosition = startPosition + new Vector2(0, 70f);
        float targetAlpha = 0f;

        // 动画持续时间(可根据需要调整)
        float duration = 0.35f;
        float elapsed = 0f;

        // 线性插值
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float t = elapsed / duration;

            rectTransform.anchoredPosition = Vector2.Lerp(startPosition, targetPosition, t);
            canvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, t);

            yield return null;
        }

        // 恢复初始值
        rectTransform.anchoredPosition = startPosition;
        canvasGroup.alpha = startAlpha;

        targetUI.SetActive(false);

        //消逝协程动画数自增----最高到3
        AnimateUICount[objectIndex]++;
    }
  • 消逝动画的添加以及其协程的添加

  • 消逝动画并没有采用添加 animator 的方式实现,而是采用了直接通过脚本控制 canvasgroup 的 alpha 值来实现的,这样做的好处是可以更灵活地控制动画效果,比如可以设置不同的动画时间,不同的动画曲线等。

  • 小问题在于协程延后的问题,通过单一变量来追踪协程的话会出现,快速点击时,第一次的协程被追踪,其他的协程也已经开始,最终协程追踪第三次点击的协程,当点击到恢复次数时,会出现第二次协程未完毕的情况,,应该是 yield return null;延迟一帧的锅吧,并没有排查到具体原因,当时采用这种变量自增来跟踪 AnimateUICount[objectIndex]++;避免了

  • 小小缺点就是还得想法实现中间点击也能恢复次数,因为加了这个消逝动画三次恢复次数的判断用的这个变量。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
    // 加载动画---点击他人恢复次数
    public void StartSequence(int clickCountsobject,int loadobject,int n,bool YNthree)
    {
        // 激活threeloadObject并播放动画
        threeloadObject[loadobject].SetActive(true);

        // 获取动画组件并播放
        Animator animator = threeloadObject[loadobject].GetComponent<Animator>();
        if (animator != null)
        {
            animator.SetTrigger("Load Animation");

            // 启动协程等待动画完成
            StartCoroutine(WaitForAnimationFinish(animator,clickCountsobject,loadobject, n, YNthree));
        }
        else
        {
            Debug.LogError( threeloadObject[loadobject].name + "上没有Animator组件!");
        }
    }

    // 协程:等待动画播放完成----点击他人供应的
    IEnumerator WaitForAnimationFinish(Animator animator, int clickCountsobject, int loadobject, int n, bool YNthree)
    {
        // 等待动画开始播放
        yield return new WaitForEndOfFrame();

        // 获取当前播放的动画状态信息(层索引默认为0
        AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);

        // 等待动画播放完成(normalizedTime >= 1表示播放完毕)
        yield return new WaitWhile(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1);

        // 动画播放完成后执行的操作
        ExecuteAfterAnimation(clickCountsobject,loadobject, n, YNthree);
    }

    // 动画完成后执行:激活目标对象并禁用源对象
    void ExecuteAfterAnimation(int clickCountsobject, int loadobject, int n,bool YNthree)
    {
        //重置次数
        clickCounts[clickCountsobject] = 3;

        if (YNthree)
        {
            // 激活threeObject
            for (int i = 0; i < 3; i++)
            {
                int index = i + 3 * n;
                if (threeObject[index] != null)
                {
                    threeObject[index].SetActive(true);
                }
                else
                {
                    Debug.LogWarning($"threeObject[{index}] 未赋值!");
                }
            }
        }
        else
        {
            threeObject[2 + 3 * n].SetActive(true);
        }
        //靠他人恢复的重置
        if (clickCountsobject == 10 || clickCountsobject == 15 || clickCountsobject == 16)
        {
            //重置消逝协程动画数
            AnimateUICount[clickCountsobject] = 0;
        }

        // 禁用threeloadObject
        threeloadObject[loadobject].SetActive(false);
    }

    // 协程:等待动画播放完成----为有特殊的加载动画的物体服务(点击自身供应的)
    IEnumerator LittleWaitForAnimationFinish(Animator animator, int objectIndex)
    {
        // 等待动画开始播放
        yield return new WaitForEndOfFrame();

        // 获取当前播放的动画状态信息(层索引默认为0
        AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);

        // 等待动画播放完成(normalizedTime >= 1表示播放完毕)
        yield return new WaitWhile(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1);

        // 动画播放完成后执行的操作
        clickCounts[objectIndex] = 3;

        if (objectIndex < 6)
        {
            // 取消激活threeloadObject
            threeloadObject[objectIndex].SetActive(false);
        }
        else
        {
            //重置消逝协程动画数
            AnimateUICount[objectIndex] = 0;
        }

    }
  • 这里的加载动画的实现,主要是通过激活 threeloadObject 并播放动画,然后等待动画播放完成,再执行动画完成后的操作,这里的动画播放完成的判断是通过 normalizedTime 来实现的,当 normalizedTime 等于 1 时,表示动画播放完毕。

  • 动画播放完成后执行的操作主要是激活目标对象并禁用源对象,这里的目标对象是 threeObject,根据点击的序号来确定三个物体的位置,并激活。

  • 比较简单不做追叙。

追加动画逻辑

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class Finalconfirmation : MonoBehaviour
{
    [Header("成品一层")]
    [Tooltip("按顺序排列序号")]
    public GameObject[] targetObjectOne; // UI 图像

    [Header("成品二层")]
    [Tooltip("按顺序排列序号")]
    public GameObject[] targetObjectTwo; // UI 图像

    [Header("成品三层")]
    [Tooltip("按顺序排列序号")]
    public GameObject[] targetObjectThree; // UI 图像

    [Header("成品特殊材料")]
    [Tooltip("按顺序排列序号")]
    public GameObject[] particularObject; // UI 图像

    [Header("成品")]
    [Tooltip("按顺序排列序号")]
    public GameObject[] finallyObject; // UI 图像


    //委托事件
    public static event Action<int> OnObjectClicked;

    // 存储已激活的对象用的列表,以便后续禁用
    private List<GameObject> activatedObjects = new List<GameObject>();

    int currentLayer = 1;

    bool firstice = false;


    // 启用时订阅事件------使用 OnEnable 和 OnDisable 来订阅和取消订阅事件
    void OnEnable()
    {
        OnObjectClicked += HandleObjectClicked;
    }

    // 禁用时取消订阅事件
    void OnDisable()
    {
        OnObjectClicked -= HandleObjectClicked;
    }

    //静态方法只负责触发事件,不包含业务逻辑
    public static void TriggerObjectClicked(int objectIndex)
    {
        OnObjectClicked?.Invoke(objectIndex);
    }

    IEnumerator fadeIce(GameObject iceobjecet)
    {
        CanvasGroup canvasGroup = iceobjecet.GetComponent<CanvasGroup>();
        float startAlpha = canvasGroup.alpha;
        float targetAlpha = 0f;
        // 动画持续时间(可根据需要调整)
        float duration = 0.35f;
        float elapsed = 0f;

        // 线性插值
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float t = elapsed / duration;

            canvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, t);

            yield return null;
        }

        canvasGroup.alpha = startAlpha;

        Debug.Log("动画完成");

        iceobjecet.SetActive(false);
    }

    IEnumerator fadeAll(GameObject allobjecet,int objectIndex)
    {

        if (objectIndex == 20 && currentLayer == 4)
        {
            finallyObject[1].SetActive(true);
            finallyObject[2].SetActive(true);

            yield return new WaitForSeconds(finallyObject[2].GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).length);
        }

        RectTransform rectTransform = allobjecet.GetComponent<RectTransform>();
        CanvasGroup canvasGroup = allobjecet.GetComponent<CanvasGroup>();

        Vector2 startPosition = rectTransform.anchoredPosition;
        float startAlpha = canvasGroup.alpha;

        Vector2 targetPosition = startPosition + new Vector2(0, 70f);

        float targetAlpha = 0f;
        // 动画持续时间(可根据需要调整)
        float duration = 0.35f;
        float elapsed = 0f;

        // 线性插值
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float t = elapsed / duration;

            rectTransform.anchoredPosition = Vector2.Lerp(startPosition, targetPosition, t);
            canvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, t);

            yield return null;
        }

        rectTransform.anchoredPosition = startPosition;
        canvasGroup.alpha = startAlpha;

        allobjecet.SetActive(false);


        //杯套杯盖
        finallyObject[1].SetActive(false);
        finallyObject[2].SetActive(false);

        // 禁用所有之前激活的对象
        foreach (GameObject obj in activatedObjects)
        {
            if (obj != null)
            {
                obj.SetActive(false);
            }
        }

        //重置值
        firstice = false;

        // 清空列表
        activatedObjects.Clear();

        currentLayer = 1;


        allobjecet.SetActive(true);

        Debug.Log("最终动画完成");
    }

    public void HandleObjectClicked(int objectIndex)
    {
        //冰初次特殊处理
        if(objectIndex == 0 && !firstice)
        {
            particularObject[10].SetActive(true);

            activatedObjects.Add(particularObject[10]);

            firstice = true;

            return;
        }

        if (objectIndex < 17 && objectIndex != 0)
        {
            //独立的错误提示
            particularObject[11].SetActive(false);

            GameObject[] currentArray = null;

            // 根据当前层数选择对应的数组
            switch (currentLayer)
            {
                case 1:
                    currentArray = targetObjectOne;
                    break;
                case 2:
                    currentArray = targetObjectTwo;
                    break;
                case 3:
                    currentArray = targetObjectThree;
                    break;
            }


            if (objectIndex > 10 && objectIndex < 17)
            {
                if (currentLayer == 4)
                {
                    particularObject[objectIndex - 7].SetActive(true);

                    activatedObjects.Add(particularObject[objectIndex - 7]);

                    return;
                }
                else
                {
                    //独立的错误提示
                    particularObject[11].SetActive(true);

                    return;
                }
            }


            if (currentArray != null)
            {
                //冰特殊处理
                if(firstice)
                {
                    Debug.Log("有首次冰存在");

                    StartCoroutine(fadeIce(particularObject[10]));

                    //手动关一下前面的冰免得重影
                    if(currentLayer == 2)
                    {
                        StartCoroutine(fadeIce(targetObjectOne[0]));
                    }
                    else if(currentLayer == 3)
                    {
                        StartCoroutine(fadeIce(targetObjectTwo[0]));
                    }

                    currentArray[0].SetActive(true);

                    activatedObjects.Add(currentArray[0]);

                }

                //固定层的物体额外启用
                if (objectIndex > 5 && objectIndex < 10)
                {
                    particularObject[objectIndex - 6].SetActive(true);

                    activatedObjects.Add(particularObject[objectIndex - 6]);
                }

                currentArray[objectIndex].SetActive(true);

                activatedObjects.Add(currentArray[objectIndex]);

                // 执行点击成功后的操作
                Debug.Log($"层 {currentLayer} 中的对象 {objectIndex} 被点击");

                // 增加层数,但不超过3层----超过三层就空了----空了就不执行
                if (currentLayer < 4)
                {
                    currentLayer++;
                }
            }
            else
            {
                // 执行点击失败后的操作
                Debug.Log($"层 {currentLayer} 中的对象 {objectIndex} 没有被设置最终图");
            }
        }

        if(objectIndex == 20 || objectIndex == 21)
        {
            StartCoroutine(fadeAll(finallyObject[0], objectIndex));
        }
    }
}
  • 直接通过委托事件获取点击的物体的序号来触发点击事件,并在点击事件中执行具体的业务逻辑。

  • 冰比较特殊,首次点击和后面的点击动画不一样,所以要拆分出来

  • 薄荷等水果也比较特殊,是固定位置的特效,所以同样单独处理

  • 由于杯子最多填充三次液体,所以液体类只能追加三层

  • 这里忘了咖啡也比较特殊,当咖啡在二三层时,需要额外激活一段小动画,所以也要单独处理下,不过由于忘了,所以没加

总结

  • 大部分动画都是用的启用即播放这一特点,一个物体承载动画,当该物体被启用时播放。

  • 通过复制同一物体或脚本复制 animator 值来实现动画的复用。

  • 协程的使用可以实现多次点击动画,以及延迟的功能

  • 由于开始不希望涉及脚本间通信,所以游戏核心脚本有点臃肿了,个人回头看都感觉有点难受

  • BGM 和音效的添加就很简单了,由于只是练练手防止手生,就不加这些功能了。

  • 耗时一周左右,中间有搁置,因为打上游戏了,闲的没事干抽空写写,加起来大概一千多行的小游戏,不过还是很有意思的。

Made with ❤️ by KKPT
最后更新于 Aug 31, 2025 23:01 +0800
使用 Hugo 构建
主题 StackJimmy 设计