文章详情

为了完成您所描述的数据库升级和相关的代码修改,以下是详细的步骤和完整的代码示例。这些步骤涵盖了数据库迁移、实体类更新、RPC 消息定义更新以及服务器端逻辑的调整,以支持新增的职业、性别、坐标、等级、经验和转生等信息。

## 步骤一:数据库迁移

首先,需要在现有的 `actor` 表中添加新的字段。假设您使用的是 MySQL 数据库,以下是相应的 SQL 语句:

ALTER TABLE actor
ADD COLUMN profession INT NOT NULL DEFAULT 1 COMMENT '职业 (1, 2, 3)',
ADD COLUMN gender INT NOT NULL DEFAULT 1 COMMENT '性别 (1, 2)',
ADD COLUMN pos_x DOUBLE NOT NULL DEFAULT 0.0 COMMENT '当前坐标 X',
ADD COLUMN pos_y DOUBLE NOT NULL DEFAULT 0.0 COMMENT '当前坐标 Y',
ADD COLUMN level INT NOT NULL DEFAULT 1 COMMENT '等级',
ADD COLUMN experience INT NOT NULL DEFAULT 0 COMMENT '经验',
ADD COLUMN rebirth INT NOT NULL DEFAULT 0 COMMENT '转生';

**执行步骤:**

1. **备份数据库**:在进行任何数据库迁移之前,务必备份当前数据库,以防出现意外情况。
2. **执行迁移脚本**:使用数据库管理工具(如 phpMyAdmin、MySQL Workbench)或命令行工具执行上述 SQL 语句。
3. **验证迁移结果**:确保新字段已成功添加,并且默认值设置正确。

## 步骤二:更新 `Actor` 类

在服务器端代码中,需要更新 `Actor` 类以包含新的字段。

export class Actor {
  constructor(
    public id: number,
    public account: string,
    public nickname: string,
    public curSceneId: number,
    public profession: number,   // 新增职业
    public gender: number,       // 新增性别
    public posX: number,         // 新增坐标 X
    public posY: number,         // 新增坐标 Y
    public level: number,        // 新增等级
    public experience: number,   // 新增经验
    public rebirth: number,      // 新增转生
    public curReplicationId?: number
  ) {}
}

**注意事项:**

– 确保所有创建 `Actor` 实例的地方都传递了新的字段。
– 根据需要,可以为新字段设置默认值或从数据库中读取相应的数据。

## 步骤三:更新数据库操作

在 `GameManager` 类中,需要更新与 `actor` 表交互的数据库查询,以包含新的字段。

1. 修改 `enterGame` 方法

/***
 * 进入游戏,创建Player
 */
enterGame(account: string) {
  return new Promise<void>((resolve, reject) => {
    const query = `
      SELECT id, scene_id, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth
      FROM actor
      WHERE account = ?
    `;
    this.connection.query(query, [account], (err, result: any) => {
      if (err) {
        reject(err.message);
        return;
      }

      const actors = result.map((e: any) => new Actor(
        e.id,
        account,
        e.nickname,
        e.scene_id,
        e.profession,
        e.gender,
        e.pos_x,
        e.pos_y,
        e.level,
        e.experience,
        e.rebirth,
        e.scene_id // 假设 curReplicationId 初始为当前场景 ID
      ));
      PlayerManager.Instance.createPlayer(account, actors);
      resolve();
    });
  });
}

2. 修改 `createActor` 方法

