链表实现事件链,刷题笔记

leetcode.. 2025-07-28 21:29:47

指针刷题日记
指针的定义和删除


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace testnode
{
    public class ListNode {
        public int val;
        public ListNode next;
        public ListNode(int val = 0,ListNode next = null) {
            this.val = val;
            this.next = next;
        }
    }
    internal class Program
    {
        //递归的方式
        //代码最简洁
        public static ListNode RemoveElementsD(ListNode head, int val)
        {
            if (head == null)
            {
                return head;
            }
            head.next = RemoveElementsD(head.next, val);
            return head.val == val ? head.next:head;
        }
        //情况判断的方式
        //效率最高
        public static ListNode RemoveElements(ListNode head, int val)
        {
            ListNode upNode = head;
            ListNode doNode = head;
            if (head == null)
            {
                return head;
            }
            while (upNode != null)//结点为upnode,而不是upnode.next
            {
                //这个是错误所在,没有对头结点的位置进行重置仍然保留
                if (upNode.val == val && doNode.val == val)
                {
                    upNode = upNode.next;
                    doNode = upNode;
                    head = doNode;
                }
                else if (upNode.val == val && doNode.val != val)
                {
                    doNode.next = upNode.next;
                    upNode = upNode.next;
                }
                else if(upNode.val != val && doNode.val != val)
                {
                    doNode = upNode;
                    upNode = upNode.next;
                }
            }
            return head;
        }
        static void Main(string[] args)
        {
           int[] headArray = new[] { 6, 2, 6, 3, 4,5, 6 };
           int val = 6;
           ListNode head = null;
           ListNode tail = null;
            foreach (var item in headArray)
            {
                ListNode newNode = new ListNode(item);//创建新的节点
                if (head == null)
                {
                    head = newNode;//需要两个节点便于保存和遍历
                    tail = newNode;
                }
                else 
                { 
                    tail.next = newNode;
                    tail = newNode;
                }
            }
            RemoveElements(head,val);
        }
    }
}


在unity开发中的实际运用
基于指针的特性,删除插入效率高,遍历效率低,适用于需要频繁插入删除的场景

对于第一种解题方式,通过递归的方式进行求解
,这种方式简洁,但是也存在一定的风险,就是栈溢出 (Stack Overflow): 如果链表非常长(例如几十万个节点),每次递归调用都会占用栈空间。这在Unity或者任何C#应用程序中都可能导致 StackOverflowException。在游戏开发中,我们通常会避免深度递归,因为它不好调试且有性能风险。
但是在某些场景任然适用,比如
任务队列 / 事件链:
示例: 玩家完成一系列动作(如:捡起物品->使用技能->触发动画)。你可以用一个短的 ListNode 链表来表示这些顺序执行的任务。当一个任务完成后,通过类似 RemoveElementsD 的逻辑,可以把已完成的任务节点“删除”并继续下一个。递归在这里可能用于处理嵌套任务。
优势: 链表的插入和删除效率高,特别是中间部分。
接下来看看如何运用到实际开发中

首先定义基本的数据结构
TaskNode用于存储人物节点
csharp
using UnityEngine;
using System.Collections;

public class TaskNode
{
    public GameTask Task;   // 存储实际的任务对象
    public TaskNode Next;   // 指向下一个任务节点

    public TaskNode(GameTask task)
    {
        Task = task;
        Next = null;
    }
}

// 自定义任务链表类
public class TaskLinkedList
{
    public TaskNode Head; // 链表的头节点

    // 添加任务到链表尾部
    public void AddTask(GameTask task)
    {
        TaskNode newNode = new TaskNode(task);
        if (Head == null)
        {
            Head = newNode;
            return;
        }

        TaskNode current = Head;
        while (current.Next != null)
        {
            current = current.Next;
        }
        current.Next = newNode;
    }

