如何避免游戏中Character与SustainedAction的循环引用?
解决Character与SustainedAction循环引用的设计方案
针对你提出的循环引用问题,结合给定的三个约束条件,这里提供一个基于弱引用+动作管理器的无循环引用设计方案,同时保留原有性能特性:
核心设计思路
- 打破循环强引用:让
SustainedAction持有Character的弱引用,而Character持有SustainedAction的强引用,避免双向强引用导致的GC回收障碍 - 保留本地列表性能:
Character仍维护参与动作的强引用列表,保证ShowActionList()的调用效率 - 统一生命周期管理:新增动作管理器负责动作的创建、注册和全局更新,同时支持任意参与者触发动作结束
修改后的代码实现
Character类
public class Character { public string Name { get; set; } // 保留本地强引用列表,满足ShowActionList的性能需求 public List<SustainedAction> ActionsInParticipation = new(); public void PlayTalk(IEnumerable<Character> targets) { ActionManager.Instance.CreateAction<Talk>(this, targets); } public void PlayLecture(IEnumerable<Character> targets) { ActionManager.Instance.CreateAction<Lecture>(this, targets); } public void ShowActionList() { foreach(var action in ActionsInParticipation) Debug.Log(action.Name); } // 任意角色可调用此方法结束动作 public void EndAction(SustainedAction action) { if (ActionsInParticipation.Contains(action)) { action.End(); } } // 供动作内部调用,移除自身引用 internal void RemoveAction(SustainedAction action) { ActionsInParticipation.Remove(action); } }
SustainedAction抽象类及子类
public abstract class SustainedAction { // 用弱引用存储所有参与者,避免循环强引用 private List<WeakReference<Character>> _participants = new(); public string Name { get; protected set; } public int LeftTime { get; protected set; } protected SustainedAction(IEnumerable<Character> participants, string name, int leftTime = 5) { Name = name; LeftTime = leftTime; // 初始化参与者弱引用,并让每个角色持有动作的强引用 foreach (var character in participants) { _participants.Add(new WeakReference<Character>(character)); character.ActionsInParticipation.Add(this); } ActionManager.Instance.RegisterAction(this); } public void End() { // 执行自定义结束条件检查... // 通知所有存活的角色移除该动作 foreach (var weakRef in _participants) { if (weakRef.TryGetTarget(out Character character)) { character.RemoveAction(this); } } // 从管理器注销,释放资源 ActionManager.Instance.UnregisterAction(this); _participants.Clear(); } public void Update(int time) { // 执行动作逻辑... LeftTime -= time; if (LeftTime < 0) End(); } } public class Talk : SustainedAction { public Talk(IEnumerable<Character> participants) : base(participants, "Talk", 10) { } } public class Lecture : SustainedAction { public Lecture(IEnumerable<Character> participants) : base(participants, "Lecture", 90) { } }
动作管理器(统一维护动作生命周期)
public class ActionManager { public static ActionManager Instance { get; } = new ActionManager(); private List<SustainedAction> _activeActions = new(); // 创建动作,自动包含发起者和目标群体 public T CreateAction<T>(Character initiator, IEnumerable<Character> targets) where T : SustainedAction { var allParticipants = new List<Character> { initiator }; allParticipants.AddRange(targets); return Activator.CreateInstance(typeof(T), allParticipants) as T; } public void RegisterAction(SustainedAction action) { _activeActions.Add(action); } public void UnregisterAction(SustainedAction action) { _activeActions.Remove(action); } // 全局更新所有活跃动作 public void UpdateAllActions(int time) { // 遍历列表副本,避免修改原集合时引发异常 foreach (var action in _activeActions.ToList()) { action.Update(time); } } }
方案优势说明
- 无循环引用:角色持有动作强引用,动作持有角色弱引用,GC可正常回收不再被引用的对象
- 性能达标:
ShowActionList()直接遍历本地列表,无需全局查询,满足高频率调用需求 - 支持多参与者:通过参与者列表替代单个Player/Target,自然支持多玩家多目标场景
- 灵活结束动作:任意角色可调用
EndAction()触发动作结束,动作自身也会在时间耗尽时自动结束
内容的提问来源于stack exchange,提问作者user185266
相关产品推荐
相关产品推荐