async createActor(account: string, data: any) {
  return new Promise<void>((resolve, reject) => {
    if (!data.nickname) {
      reject("no nickname");
      return;
    }

    const insertQuery = `
      INSERT INTO actor 
      (account, scene_id, created_time, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth) 
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `;
    const insertValues = [
      account,
      ServerIdEnum.Scene1,
      dayjs().format("YYYY-MM-DD HH:mm:ss"),
      data.nickname,
      data.profession || 1,   // 默认职业为1
      data.gender || 1,       // 默认性别为1
      data.posX || 0.0,       // 默认坐标 X
      data.posY || 0.0,       // 默认坐标 Y
      data.level || 1,        // 默认等级为1
      data.experience || 0,   // 默认经验为0
      data.rebirth || 0       // 默认转生为0
    ];

    this.connection.query(insertQuery, insertValues, (err, result) => {
      if (err) {
        reject(err.message);
        return;
      }

      const selectQuery = `
        SELECT id, scene_id, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth 
        FROM actor 
        WHERE account = ?
      `;
      this.connection.query(selectQuery, [account], (err, result: any) => {
        if (err) {
          reject(err.message);
          return;
        }

        const player = PlayerManager.Instance.getPlayer(account);
        const actors = result.map((e: any) => new Actor(
          e.id,
          account,
          e.nickname,
          e.scene_id,
          e.profession,
          e.gender,
          e.pos_x,
          e.pos_y,
          e.level,
          e.experience,
          e.rebirth,
          e.scene_id
        ));
        player.actors = actors;
        resolve();
      });
    });
  });
}

3. 修改 `listActor` 方法

async listActor(account: string) {
  return new Promise<any>((resolve, reject) => {
    const query = `
      SELECT id, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth 
      FROM actor 
      WHERE account = ?
    `;
    this.connection.query(query, [account], (err, result: any) => {
      if (err) {
        reject(err.message);
        return;
      }

      const actors = result?.map((e: any) => ({
        id: e.id,
        nickname: e.nickname,
        profession: e.profession,
        gender: e.gender,
        posX: e.pos_x,
        posY: e.pos_y,
        level: e.level,
        experience: e.experience,
        rebirth: e.rebirth
      })) || [];
      resolve({
        actors,
      });
    });
  });
}

4. 修改 `enterScene` 方法(如有需要)

如果进入场景时需要初始化角色的坐标等信息,可以相应地更新 `enterScene` 方法。

async enterScene(account: string, data?: any) {
  const player = PlayerManager.Instance.getPlayer(account);
  if (data?.actorId !== undefined) {
    player.curActorId = data.actorId;
  }
  const actor = player.getActor();
  await this.getClient(actor.curSceneId).call("enterActor", [actor.id]);

  // 更新角色在数据库中的坐标(如果需要)
  const updateQuery = `
    UPDATE actor 
    SET pos_x = ?, pos_y = ?
    WHERE id = ?
  `;
  const updateValues = [actor.posX, actor.posY, actor.id];
  this.connection.query(updateQuery, updateValues, (err) => {
    if (err) {
      this.logger.error(`Failed to update actor position: ${err.message}`);
    }
  });

  return {
    sceneId: actor.curSceneId,
  };
}

## 步骤四:更新 Protobuf 定义

需要在 Protobuf 消息中包含新的字段,以确保数据能够正确地在客户端和服务器之间传输。

修改 `IActor` 消息

在您的 `.proto` 文件中,更新 `IActor` 消息以包含新字段:

message IActor {
  int32 id = 1;
  string nickname = 2;
  double posX = 3;
  double posY = 4;
  int32 profession = 5;  // 新增职业
  int32 gender = 6;      // 新增性别
  string account = 7;
  int32 sceneId = 8;
  int32 level = 9;       // 新增等级
  int32 experience = 10; // 新增经验
  int32 rebirth = 11;     // 新增转生
}

更新相关响应消息

确保所有返回包含 `IActor` 的消息都能传输新的字段。例如,在 `ListActorResData` 中:

message ListActorResData{
  repeated IActor actors = 1;
}

**注意事项:**

– 修改 `.proto` 文件后,需要重新生成相应的代码(如使用 `protobufjs` 或其他工具)。
– 确保客户端也更新了对应的 Protobuf 定义,以正确解析新的字段。

## 步骤五:更新 Scene 类

在 `Scene` 类中,需要确保新的字段在场景中正确处理和同步。

修改 `enterActor` 方法

