Files
netease-modsdk-wiki/docs/wiki/modsdk/why-system.md
2025-03-20 11:52:46 +08:00

5.7 KiB
Raw Permalink Blame History

hidden
hidden
true

🧩 为什么需要两个系统System用餐厅来理解

想象一下游戏就像一家餐厅:

  • 服务器系统 = 厨房(决定食物实际内容)
  • 客户端系统 = 餐厅前厅(呈现给顾客看到的体验)
graph TB
    subgraph "游戏架构(像餐厅一样)"
        ServerSystem["服务器系统(厨房)"] -->|发送实际变化| ClientSystem["客户端系统(前厅)"]
        ClientSystem -->|发送玩家请求| ServerSystem
    end

👨‍🍳 服务器系统:游戏的"厨房"

  • 职责: 决定游戏中真正发生了什么
  • 简单理解:
    • 计算伤害(怪物真的死了吗?)
    • 决定方块是否被破坏
    • 控制物品掉落
    • 存储真实的游戏数据

👨‍💼 客户端系统:游戏的"前厅"

  • 职责: 让玩家看到并互动
  • 简单理解:
    • 显示画面和声音
    • 处理你的鼠标点击和按键
    • 播放爆炸、火焰等特效
    • 展示菜单和界面

📮 系统间如何交流:像传纸条一样

当你在游戏中点击方块,发生了什么?

sequenceDiagram
    participant 你
    participant 前厅 as 客户端
    participant 厨房 as 服务器
    
    你->>前厅: 点击挖石头
    前厅->>厨房: 传纸条:"玩家想挖石头"
    厨房->>厨房: 检查:允许挖吗?
    厨房->>前厅: 回复:"可以,石头没了,掉落石头物品"
    前厅->>你: 播放挖矿动画和声音,显示物品

⚠️ 为什么不能混在一起?

就像餐厅里顾客不能随便进厨房一样:

  • 客户端不能直接修改游戏数据(否则会作弊)
  • 服务器不负责播放声音和动画(它只关心真实数据)

🌟 简单例子:击打树木得钻石

简化理解版

  1. 你点击树木(客户端检测到)
  2. 客户端发消息"嘿,服务器,玩家点击了这棵树"
  3. 服务器决定"好,我给他一颗钻石"
  4. 服务器通知客户端"请在玩家背包里显示一颗新钻石"
  5. 客户端更新画面:你看到钻石出现在背包里

🔑 记住这个简单规则

想象游戏是一部电影:

  • 服务器是导演(决定故事情节)
  • 客户端是摄像机和屏幕(展示给观众看)

记住:服务器决定发生什么,客户端决定如何展示

💡 初学者提示

如果遇到问题,先问自己:

  • "这个功能应该由谁负责?是真实游戏规则还是显示效果?"
  • "我需要发送什么消息让另一边知道?"

学会这种思考方式你的Mod就能正确运行啦

🎀 那么总结一下吧!

核心架构:客户端-服务器分离

graph TB
    subgraph "游戏架构"
        ServerSystem["服务器系统(游戏逻辑)"] -->|发送状态更新| ClientSystem["客户端系统(视觉呈现)"]
        ClientSystem -->|发送玩家操作| ServerSystem
        
        ServerSystem -->|读写| GameState["游戏状态(权威数据)"]
        ServerSystem -->|管理| Components["游戏组件"]
        ClientSystem -->|渲染| UI["界面与特效"]
    end

双系统模型的必要性

服务器系统 (ServerSystem)
  • 职责: 维护游戏的"真实状态"
  • 具体功能:
    • 处理游戏核心逻辑(如战斗计算、物品掉落)
    • 管理AI行为与路径寻找
    • 执行世界生成与物理规则
    • 拥有数据的最终决定权
客户端系统 (ClientSystem)
  • 职责: 处理玩家的直接体验
  • 具体功能:
    • 渲染游戏画面与UI界面
    • 处理玩家输入
    • 播放音效与粒子效果
    • 进行预测性渲染(平滑过渡)

系统间通信的关键:事件机制

sequenceDiagram
    participant Player as 玩家
    participant Client as 客户端系统
    participant Server as 服务器系统
    
    Player->>Client: 点击方块
    Client->>Server: 发送BlockUseEvent事件
    Server->>Server: 处理游戏逻辑
    Server->>Client: 发送状态更新事件
    Client->>Player: 显示视觉反馈
事件驱动设计的优势
  • 松耦合: 系统间无需直接相互调用
  • 可扩展: 多个系统可响应同一事件
  • 清晰职责: 服务端决定结果,客户端呈现效果
实例:击打方块流程
  1. 客户端: 检测到玩家点击方块,发送BlockUseEvent
  2. 服务端:
    • 接收到事件,判断方块是否可被破坏
    • 更新方块状态,计算掉落物
  3. 客户端:
    • 播放方块破坏动画和音效
    • 显示掉落物

常见问题与最佳实践

避免的错误模式
  • 在客户端直接修改游戏状态
  • 在服务端处理UI渲染逻辑
  • 遗漏事件监听导致功能不同步
最佳实践
  • 服务端验证所有游戏逻辑(防作弊)
  • 合理使用自定义事件进行系统间通信
  • 保持客户端代码专注于视觉体验优化
  • 在事件参数中携带足够的上下文信息

跨系统通信示例

# 服务端发送自定义事件到客户端
def spawn_special_effect(self, position):
    comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
    comp.NotifyToClient(player_id, "ShowSpecialEffect", {"position": position})

# 客户端监听并处理
def listen_events(self):
    self.ListenForEvent("MyMod", "MyServerSystem", "ShowSpecialEffect", 
                        self, self.on_special_effect)

def on_special_effect(self, event_data):
    pos = event_data["position"]
    # 在客户端显示特效...

通过掌握客户端-服务端分离的设计模式你将能够创建更稳定、可扩展且性能优秀的Mod。记住服务端决定发生什么,客户端决定如何展示