    // 获取下一个待执行的任务
    public GameTask GetNextPendingTask()
    {
        if (Head == null)
            return null;

        if ((Head.Task.IsCompleted || Head.Task.IsSkipped) && Head.Next != null)
        {
            Debug.Log($"清理头部已完成任务:{Head.Task.Description}");
            Head = Head.Next; 
            return GetNextPendingTask();
        }

        return Head.Task;
    }

 
    public void CleanCompletedTasks()
    {
        if (Head == null) return;

        TaskNode dummyHead = new TaskNode(null); // 虚拟头节点
        dummyHead.Next = Head;
        TaskNode current = dummyHead;

        while (current.Next != null)
        {
            if (current.Next.Task.IsCompleted || current.Next.Task.IsSkipped) // 检测任务是否完成或者跳过
            {
                Debug.Log($"清理任务: {current.Next.Task.Description}");
                current.Next = current.Next.Next; 
            }
            else
            {
                current = current.Next; // 移动到下一个节点
            }
        }
        Head = dummyHead.Next;  // 更新 Head
    }

    public void AdvanceHead()
    {
        if (Head != null)
        {
            if (Head.Task.IsCompleted || Head.Task.IsSkipped) // 再次确认是否已完成
            {
                Head = Head.Next; // 直接前进到下一个任务
            }
        }
    }

    public void PrintTaskList()
    {
        if (Head == null)
        {
            Debug.Log("任务链表为空。");
            return;
        }

        TaskNode current = Head;
        string log = "任务链表: ";
        while (current != null)
        {
            log += $"[{current.Task.Description} ({(current.Task.IsCompleted ? "C" : (current.Task.IsSkipped ? "S" : "P"))})] -> "; // "C" completed, "P" pending
            current = current.Next;
        }
        Debug.Log(log + "END");
    }
}

 

其次定义号任务节点,在后续定义具体任务的时候会使用到


using UnityEngine;
using System.Collections;
using System.Collections.Generic;

// 任务基类 所有具体的任务都需要继承它
public abstract class GameTask
{
    public string Description { get; protected set; }
    public bool IsCompleted { get; protected set; } = false; // 任务的完成状态
    public bool IsRunning { get; protected set; } = false; // 任务是否正在运行
    public bool IsSkipped { get; set; } = false;  //任务是否被跳过
    public GameManager GameManagerRef { get; set; }

    public GameTask(string description)
    {
        Description = description;
    }

    public abstract IEnumerator Execute();

    // 标记任务完成
    public virtual void MarkAsCompleted()
    {
        IsCompleted = true;
        IsRunning = false;
        Debug.Log($"任务 '{Description}'  已完成.");
    }

    // 标记任务跳过
    public virtual void SkipTask()
    {
        IsSkipped = true;
        IsRunning = false;
        Debug.Log($"任务 '{Description}'  已跳过.");
    }
}


接下来我们需要定义一个用于执行时间链的函数,需要使用到上面定义的NodeAndLink


// TaskExecutor MonoBehaviour
using System.Collections;
using UnityEngine;

public class TaskExecutorCustomLinkedList : MonoBehaviour
{
    public TaskLinkedList TaskList = new TaskLinkedList();
    private bool isExecutingTask = false;//判断当前是否有其他任务正在执行
    public GameManager GameManagerRef { get; set; }

    void Start()
    {
        if (GameManagerRef == null) GameManagerRef = FindObjectOfType<GameManager>();
    }

    void Update()
    {
        // 确保 GameManagerRef 不为 null
        if (GameManagerRef == null)
        {
            Debug.LogError("GameManagerRef is not assigned!");
            return;
        }
        // 执行任务的逻辑,避免 Update 过于复杂
        if (!isExecutingTask)
        {
            GameTask task = TaskList.GetNextPendingTask();
            if (task != null)
            {
                StartCoroutine(ExecuteTask(task));
            }
            else
            {
                //Debug.Log("所有任务已完成。");
            }
        }
    }

    // 添加任务
    public void AddTask(GameTask task)
    {
        task.GameManagerRef = GameManagerRef; // 设置 Game Manager 引用
        TaskList.AddTask(task);
        Debug.Log($"任务 '{task.Description}' 已添加到队列。");
    }