enterActor(actorId: number) {
  return new Promise<void>((resolve, reject) => {
    const query = `
      SELECT id, scene_id, account, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth 
      FROM actor 
      WHERE id = ?
    `;
    this.connection.query(query, [actorId], (err, result: any) => {
      if (err) {
        reject(err.message);
        return;
      }

      const target = result?.[0];
      if (!target) {
        reject("no actor");
        return;
      }

      const actor = {
        id: target.id,
        account: target.account,
        nickname: target.nickname,
        posX: target.pos_x,
        posY: target.pos_y,
        profession: target.profession,
        gender: target.gender,
        level: target.level,
        experience: target.experience,
        rebirth: target.rebirth,
        sceneId: target.scene_id,
      };
      this.state.actors.push(actor);
      resolve();
    });
  });
}

修改 `inputFromClient` 方法

根据新的字段需求,可能需要调整输入处理逻辑。例如,处理等级和经验的变化:

inputFromClient(actorId: number, data: any) {
  this.pendingInput.push(data);
  const actor = this.state.actors.find((e) => e.id === actorId);
  if (!actor) {
    return;
  }
  const { directionX, directionY, dt, experienceGained } = data;
  
  // 更新位置
  actor.posX += directionX * ACTOR_SPEED * dt;
  actor.posY += directionY * ACTOR_SPEED * dt;

  // 更新经验
  if (experienceGained) {
    actor.experience += experienceGained;
    // 处理升级逻辑
    while (actor.experience >= getExperienceForNextLevel(actor.level)) {
      actor.experience -= getExperienceForNextLevel(actor.level);
      actor.level += 1;
      // 可能需要通知客户端升级
    }
  }

  console.log('服务端inputFromClient', actorId, data, actor, this.state);
}

// 示例函数:获取下一级所需经验
function getExperienceForNextLevel(currentLevel: number): number {
  return currentLevel * 1000; // 示例逻辑
}

## 步骤六:更新 RPC 服务器和客户端通信

确保所有与 `Actor` 相关的 RPC 方法能够正确传输新的字段。

1. 修改 `StateFromServer` 消息

确保 `StateFromServer` 包含了完整的 `IActor` 信息。

// 已在 Protobuf 中定义,确保发送时包含所有字段
this.game.send("stateFromServer", [
  accounts,
  {
    state,
    input,
  },
]);

2. 修改 `createDBConnection` 函数(如果需要)

确保数据库连接支持新字段的数据类型(如 `DOUBLE`、`INT` 等)。

import mysql from 'mysql2';

export function createDBConnection() {
  const connection = mysql.createConnection({
    host: 'localhost',
    user: 'your_username',
    password: 'your_password',
    database: 'your_database',
    // 其他配置
  });

  connection.connect((err) => {
    if (err) {
      console.error('数据库连接失败:', err);
      process.exit(1);
    }
    console.log('数据库连接成功');
  });

  return connection;
}

## 步骤七:测试和验证

1. **单元测试**:编写或更新现有的单元测试,确保新字段的逻辑正确。
2. **集成测试**:确保数据库、服务器和客户端之间的交互能够正确传输和处理新字段。
3. **手动测试**:通过游戏客户端创建新角色,验证新字段是否正确保存、显示和同步。
4. **日志检查**:查看服务器日志,确保没有错误,并且新字段的操作记录正确。

## 完整代码示例

以下是更新后的关键代码片段,综合了上述步骤。

`Actor` 类

export class Actor {
  constructor(
    public id: number,
    public account: string,
    public nickname: string,
    public curSceneId: number,
    public profession: number,
    public gender: number,
    public posX: number,
    public posY: number,
    public level: number,
    public experience: number,
    public rebirth: number,
    public curReplicationId?: number
  ) {}
}

`GameManager` 类

import { RpcServer, ServerIdEnum, RpcFunc, Logger, ServerPort, RpcClient, Inject, createDBConnection } from "../common";
import { PlayerManager } from "./PlayerManager";
import { Connection } from "mysql2";
import { Actor } from "./Actor";
import dayjs from "dayjs";
import { Singleton } from "../common/common/base";

