来源信息
- 仓库
- pluginagentmarketplace/custom-plugin-server-side-game-dev
- 最近来源活动
- 2025年12月30日 11:46
- 检测到的 SKILL.md 语言
- 英语
- 星标
- 1
- 分支
- 1
安装方式
默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。
检查来源文件
决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。
菜单
默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。
决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/pluginagentmarketplace/custom-plugin-server-side-game-dev --skill design-patterns命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Asynchronous programming models including coroutines, async/await, and reactive patterns
Game server communication protocols including gRPC, REST, and custom binary protocols
Efficient data serialization for game networking including Protobuf, FlatBuffers, and custom binary
基于 SOC 职业分类
正在显示 SKILL.md
| name | design-patterns |
| description | Game server design patterns including ECS, command pattern, and event sourcing |
| sasmp_version | 1.3.0 |
| version | 2.0.0 |
| bonded_agent | 01-game-server-architect |
| bond_type | SECONDARY_BOND |
| parameters | {"required":["pattern_type"],"optional":["storage_backend"],"validation":{"pattern_type":{"type":"string","enum":["ecs","command","observer","state_machine","object_pool","event_sourcing"]},"storage_backend":{"type":"string","enum":["memory","redis","postgresql"],"default":"memory"}}} |
| retry_config | {"max_attempts":1,"fallback":"none"} |
| observability | {"logging":{"level":"debug","fields":["pattern","entity_count","event_type"]},"metrics":[{"name":"ecs_entities_total","type":"gauge"},{"name":"command_execution_duration_us","type":"histogram"},{"name":"event_sourcing_replay_duration_ms","type":"histogram"},{"name":"object_pool_utilization","type":"gauge"}]} |
Apply proven design patterns for scalable, maintainable game server architecture.
| Pattern | Purpose | Use Case |
|---|---|---|
| ECS | Data-oriented design | Entity management |
| Command | Action encapsulation | Input replay, undo |
| Observer | Event notification | State changes |
| State Machine | State transitions | Player states |
| Object Pool | Memory efficiency | Bullets, particles |
| Event Sourcing | Audit trail | Match replay |
// Components - pure data, no logic
struct Position { float x, y, z; };
struct Velocity { float dx, dy, dz; };
struct Health { int current, max; };
struct NetworkSync { uint32_t last_sync_tick; };
// Entity is just an ID
using Entity = uint32_t;
// Component storage
template<typename T>
class ComponentArray {
std::unordered_map<Entity, T> components;
public:
void add(Entity e, T component) {
components[e] = component;
}
T* get(Entity e) {
auto it = components.find(e);
it != components.() ? &it->second : ;
}
{
components.(e);
}
};
{
:
{
(& [entity, pos] : world.<Position, Velocity>()) {
& vel = world.<Velocity>(entity);
pos.x += vel.dx * dt;
pos.y += vel.dy * dt;
pos.z += vel.dz * dt;
}
}
};
{
:
{
(* health = world.<Health>(target)) {
health->current -= amount;
(health->current <= ) {
world.<EntityDied>(target);
}
}
}
};
struct GameCommand {
uint64_t tick;
uint32_t playerId;
virtual ~GameCommand() = default;
virtual void execute(GameState& state) = 0;
virtual void undo(GameState& state) = 0;
virtual std::unique_ptr<GameCommand> clone() const = 0;
};
struct MoveCommand : GameCommand {
Vector3 direction;
Vector3 previousPosition; // For undo
void execute(GameState& state) override {
auto& player = state.players[playerId];
previousPosition = player.position;
player.velocity = direction * player.speed;
}
void undo(GameState& state) override {
state.players[playerId].position = previousPosition;
}
std::unique_ptr<GameCommand> clone() const override {
return std::make_unique<MoveCommand>(*this);
}
};
// Command history for replay
class CommandHistory {
std::vector<std::unique_ptr<GameCommand>> history;
public:
void record(std::unique_ptr<GameCommand> cmd) {
history.push_back(std::move(cmd));
}
GameState replay(const GameState& initial) {
GameState state = initial;
for (const auto& cmd : history) {
cmd->execute(state);
}
return state;
}
void undoLast(GameState& state) {
if (!history.empty()) {
history.back()->undo(state);
history.pop_back();
}
}
};
// Events are immutable facts
struct GameEvent {
uint64_t timestamp;
uint64_t sequence;
virtual ~GameEvent() = default;
virtual void apply(GameState& state) const = 0;
virtual std::string serialize() const = 0;
};
struct PlayerDamaged : GameEvent {
uint32_t playerId;
uint32_t sourceId;
int damage;
DamageType type;
void apply(GameState& state) const override {
auto& player = state.players[playerId];
player.health -= damage;
player.lastDamageSource = sourceId;
}
std::string serialize() const override {
return fmt::format("{{\"type\":\"damage\",\"player\":{},\"damage\":{}}}",
playerId, damage);
}
};
class EventStore {
std::vector<std::unique_ptr<GameEvent>> events;
public:
void append(std::unique_ptr<GameEvent> event) {
event->sequence = events.size();
events.push_back(std::move(event));
}
GameState rebuildState() const {
GameState state;
for (const auto& event : events) {
event->apply(state);
}
return state;
}
GameState rebuildAtTime(uint64_t timestamp) const {
GameState state;
for (const auto& event : events) {
if (event->timestamp > timestamp) break;
event->apply(state);
}
return state;
}
};
template<typename T, size_t N>
class ObjectPool {
struct PoolEntry {
T object;
bool in_use = false;
};
std::array<PoolEntry, N> pool;
std::stack<size_t> free_indices;
std::mutex mutex;
public:
ObjectPool() {
for (size_t i = 0; i < N; ++i) {
free_indices.push(i);
}
}
T* acquire() {
std::lock_guard<std::mutex> lock(mutex);
if (free_indices.empty()) return nullptr;
size_t idx = free_indices.top();
free_indices.pop();
pool[idx].in_use = true;
return &pool[idx].object;
}
void release(T* obj) {
std::lock_guard<std::mutex> lock(mutex);
size_t idx = obj - &pool[0].object;
pool[idx].object.reset();
pool[idx].in_use = false;
free_indices.push(idx);
}
size_t available() const {
return free_indices.size();
}
};
// Usage
ObjectPool<Bullet, 1000> bulletPool;
void fireBullet(Player& player) {
Bullet* bullet = bulletPool.acquire();
if (bullet) {
bullet->init(player.position, player.aimDirection);
activeBullets.push_back(bullet);
}
}
void onBulletHit(Bullet* bullet) {
activeBullets.remove(bullet);
bulletPool.release(bullet);
}
enum class PlayerState {
Idle, Running, Jumping, Attacking, Dead
};
class PlayerStateMachine {
PlayerState current = PlayerState::Idle;
std::unordered_map<PlayerState,
std::unordered_set<PlayerState>> transitions;
public:
PlayerStateMachine() {
// Define valid transitions
transitions[PlayerState::Idle] = {
PlayerState::Running,
PlayerState::Jumping,
PlayerState::Attacking,
PlayerState::Dead
};
transitions[PlayerState::Running] = {
PlayerState::Idle,
PlayerState::Jumping,
PlayerState::Dead
};
transitions[PlayerState::Jumping] = {
PlayerState::Idle,
PlayerState::Dead
};
transitions[PlayerState::Attacking] = {
PlayerState::Idle,
PlayerState::Dead
};
// Dead is terminal - no transitions out
transitions[PlayerState::Dead] = {};
}
bool canTransition(PlayerState newState) const {
auto it = transitions.find(current);
return it != transitions.end() &&
it->second.count(newState) > 0;
}
bool transition(PlayerState newState) {
if (!canTransition(newState)) return false;
onExit(current);
current = newState;
onEnter(current);
return true;
}
private:
void onEnter(PlayerState state) {
switch (state) {
case PlayerState::Jumping:
player->velocity.y = JUMP_FORCE;
break;
case PlayerState::Dead:
player->onDeath();
break;
}
}
void onExit(PlayerState state) {
// Cleanup for previous state
}
};
| Pattern | Problem | Root Cause | Solution |
|---|---|---|---|
| ECS | Component fragmentation | Sparse storage | Archetype-based storage |
| Command | Memory bloat | Unbounded history | Limit history size |
| Event Sourcing | Slow replay | Too many events | Periodic snapshots |
| Object Pool | Exhaustion | Leaks | Track allocations |
| State Machine | Invalid state | Missing transition | Validate all paths |
// ECS diagnostics
void debugECS(World& world) {
std::cout << "Entities: " << world.entityCount() << std::endl;
std::cout << "Components per type:\n";
world.forEachArchetype([](const Archetype& a) {
std::cout << " " << a.signature() << ": " << a.size() << "\n";
});
}
// Object pool health
void debugPool(ObjectPool& pool) {
std::cout << "Pool utilization: "
<< (pool.capacity() - pool.available())
<< "/" << pool.capacity() << std::endl;
}
// Event store stats
void debugEventStore(EventStore& store) {
std::cout << "Events: " << store.size() << std::endl;
std::cout << "Rebuild time: " << measureRebuildTime(store) << "ms\n";
}
#include <gtest/gtest.h>
TEST(ECS, CreatesAndQueriesEntities) {
World world;
Entity player = world.createEntity();
world.add<Position>(player, {0, 0, 0});
world.add<Velocity>(player, {1, 0, 0});
auto entities = world.query<Position, Velocity>();
EXPECT_EQ(entities.size(), 1);
}
TEST(Command, UndoRestoresState) {
GameState state;
state.players[1].position = {0, 0, 0};
auto cmd = std::make_unique<MoveCommand>();
cmd->playerId = 1;
cmd->direction = {1, 0, 0};
cmd->execute(state);
EXPECT_NE(state.players[1].velocity.x, 0);
cmd->undo(state);
EXPECT_EQ(state.players[1].position.x, 0);
}
TEST(EventSourcing, RebuildMatchesLive) {
EventStore store;
GameState liveState;
for (int i = 0; i < 100; ++i) {
auto event = std::make_unique<PlayerDamaged>();
event->playerId = 1;
event->damage = 1;
event->apply(liveState);
store.append(std::move(event));
}
auto rebuiltState = store.rebuildState();
EXPECT_EQ(rebuiltState.players[1].health, liveState.players[1].health);
}
TEST(ObjectPool, AcquireAndRelease) {
ObjectPool<Bullet, 10> pool;
std::vector<Bullet*> acquired;
for (int i = 0; i < 10; ++i) {
acquired.push_back(pool.acquire());
EXPECT_NE(acquired.back(), nullptr);
}
// Pool exhausted
EXPECT_EQ(pool.acquire(), nullptr);
// Release one
pool.release(acquired.back());
acquired.pop_back();
// Can acquire again
EXPECT_NE(pool.acquire(), nullptr);
}
assets/ - Pattern implementationsreferences/ - Design pattern catalogs