    // 执行单个任务 (主执行逻辑,使用协程)
    private IEnumerator ExecuteTask(GameTask task)
    {
        isExecutingTask = true;
        // 确保任务的 GameManagerRef 已设置
        if (task.GameManagerRef == null)
            task.GameManagerRef = GameManagerRef;

        // 执行任务
        Debug.Log($"开始执行: {task.Description}");
        yield return task.Execute();

        // 任务执行完毕后,检查状态,并做相应的处理
        // 1. 任务已完成,清理链表 (注意这里使用 CleanCompletedTasks!)
        // 2. 任务未完成或被跳过
        Debug.Log($"任务 '{task.Description}' 执行完毕。");
        TaskList.CleanCompletedTasks();
        TaskList.AdvanceHead(); //  如果任务已完成或跳过,Head 需要前进
        isExecutingTask = false;
    }

    // 外部调用接口,清理所有已完成的任务
    public void CleanAllCompletedTasks()
    {
        TaskList.CleanCompletedTasks();
    }

    // 外部调用接口,清空所有未完成的任务
    public void ClearAllTasks()
    {
        TaskList = new TaskLinkedList();  // 创建一个新的空链表
        TaskExecutorCustomLinkedListDebugLog("所有任务已清除.");
    }

    // 调试用日志
    public void TaskExecutorCustomLinkedListDebugLog(string message)
    {
        Debug.Log($"[TaskExecutorCustomLinkedList]: {message}");
    }
}

```
根据上述定义,实现具体的类

```csharp
// 具体的任务类:  捡起物品的任务
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PickUpItemTask : GameTask
{
    private string itemName;

    public PickUpItemTask(string item) : base($"捡起物品: {item}")
    {
        itemName = item;
    }

    public override IEnumerator Execute()
    {
        IsRunning = true;
        Debug.Log($"开始执行任务: {Description}");
        // 模拟捡起物品的逻辑和延迟
        yield return new WaitForSeconds(1.5f); //模拟捡起物品的时间
        // 执行完成,标记完成
        MarkAsCompleted();
        GameManagerRef.UIController.ShowMessage($"你捡起了 {itemName}!");
    }
}
// 具体的任务类:使用技能
public class UseSkillTask : GameTask
{
    private string skillName;
    public UseSkillTask(string skill) : base($"使用技能: {skill}")
    {
        skillName = skill;
    }

    public override IEnumerator Execute()
    {
        IsRunning = true;
        Debug.Log($"开始执行任务: {Description}");
        // 模拟技能释放的逻辑
        yield return new WaitForSeconds(0.8f); // 模拟一些技能时间
        MarkAsCompleted();
        GameManagerRef.PlayerController.CastSkill(skillName);
        GameManagerRef.UIController.ShowMessage($"你使用了 {skillName}!");
    }
}

// 具体的任务类: 触发动画
public class TriggerAnimationTask : GameTask
{
    private string animName;

    public TriggerAnimationTask(string anim) : base($"播放动画: {anim}")
    {
        animName = anim;
    }

    public override IEnumerator Execute()
    {
        IsRunning = true;
        Debug.Log($"开始执行任务: {Description}");
        // 等待播放动画完成
        yield return new WaitForSeconds(2.0f); // 模拟动画播放时间
        MarkAsCompleted();
        GameManagerRef.PlayerController.PlayAnimation(animName);
        GameManagerRef.UIController.ShowMessage($"动画 {animName} 播放完毕。");
    }
}

// 复合任务: 可以包含其他子任务
public class CompositeTask : GameTask
{
    public List<GameTask> SubTasks = new List<GameTask>();
    private int currentSubTaskIndex = 0; // 当前执行的子任务索引

    public CompositeTask(string description) : base(description)
    {

    }

    public override IEnumerator Execute()
    {
        IsRunning = true;
        Debug.Log($"开始执行组合任务: {Description}");

        // 循环执行子任务
        while (currentSubTaskIndex < SubTasks.Count)
        {
            GameTask subTask = SubTasks[currentSubTaskIndex];
            subTask.GameManagerRef = GameManagerRef;  // 确保子任务能访问 GameManager
            Debug.Log($"    开始执行子任务: {subTask.Description}");
            yield return subTask.Execute();  // 执行子任务(递归的核心!)

            if (!subTask.IsCompleted && !subTask.IsSkipped)
            {
                Debug.LogError($"    子任务 '{subTask.Description}' 执行失败,终止组合任务.");
                IsRunning = false;
                IsSkipped = true; // 标记组合任务为跳过,或者根据逻辑处理
                yield break; // 结束协程
            }

            currentSubTaskIndex++; // 移动到下一个子任务
            Debug.Log($"    子任务 '{subTask.Description}' 执行完毕.");
        }

        // 所有子任务都已完成
        MarkAsCompleted();
        Debug.Log($"组合任务 '{Description}' 执行完毕.");
    }