/***
 * Game服务器
 */
export class GameManager extends Singleton {
  static get Instance() {
    return super.GetInstance<GameManager>();
  }

  server: RpcServer;

  //日志
  @Inject
  logger: Logger;

  // 数据库连接
  connection: Connection = createDBConnection();

  async init() {
    this.server = await RpcServer.Create({
      port: ServerPort.Game,
      logger: this.logger,
      implement: this,
    });

    this.server.on("register", this.handleRegister.bind(this));
    this.logger.info("Game服务器启动!");
    await new Promise((resolve) => setTimeout(resolve, 1000));
  }

  handleRegister(client: RpcClient) {
    this.logger.info("register", client.id);
    // gateway网关服务断开时,清空所有玩家(如果把网关服务做成集群,只需要清空部分玩家)
    switch (client.id) {
      case ServerIdEnum.Gateway:
        client.on("close", () => {
          this.clear();
        });
        break;
      case ServerIdEnum.Scene1:
      case ServerIdEnum.Scene2:
        client.on("close", () => {
          const accounts = PlayerManager.Instance.getPlayers()
            .filter((e) => e.getActor().curSceneId === client.id)
            .map((e) => e.account);
          this.getClient(ServerIdEnum.Gateway).send("disconnect", [accounts]);
        });
      default:
        break;
    }
  }

  clear() {
    const accounts = PlayerManager.Instance.getPlayers().map((e) => e.account);
    for (const account of accounts) {
      this.leaveGame(account);
    }
  }

  getClient(type: number) {
    const client = this.server.clients.get(type);
    if (client) {
      return client;
    } else {
      throw new Error("getClient error no client");
    }
  }

  /***
   * 进入游戏,创建Player
   */
  enterGame(account: string) {
    return new Promise<void>((resolve, reject) => {
      const query = `
        SELECT id, scene_id, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth 
        FROM actor 
        WHERE account = ?
      `;
      this.connection.query(query, [account], (err, result: any) => {
        if (err) {
          reject(err.message);
          return;
        }

        const actors = result.map((e: any) => new Actor(
          e.id,
          account,
          e.nickname,
          e.scene_id,
          e.profession,
          e.gender,
          e.pos_x,
          e.pos_y,
          e.level,
          e.experience,
          e.rebirth,
          e.scene_id
        ));
        PlayerManager.Instance.createPlayer(account, actors);
        resolve();
      });
    });
  }

  /***
   * 离开游戏,移除Player和场景的Actor
   */
  async leaveGame(account: string) {
    // 删除场景里的Actor
    await this.leaveScene(account).catch((e) => {
      this.logger.error(e);
    });
    // 删除副本里的Actor
    await this.leaveReplication(account).catch((e) => {
      this.logger.error(e);
    });
    // 删除Player实例
    PlayerManager.Instance.removePlayer(account);
  }

  async createActor(account: string, data: any) {
    return new Promise<void>((resolve, reject) => {
      if (!data.nickname) {
        reject("no nickname");
        return;
      }

      const insertQuery = `
        INSERT INTO actor 
        (account, scene_id, created_time, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth) 
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `;
      const insertValues = [
        account,
        ServerIdEnum.Scene1,
        dayjs().format("YYYY-MM-DD HH:mm:ss"),
        data.nickname,
        data.profession || 1,
        data.gender || 1,
        data.posX || 0.0,
        data.posY || 0.0,
        data.level || 1,
        data.experience || 0,
        data.rebirth || 0
      ];

      this.connection.query(insertQuery, insertValues, (err, result) => {
        if (err) {
          reject(err.message);
          return;
        }

        const selectQuery = `
          SELECT id, scene_id, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth 
          FROM actor 
          WHERE account = ?
        `;
        this.connection.query(selectQuery, [account], (err, result: any) => {
          if (err) {
            reject(err.message);
            return;
          }

          const player = PlayerManager.Instance.getPlayer(account);
          const actors = result.map((e: any) => new Actor(
            e.id,
            account,
            e.nickname,
            e.scene_id,
            e.profession,
            e.gender,
            e.pos_x,
            e.pos_y,
            e.level,
            e.experience,
            e.rebirth,
            e.scene_id
          ));
          player.actors = actors;
          resolve();
        });
      });
    });
  }

