前言
解释:
之前用的ScreenToGif录屏,因为上传的.gif最大不超过5MB,所以做了不少删帧和色彩弱化等处理,这才导致色彩看上去不是很舒服,不要盯着它看太久,不然会有点难受...
由于使用的素材是现成的,没有花时间精力去扒,所以细节方面可能不是做的很好。
说明:
这次的甜品消消乐虽然看上去好像挺简单普通的,但是相对于之前的坦克大战,某种程度上已然提升难度了,归类到了算法类层的游戏。
拉到最后看代码你就能理解我的意思了,坦克大战的所有代码加起来还不如消消乐两个类的代码多,其他各种细节,坦克也只能望其项背...
如果坦克大战是低阶的,那么甜品消消乐就是中阶的,有着不少的动画效果和交互(碰撞检测和甜品的控制等),游戏运行的所有机制和功能需要在模型层上进行操控。
这次的消消乐需要一定的逻辑思维、算法基础、Unity综合能力和编程基础。
方正粗圆的字体和布灵布灵的闪光特效会赋予游戏可爱风的氛围感。
我们把物体分为模型层和渲染层,这样它们各司其职,降低工作的耦合性。
由于12.29日放假前还剩 两门课设答辩和一门考试,不足之处以后再补充...
项目效果展示
项目概况
素材展示
游戏资源(音频、图片、特效等)
场景和包(动画。渲染精灵和控制器等)
游戏脚本(UI、Runtime等)
......太多了,只选择了部分作为展示
场景展示
在Gizmos目录下,可以找到对应按钮隐藏摄像机,这样处理场景的时候会舒服很多
Clone都放上会有些杂乱,都删掉,只放了一个背景图
场景元素
开始按钮(玩家通过点击开始按钮,进入新的一局游戏)
重玩按钮(玩家可以及时返回开始界面进行相应操作)
时钟(显示游戏剩余时间,一局游戏初始化时间是60s)
计分板(显示当前游戏得分)
普通甜品(甜甜圈、糖果、起司...至少同时有3个在同一排或同一列时,可以消除)
特殊甜品(一定条件下会触发2种特殊甜品道具,一种是带有左右晃动特效的甜品,可以消除一整排的甜品;另一种是带有上下晃动特效的甜品,可以消除一整列的甜品)
积分面板(游戏时间结束后,会用类似飞跃的动画效果产生得分界面窗口,逐渐变大显示,最终定格。点击重玩立即开始下一局游戏,叉掉返回开始界面)
玩法介绍
- 点击开始进入游戏,每一局新开的游戏设时60s(可以根据需要调整),用最快的速度消除出现在同一行或者同一列的相同的甜点会获得相应积分。
- 尽量用最优的策略使得一定时间内的积分最大化(不过这一点,游戏玩熟了可能才能渐渐领悟,属于大师级别的操作了。可以用ACM相关算法写个外挂,应该是用动态规划算法处理)。
- 当符合特定消除条件的甜品被消除后,会随机产生两类特殊甜品的一种,这个甜品会瞬间清除一整行或者一整列的甜品,使得积分暴增。
- 相同时间内积分高者或者相同积分用时少者优胜。
版本说明
小虾选用的是Unity个人版,采用的是Unity一个较新的版本-2021.1.16,这里建议小伙伴们下载和教程相同版本的,不然真的会有可能遇到卡点消耗心情哈。
可以和我一样在Unity Hub里面安装,Hub感觉挺好用的。
但是每次重启后都要手动激活许可证,这是真的挺烦的。
项目源码
小虾在这里主要介绍部分核心代码实现,想知道其他部分实现的小伙伴在下方留言,我再添加~
UI-PropertyDrawers-MenuOptions.cs
using System; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using UnityEngine.SceneManagement; using UnityEditor.Experimental.SceneManagement; namespace UnityEditor.UI { ////// This script adds the UI menu options to the Unity Editor. /// static internal class MenuOptions { private const string kUILayerName = "UI"; private const string kStandardSpritePath = "UI/Skin/UISprite.psd"; private const string kBackgroundSpritePath = "UI/Skin/Background.psd"; private const string kInputFieldBackgroundPath = "UI/Skin/InputFieldBackground.psd"; private const string kKnobPath = "UI/Skin/Knob.psd"; private const string kCheckmarkPath = "UI/Skin/Checkmark.psd"; private const string kDropdownArrowPath = "UI/Skin/DropdownArrow.psd"; private const string kMaskPath = "UI/Skin/UIMask.psd"; static private DefaultControls.Resources s_StandardResources; static private DefaultControls.Resources GetStandardResources() { if (s_StandardResources.standard == null) { s_StandardResources.standard = AssetDatabase.GetBuiltinExtraResource(kStandardSpritePath); s_StandardResources.background = AssetDatabase.GetBuiltinExtraResource (kBackgroundSpritePath); s_StandardResources.inputField = AssetDatabase.GetBuiltinExtraResource (kInputFieldBackgroundPath); s_StandardResources.knob = AssetDatabase.GetBuiltinExtraResource (kKnobPath); s_StandardResources.checkmark = AssetDatabase.GetBuiltinExtraResource (kCheckmarkPath); s_StandardResources.dropdown = AssetDatabase.GetBuiltinExtraResource (kDropdownArrowPath); s_StandardResources.mask = AssetDatabase.GetBuiltinExtraResource (kMaskPath); } return s_StandardResources; } private class DefaultEditorFactory : DefaultControls.IFactoryControls { public static DefaultEditorFactory Default = new DefaultEditorFactory(); public GameObject CreateGameObject(string name, params Type[] components) { return ObjectFactory.CreateGameObject(name, components); } } private class FactorySwapToEditor : IDisposable { DefaultControls.IFactoryControls factory; public FactorySwapToEditor() { factory = DefaultControls.factory; DefaultControls.factory = DefaultEditorFactory.Default; } public void Dispose() { DefaultControls.factory = factory; } } private static void SetPositionVisibleinSceneView(RectTransform canvasRTransform, RectTransform itemTransform) { SceneView sceneView = SceneView.lastActiveSceneView; // Couldn't find a SceneView. Don't set position. if (sceneView == null || sceneView.camera == null) return; // Create world space Plane from canvas position. Vector2 localPlanePosition; Camera camera = sceneView.camera; Vector3 position = Vector3.zero; if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRTransform, new Vector2(camera.pixelWidth / 2, camera.pixelHeight / 2), camera, out localPlanePosition)) { // Adjust for canvas pivot localPlanePosition.x = localPlanePosition.x + canvasRTransform.sizeDelta.x * canvasRTransform.pivot.x; localPlanePosition.y = localPlanePosition.y + canvasRTransform.sizeDelta.y * canvasRTransform.pivot.y; localPlanePosition.x = Mathf.Clamp(localPlanePosition.x, 0, canvasRTransform.sizeDelta.x); localPlanePosition.y = Mathf.Clamp(localPlanePosition.y, 0, canvasRTransform.sizeDelta.y); // Adjust for anchoring position.x = localPlanePosition.x - canvasRTransform.sizeDelta.x * itemTransform.anchorMin.x; position.y = localPlanePosition.y - canvasRTransform.sizeDelta.y * itemTransform.anchorMin.y; Vector3 minLocalPosition; minLocalPosition.x = canvasRTransform.sizeDelta.x * (0 - canvasRTransform.pivot.x) + itemTransform.sizeDelta.x * itemTransform.pivot.x; minLocalPosition.y = canvasRTransform.sizeDelta.y * (0 - canvasRTransform.pivot.y) + itemTransform.sizeDelta.y * itemTransform.pivot.y; Vector3 maxLocalPosition; maxLocalPosition.x = canvasRTransform.sizeDelta.x * (1 - canvasRTransform.pivot.x) - itemTransform.sizeDelta.x * itemTransform.pivot.x; maxLocalPosition.y = canvasRTransform.sizeDelta.y * (1 - canvasRTransform.pivot.y) - itemTransform.sizeDelta.y * itemTransform.pivot.y; position.x = Mathf.Clamp(position.x, minLocalPosition.x, maxLocalPosition.x); position.y = Mathf.Clamp(position.y, minLocalPosition.y, maxLocalPosition.y); } itemTransform.anchoredPosition = position; itemTransform.localRotation = Quaternion.identity; itemTransform.localScale = Vector3.one; } private static void PlaceUIElementRoot(GameObject element, MenuCommand menuCommand) { GameObject parent = menuCommand.context as GameObject; bool explicitParentChoice = true; if (parent == null) { parent = GetOrCreateCanvasGameObject(); explicitParentChoice = false; // If in Prefab Mode, Canvas has to be part of Prefab contents, // otherwise use Prefab root instead. PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); if (prefabStage != null && !prefabStage.IsPartOfPrefabContents(parent)) parent = prefabStage.prefabContentsRoot; } if (parent.GetComponentsInParent
Editor-EventSystem-EventTriggerEditors.cs
using System; using UnityEngine; using UnityEngine.EventSystems; namespace UnityEditor.EventSystems { [CustomEditor(typeof(EventTrigger), true)] public class EventTriggerEditor : Editor { SerializedProperty m_DelegatesProperty; GUIContent m_IconToolbarMinus; GUIContent m_EventIDName; GUIContent[] m_EventTypes; GUIContent m_AddButonContent; protected virtual void OnEnable() { m_DelegatesProperty = serializedObject.FindProperty("m_Delegates"); m_AddButonContent = EditorGUIUtility.TrTextContent("Add New Event Type"); m_EventIDName = new GUIContent(""); // Have to create a copy since otherwise the tooltip will be overwritten. m_IconToolbarMinus = new GUIContent(EditorGUIUtility.IconContent("Toolbar Minus")); m_IconToolbarMinus.tooltip = "Remove all events in this list."; string[] eventNames = Enum.GetNames(typeof(EventTriggerType)); m_EventTypes = new GUIContent[eventNames.Length]; for (int i = 0; i < eventNames.Length; ++i) { m_EventTypes[i] = new GUIContent(eventNames[i]); } } public override void OnInspectorGUI() { serializedObject.Update(); int toBeRemovedEntry = -1; EditorGUILayout.Space(); Vector2 removeButtonSize = GUIStyle.none.CalcSize(m_IconToolbarMinus); for (int i = 0; i < m_DelegatesProperty.arraySize; ++i) { SerializedProperty delegateProperty = m_DelegatesProperty.GetArrayElementAtIndex(i); SerializedProperty eventProperty = delegateProperty.FindPropertyRelative("eventID"); SerializedProperty callbacksProperty = delegateProperty.FindPropertyRelative("callback"); m_EventIDName.text = eventProperty.enumDisplayNames[eventProperty.enumValueIndex]; EditorGUILayout.PropertyField(callbacksProperty, m_EventIDName); Rect callbackRect = GUILayoutUtility.GetLastRect(); Rect removeButtonPos = new Rect(callbackRect.xMax - removeButtonSize.x - 8, callbackRect.y + 1, removeButtonSize.x, removeButtonSize.y); if (GUI.Button(removeButtonPos, m_IconToolbarMinus, GUIStyle.none)) { toBeRemovedEntry = i; } EditorGUILayout.Space(); } if (toBeRemovedEntry > -1) { RemoveEntry(toBeRemovedEntry); } Rect btPosition = GUILayoutUtility.GetRect(m_AddButonContent, GUI.skin.button); const float addButonWidth = 200f; btPosition.x = btPosition.x + (btPosition.width - addButonWidth) / 2; btPosition.width = addButonWidth; if (GUI.Button(btPosition, m_AddButonContent)) { ShowAddTriggermenu(); } serializedObject.ApplyModifiedProperties(); } private void RemoveEntry(int toBeRemovedEntry) { m_DelegatesProperty.DeleteArrayElementAtIndex(toBeRemovedEntry); } void ShowAddTriggermenu() { // Now create the menu, add items and show it GenericMenu menu = new GenericMenu(); for (int i = 0; i < m_EventTypes.Length; ++i) { bool active = true; // Check if we already have a Entry for the current eventType, if so, disable it for (int p = 0; p < m_DelegatesProperty.arraySize; ++p) { SerializedProperty delegateEntry = m_DelegatesProperty.GetArrayElementAtIndex(p); SerializedProperty eventProperty = delegateEntry.FindPropertyRelative("eventID"); if (eventProperty.enumValueIndex == i) { active = false; } } if (active) menu.AddItem(m_EventTypes[i], false, OnAddNewSelected, i); else menu.AddDisabledItem(m_EventTypes[i]); } menu.ShowAsContext(); Event.current.Use(); } private void OnAddNewSelected(object index) { int selected = (int)index; m_DelegatesProperty.arraySize += 1; SerializedProperty delegateEntry = m_DelegatesProperty.GetArrayElementAtIndex(m_DelegatesProperty.arraySize - 1); SerializedProperty eventProperty = delegateEntry.FindPropertyRelative("eventID"); eventProperty.enumValueIndex = selected; serializedObject.ApplyModifiedProperties(); } } }
Runtime-UIElements-ExecuteEvents.cs
using System; using System.Collections.Generic; using UnityEngine.Pool; namespace UnityEngine.EventSystems { public static class ExecuteEvents { public delegate void EventFunction(T1 handler, BaseEventData eventData); public static T ValidateEventData (BaseEventData data) where T : class { if ((data as T) == null) throw new ArgumentException(String.Format("Invalid type: {0} passed to event expecting {1}", data.GetType(), typeof(T))); return data as T; } private static readonly EventFunction s_PointerMoveHandler = Execute; private static void Execute(IPointerMoveHandler handler, BaseEventData eventData) { handler.OnPointerMove(ValidateEventData (eventData)); } private static readonly EventFunction s_PointerEnterHandler = Execute; private static void Execute(IPointerEnterHandler handler, BaseEventData eventData) { handler.OnPointerEnter(ValidateEventData (eventData)); } private static readonly EventFunction s_PointerExitHandler = Execute; private static void Execute(IPointerExitHandler handler, BaseEventData eventData) { handler.OnPointerExit(ValidateEventData (eventData)); } private static readonly EventFunction s_PointerDownHandler = Execute; private static void Execute(IPointerDownHandler handler, BaseEventData eventData) { handler.OnPointerDown(ValidateEventData (eventData)); } private static readonly EventFunction s_PointerUpHandler = Execute; private static void Execute(IPointerUpHandler handler, BaseEventData eventData) { handler.OnPointerUp(ValidateEventData (eventData)); } private static readonly EventFunction s_PointerClickHandler = Execute; private static void Execute(IPointerClickHandler handler, BaseEventData eventData) { handler.OnPointerClick(ValidateEventData (eventData)); } private static readonly EventFunction s_InitializePotentialDragHandler = Execute; private static void Execute(IInitializePotentialDragHandler handler, BaseEventData eventData) { handler.OnInitializePotentialDrag(ValidateEventData (eventData)); } private static readonly EventFunction s_BeginDragHandler = Execute; private static void Execute(IBeginDragHandler handler, BaseEventData eventData) { handler.OnBeginDrag(ValidateEventData (eventData)); } private static readonly EventFunction s_DragHandler = Execute; private static void Execute(IDragHandler handler, BaseEventData eventData) { handler.OnDrag(ValidateEventData (eventData)); } private static readonly EventFunction s_EndDragHandler = Execute; private static void Execute(IEndDragHandler handler, BaseEventData eventData) { handler.OnEndDrag(ValidateEventData (eventData)); } private static readonly EventFunction s_DropHandler = Execute; private static void Execute(IDropHandler handler, BaseEventData eventData) { handler.OnDrop(ValidateEventData (eventData)); } private static readonly EventFunction s_ScrollHandler = Execute; private static void Execute(IScrollHandler handler, BaseEventData eventData) { handler.OnScroll(ValidateEventData (eventData)); } private static readonly EventFunction s_UpdateSelectedHandler = Execute; private static void Execute(IUpdateSelectedHandler handler, BaseEventData eventData) { handler.OnUpdateSelected(eventData); } private static readonly EventFunction s_SelectHandler = Execute; private static void Execute(ISelectHandler handler, BaseEventData eventData) { handler.OnSelect(eventData); } private static readonly EventFunction s_DeselectHandler = Execute; private static void Execute(IDeselectHandler handler, BaseEventData eventData) { handler.OnDeselect(eventData); } private static readonly EventFunction s_MoveHandler = Execute; private static void Execute(IMoveHandler handler, BaseEventData eventData) { handler.OnMove(ValidateEventData (eventData)); } private static readonly EventFunction s_SubmitHandler = Execute; private static void Execute(ISubmitHandler handler, BaseEventData eventData) { handler.OnSubmit(eventData); } private static readonly EventFunction s_CancelHandler = Execute; private static void Execute(ICancelHandler handler, BaseEventData eventData) { handler.OnCancel(eventData); } public static EventFunction pointerMoveHandler { get { return s_PointerMoveHandler; } } public static EventFunction pointerEnterHandler { get { return s_PointerEnterHandler; } } public static EventFunction pointerExitHandler { get { return s_PointerExitHandler; } } public static EventFunction pointerDownHandler { get { return s_PointerDownHandler; } } public static EventFunction pointerUpHandler { get { return s_PointerUpHandler; } } public static EventFunction pointerClickHandler { get { return s_PointerClickHandler; } } public static EventFunction initializePotentialDrag { get { return s_InitializePotentialDragHandler; } } public static EventFunction beginDragHandler { get { return s_BeginDragHandler; } } public static EventFunction dragHandler { get { return s_DragHandler; } } public static EventFunction endDragHandler { get { return s_EndDragHandler; } } public static EventFunction dropHandler { get { return s_DropHandler; } } public static EventFunction scrollHandler { get { return s_ScrollHandler; } } public static EventFunction updateSelectedHandler { get { return s_UpdateSelectedHandler; } } public static EventFunction selectHandler { get { return s_SelectHandler; } } public static EventFunction deselectHandler { get { return s_DeselectHandler; } } public static EventFunction moveHandler { get { return s_MoveHandler; } } public static EventFunction submitHandler { get { return s_SubmitHandler; } } public static EventFunction cancelHandler { get { return s_CancelHandler; } } private static void GetEventChain(GameObject root, IList eventChain) { eventChain.Clear(); if (root == null) return; var t = root.transform; while (t != null) { eventChain.Add(t); t = t.parent; } } public static bool Execute (GameObject target, BaseEventData eventData, EventFunction functor) where T : IEventSystemHandler { var internalHandlers = ListPool .Get(); GetEventList (target, internalHandlers); // if (s_InternalHandlers.Count > 0) // Debug.Log("Executinng " + typeof (T) + " on " + target); var internalHandlersCount = internalHandlers.Count; for (var i = 0; i < internalHandlersCount; i++) { T arg; try { arg = (T)internalHandlers[i]; } catch (Exception e) { var temp = internalHandlers[i]; Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e)); continue; } try { functor(arg, eventData); } catch (Exception e) { Debug.LogException(e); } } var handlerCount = internalHandlers.Count; ListPool .Release(internalHandlers); return handlerCount > 0; } /// /// Execute the specified event on the first game object underneath the current touch. /// private static readonly Lists_InternalTransformList = new List (30); public static GameObject ExecuteHierarchy (GameObject root, BaseEventData eventData, EventFunction callbackFunction) where T : IEventSystemHandler { GetEventChain(root, s_InternalTransformList); var internalTransformListCount = s_InternalTransformList.Count; for (var i = 0; i < internalTransformListCount; i++) { var transform = s_InternalTransformList[i]; if (Execute(transform.gameObject, eventData, callbackFunction)) return transform.gameObject; } return null; } private static bool ShouldSendToComponent (Component component) where T : IEventSystemHandler { var valid = component is T; if (!valid) return false; var behaviour = component as Behaviour; if (behaviour != null) return behaviour.isActiveAndEnabled; return true; } /// /// Get the specified object's event event. /// private static void GetEventList(GameObject go, IList results) where T : IEventSystemHandler { // Debug.LogWarning("GetEventList<" + typeof(T).Name + ">"); if (results == null) throw new ArgumentException("Results array is null", "results"); if (go == null || !go.activeInHierarchy) return; var components = ListPool .Get(); go.GetComponents(components); var componentsCount = components.Count; for (var i = 0; i < componentsCount; i++) { if (!ShouldSendToComponent (components[i])) continue; // Debug.Log(string.Format("{2} found! On {0}.{1}", go, s_GetComponentsScratch[i].GetType(), typeof(T))); results.Add(components[i] as IEventSystemHandler); } ListPool .Release(components); // Debug.LogWarning("end GetEventList<" + typeof(T).Name + ">"); } /// /// Whether the specified game object will be able to handle the specified event. /// public static bool CanHandleEvent(GameObject go) where T : IEventSystemHandler { var internalHandlers = ListPool .Get(); GetEventList (go, internalHandlers); var handlerCount = internalHandlers.Count; ListPool .Release(internalHandlers); return handlerCount != 0; } /// /// Bubble the specified event on the game object, figuring out which object will actually receive the event. /// public static GameObject GetEventHandler(GameObject root) where T : IEventSystemHandler { if (root == null) return null; Transform t = root.transform; while (t != null) { if (CanHandleEvent (t.gameObject)) return t.gameObject; t = t.parent; } return null; } } }
以上就是Unity3D实现甜品消消乐游戏的详细内容,更多关于Unity3D消消乐游戏的资料请关注脚本之家其它相关文章!