using System.Collections.Concurrent;
using System.Text;
using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Base.Addons;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Common.Event;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
{
///
/// 建议使用一个类来存储常量,方便重用
///
public class ExampleGameModuleConstant
{
public static GameModuleDepend GameModuleDepend => _depends;
public const string ExampleGameModule = "fungame.example.gamemodule";
public const string ExampleMap = "fungame.example.gamemap";
public const string ExampleCharacter = "fungame.example.character";
public const string ExampleSkill = "fungame.example.skill";
public const string ExampleItem = "fungame.example.item";
private static readonly string[] Maps = [ExampleMap];
private static readonly string[] Characters = [ExampleCharacter];
private static readonly string[] Skills = [ExampleSkill];
private static readonly string[] Items = [ExampleItem];
private static readonly GameModuleDepend _depends = new(Maps, Characters, Skills, Items);
}
///
/// GameModule 是用于客户端的模组。每个模组都有一个对应的服务器模组,可以简单理解为“一种游戏模式”
/// 必须继承基类:
/// 继承事件接口并实现其方法来使模组生效。例如继承:
///
public class ExampleGameModule : GameModule, IGamingUpdateInfoEvent
{
public override string Name => ExampleGameModuleConstant.ExampleGameModule;
public override string Description => "My First GameModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override string DefaultMap => GameModuleDepend.MapsDepend.Length > 0 ? GameModuleDepend.MapsDepend[0] : "";
public override GameModuleDepend GameModuleDepend => ExampleGameModuleConstant.GameModuleDepend;
public override RoomType RoomType => RoomType.Mix;
public override int MaxUsers => 8;
public override bool HideMain => false;
public ExampleGameModule()
{
// 构造函数中可以指定模组连接到哪个模组服务器。
// 如果你使用自己的,保持默认即可:删除下面两行,并将模组服务器的名称设置为与此模组的名称相同
IsConnectToOtherServerModule = true;
AssociatedServerModuleName = ExampleGameModuleConstant.ExampleGameModule;
}
protected Gaming? Instance;
protected Room room = General.HallInstance;
protected List users = [];
protected Dictionary characters = [];
public override void StartGame(Gaming instance, params object[] args)
{
Instance = instance;
// 取得房间玩家等信息
GamingEventArgs eventArgs = instance.EventArgs;
room = eventArgs.Room;
users = eventArgs.Users;
// 客户端做好准备后,等待服务器的消息通知,下面可以根据需求进一步处理
}
public override void StartUI(params object[] args)
{
/// 如果模组不依附 类启动,或者没有UI,则不需要重写此方法
}
public void GamingUpdateInfoEvent(object sender, GamingEventArgs e, Dictionary data)
{
// 在下方的Server示例中,服务器发来的data中,包含check字符串,因此客户端要主动发起确认连接的请求。
if (data.ContainsKey("info_type"))
{
// 反序列化得到指定key的值
string info_type = DataRequest.GetDictionaryJsonObject(data, "info_type") ?? "";
if (info_type == "check")
{
Guid token = DataRequest.GetDictionaryJsonObject(data, "connect_token");
// 发起连接确认请求
DataRequest request = Controller.NewDataRequest(GamingType.Connect);
// 传递参数
request.AddRequestData("username", ((Gaming)sender).CurrentUser.Username);
request.AddRequestData("connect_token", token);
if (request.SendRequest() == RequestResult.Success)
{
string msg = request.GetResult("msg") ?? "";
Controller.WriteLine(msg);
}
request.Dispose();
}
}
}
}
///
/// 模组服务器:必须继承基类:
/// 使用switch块分类处理 。
///
public class ExampleGameModuleServer : GameModuleServer, IHotReloadAware
{
///
/// 注意:服务器模组的名称必须和模组名称相同。除非你指定了 和
///
public override string Name => ExampleGameModuleConstant.ExampleGameModule;
public override string Description => "My First GameModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override string DefaultMap => GameModuleDepend.MapsDepend.Length > 0 ? GameModuleDepend.MapsDepend.First() : "";
public override GameModuleDepend GameModuleDepend => ExampleGameModuleConstant.GameModuleDepend;
///
/// 创建一个工作类,接收服务器启动参数的同时,还能定义一些需要的属性
///
///
private readonly struct ModuleServerWorker(GamingObject obj)
{
public GamingObject GamingObject { get; } = obj;
public List ConnectedUser { get; } = [];
public List CharactersForPick { get; } = [];
public Dictionary UserCharacters { get; } = [];
public Dictionary> UserData { get; } = [];
}
private ConcurrentDictionary Workers { get; } = [];
public override bool StartServer(GamingObject obj, params object[] args)
{
// 因为模组是单例的,需要为这个房间创建一个工作类接收参数,不能直接用本地变量处理
ModuleServerWorker worker = new(obj);
Workers[obj.Room.Roomid] = worker;
// 创建一个线程执行Test(),因为这个方法必须立即返回
TaskUtility.NewTask(async () => await Test(obj, worker)).OnError(Controller.Error);
return true;
}
private async Task Test(GamingObject obj, ModuleServerWorker worker)
{
Controller.WriteLine("欢迎各位玩家进入房间 " + obj.Room.Roomid + " 。");
// 通常,我们可以对客户端的连接状态进行确认,此方法展示如何确认客户端的连接
// 有两种确认的方式,1是服务器主动确认,2是客户端发起确认
// 在FunGame项目中,建议永远使用客户端主动发起请求,因为服务器主动发起的实现难度较高
// 下面的演示基于综合的两种情况:服务器主动发送通知,客户端收到后,发起确认
// UpdateInfo是一个灵活的类型。如果发送check字符串,意味着服务器要求客户端发送确认
Dictionary data = [];
data.Add("info_type", "check");
// 进阶示例:传递一个token,让客户端返回
Guid token = Guid.NewGuid();
data.Add("connect_token", token);
// 我们保存到字典UserData中,这样可以方便跨方法检查变量
foreach (string username in obj.Users.Select(u => u.Username).Distinct())
{
if (worker.UserData.TryGetValue(username, out Dictionary? value))
{
value.Add("connect_token", token);
}
else
{
worker.UserData.Add(username, []);
worker.UserData[username].Add("connect_token", token);
}
}
await SendGamingMessage(obj.All.Values, GamingType.UpdateInfo, data);
// 新建一个线程等待所有玩家确认,如果超时则取消游戏,30秒
// 每200ms确认一次,不需要太频繁
await WaitForUsers(30, async () =>
{
if (worker.ConnectedUser.Count == obj.Users.Count)
{
Controller.WriteLine("所有玩家都已经连接。");
return true;
}
return false;
}, 200, async () =>
{
Controller.WriteLine("等待玩家连接超时,放弃该局游戏!", LogLevel.Warning);
await CancelGame(obj, worker, "由于等待超时,游戏已取消!");
}, async () =>
{
// 所有玩家都连接完毕了,可以建立一个回合制游戏了
await StartGame(obj, worker);
});
}
public override async Task> GamingMessageHandler(IServerModel model, GamingType type, Dictionary data)
{
Dictionary result = [];
// 获取model所在的房间工作类
ModuleServerWorker worker = Workers[model.InRoom.Roomid];
GamingObject obj = worker.GamingObject;
string username = model.User.Username;
switch (type)
{
case GamingType.Connect:
{
// 编写处理“连接”命令的逻辑
// 如果需要处理客户端传递的参数:获取与客户端约定好的参数key对应的值
string un = NetworkUtility.JsonDeserializeFromDictionary(data, "username") ?? "";
Guid token = NetworkUtility.JsonDeserializeFromDictionary(data, "connect_token");
if (un == username && worker.UserData.TryGetValue(username, out Dictionary? value) && value != null && (value["connect_token"]?.Equals(token) ?? false))
{
worker.ConnectedUser.Add(obj.Users.Where(u => u.Username == username).First());
Controller.WriteLine(username + " 已经连接。");
}
else Controller.WriteLine(username + " 确认连接失败!", LogLevel.Warning);
break;
}
case GamingType.PickCharacter:
{
// 客户端选完了角色这里就要处理了
long id = NetworkUtility.JsonDeserializeFromDictionary(data, "id");
if (worker.CharactersForPick.FirstOrDefault(c => c.Id == id) is Character character)
{
// 如果有人选一样的,你还没有做特殊处理的话,为了防止意外,最好复制一份
worker.UserCharacters[username] = character.Copy();
}
break;
}
case GamingType.Skill:
{
string e = NetworkUtility.JsonDeserializeFromDictionary(data, "event") ?? "";
if (e.Equals("SelectSkillTargets", StringComparison.CurrentCultureIgnoreCase))
{
long caster = NetworkUtility.JsonDeserializeFromDictionary(data, "caster");
long[] targets = NetworkUtility.JsonDeserializeFromDictionary(data, "targets") ?? [];
// 接收客户端传来的目标序号并记录
if (worker.UserData.TryGetValue(username, out Dictionary? value) && value != null)
{
value.Add("SkillTargets", targets);
}
}
break;
}
default:
await Task.Delay(1);
break;
}
return result;
}
private async Task CancelGame(GamingObject obj, ModuleServerWorker worker, string reason)
{
// 通知所有玩家
await SendAllTextMessage(obj, reason);
// 结束
SendEndGame(obj);
worker.ConnectedUser.Clear();
Workers.Remove(obj.Room.Roomid, out _);
}
private async Task WaitForUsers(int waitSeconds, Func> waitSomething, int delay, Func onTimeout, Func onCompleted)
{
// 这是一个用于等待的通用辅助方法
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(waitSeconds));
CancellationToken ct = cts.Token;
while (!ct.IsCancellationRequested)
{
try
{
if (await waitSomething())
{
await onCompleted();
return;
}
await Task.Delay(delay, ct);
}
catch (System.Exception e) when (e is not OperationCanceledException)
{
Controller.Error(e);
await onTimeout();
return;
}
}
// 异常和超时都走超时逻辑
await onTimeout();
}
private async Task StartGame(GamingObject obj, ModuleServerWorker worker)
{
Dictionary characters = [];
List characterPickeds = [];
Dictionary data = [];
// 首先,让玩家们选择角色
// 需要一个待选的角色池
// 这些角色可以从工厂中获取,比如:
Character character1 = Factory.OpenFactory.GetInstance(1, "", []);
worker.CharactersForPick.Add(character1);
// 或者在什么地方已经有个列表?则使用复制方法
if (ModuleLoader != null && ModuleLoader.Characters.Count > 0)
{
CharacterModule characterModule = ModuleLoader.Characters.Values.First();
Character character2 = characterModule.Characters.Values.FirstOrDefault()?.Copy() ?? Factory.GetCharacter();
if (character2.Id > 0)
{
worker.CharactersForPick.Add(character2);
}
}
// 传整个对象或者id都可以,看你心情,推荐用id,轻量,方便
data["list"] = worker.CharactersForPick.Select(c => c.Id);
await SendGamingMessage(obj.All.Values, GamingType.PickCharacter, data);
// 依然等待
await WaitForUsers(30, async () =>
{
if (worker.UserCharacters.Count == obj.Users.Count)
{
Controller.WriteLine("所有玩家都已经完成选择。");
return true;
}
return false;
}, 200, async () =>
{
await CancelGame(obj, worker, "由于等待超时,游戏已取消!");
}, async () =>
{
try
{
// 得到一个最终列表
List finalList = [.. worker.UserCharacters.Values];
// 这里我们可以随意对角色们进行升级和赋能
int clevel = 60;
int slevel = 6;
int mlevel = 8;
foreach (Character c in finalList)
{
c.Level = clevel;
c.NormalAttack.Level = mlevel;
// 假设要给所有角色发一个编号为1的技能
Skill s = Factory.OpenFactory.GetInstance(1, "", []);
s.Level = slevel;
c.Skills.Add(s);
}
// 重点,创建一个战斗队列
// 注意,整个游戏中,finalList及其内部角色对象的引用始终不变,请放心使用
MixGamingQueue queue = new(finalList, (str) =>
{
// 战斗日志可以直接通过传输信息的方式输出回客户端
_ = SendAllTextMessage(obj, str);
});
// 如果你需要开启战棋地图模式
GameMap? map = GameModuleDepend.Maps.FirstOrDefault();
if (map != null)
{
queue.LoadGameMap(map);
}
// 关键,监听任何事件
// 在客户端中,通过事件可以很方便地对UI进行操作以同步界面状态,而在服务端则需要套一层网络层
//queue.TurnStartEvent += Queue_TurnStartEvent;
//queue.DecideActionEvent += Queue_DecideActionEvent;
//queue.SelectNormalAttackTargetsEvent += Queue_SelectNormalAttackTargetsEvent;
//queue.SelectSkillEvent += Queue_SelectSkillEvent;
//queue.SelectNonDirectionalSkillTargetsEvent += Queue_SelectNonDirectionalSkillTargetsEvent;
//queue.SelectItemEvent += Queue_SelectItemEvent;
//queue.QueueUpdatedEvent += Queue_QueueUpdatedEvent;
//queue.TurnEndEvent += Queue_TurnEndEvent;
// 我们示范两个事件,一是选择技能目标,需要和客户端交互的事件
queue.SelectSkillTargetsEvent += (queue, caster, skill, enemys, teammates, castRange) =>
{
/// 如果你的逻辑都写在 里就不用这么麻烦每次都传 obj 和 worker 了。
return Queue_SelectSkillTargetsEvent(worker, caster, skill, enemys, teammates, castRange);
};
// 二是角色行动完毕,需要通知客户端更新状态的事件
queue.CharacterActionTakenEvent += Queue_CharacterActionTakenEvent;
// 战棋地图模式需要额外绑定的事件(如果你在map类里没有处理的话,这里还可以处理)
if (queue.Map != null)
{
//queue.SelectTargetGridEvent += Queue_SelectTargetGridEvent;
//queue.CharacterMoveEvent += Queue_CharacterMoveEvent;
}
queue.InitActionQueue();
// 这里我们仅演示自动化战斗,指令战斗还需要实现其他的消息处理类型/事件
// 自动化战斗时上述绑定的事件可能不会触发,参见GamingQueue的内部实现
queue.SetCharactersToAIControl(cancel: false, finalList);
// 总游戏时长
double totalTime = 0;
// 总死亡数
int deaths = 0;
// 总回合数
int max = 999;
int i = 1;
while (i < max)
{
if (i == (max - 1))
{
// 为了防止回合数超标,游戏近乎死局,可以设置一个上限,然后随便让一个人赢
await SendAllTextMessage(obj, $"=== 终局审判 ===");
Dictionary hp = [];
foreach (Character c in finalList)
{
hp.TryAdd(c, Calculation.Round4Digits(c.HP / c.MaxHP));
}
double maxhp = hp.Values.Max();
Character winner = hp.Keys.Where(c => hp[c] == maxhp).First();
await SendAllTextMessage(obj, "[ " + winner + " ] 成为了天选之人!!");
foreach (Character c in finalList.Where(c => c != winner && c.HP > 0))
{
await SendAllTextMessage(obj, "[ " + winner + " ] 对 [ " + c + " ] 造成了 99999999999 点真实伤害。");
queue.DeathCalculation(winner, c);
}
queue.EndGameInfo(winner);
break;
}
// 检查是否有角色可以行动
Character? characterToAct = queue.NextCharacter();
// 处理回合
if (characterToAct != null)
{
await SendAllTextMessage(obj, $"=== Round {i++} ===");
await SendAllTextMessage(obj, "现在是 [ " + characterToAct + " ] 的回合!");
if (queue.Queue.Count == 0)
{
break;
}
bool isGameEnd = queue.ProcessTurn(characterToAct);
if (isGameEnd)
{
break;
}
queue.DisplayQueue();
}
// 时间流逝,这样能知道下一个是谁可以行动
totalTime += queue.TimeLapse();
if (queue.Eliminated.Count > deaths)
{
deaths = queue.Eliminated.Count;
}
}
await SendAllTextMessage(obj, "--- End ---");
await SendAllTextMessage(obj, "总游戏时长:" + Calculation.Round2Digits(totalTime));
// 赛后统计,充分利用 GamingQueue 提供的功能
await SendAllTextMessage(obj, "=== 伤害排行榜 ===");
int top = finalList.Count;
int count = 1;
foreach (Character character in queue.CharacterStatistics.OrderByDescending(d => d.Value.TotalDamage).Select(d => d.Key))
{
StringBuilder builder = new();
CharacterStatistics stats = queue.CharacterStatistics[character];
builder.AppendLine($"{count++}. [ {character.ToStringWithLevel()} ] ({stats.Kills} / {stats.Assists})");
builder.AppendLine($"存活时长:{stats.LiveTime} / 存活回合数:{stats.LiveRound} / 行动回合数:{stats.ActionTurn} / 总计决策数:{stats.TurnDecisions} / 总计决策点:{stats.UseDecisionPoints}");
builder.AppendLine($"总计伤害:{stats.TotalDamage} / 总计物理伤害:{stats.TotalPhysicalDamage} / 总计魔法伤害:{stats.TotalMagicDamage}");
builder.AppendLine($"总承受伤害:{stats.TotalTakenDamage} / 总承受物理伤害:{stats.TotalTakenPhysicalDamage} / 总承受魔法伤害:{stats.TotalTakenMagicDamage}");
builder.Append($"每秒伤害:{stats.DamagePerSecond} / 每回合伤害:{stats.DamagePerTurn}");
await SendAllTextMessage(obj, builder.ToString());
}
}
catch (System.Exception e)
{
TXTHelper.AppendErrorLog(e.ToString());
Controller.Error(e);
}
finally
{
// 结束
SendEndGame(obj);
worker.ConnectedUser.Clear();
Workers.Remove(obj.Room.Roomid, out _);
}
});
}
private List Queue_SelectSkillTargetsEvent(ModuleServerWorker worker, Character caster, Skill skill, List enemys, List teammates, List castRange)
{
// 这是一个需要与客户端交互的事件,其他的选择事件与之做法相同
// SyncAwaiter是一个允许同步方法安全等待异步任务完成的工具类
return SyncAwaiter.WaitResult(RequestClientSelectSkillTargets(worker, caster, skill, enemys, teammates, castRange));
}
private async Task> RequestClientSelectSkillTargets(ModuleServerWorker worker, Character caster, Skill skill, List enemys, List teammates, List castRange)
{
List selectTargets = [];
Dictionary data = [];
data.Add("event", "SelectSkillTargets");
data.Add("caster", caster.Id);
data.Add("skill", skill.Id);
data.Add("enemys", enemys.Select(c => c.Id));
data.Add("teammates", teammates.Select(c => c.Id));
data.Add("castRange", castRange.Select(g => g.Id));
await SendGamingMessage(_clientModels, GamingType.Skill, data);
await WaitForUsers(30, async () =>
{
string username = worker.UserCharacters.FirstOrDefault(kv => kv.Value == caster).Key;
return worker.UserData.TryGetValue(username, out Dictionary? value) && value != null && value.ContainsKey("SkillTargets");
}, 200, async () => await Task.CompletedTask, async () =>
{
string username = worker.UserCharacters.FirstOrDefault(kv => kv.Value == caster).Key;
if (worker.UserData.TryGetValue(username, out Dictionary? value) && value != null && value.TryGetValue("SkillTargets", out object? value2) && value2 is long[] targets)
{
selectTargets.AddRange(worker.UserCharacters.Values.Where(c => targets.Contains(c.Id)));
}
});
return selectTargets;
}
private void Queue_CharacterActionTakenEvent(GamingQueue queue, Character actor, DecisionPoints dp, CharacterActionType type, RoundRecord record)
{
Dictionary data = [];
data.Add("event", "CharacterActionTaken");
data.Add("actor", actor.Id);
data.Add("dp", dp);
data.Add("type", type);
// 通知就行,无需等待
_ = SendGamingMessage(_clientModels, GamingType.Round, data);
}
private async Task SendAllTextMessage(GamingObject obj, string str)
{
// 工具方法,向所有人推送文本消息
Dictionary data = [];
data.Add("showmessage", true);
data.Add("msg", str);
await SendGamingMessage(obj.All.Values, GamingType.UpdateInfo, data);
}
protected HashSet _clientModels = [];
///
/// 匿名服务器允许客户端不经过FunGameServer的登录验证就能建立一个游戏模组连接
/// 匿名服务器示例
///
///
///
///
public override bool StartAnonymousServer(IServerModel model, Dictionary data)
{
// 可以做验证处理(这只是个演示,具体实现只需要双方约定,收发什么都无所谓)
string access_token = NetworkUtility.JsonDeserializeFromDictionary(data, "access_token") ?? "";
if (access_token == "approval_access_token")
{
// 接收连接匿名服务器的客户端
_clientModels.Add(model);
return true;
}
return false;
}
public override void CloseAnonymousServer(IServerModel model)
{
// 移除客户端
_clientModels.Remove(model);
}
///
/// 接收并处理匿名服务器消息
///
///
///
///
public override async Task> AnonymousGameServerHandler(IServerModel model, Dictionary data)
{
Dictionary result = [];
// 根据服务器和客户端的数据传输约定,自行处理 data,并返回。
if (data.Count > 0)
{
await Task.Delay(1);
}
result.Add("msg", "匿名服务器已经收到消息了");
return result;
}
///
/// 热更新示例:必须实现 接口才会被热更新模式加载这个模组
/// 如果想要实现端运行的所有模组都能热更新,那么这些模组都必须实现了这个接口(包括 ,, 等等……)
///
public void OnBeforeUnload()
{
// 这个方法会在模组被卸载前调用,因此,这里要清理一些状态,让框架可以正确卸载模组
// 假设,这是个匿名服务器,因此它需要清理匿名连接
GamingObjects.Clear();
_ = Send(_clientModels, SocketMessageType.EndGame, Factory.GetRoom(), Factory.GetUser());
IServerModel[] models = [.. _clientModels];
foreach (IServerModel model in models)
{
model.NowGamingServer = null;
CloseAnonymousServer(model);
}
}
}
///
/// 地图:必须继承基类:
///
public class ExampleGameMap : GameMap
{
public override string Name => ExampleGameModuleConstant.ExampleMap;
public override string Description => "My First GameMap";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override int Length => 12;
public override int Width => 12;
public override int Height => 6;
public override float Size => 4.0f;
public override GameMap InitGamingQueue(IGamingQueue queue)
{
// 因为模组在模组管理器中都是单例的,所以每次游戏都需要返回一个新的地图对象给队列
GameMap map = new ExampleGameMap();
map.Load();
// 做一些绑定,以便介入游戏队列
/// 但是,传入的 queue 可能不是 ,要做类型检查
// 不使用框架的实现时,需要地图作者与游戏队列的作者做好适配
if (queue is GamingQueue gq)
{
gq.SelectTargetGridEvent += Gq_SelectTargetGrid;
}
return map;
}
private Grid Gq_SelectTargetGrid(GamingQueue queue, Character character, List enemys, List teammates, GameMap map, List canMoveGrids)
{
// 介入选择,假设这里更新界面,让玩家选择目的地
return Grid.Empty;
}
}
///
/// 角色:必须继承基类:
///
public class ExampleCharacterModule : CharacterModule
{
public override string Name => ExampleGameModuleConstant.ExampleCharacter;
public override string Description => "My First CharacterModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override Dictionary Characters
{
get
{
Dictionary dict = [];
// 构建一个你想要的角色
Character c = Factory.GetCharacter();
c.Name = "Oshima";
c.FirstName = "Shiya";
c.NickName = "OSM";
c.MagicType = MagicType.PurityNatural;
c.InitialHP = 30;
c.InitialSTR = 20;
c.InitialAGI = 10;
c.InitialINT = 5;
c.InitialATK = 100;
c.InitialDEF = 10;
dict.Add(c.Name, c);
return dict;
}
}
protected override Factory.EntityFactoryDelegate CharacterFactory()
{
// 上面示例用 Characters 是预定义的
// 这里的工厂模式则是根据传进来的参数定制生成角色,只要重写这个方法就能注册工厂了
return (id, name, args) =>
{
return null;
};
}
public static Character CreateCharacter(long id, string name, Dictionary args)
{
// 注册工厂后,后续创建角色只需要这样调用
return Factory.OpenFactory.GetInstance(id, name, args);
}
}
///
/// 技能:必须继承基类:
///
public class ExampleSkillModule : SkillModule
{
public override string Name => ExampleGameModuleConstant.ExampleSkill;
public override string Description => "My First SkillModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override Dictionary Skills
{
get
{
Dictionary dict = [];
/// 技能应该在新建类继承Skill实现,再自行构造并加入此列表。
/// 技能的实现示例参见:
return dict;
}
}
protected override Factory.EntityFactoryDelegate SkillFactory()
{
// 注册一个工厂,根据id和name,返回一个你继承实现了的类对象。所有的工厂使用方法参考 Character,都是一样的
return (id, name, args) =>
{
return null;
};
}
protected override Factory.EntityFactoryDelegate EffectFactory()
{
return (id, name, args) =>
{
// 以下是一个示例,实际开发中 id,name,args 怎么处置,看你心情
Skill? skill = null;
if (args.TryGetValue("skill", out object? value) && value is Skill s)
{
skill = s;
}
skill ??= new OpenSkill(id, name, args);
/// 如 中所说,特效需要在工厂中注册,方便重用
if (id == 1001)
{
return new ExampleOpenEffectExATK2(skill, args);
}
return null;
};
}
}
///
/// 物品:必须继承基类:
///
public class ExampleItemModule : ItemModule
{
public override string Name => ExampleGameModuleConstant.ExampleItem;
public override string Description => "My First ItemModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override Dictionary Items
{
get
{
Dictionary dict = [];
/// 物品应该新建类继承Item实现,再自行构造并加入此列表。
/// 物品的实现示例参见:
return dict;
}
}
protected override Factory.EntityFactoryDelegate- ItemFactory()
{
// 注册一个工厂,根据id和name,返回一个你继承实现了的类对象。
return (id, name, args) =>
{
return null;
};
}
}
}