  async listActor(account: string) {
    return new Promise<any>((resolve, reject) => {
      const query = `
        SELECT id, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth 
        FROM actor 
        WHERE account = ?
      `;
      this.connection.query(query, [account], (err, result: any) => {
        if (err) {
          reject(err.message);
          return;
        }

        const actors = result?.map((e: any) => ({
          id: e.id,
          nickname: e.nickname,
          profession: e.profession,
          gender: e.gender,
          posX: e.pos_x,
          posY: e.pos_y,
          level: e.level,
          experience: e.experience,
          rebirth: e.rebirth
        })) || [];
        resolve({
          actors,
        });
      });
    });
  }

  /***
   *  通知场景服务器玩家进入了
   * 首次进入场景时,客户端传递选择的角色id
   */
  async enterScene(account: string, data?: any) {
    const player = PlayerManager.Instance.getPlayer(account);
    if (data?.actorId !== undefined) {
      player.curActorId = data.actorId;
    }
    const actor = player.getActor();
    await this.getClient(actor.curSceneId).call("enterActor", [actor.id]);

    // 更新角色在数据库中的坐标
    const updateQuery = `
      UPDATE actor 
      SET pos_x = ?, pos_y = ?
      WHERE id = ?
    `;
    const updateValues = [actor.posX, actor.posY, actor.id];
    this.connection.query(updateQuery, updateValues, (err) => {
      if (err) {
        this.logger.error(`Failed to update actor position: ${err.message}`);
      }
    });

    return {
      sceneId: actor.curSceneId,
    };
  }

  /***
   *   // 通知场景服务器,玩家离开了
   */
  async leaveScene(account: string) {
    const player = PlayerManager.Instance.getPlayer(account);
    const actor = player.getActor();
    return await this.getClient(actor.curSceneId).call("leaveActor", [actor.id]);
  }

  async changeScene(account: string, data: any) {
    return new Promise<void>(async (resolve, reject) => {
      const player = PlayerManager.Instance.getPlayer(account);
      const actor = player.getActor();
      const sceneId = data.sceneId;
      await this.leaveScene(account);
      // 修改db数据
      const updateQuery = `
        UPDATE actor 
        SET scene_id = ?
        WHERE account = ? AND id = ?
      `;
      const updateValues = [sceneId, account, actor.id];
      this.connection.query(updateQuery, updateValues, async (err, result) => {
        if (err) {
          reject(err.message);
          return;
        }
        // 修改内存数据
        actor.curSceneId = sceneId;
        await this.enterScene(account);

        resolve();
      });
    });
  }

  async createReplication(account: string, data: any) {
    const player = PlayerManager.Instance.getPlayer(account);
    const actor = player.getActor();
    const replicationType = data.replicationType;
    // 创建副本
    const { data: replicationId } = await this.getClient(ServerIdEnum.ReplicationManager).call("createReplication", [
      replicationType,
    ]);

    // 离开场景
    await this.leaveScene(account);

    // 修改内存信息
    actor.curReplicationId = replicationId;
    // 进入副本
    await this.getClient(replicationId).call("enterActor", [actor.id]);
  }

  async leaveReplication(account: string) {
    const player = PlayerManager.Instance.getPlayer(account);
    const actor = player.getActor();

    if (actor.curReplicationId !== undefined) {
      try {
        // 通知副本服务离开了
        await this.getClient(actor.curReplicationId).call("leaveActor", [actor.id]);
      } catch (e) {
        this.logger.error(e);
      }

      // 修改内存信息
      actor.curReplicationId = undefined;
    }

    // 告诉客户端回到哪个场景
    return {
      sceneId: actor.curSceneId,
    };
  }