    public void AddSubTask(GameTask task)
    {
        SubTasks.Add(task);
    }
}

 

还需要定义一个游戏管理类

// 游戏管理器,负责组织
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
    public TaskExecutorCustomLinkedList TaskExecutor;
    public PlayerController PlayerController;
    public UIController UIController;
    public Button onSkip;
    void Awake()
    {
        // 自动查找或者创建必要组件 - 确保TaskExecutor,PlayerController, UIController 都被找到了
        if (TaskExecutor == null)
        {
            GameObject taskExecutorGO = new GameObject("TaskExecutor");
            TaskExecutor = taskExecutorGO.AddComponent<TaskExecutorCustomLinkedList>();
            TaskExecutor.GameManagerRef = this; // 设置GameManager引用
        }

        if (PlayerController == null)
        {
            GameObject playerGO = new GameObject("Player");
            PlayerController = playerGO.AddComponent<PlayerController>();
        }

        if (UIController == null)
        {
            GameObject uiGO = new GameObject("UI");
            UIController = uiGO.AddComponent<UIController>();
        }

    }

    void Start()
    {
        // 1. 找到组件
        if (TaskExecutor == null) TaskExecutor = FindObjectOfType<TaskExecutorCustomLinkedList>();
        if (PlayerController == null) PlayerController = FindObjectOfType<PlayerController>();
        if (UIController == null) UIController = FindObjectOfType<UIController>();
        // 2. 初始化TaskExecutor 的 GameManager 引用
        if (TaskExecutor != null)
        {
            TaskExecutor.GameManagerRef = this;
        }

        // 3. 创建任务 (示例)
        // (使用AddTask 添加单独的任务)
        TaskExecutor.AddTask(new PickUpItemTask("钥匙"));
        TaskExecutor.AddTask(new UseSkillTask("火球术"));
        TaskExecutor.AddTask(new TriggerAnimationTask("攻击动画"));

        // (使用AddTask 添加复合任务)
        CompositeTask compoundTask = new CompositeTask("打开宝箱");
        compoundTask.AddSubTask(new TriggerAnimationTask("宝箱开启动画"));
        compoundTask.AddSubTask(new PickUpItemTask("金币"));
        compoundTask.AddSubTask(new UseSkillTask("叮铃声"));
        TaskExecutor.AddTask(compoundTask); // 将复合任务添加到队列中

        TaskExecutor.TaskList.PrintTaskList();
        //当触发某个任务的时候,就可以跳过指定的人物
        onSkip.onClick.AddListener(() => {
            compoundTask.IsSkipped = true;
        });
    }

    // 外部调用示例:在某个按钮点击时手动清理队列
    public void OnClickCleanButton()
    {
        TaskExecutor.CleanAllCompletedTasks();
        TaskExecutor.TaskList.PrintTaskList();
    }

    // 外部调用示例:取消所有待执行的任务
    public void OnClickClearAllButton()
    {
        TaskExecutor.ClearAllTasks();
    }
    /// <summary>
    /// 按钮调用
    /// </summary>
    public void skipTask()
    {
        
    }
}
 

其他类,支持扩展,在具体实践中可以添加需要的功能

```csharp
// UI和玩家控制器,用于模拟游戏行为
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public void CastSkill(string skillName)
    {
        Debug.Log($"Player casts skill: {skillName}");
    }

    public void PlayAnimation(string animName)
    {
        Debug.Log($"Player plays animation: {animName}");
    }
}
 


using UnityEngine;

public class UIController : MonoBehaviour
{
    public void ShowMessage(string message)
    {
        Debug.Log($"UI Message: {message}");
    }
}

 


 

...全文
58 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

12

社区成员

发帖
与我相关
我的任务
社区描述
一个喜欢独有游戏开发的社区
学习方法技术美术游戏程序 技术论坛(原bbs) 江西省·南昌市
社区管理员
  • 辻渃mercury
  • 写代码的伯恩
  • 卯相爱吃番茄酱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