mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 19:03:29 +08:00
- 将所有DateTime.Now替换为DateTime.UtcNow以确保时区一致性 - 修复文档中的时间戳记录方式 - 更新测试代码中的时间戳生成逻辑 - 统一框架各模块的时间记录标准
1640 lines
47 KiB
Markdown
1640 lines
47 KiB
Markdown
# 高级模式教程
|
|
|
|
> 深入学习 GFramework 的高级特性和设计模式,构建更复杂和可维护的游戏系统。
|
|
|
|
## 目录
|
|
|
|
- [架构模式](#架构模式)
|
|
- [事件驱动架构](#事件驱动架构)
|
|
- [插件系统](#插件系统)
|
|
- [网络集成](#网络集成)
|
|
|
|
## 架构模式
|
|
|
|
### 1. CQRS (命令查询职责分离)
|
|
|
|
实现完整的 CQRS 模式,分离读写操作:
|
|
|
|
```csharp
|
|
using GFramework.Core.command;
|
|
using GFramework.Core.query;
|
|
using GFramework.Core.events;
|
|
|
|
// 命令 - 负责写操作
|
|
public class CreatePlayerCommand : AbstractCommand
|
|
{
|
|
public string PlayerName { get; set; }
|
|
public PlayerClass Class { get; set; }
|
|
public Vector3 InitialPosition { get; set; }
|
|
|
|
protected override void OnExecute()
|
|
{
|
|
var playerModel = GetModel<PlayerModel>();
|
|
|
|
// 验证命令
|
|
if (string.IsNullOrWhiteSpace(PlayerName))
|
|
throw new ArgumentException("Player name cannot be empty");
|
|
|
|
if (playerModel.PlayerExists(PlayerName))
|
|
throw new InvalidOperationException($"Player {PlayerName} already exists");
|
|
|
|
// 创建玩家
|
|
var playerData = new PlayerData
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = PlayerName,
|
|
Class = Class,
|
|
Position = InitialPosition,
|
|
Level = 1,
|
|
Experience = 0,
|
|
Health = 100,
|
|
MaxHealth = 100,
|
|
Mana = 50,
|
|
MaxMana = 50,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
// 保存玩家数据
|
|
playerModel.AddPlayer(playerData);
|
|
|
|
// 发送事件
|
|
SendEvent(new PlayerCreatedEvent { Player = playerData });
|
|
}
|
|
}
|
|
|
|
// 查询 - 负责读操作
|
|
public class GetPlayerQuery : AbstractQuery<PlayerData>
|
|
{
|
|
public string PlayerName { get; set; }
|
|
|
|
protected override PlayerData OnDo()
|
|
{
|
|
var playerModel = GetModel<PlayerModel>();
|
|
return playerModel.GetPlayer(PlayerName);
|
|
}
|
|
}
|
|
|
|
public class GetAllPlayersQuery : AbstractQuery<List<PlayerData>>
|
|
{
|
|
public PlayerClass? FilterByClass { get; set; }
|
|
public int? MinLevel { get; set; }
|
|
|
|
protected override List<PlayerData> OnDo()
|
|
{
|
|
var playerModel = GetModel<PlayerModel>();
|
|
var players = playerModel.GetAllPlayers();
|
|
|
|
// 应用过滤器
|
|
if (FilterByClass.HasValue)
|
|
{
|
|
players = players.Where(p => p.Class == FilterByClass.Value).ToList();
|
|
}
|
|
|
|
if (MinLevel.HasValue)
|
|
{
|
|
players = players.Where(p => p.Level >= MinLevel.Value).ToList();
|
|
}
|
|
|
|
return players;
|
|
}
|
|
}
|
|
|
|
// 复杂查询示例
|
|
public class GetPlayerStatisticsQuery : AbstractQuery<PlayerStatistics>
|
|
{
|
|
public string PlayerName { get; set; }
|
|
|
|
protected override PlayerStatistics OnDo()
|
|
{
|
|
var playerModel = GetModel<PlayerModel>();
|
|
var player = playerModel.GetPlayer(PlayerName);
|
|
|
|
if (player == null)
|
|
return null;
|
|
|
|
// 计算统计数据
|
|
var statistics = new PlayerStatistics
|
|
{
|
|
PlayerId = player.Id,
|
|
PlayerName = player.Name,
|
|
Level = player.Level,
|
|
Experience = player.Experience,
|
|
ExperienceToNextLevel = CalculateExperienceToNextLevel(player.Level),
|
|
HealthPercentage = (float)player.Health / player.MaxHealth * 100,
|
|
ManaPercentage = (float)player.Mana / player.MaxMana * 100,
|
|
PlayTime = player.PlayTime,
|
|
LastLogin = player.LastLogin,
|
|
AchievementsUnlocked = player.Achievements.Count,
|
|
TotalItemsOwned = player.Inventory.Count
|
|
};
|
|
|
|
return statistics;
|
|
}
|
|
|
|
private int CalculateExperienceToNextLevel(int currentLevel)
|
|
{
|
|
// 经验值计算公式
|
|
return currentLevel * 1000 + (currentLevel - 1) * 500;
|
|
}
|
|
}
|
|
|
|
// 事件 - 领域事件
|
|
public struct PlayerCreatedEvent
|
|
{
|
|
public PlayerData Player { get; set; }
|
|
}
|
|
|
|
public struct PlayerLevelUpEvent
|
|
{
|
|
public string PlayerName { get; set; }
|
|
public int NewLevel { get; set; }
|
|
public int NewMaxHealth { get; set; }
|
|
public int NewMaxMana { get; set; }
|
|
}
|
|
|
|
// 使用示例
|
|
[ContextAware]
|
|
[Log]
|
|
public partial class PlayerService : IController
|
|
{
|
|
public void CreateNewPlayer(string name, PlayerClass playerClass)
|
|
{
|
|
var command = new CreatePlayerCommand
|
|
{
|
|
PlayerName = name,
|
|
Class = playerClass,
|
|
InitialPosition = Vector3.Zero
|
|
};
|
|
|
|
try
|
|
{
|
|
Context.SendCommand(command);
|
|
Logger.Info($"Player {name} created successfully");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Error($"Failed to create player {name}: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public PlayerData GetPlayer(string name)
|
|
{
|
|
var query = new GetPlayerQuery { PlayerName = name };
|
|
return Context.SendQuery(query);
|
|
}
|
|
|
|
public List<PlayerData> GetAllPlayers(PlayerClass? classFilter = null, int? minLevel = null)
|
|
{
|
|
var query = new GetAllPlayersQuery
|
|
{
|
|
FilterByClass = classFilter,
|
|
MinLevel = minLevel
|
|
};
|
|
return Context.SendQuery(query);
|
|
}
|
|
|
|
public PlayerStatistics GetPlayerStatistics(string playerName)
|
|
{
|
|
var query = new GetPlayerStatisticsQuery { PlayerName = playerName };
|
|
return Context.SendQuery(query);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. 领域驱动设计 (DDD)
|
|
|
|
实现领域驱动设计的核心概念:
|
|
|
|
```csharp
|
|
// 领域实体
|
|
public class Player : AbstractEntity
|
|
{
|
|
public PlayerId Id { get; private set; }
|
|
public PlayerName Name { get; private set; }
|
|
public PlayerClass Class { get; private set; }
|
|
public Level Level { get; private set; }
|
|
public Experience Experience { get; private set; }
|
|
public Health Health { get; private set; }
|
|
public Mana Mana { get; private set; }
|
|
public Inventory Inventory { get; private set; }
|
|
public List<Achievement> Achievements { get; private set; }
|
|
|
|
private List<IDomainEvent> _domainEvents = new();
|
|
|
|
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
|
|
|
|
public Player(PlayerId id, PlayerName name, PlayerClass playerClass)
|
|
{
|
|
Id = id ?? throw new ArgumentNullException(nameof(id));
|
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
|
Class = playerClass;
|
|
|
|
Level = new Level(1);
|
|
Experience = new Experience(0);
|
|
Health = new Health(100, 100);
|
|
Mana = new Mana(50, 50);
|
|
Inventory = new Inventory();
|
|
Achievements = new List<Achievement>();
|
|
|
|
// 添加领域事件
|
|
AddDomainEvent(new PlayerCreatedDomainEvent(Id, Name, Class));
|
|
}
|
|
|
|
public void GainExperience(int amount)
|
|
{
|
|
if (amount <= 0)
|
|
throw new ArgumentException("Experience amount must be positive");
|
|
|
|
var oldLevel = Level.Value;
|
|
Experience.Add(amount);
|
|
|
|
// 检查是否升级
|
|
while (Experience.Value >= CalculateRequiredExperience(Level.Value + 1))
|
|
{
|
|
LevelUp();
|
|
}
|
|
|
|
if (Level.Value > oldLevel)
|
|
{
|
|
AddDomainEvent(new PlayerLevelUpDomainEvent(Id, Name, Level.Value, oldLevel));
|
|
}
|
|
}
|
|
|
|
public void TakeDamage(int damage)
|
|
{
|
|
if (damage < 0)
|
|
throw new ArgumentException("Damage cannot be negative");
|
|
|
|
Health.Reduce(damage);
|
|
|
|
if (Health.Value <= 0)
|
|
{
|
|
Health.Value = 0;
|
|
AddDomainEvent(new PlayerDiedDomainEvent(Id, Name));
|
|
}
|
|
else
|
|
{
|
|
AddDomainEvent(new PlayerDamagedDomainEvent(Id, Name, damage, Health.Value));
|
|
}
|
|
}
|
|
|
|
public void Heal(int amount)
|
|
{
|
|
if (amount <= 0)
|
|
throw new ArgumentException("Heal amount must be positive");
|
|
|
|
Health.Increase(amount);
|
|
AddDomainEvent(new PlayerHealedDomainEvent(Id, Name, amount, Health.Value));
|
|
}
|
|
|
|
public void AddItem(Item item)
|
|
{
|
|
if (item == null)
|
|
throw new ArgumentNullException(nameof(item));
|
|
|
|
Inventory.AddItem(item);
|
|
AddDomainEvent(new ItemAddedDomainEvent(Id, Name, item.Id, item.Name));
|
|
}
|
|
|
|
public void RemoveItem(ItemId itemId)
|
|
{
|
|
var item = Inventory.GetItem(itemId);
|
|
if (item == null)
|
|
throw new InvalidOperationException($"Item {itemId} not found in inventory");
|
|
|
|
Inventory.RemoveItem(itemId);
|
|
AddDomainEvent(new ItemRemovedDomainEvent(Id, Name, itemId, item.Name));
|
|
}
|
|
|
|
private void LevelUp()
|
|
{
|
|
var oldLevel = Level.Value;
|
|
Level.Increase();
|
|
|
|
// 增加属性
|
|
var healthIncrease = CalculateHealthIncrease(Class);
|
|
var manaIncrease = CalculateManaIncrease(Class);
|
|
|
|
Health.IncreaseMax(healthIncrease);
|
|
Health.RestoreToFull();
|
|
|
|
Mana.IncreaseMax(manaIncrease);
|
|
Mana.RestoreToFull();
|
|
|
|
Logger.Info($"Player {Name.Value} leveled up to {Level.Value}");
|
|
}
|
|
|
|
private int CalculateRequiredExperience(int level)
|
|
{
|
|
return level * 1000 + (level - 1) * 500;
|
|
}
|
|
|
|
private int CalculateHealthIncrease(PlayerClass playerClass)
|
|
{
|
|
return playerClass switch
|
|
{
|
|
PlayerClass.Warrior => 20,
|
|
PlayerClass.Mage => 10,
|
|
PlayerClass.Rogue => 15,
|
|
PlayerClass.Priest => 12,
|
|
_ => 15
|
|
};
|
|
}
|
|
|
|
private int CalculateManaIncrease(PlayerClass playerClass)
|
|
{
|
|
return playerClass switch
|
|
{
|
|
PlayerClass.Warrior => 5,
|
|
PlayerClass.Mage => 20,
|
|
PlayerClass.Rogue => 8,
|
|
PlayerClass.Priest => 15,
|
|
_ => 10
|
|
};
|
|
}
|
|
|
|
private void AddDomainEvent(IDomainEvent domainEvent)
|
|
{
|
|
_domainEvents.Add(domainEvent);
|
|
}
|
|
|
|
public void ClearDomainEvents()
|
|
{
|
|
_domainEvents.Clear();
|
|
}
|
|
}
|
|
|
|
// 值对象
|
|
public record PlayerId(Guid Value)
|
|
{
|
|
public static PlayerId New() => new(Guid.NewGuid());
|
|
}
|
|
|
|
public record PlayerName(string Value)
|
|
{
|
|
public PlayerName(string value) : base(value?.Trim() ?? string.Empty)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(Value))
|
|
throw new ArgumentException("Player name cannot be empty");
|
|
|
|
if (Value.Length > 50)
|
|
throw new ArgumentException("Player name cannot exceed 50 characters");
|
|
}
|
|
}
|
|
|
|
public record Level(int Value)
|
|
{
|
|
public Level(int value) : base(Math.Max(1, value))
|
|
{
|
|
if (value < 1)
|
|
throw new ArgumentException("Level cannot be less than 1");
|
|
|
|
if (value > 100)
|
|
throw new ArgumentException("Level cannot exceed 100");
|
|
}
|
|
|
|
public void Increase() => Value + 1;
|
|
}
|
|
|
|
public record Experience(int Value)
|
|
{
|
|
public Experience(int value) : base(Math.Max(0, value))
|
|
{
|
|
if (value < 0)
|
|
throw new ArgumentException("Experience cannot be negative");
|
|
}
|
|
|
|
public void Add(int amount) => Value + amount;
|
|
}
|
|
|
|
public record Health(int Value, int MaxValue)
|
|
{
|
|
public Health(int value, int maxValue) : base(Math.Max(0, value), Math.Max(1, maxValue))
|
|
{
|
|
if (value < 0)
|
|
throw new ArgumentException("Health cannot be negative");
|
|
|
|
if (maxValue <= 0)
|
|
throw new ArgumentException("Max health must be positive");
|
|
|
|
if (value > maxValue)
|
|
Value = maxValue;
|
|
}
|
|
|
|
public void Reduce(int amount) => new(Math.Max(0, Value - amount), MaxValue);
|
|
|
|
public void Increase(int amount) => new(Math.Min(MaxValue, Value + amount), MaxValue);
|
|
|
|
public void IncreaseMax(int amount) => new(Value, MaxValue + amount);
|
|
|
|
public void RestoreToFull() => new(MaxValue, MaxValue);
|
|
}
|
|
|
|
// 领域事件
|
|
public interface IDomainEvent
|
|
{
|
|
DateTime OccurredAt { get; }
|
|
}
|
|
|
|
public record PlayerCreatedDomainEvent(PlayerId PlayerId, PlayerName PlayerName, PlayerClass Class)
|
|
: IDomainEvent
|
|
{
|
|
public DateTime OccurredAt { get; } = DateTime.UtcNow;
|
|
}
|
|
|
|
public record PlayerLevelUpDomainEvent(PlayerId PlayerId, PlayerName PlayerName, int NewLevel, int OldLevel)
|
|
: IDomainEvent
|
|
{
|
|
public DateTime OccurredAt { get; } = DateTime.UtcNow;
|
|
}
|
|
|
|
// 领域服务
|
|
public interface IPlayerDomainService
|
|
{
|
|
bool CanPlayerAttack(Player attacker, Player target);
|
|
int CalculateDamage(Player attacker, Player target);
|
|
bool IsPlayerAlive(Player player);
|
|
}
|
|
|
|
public class PlayerDomainService : IPlayerDomainService
|
|
{
|
|
public bool CanPlayerAttack(Player attacker, Player target)
|
|
{
|
|
return IsPlayerAlive(attacker) && IsPlayerAlive(target) &&
|
|
attacker.Id != target.Id &&
|
|
Vector3.Distance(attacker.Position, target.Position) <= GetAttackRange(attacker.Class);
|
|
}
|
|
|
|
public int CalculateDamage(Player attacker, Player target)
|
|
{
|
|
var baseDamage = GetBaseDamage(attacker.Class, attacker.Level.Value);
|
|
var weaponDamage = attacker.Inventory.EquippedWeapon?.Damage ?? 0;
|
|
var defense = target.Inventory.EquippedArmor?.Defense ?? 0;
|
|
|
|
var totalDamage = baseDamage + weaponDamage - defense;
|
|
return Math.Max(1, totalDamage); // 最少造成1点伤害
|
|
}
|
|
|
|
public bool IsPlayerAlive(Player player)
|
|
{
|
|
return player.Health.Value > 0;
|
|
}
|
|
|
|
private float GetAttackRange(PlayerClass playerClass)
|
|
{
|
|
return playerClass switch
|
|
{
|
|
PlayerClass.Warrior => 1.5f,
|
|
PlayerClass.Mage => 10.0f,
|
|
PlayerClass.Rogue => 1.2f,
|
|
PlayerClass.Priest => 5.0f,
|
|
_ => 2.0f
|
|
};
|
|
}
|
|
|
|
private int GetBaseDamage(PlayerClass playerClass, int level)
|
|
{
|
|
var baseDamage = playerClass switch
|
|
{
|
|
PlayerClass.Warrior => 15,
|
|
PlayerClass.Mage => 8,
|
|
PlayerClass.Rogue => 12,
|
|
PlayerClass.Priest => 6,
|
|
_ => 10
|
|
};
|
|
|
|
return baseDamage + (level - 1) * 2; // 等级加成
|
|
}
|
|
}
|
|
```
|
|
|
|
## 事件驱动架构
|
|
|
|
### 1. 事件溯源模式
|
|
|
|
实现事件的持久化和重放:
|
|
|
|
```csharp
|
|
using GFramework.Core.events;
|
|
using System.Collections.Concurrent;
|
|
|
|
public class EventStore : IEventStore
|
|
{
|
|
private readonly ConcurrentDictionary<string, List<IDomainEvent>> _eventStreams = new();
|
|
private readonly ConcurrentDictionary<string, List<IDomainEvent>> _snapshots = new();
|
|
private readonly object _lock = new();
|
|
|
|
public async Task SaveEventsAsync(string streamId, IEnumerable<IDomainEvent> events, int expectedVersion = -1)
|
|
{
|
|
if (!_eventStreams.TryGetValue(streamId, out var eventStream))
|
|
{
|
|
eventStream = new List<IDomainEvent>();
|
|
_eventStreams[streamId] = eventStream;
|
|
}
|
|
|
|
lock (_lock)
|
|
{
|
|
if (expectedVersion >= 0 && eventStream.Count != expectedVersion)
|
|
{
|
|
throw new ConcurrencyException($"Expected version {expectedVersion}, but stream has {eventStream.Count} events");
|
|
}
|
|
|
|
foreach (var evt in events)
|
|
{
|
|
eventStream.Add(evt);
|
|
}
|
|
|
|
// 定期创建快照
|
|
if (eventStream.Count % 100 == 0)
|
|
{
|
|
await CreateSnapshotAsync(streamId);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<IDomainEvent>> GetEventsAsync(string streamId, int fromVersion = 0)
|
|
{
|
|
// 检查是否可以从快照开始
|
|
var snapshotVersion = GetSnapshotVersion(streamId, fromVersion);
|
|
if (snapshotVersion >= 0)
|
|
{
|
|
var events = new List<IDomainEvent>();
|
|
var snapshot = _snapshots[streamId][snapshotVersion];
|
|
events.Add(snapshot);
|
|
|
|
// 添加快照之后的事件
|
|
if (_eventStreams.TryGetValue(streamId, out var eventStream))
|
|
{
|
|
events.AddRange(eventStream.Skip(snapshotVersion + 1));
|
|
}
|
|
|
|
return events;
|
|
}
|
|
|
|
// 返回所有事件
|
|
return _eventStreams.TryGetValue(streamId, out var eventStream)
|
|
? eventStream.Skip(fromVersion)
|
|
: Enumerable.Empty<IDomainEvent>();
|
|
}
|
|
|
|
private async Task CreateSnapshotAsync(string streamId)
|
|
{
|
|
// 这里应该根据事件重建聚合状态
|
|
// 简化实现,实际应该更复杂
|
|
if (_eventStreams.TryGetValue(streamId, out var eventStream))
|
|
{
|
|
var snapshot = new AggregateSnapshot(streamId, eventStream.Count - 1);
|
|
|
|
if (!_snapshots.TryGetValue(streamId, out var snapshotList))
|
|
{
|
|
snapshotList = new List<IDomainEvent>();
|
|
_snapshots[streamId] = snapshotList;
|
|
}
|
|
|
|
snapshotList.Add(snapshot);
|
|
}
|
|
}
|
|
|
|
private int GetSnapshotVersion(string streamId, int fromVersion)
|
|
{
|
|
if (!_snapshots.TryGetValue(streamId, out var snapshotList))
|
|
return -1;
|
|
|
|
// 找到最近的快照版本
|
|
for (int i = snapshotList.Count - 1; i >= 0; i--)
|
|
{
|
|
if (((AggregateSnapshot)snapshotList[i]).Version >= fromVersion)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
public interface IEventStore
|
|
{
|
|
Task SaveEventsAsync(string streamId, IEnumerable<IDomainEvent> events, int expectedVersion = -1);
|
|
Task<IEnumerable<IDomainEvent>> GetEventsAsync(string streamId, int fromVersion = 0);
|
|
}
|
|
|
|
public record AggregateSnapshot(string StreamId, int Version) : IDomainEvent
|
|
{
|
|
public DateTime OccurredAt { get; } = DateTime.UtcNow;
|
|
}
|
|
|
|
public class ConcurrencyException : Exception
|
|
{
|
|
public ConcurrencyException(string message) : base(message) { }
|
|
}
|
|
|
|
// 聚合根重建器
|
|
public class AggregateRootBuilder
|
|
{
|
|
private readonly IEventStore _eventStore;
|
|
|
|
public AggregateRootBuilder(IEventStore eventStore)
|
|
{
|
|
_eventStore = eventStore;
|
|
}
|
|
|
|
public async Task<T> RebuildAsync<T>(string aggregateId) where T : AggregateRoot, new()
|
|
{
|
|
var events = await _eventStore.GetEventsAsync(aggregateId);
|
|
var aggregate = new T();
|
|
|
|
foreach (var evt in events)
|
|
{
|
|
aggregate.ApplyEvent(evt);
|
|
}
|
|
|
|
aggregate.ClearUncommittedEvents();
|
|
return aggregate;
|
|
}
|
|
}
|
|
|
|
public abstract class AggregateRoot
|
|
{
|
|
public string Id { get; protected set; }
|
|
public int Version { get; protected set; }
|
|
|
|
private readonly List<IDomainEvent> _uncommittedEvents = new();
|
|
|
|
public IReadOnlyCollection<IDomainEvent> GetUncommittedEvents() => _uncommittedEvents.AsReadOnly();
|
|
public void ClearUncommittedEvents() => _uncommittedEvents.Clear();
|
|
|
|
protected void ApplyEvent(IDomainEvent evt)
|
|
{
|
|
// 调用具体的事件应用方法
|
|
When(evt);
|
|
|
|
// 添加到未提交事件列表
|
|
_uncommittedEvents.Add(evt);
|
|
Version++;
|
|
}
|
|
|
|
protected abstract void When(IDomainEvent evt);
|
|
|
|
public void LoadFromHistory(IEnumerable<IDomainEvent> events)
|
|
{
|
|
foreach (var evt in events)
|
|
{
|
|
When(evt);
|
|
Version++;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. 事件总线模式
|
|
|
|
实现灵活的事件路由和处理:
|
|
|
|
```csharp
|
|
using GFramework.Core.events;
|
|
|
|
public class EventBus : IEventBus
|
|
{
|
|
private readonly Dictionary<Type, List<IEventHandler>> _handlers = new();
|
|
private readonly Dictionary<Type, List<IAsyncEventHandler>> _asyncHandlers = new();
|
|
private readonly IEventStore _eventStore;
|
|
private readonly object _lock = new();
|
|
|
|
public EventBus(IEventStore eventStore = null)
|
|
{
|
|
_eventStore = eventStore;
|
|
}
|
|
|
|
public void Subscribe<T>(IEventHandler<T> handler) where T : IEvent
|
|
{
|
|
lock (_lock)
|
|
{
|
|
var eventType = typeof(T);
|
|
if (!_handlers.ContainsKey(eventType))
|
|
{
|
|
_handlers[eventType] = new List<IEventHandler>();
|
|
}
|
|
|
|
_handlers[eventType].Add(handler);
|
|
}
|
|
}
|
|
|
|
public void SubscribeAsync<T>(IAsyncEventHandler<T> handler) where T : IEvent
|
|
{
|
|
lock (_lock)
|
|
{
|
|
var eventType = typeof(T);
|
|
if (!_asyncHandlers.ContainsKey(eventType))
|
|
{
|
|
_asyncHandlers[eventType] = new List<IAsyncEventHandler>();
|
|
}
|
|
|
|
_asyncHandlers[eventType].Add(handler);
|
|
}
|
|
}
|
|
|
|
public async Task PublishAsync<T>(T evt) where T : IEvent
|
|
{
|
|
// 持久化事件(如果有事件存储)
|
|
if (_eventStore != null && evt is IDomainEvent domainEvent)
|
|
{
|
|
var streamId = GetStreamId(domainEvent);
|
|
await _eventStore.SaveEventsAsync(streamId, new[] { domainEvent });
|
|
}
|
|
|
|
var eventType = typeof(T);
|
|
var tasks = new List<Task>();
|
|
|
|
// 处理同步处理器
|
|
if (_handlers.TryGetValue(eventType, out var syncHandlers))
|
|
{
|
|
foreach (var handler in syncHandlers)
|
|
{
|
|
try
|
|
{
|
|
if (handler is IEventHandler<T> typedHandler)
|
|
{
|
|
typedHandler.Handle(evt);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// 记录错误但不中断其他处理器
|
|
GD.PrintErr($"Error in event handler: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 处理异步处理器
|
|
if (_asyncHandlers.TryGetValue(eventType, out var asyncHandlers))
|
|
{
|
|
foreach (var handler in asyncHandlers)
|
|
{
|
|
try
|
|
{
|
|
if (handler is IAsyncEventHandler<T> typedHandler)
|
|
{
|
|
tasks.Add(typedHandler.HandleAsync(evt));
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
GD.PrintErr($"Error in async event handler: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 等待所有异步处理器完成
|
|
if (tasks.Count > 0)
|
|
{
|
|
await Task.WhenAll(tasks);
|
|
}
|
|
}
|
|
|
|
private string GetStreamId(IDomainEvent domainEvent)
|
|
{
|
|
// 根据领域事件类型生成流ID
|
|
return domainEvent switch
|
|
{
|
|
PlayerCreatedDomainEvent evt => $"player-{evt.PlayerId}",
|
|
PlayerLevelUpDomainEvent evt => $"player-{evt.PlayerId}",
|
|
PlayerDiedDomainEvent evt => $"player-{evt.PlayerId}",
|
|
_ => $"unknown-{Guid.NewGuid()}"
|
|
};
|
|
}
|
|
}
|
|
|
|
public interface IEventBus
|
|
{
|
|
void Subscribe<T>(IEventHandler<T> handler) where T : IEvent;
|
|
void SubscribeAsync<T>(IAsyncEventHandler<T> handler) where T : IEvent;
|
|
Task PublishAsync<T>(T evt) where T : IEvent;
|
|
}
|
|
|
|
public interface IEventHandler<in T> where T : IEvent
|
|
{
|
|
void Handle(T evt);
|
|
}
|
|
|
|
public interface IAsyncEventHandler<in T> where T : IEvent
|
|
{
|
|
Task HandleAsync(T evt);
|
|
}
|
|
|
|
// 事件处理器示例
|
|
public class PlayerEventHandler : IEventHandler<PlayerLevelUpEvent>,
|
|
IAsyncEventHandler<PlayerDiedEvent>
|
|
{
|
|
private readonly IPlayerRepository _playerRepository;
|
|
private readonly INotificationService _notificationService;
|
|
|
|
public PlayerEventHandler(IPlayerRepository playerRepository,
|
|
INotificationService notificationService)
|
|
{
|
|
_playerRepository = playerRepository;
|
|
_notificationService = notificationService;
|
|
}
|
|
|
|
public void Handle(PlayerLevelUpEvent evt)
|
|
{
|
|
// 同步处理升级事件
|
|
var player = _playerRepository.GetById(evt.PlayerId);
|
|
if (player != null)
|
|
{
|
|
player.Level = evt.NewLevel;
|
|
_playerRepository.Update(player);
|
|
|
|
GD.Print($"Player {evt.PlayerName} leveled up to {evt.NewLevel}");
|
|
}
|
|
}
|
|
|
|
public async Task HandleAsync(PlayerDiedEvent evt)
|
|
{
|
|
// 异步处理死亡事件
|
|
var player = _playerRepository.GetById(evt.PlayerId);
|
|
if (player != null)
|
|
{
|
|
player.IsAlive = false;
|
|
player.DeathTime = DateTime.UtcNow;
|
|
_playerRepository.Update(player);
|
|
|
|
// 发送通知
|
|
await _notificationService.SendNotificationAsync(
|
|
$"Player {evt.PlayerName} has died",
|
|
NotificationType.Warning
|
|
);
|
|
|
|
// 记录到外部系统
|
|
await LogPlayerDeathToAnalyticsAsync(evt);
|
|
}
|
|
}
|
|
|
|
private async Task LogPlayerDeathToAnalyticsAsync(PlayerDiedEvent evt)
|
|
{
|
|
// 发送分析数据
|
|
await Task.Delay(100); // 模拟网络请求
|
|
GD.Print($"Death analytics sent for player {evt.PlayerId}");
|
|
}
|
|
}
|
|
```
|
|
|
|
## 插件系统
|
|
|
|
### 1. 插件架构
|
|
|
|
实现可扩展的插件系统:
|
|
|
|
```csharp
|
|
using System.Reflection;
|
|
using System.Collections.Concurrent;
|
|
|
|
public interface IPlugin
|
|
{
|
|
string Name { get; }
|
|
string Version { get; }
|
|
string Description { get; }
|
|
string Author { get; }
|
|
string[] Dependencies { get; }
|
|
|
|
Task InitializeAsync(IPluginContext context);
|
|
Task ShutdownAsync();
|
|
bool IsCompatible(string frameworkVersion);
|
|
}
|
|
|
|
public interface IPluginContext
|
|
{
|
|
IArchitecture Architecture { get; }
|
|
IServiceContainer Services { get; }
|
|
IEventManager Events { get; }
|
|
ILogger Logger { get; }
|
|
|
|
void RegisterService<T>(T service) where T : class;
|
|
T GetService<T>() where T : class;
|
|
void RegisterEventHandler<T>(IEventHandler<T> handler) where T : IEvent;
|
|
}
|
|
|
|
public class PluginManager : IPluginContext
|
|
{
|
|
private readonly Dictionary<string, IPlugin> _loadedPlugins = new();
|
|
private readonly ConcurrentDictionary<Type, object> _services = new();
|
|
private readonly List<IEventHandler> _eventHandlers = new();
|
|
private readonly IArchitecture _architecture;
|
|
private readonly ILogger _logger;
|
|
|
|
public IArchitecture Architecture => _architecture;
|
|
public IServiceContainer Services => this;
|
|
public IEventManager Events => _architecture.Context;
|
|
public ILogger Logger => _logger;
|
|
|
|
public PluginManager(IArchitecture architecture, ILogger logger)
|
|
{
|
|
_architecture = architecture;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task LoadPluginAsync(string pluginPath)
|
|
{
|
|
try
|
|
{
|
|
_logger.Info($"Loading plugin from: {pluginPath}");
|
|
|
|
// 加载程序集
|
|
var assembly = Assembly.LoadFrom(pluginPath);
|
|
|
|
// 查找插件类型
|
|
var pluginTypes = assembly.GetTypes()
|
|
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
|
|
|
|
foreach (var pluginType in pluginTypes)
|
|
{
|
|
var plugin = (IPlugin)Activator.CreateInstance(pluginType);
|
|
await LoadPluginInstanceAsync(plugin);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error($"Failed to load plugin from {pluginPath}: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private async Task LoadPluginInstanceAsync(IPlugin plugin)
|
|
{
|
|
// 检查兼容性
|
|
if (!plugin.IsCompatible(GetCurrentFrameworkVersion()))
|
|
{
|
|
throw new IncompatiblePluginException(
|
|
$"Plugin {plugin.Name} v{plugin.Version} is not compatible with framework version {GetCurrentFrameworkVersion()}"
|
|
);
|
|
}
|
|
|
|
// 检查依赖
|
|
foreach (var dependency in plugin.Dependencies)
|
|
{
|
|
if (!_loadedPlugins.ContainsKey(dependency))
|
|
{
|
|
throw new MissingDependencyException(
|
|
$"Plugin {plugin.Name} requires plugin {dependency} to be loaded first"
|
|
);
|
|
}
|
|
}
|
|
|
|
// 初始化插件
|
|
await plugin.InitializeAsync(this);
|
|
_loadedPlugins[plugin.Name] = plugin;
|
|
|
|
_logger.Info($"Plugin {plugin.Name} v{plugin.Version} loaded successfully");
|
|
}
|
|
|
|
public async Task UnloadPluginAsync(string pluginName)
|
|
{
|
|
if (!_loadedPlugins.TryGetValue(pluginName, out var plugin))
|
|
{
|
|
throw new PluginNotFoundException($"Plugin {pluginName} is not loaded");
|
|
}
|
|
|
|
try
|
|
{
|
|
// 检查是否有其他插件依赖此插件
|
|
var dependentPlugins = _loadedPlugins.Values
|
|
.Where(p => p.Dependencies.Contains(pluginName))
|
|
.ToList();
|
|
|
|
if (dependentPlugins.Any())
|
|
{
|
|
var dependentNames = string.Join(", ", dependentPlugins.Select(p => p.Name));
|
|
throw new DependencyException(
|
|
$"Cannot unload plugin {pluginName} because the following plugins depend on it: {dependentNames}"
|
|
);
|
|
}
|
|
|
|
// 关闭插件
|
|
await plugin.ShutdownAsync();
|
|
_loadedPlugins.Remove(pluginName);
|
|
|
|
_logger.Info($"Plugin {pluginName} unloaded successfully");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error($"Failed to unload plugin {pluginName}: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public IEnumerable<IPlugin> GetLoadedPlugins()
|
|
{
|
|
return _loadedPlugins.Values.ToList();
|
|
}
|
|
|
|
public IPlugin GetPlugin(string name)
|
|
{
|
|
return _loadedPlugins.TryGetValue(name, out var plugin) ? plugin : null;
|
|
}
|
|
|
|
// IPluginContext 实现
|
|
public void RegisterService<T>(T service) where T : class
|
|
{
|
|
_services.TryAdd(typeof(T), service);
|
|
_logger.Debug($"Service {typeof(T).Name} registered by plugin system");
|
|
}
|
|
|
|
public T GetService<T>() where T : class
|
|
{
|
|
if (_services.TryGetValue(typeof(T), out var service))
|
|
{
|
|
return (T)service;
|
|
}
|
|
|
|
// 尝试从架构中获取
|
|
return _architecture.Context.GetUtility<T>();
|
|
}
|
|
|
|
public void RegisterEventHandler<T>(IEventHandler<T> handler) where T : IEvent
|
|
{
|
|
_eventHandlers.Add(handler);
|
|
_architecture.Context.RegisterEvent<T>(handler.Handle);
|
|
_logger.Debug($"Event handler for {typeof(T).Name} registered by plugin system");
|
|
}
|
|
|
|
private string GetCurrentFrameworkVersion()
|
|
{
|
|
return "1.0.0"; // 应该从配置或程序集中读取
|
|
}
|
|
}
|
|
|
|
// 插件示例
|
|
public class ChatPlugin : IPlugin
|
|
{
|
|
public string Name => "Chat";
|
|
public string Version => "1.2.0";
|
|
public string Description => "In-game chat system with moderation features";
|
|
public string Author => "GameStudio";
|
|
public string[] Dependencies => new[] { "Authentication" };
|
|
|
|
private IChatService _chatService;
|
|
private ILogger _logger;
|
|
|
|
public async Task InitializeAsync(IPluginContext context)
|
|
{
|
|
_logger = context.Logger;
|
|
|
|
// 创建聊天服务
|
|
_chatService = new ChatService(context.Architecture);
|
|
|
|
// 注册服务
|
|
context.RegisterService<IChatService>(_chatService);
|
|
|
|
// 注册事件处理器
|
|
context.RegisterEventHandler<PlayerJoinEvent>(OnPlayerJoin);
|
|
context.RegisterEventHandler<PlayerLeaveEvent>(OnPlayerLeave);
|
|
context.RegisterEventHandler<ChatMessageEvent>(OnChatMessage);
|
|
|
|
_logger.Info("Chat plugin initialized");
|
|
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
public async Task ShutdownAsync()
|
|
{
|
|
_chatService?.Dispose();
|
|
_logger.Info("Chat plugin shutdown");
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
public bool IsCompatible(string frameworkVersion)
|
|
{
|
|
// 检查版本兼容性
|
|
return Version.TryParse(frameworkVersion, out var version) &&
|
|
version >= new Version(1, 0, 0);
|
|
}
|
|
|
|
private void OnPlayerJoin(PlayerJoinEvent evt)
|
|
{
|
|
_chatService.SendSystemMessage($"{evt.PlayerName} joined the game");
|
|
}
|
|
|
|
private void OnPlayerLeave(PlayerLeaveEvent evt)
|
|
{
|
|
_chatService.SendSystemMessage($"{evt.PlayerName} left the game");
|
|
}
|
|
|
|
private void OnChatMessage(ChatMessageEvent evt)
|
|
{
|
|
_chatService.SendMessage(evt.PlayerId, evt.Message, evt.Channel);
|
|
}
|
|
}
|
|
|
|
public interface IChatService
|
|
{
|
|
void SendMessage(string playerId, string message, string channel = "global");
|
|
void SendSystemMessage(string message);
|
|
void Dispose();
|
|
}
|
|
|
|
public class ChatService : IChatService
|
|
{
|
|
private readonly IArchitecture _architecture;
|
|
private readonly Dictionary<string, ChatChannel> _channels = new();
|
|
|
|
public ChatService(IArchitecture architecture)
|
|
{
|
|
_architecture = architecture;
|
|
InitializeChannels();
|
|
}
|
|
|
|
private void InitializeChannels()
|
|
{
|
|
_channels["global"] = new ChatChannel("global", "Global Chat");
|
|
_channels["trade"] = new ChatChannel("trade", "Trade Chat");
|
|
_channels["guild"] = new ChatChannel("guild", "Guild Chat");
|
|
}
|
|
|
|
public void SendMessage(string playerId, string message, string channel = "global")
|
|
{
|
|
if (!_channels.TryGetValue(channel, out var chatChannel))
|
|
{
|
|
throw new ArgumentException($"Unknown channel: {channel}");
|
|
}
|
|
|
|
var chatMessage = new ChatMessage
|
|
{
|
|
PlayerId = playerId,
|
|
Message = FilterMessage(message),
|
|
Timestamp = DateTime.UtcNow,
|
|
Channel = channel
|
|
};
|
|
|
|
chatChannel.AddMessage(chatMessage);
|
|
|
|
// 发送事件
|
|
_architecture.Context.SendEvent(new ChatMessageReceivedEvent(chatMessage));
|
|
}
|
|
|
|
public void SendSystemMessage(string message)
|
|
{
|
|
var systemMessage = new ChatMessage
|
|
{
|
|
PlayerId = "system",
|
|
Message = message,
|
|
Timestamp = DateTime.UtcNow,
|
|
Channel = "system"
|
|
};
|
|
|
|
_channels["global"].AddMessage(systemMessage);
|
|
_architecture.Context.SendEvent(new ChatMessageReceivedEvent(systemMessage));
|
|
}
|
|
|
|
private string FilterMessage(string message)
|
|
{
|
|
// 实现聊天过滤逻辑
|
|
return message;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_channels.Clear();
|
|
}
|
|
}
|
|
|
|
public class ChatChannel
|
|
{
|
|
public string Name { get; }
|
|
public string Description { get; }
|
|
public Queue<ChatMessage> Messages { get; } = new();
|
|
public int MaxMessages { get; set; } = 100;
|
|
|
|
public ChatChannel(string name, string description)
|
|
{
|
|
Name = name;
|
|
Description = description;
|
|
}
|
|
|
|
public void AddMessage(ChatMessage message)
|
|
{
|
|
Messages.Enqueue(message);
|
|
|
|
// 限制消息数量
|
|
while (Messages.Count > MaxMessages)
|
|
{
|
|
Messages.Dequeue();
|
|
}
|
|
}
|
|
}
|
|
|
|
public record ChatMessage
|
|
{
|
|
public string PlayerId { get; init; }
|
|
public string Message { get; init; }
|
|
public DateTime Timestamp { get; init; }
|
|
public string Channel { get; init; }
|
|
}
|
|
|
|
public struct ChatMessageReceivedEvent
|
|
{
|
|
public ChatMessage Message { get; init; }
|
|
}
|
|
```
|
|
|
|
## 网络集成
|
|
|
|
### 1. 网络架构
|
|
|
|
实现基于事件的网络系统:
|
|
|
|
```csharp
|
|
using Godot;
|
|
using System.Net.WebSockets;
|
|
|
|
public class NetworkManager : Node, INetworkManager
|
|
{
|
|
private ClientWebSocket _webSocket;
|
|
private CancellationTokenSource _cancellationTokenSource;
|
|
private readonly ConcurrentQueue<NetworkMessage> _messageQueue = new();
|
|
private readonly Dictionary<string, Type> _messageTypes = new();
|
|
private bool _isConnected = false;
|
|
|
|
[Signal]
|
|
public delegate void ConnectedEventHandler();
|
|
|
|
[Signal]
|
|
public delegate void DisconnectedEventHandler(string reason);
|
|
|
|
[Signal]
|
|
public delegate void MessageReceivedEventHandler(NetworkMessage message);
|
|
|
|
[Signal]
|
|
public delegate void ConnectionFailedEventHandler(string error);
|
|
|
|
public override void _Ready()
|
|
{
|
|
RegisterMessageTypes();
|
|
SetProcess(true);
|
|
}
|
|
|
|
private void RegisterMessageTypes()
|
|
{
|
|
_messageTypes["player_position"] = typeof(PlayerPositionMessage);
|
|
_messageTypes["chat_message"] = typeof(ChatMessageMessage);
|
|
_messageTypes["player_action"] = typeof(PlayerActionMessage);
|
|
_messageTypes["game_state"] = typeof(GameStateMessage);
|
|
}
|
|
|
|
public async Task ConnectAsync(string url)
|
|
{
|
|
try
|
|
{
|
|
_webSocket = new ClientWebSocket();
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
GD.Print($"Connecting to {url}");
|
|
await _webSocket.ConnectAsync(new Uri(url), _cancellationTokenSource.Token);
|
|
|
|
_isConnected = true;
|
|
EmitSignal(SignalName.Connected);
|
|
|
|
// 启动消息接收循环
|
|
_ = Task.Run(ReceiveMessagesLoop);
|
|
|
|
GD.Print("Connected to server");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
GD.PrintErr($"Connection failed: {ex.Message}");
|
|
EmitSignal(SignalName.ConnectionFailed, ex.Message);
|
|
}
|
|
}
|
|
|
|
public async Task DisconnectAsync()
|
|
{
|
|
if (_webSocket != null && _webSocket.State == WebSocketState.Open)
|
|
{
|
|
_isConnected = false;
|
|
_cancellationTokenSource?.Cancel();
|
|
|
|
try
|
|
{
|
|
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Disconnecting", CancellationToken.None);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
GD.PrintErr($"Error during disconnect: {ex.Message}");
|
|
}
|
|
|
|
EmitSignal(SignalName.Disconnected, "Manual disconnect");
|
|
}
|
|
}
|
|
|
|
public async Task SendMessageAsync(NetworkMessage message)
|
|
{
|
|
if (!_isConnected || _webSocket?.State != WebSocketState.Open)
|
|
{
|
|
GD.PrintErr("Not connected to server");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var json = Json.Stringify(message.Serialize());
|
|
var buffer = System.Text.Encoding.UTF8.GetBytes(json);
|
|
|
|
await _webSocket.SendAsync(new ArraySegment<byte>(buffer),
|
|
WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
GD.PrintErr($"Failed to send message: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task ReceiveMessagesLoop()
|
|
{
|
|
var buffer = new byte[4096];
|
|
|
|
while (_isConnected && _cancellationTokenSource?.Token.IsCancellationRequested == false)
|
|
{
|
|
try
|
|
{
|
|
var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer),
|
|
_cancellationTokenSource.Token);
|
|
|
|
if (result.MessageType == WebSocketMessageType.Text)
|
|
{
|
|
var json = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
|
|
var messageData = Json.ParseString(json);
|
|
var message = ParseMessage(messageData);
|
|
|
|
if (message != null)
|
|
{
|
|
_messageQueue.Enqueue(message);
|
|
}
|
|
}
|
|
else if (result.MessageType == WebSocketMessageType.Close)
|
|
{
|
|
_isConnected = false;
|
|
EmitSignal(SignalName.Disconnected, "Server closed connection");
|
|
break;
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// 正常的取消操作
|
|
break;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
GD.PrintErr($"Error receiving message: {ex.Message}");
|
|
_isConnected = false;
|
|
EmitSignal(SignalName.Disconnected, ex.Message);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private NetworkMessage ParseMessage(Godot.Collections.Dictionary messageData)
|
|
{
|
|
if (!messageData.ContainsKey("type"))
|
|
{
|
|
GD.PrintErr("Message missing type field");
|
|
return null;
|
|
}
|
|
|
|
var messageType = messageData["type"].ToString();
|
|
if (!_messageTypes.TryGetValue(messageType, out var type))
|
|
{
|
|
GD.PrintErr($"Unknown message type: {messageType}");
|
|
return null;
|
|
}
|
|
|
|
var message = (NetworkMessage)Activator.CreateInstance(type);
|
|
message.Deserialize(messageData);
|
|
|
|
return message;
|
|
}
|
|
|
|
public override void _Process(double delta)
|
|
{
|
|
// 处理接收到的消息
|
|
while (_messageQueue.TryDequeue(out var message))
|
|
{
|
|
EmitSignal(SignalName.MessageReceived, message);
|
|
HandleNetworkMessage(message);
|
|
}
|
|
}
|
|
|
|
private void HandleNetworkMessage(NetworkMessage message)
|
|
{
|
|
// 根据消息类型处理网络消息
|
|
switch (message)
|
|
{
|
|
case PlayerPositionMessage posMsg:
|
|
HandlePlayerPosition(posMsg);
|
|
break;
|
|
case ChatMessageMessage chatMsg:
|
|
HandleChatMessage(chatMsg);
|
|
break;
|
|
case PlayerActionMessage actionMsg:
|
|
HandlePlayerAction(actionMsg);
|
|
break;
|
|
case GameStateMessage stateMsg:
|
|
HandleGameState(stateMsg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void HandlePlayerPosition(PlayerPositionMessage message)
|
|
{
|
|
// 更新其他玩家位置
|
|
Context.SendEvent(new NetworkPlayerPositionEvent
|
|
{
|
|
PlayerId = message.PlayerId,
|
|
Position = new Vector2(message.X, message.Y)
|
|
});
|
|
}
|
|
|
|
private void HandleChatMessage(ChatMessageMessage message)
|
|
{
|
|
// 显示聊天消息
|
|
Context.SendEvent(new NetworkChatEvent
|
|
{
|
|
PlayerName = message.PlayerName,
|
|
Message = message.Content,
|
|
Channel = message.Channel
|
|
});
|
|
}
|
|
|
|
private void HandlePlayerAction(PlayerActionMessage message)
|
|
{
|
|
// 处理玩家动作
|
|
Context.SendEvent(new NetworkPlayerActionEvent
|
|
{
|
|
PlayerId = message.PlayerId,
|
|
Action = message.Action,
|
|
Data = message.Data
|
|
});
|
|
}
|
|
|
|
private void HandleGameState(GameStateMessage message)
|
|
{
|
|
// 更新游戏状态
|
|
Context.SendEvent(new NetworkGameStateEvent
|
|
{
|
|
State = message.State,
|
|
Data = message.Data
|
|
});
|
|
}
|
|
|
|
public override void _ExitTree()
|
|
{
|
|
_cancellationTokenSource?.Cancel();
|
|
_webSocket?.Dispose();
|
|
base._ExitTree();
|
|
}
|
|
}
|
|
|
|
// 网络消息基类
|
|
public abstract class NetworkMessage
|
|
{
|
|
public abstract string Type { get; }
|
|
|
|
public abstract Godot.Collections.Dictionary Serialize();
|
|
public abstract void Deserialize(Godot.Collections.Dictionary data);
|
|
}
|
|
|
|
// 具体网络消息
|
|
public class PlayerPositionMessage : NetworkMessage
|
|
{
|
|
public string PlayerId { get; set; }
|
|
public float X { get; set; }
|
|
public float Y { get; set; }
|
|
public float Rotation { get; set; }
|
|
|
|
public override string Type => "player_position";
|
|
|
|
public override Godot.Collections.Dictionary Serialize()
|
|
{
|
|
return new Godot.Collections.Dictionary
|
|
{
|
|
["type"] = Type,
|
|
["player_id"] = PlayerId,
|
|
["x"] = X,
|
|
["y"] = Y,
|
|
["rotation"] = Rotation
|
|
};
|
|
}
|
|
|
|
public override void Deserialize(Godot.Collections.Dictionary data)
|
|
{
|
|
PlayerId = data["player_id"].ToString();
|
|
X = (float)data["x"];
|
|
Y = (float)data["y"];
|
|
Rotation = (float)data["rotation"];
|
|
}
|
|
}
|
|
|
|
// 网络事件
|
|
public struct NetworkPlayerPositionEvent
|
|
{
|
|
public string PlayerId { get; set; }
|
|
public Vector2 Position { get; set; }
|
|
}
|
|
|
|
public struct NetworkChatEvent
|
|
{
|
|
public string PlayerName { get; set; }
|
|
public string Message { get; set; }
|
|
public string Channel { get; set; }
|
|
}
|
|
|
|
// 网络控制器
|
|
[ContextAware]
|
|
[Log]
|
|
public partial class NetworkController : Node, IController
|
|
{
|
|
private NetworkManager _networkManager;
|
|
|
|
public override void _Ready()
|
|
{
|
|
_networkManager = new NetworkManager();
|
|
AddChild(_networkManager);
|
|
|
|
// 连接网络事件
|
|
_networkManager.Connected += OnNetworkConnected;
|
|
_networkManager.Disconnected += OnNetworkDisconnected;
|
|
_networkManager.MessageReceived += OnNetworkMessageReceived;
|
|
_networkManager.ConnectionFailed += OnConnectionFailed;
|
|
}
|
|
|
|
public async Task ConnectToServer(string url)
|
|
{
|
|
Logger.Info($"Connecting to server: {url}");
|
|
await _networkManager.ConnectAsync(url);
|
|
}
|
|
|
|
public void SendPlayerPosition(Vector2 position)
|
|
{
|
|
var message = new PlayerPositionMessage
|
|
{
|
|
PlayerId = Context.GetModel<PlayerModel>().PlayerId,
|
|
X = position.X,
|
|
Y = position.Y,
|
|
Rotation = 0f // 根据实际需要设置
|
|
};
|
|
|
|
_networkManager.SendMessageAsync(message);
|
|
}
|
|
|
|
public void SendChatMessage(string message, string channel = "global")
|
|
{
|
|
var chatMessage = new ChatMessageMessage
|
|
{
|
|
PlayerName = Context.GetModel<PlayerModel>().Name,
|
|
Content = message,
|
|
Channel = channel
|
|
};
|
|
|
|
_networkManager.SendMessageAsync(chatMessage);
|
|
}
|
|
|
|
private void OnNetworkConnected()
|
|
{
|
|
Logger.Info("Connected to network server");
|
|
Context.SendEvent(new NetworkConnectedEvent());
|
|
}
|
|
|
|
private void OnNetworkDisconnected(string reason)
|
|
{
|
|
Logger.Info($"Disconnected from network server: {reason}");
|
|
Context.SendEvent(new NetworkDisconnectedEvent { Reason = reason });
|
|
}
|
|
|
|
private void OnNetworkMessageReceived(NetworkMessage message)
|
|
{
|
|
Logger.Debug($"Received network message: {message.Type}");
|
|
}
|
|
|
|
private void OnConnectionFailed(string error)
|
|
{
|
|
Logger.Error($"Network connection failed: {error}");
|
|
Context.SendEvent(new NetworkConnectionFailedEvent { Error = error });
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 总结
|
|
|
|
通过本高级模式教程,你已经学会了:
|
|
|
|
- ✅ **CQRS 模式** - 分离命令和查询职责
|
|
- ✅ **领域驱动设计** - 构建丰富的领域模型
|
|
- ✅ **事件溯源** - 持久化和重放领域事件
|
|
- ✅ **插件系统** - 可扩展的插件架构
|
|
- ✅ **网络集成** - 实时多人游戏网络支持
|
|
|
|
这些高级模式将帮助你构建企业级的大型游戏系统。
|
|
|
|
---
|
|
|
|
**教程版本**: 1.0.0
|
|
**更新日期**: 2026-01-12 |