  async inputFromClient(account: string, data: any) {
    const player = PlayerManager.Instance.getPlayer(account);
    const actor = player.getActor();
    const id = actor.curReplicationId ?? actor.curSceneId;
    this.getClient(id).send("inputFromClient", [actor.id, data]);
  }

  stateFromServer(players: string[], data: any) {
    this.logger.info2('Sending stateFromServer this.getClient(ServerIdEnum.Gateway).send', data);
    this.getClient(ServerIdEnum.Gateway).send("sendMessage", [players, RpcFunc.stateFromServer, data]);
  }
}

`Scene` 类

import {
  ACTOR_SPEED,
  createDBConnection,
  deepClone,
  Inject,
  IState,
  Logger,
  RpcClient,
  ServerIdEnum,
  ServerPort,
  SyncInterval,
} from "../../common";
import { Connection } from "mysql2";

export const CHILD_PROCESS_READY = "CHILD_PROCESS_READY";

export abstract class Scene {
  abstract id: ServerIdEnum;

  game: RpcClient;

  @Inject
  logger: Logger;

  connection: Connection = createDBConnection();

  state: IState = {
    actors: [],
  };
  prevState: IState = deepClone(this.state);
  pendingInput: any[] = [];

  /***
   * 作为一个客户端连接Game服务器
   */
  async init() {
    const sceneName = `Scene${this.id}`;

    this.game = await RpcClient.Create({
      netOptions: {
        port: ServerPort.Game,
        host: "localhost",
      },
      implement: this,
      logger: this.logger,
      id: this.id,
    });

    this.game.on("close", () => {
      this.clear();
    });

    setInterval(() => {
      this.stateFromServer();
    }, SyncInterval);

    // @ts-ignore
    // ipc通信,通知父进程初始化完成
    process.send(CHILD_PROCESS_READY);

    this.logger.info(`${sceneName}服务启动!`);
  }

  clear() {
    this.state = {
      actors: [],
    };
    this.prevState = deepClone(this.state);
    this.pendingInput = [];
  }

  enterActor(actorId: number) {
    return new Promise<void>((resolve, reject) => {
      const query = `
        SELECT id, scene_id, account, nickname, profession, gender, pos_x, pos_y, level, experience, rebirth 
        FROM actor 
        WHERE id = ?
      `;
      this.connection.query(query, [actorId], (err, result: any) => {
        if (err) {
          reject(err.message);
          return;
        }

        const target = result?.[0];
        if (!target) {
          reject("no actor");
          return;
        }

        const actor = {
          id: target.id,
          account: target.account,
          nickname: target.nickname,
          posX: target.pos_x,
          posY: target.pos_y,
          profession: target.profession,
          gender: target.gender,
          level: target.level,
          experience: target.experience,
          rebirth: target.rebirth,
          sceneId: target.scene_id,
        };
        this.state.actors.push(actor);
        resolve();
      });
    });
  }

  leaveActor(actorId: number) {
    const index = this.state.actors.findIndex((e) => e.id === actorId);
    if (index > -1) {
      this.state.actors.splice(index, 1);
    }
  }

  inputFromClient(actorId: number, data: any) {
    this.pendingInput.push(data);
    const actor = this.state.actors.find((e) => e.id === actorId);
    if (!actor) {
      return;
    }
    const { directionX, directionY, dt, experienceGained } = data;

    // 更新位置
    actor.posX += directionX * ACTOR_SPEED * dt;
    actor.posY += directionY * ACTOR_SPEED * dt;

    // 更新经验
    if (experienceGained) {
      actor.experience += experienceGained;
      // 处理升级逻辑
      while (actor.experience >= getExperienceForNextLevel(actor.level)) {
        actor.experience -= getExperienceForNextLevel(actor.level);
        actor.level += 1;
        // 可能需要通知客户端升级
      }
    }

    console.log('服务端inputFromClient', actorId, data, actor, this.state);
  }

  stateFromServer() {
    // 旧状态
    const state = this.prevState;
    // 旧状态和新创建之间的input
    const input = this.pendingInput;

    // 让旧状态追上新状态
    this.prevState = deepClone(this.state);
    this.pendingInput = [];

    const accounts = this.state.actors.map((e) => e.account);
    // 没有角色在场景里的话就不用发消息了
    if (!accounts.length) {
      return;
    }

    this.logger.info2('Sending stateFromServer to client:', {
      accounts,
      state,
      input,
    });

    this.game.send("stateFromServer", [
      accounts,
      {
        state,
        input,
      },
    ]);
  }
}

// 示例函数:获取下一级所需经验
function getExperienceForNextLevel(currentLevel: number): number {
  return currentLevel * 1000; // 示例逻辑
}

Protobuf 定义

syntax = "proto3";

package game;

// 进入游戏
message EnterGameReq {
  string token = 1;
}

message EnterGameRes {
  EnterGameResData data = 1;
  optional string error = 2;
}

message EnterGameResData {
  string account = 1;
}

// 获取当前玩家的角色列表
message ListActorReq {

}

message ListActorRes {
  ListActorResData data = 1;
  optional string error = 2;
}

message ListActorResData{
  repeated IActor actors = 1;
}

// 创建角色
message CreateActorReq{
  string nickname = 1;
  int32 profession = 2; // 新增职业
  int32 gender = 3;     // 新增性别
}

message CreateActorRes{
  CreateActorResData data = 1;
  optional string error = 2;
}

message CreateActorResData{

}

// 进入场景
message EnterSceneReq {
  int32 actorId = 1;
}

message EnterSceneRes {
  EnterSceneResData data = 1;
  optional string error = 2;
}

message EnterSceneResData {
  int32 sceneId = 1;
}

// 切换场景
message ChangeSceneReq {
  int32 sceneId = 1;
}

message ChangeSceneRes {
  ChangeSceneResData data = 1;
  optional string error = 2;
}

message ChangeSceneResData {
}

// 创建副本
message CreateReplicationReq {
  int32 replicationType = 1;
}

message CreateReplicationRes {
  CreateReplicationResData data = 1;
  optional string error = 2;
}

message CreateReplicationResData{
}

// 离开副本
message LeaveReplicationReq {

}

message LeaveReplicationRes {
  LeaveReplicationReqData data = 1;
  optional string error = 2;
}

message LeaveReplicationReqData {
  int32 sceneId = 1;
}

message IActor{
  int32 id = 1;
  string nickname = 2;
  double posX = 3;
  double posY = 4;
  int32 profession = 5;  // 新增职业
  int32 gender = 6;      // 新增性别
  int32 level = 7;       // 新增等级
  int32 experience = 8;  // 新增经验
  int32 rebirth = 9;     // 新增转生
  string account = 10;
  int32 sceneId = 11;
}

// 客户端操作同步
message InputFromClient {
  int32 id = 1;
  double directionX = 2;
  double directionY = 3;
  float dt = 4;
  int32 experienceGained = 5; // 新增经验获取
}

message State {
  repeated IActor actors = 1;
}

// 服务端状态同步
message StateFromServer {
  State state = 1;
  repeated InputFromClient input = 2;
}

**注意事项:**

– 修改 `.proto` 文件后,需要重新生成相应的代码。例如,使用 `protobufjs` 的话,可以运行相应的生成命令。
– 确保客户端也更新了对应的 Protobuf 定义,以正确解析新的字段。

## 结论

通过上述步骤,您可以成功地对数据库进行升级,增加职业、性别、坐标、等级、经验和转生等信息,并确保这些信息能够在服务器和客户端之间正确地传输和同步。务必在部署到生产环境之前,在测试环境中进行充分的测试,以确保所有功能正常运行。

Tags:

发表评论