提供游戏开发思路

之前在群里突发奇想了很多内容,希望对所有开发者都有帮助。为大家提供一下我个人的开发思路,做一个封存

装备套装激活思路


很多人开发游戏时候,武器装备都是一旦激活就立刻将值写死,这对于后期维护是不合理的。一般建议是 基础数值 + 浮动数值 = 实际数值 的思路。

意思是获得装备时,实际上获得的是 模板数值 生成的 编号面板,这个面板只是模板数值的百分比。当需要调出这个对应编号的装备时,会通过 模板 去 按照编号面板 取得 计算实际的基础装备白板

这样方便后期 削弱/增强 对应武器,并立即生效到所有玩家装备上


代码内容如下:

const weapon = {
    "尻川皮带": {
        lastId: 11,
        atk: 33
    }
}

const weaponMap = {
    // 武器模板数据
    weapon,
    // 武器品质模板
    qualityMap: {
        '下等': 0.8,
        '中等': 1,
        '上等': 1.2
    },
    // 初始化构建新的武器数据浮动面板
    cerateWeaponInfo(weaponName) {
        if (!this.weapon[weaponName]) return -1
        // 获取最新武器的 id
        const id = ++this.weapon[weaponName].lastId
        // 获取武器的品质的参考值

        // 这里应该为浮动的区间,在这里设置 倍率 或者 浮动区间
        const qualityValue = Math.random() * 1.1 + 0.4

        // 获取对应的武器品质评价
        const dict = Object.keys(this.qualityMap)
        const qualityName = dict.find((item, index) => {
            return qualityValue >= this.qualityMap[item] && dict[index + 1] ? qualityValue < this.qualityMap[dict[index + 1]] : true
        })
        // 生成武器浮动数据
        const map = {
            weaponName,
            qualityValue,
            qualityName,
            value: 1,
            upValue: 0,
            lv: 1,
            id
        }
        console.log(map.qualityName, map.qualityValue, `武器伤害值:${Math.floor(map.qualityValue * weaponMap.weapon[weaponName].atk)}`);
        return map
    },
    // 获取实际武器面板属性
    getWeaponInfo(id, weaponName) {
        // 略...
    }
}

// 获得 100 次尻川皮带
for (let i = 0; i < 100; i++) {
    // 获得装备的对象信息
    const result = weaponMap.cerateWeaponInfo('尻川皮带')
}

效果截图

当需要调整 尻川皮带面板的攻击时候,只要将模板数值调整即可。
会立即生效到所有玩家装备。

因此,玩家在需要的情况下,后台应立刻获取最新的武器面板参数更新。

套装加成思路


当装备了不同的武器,如果实现了套装效果,应立即增益对应的套装加成。多个套装会多次加成。

// 假设当前持有装备
const take = ['麻痹戒指', '屠龙宝刀', '暗黑护甲', '暗黑头套']


// 套装内容和效果
// have 表示需要装备的内容
// up 表示加成的内容
// {key} 代表套装的名字
const upSet = {
    '自由,深邃黑暗': {
        have: ['暗黑护甲', '暗黑头套'],
        up: {
            stk: 22,
            def: 10
        }
    },
    '麻酥酥': {
        have: ['麻痹戒指'],
        up: {
            stk: 11,
            def: 5
        }
    },
    '屠龙勇士': {
        have: ['屠龙宝刀', '屠龙王冠'],
        up: {
            stk: 11,
            def: 5,
            hp: 14
        }
    },
    '光之勇士-改': {
        have: ['屠龙宝刀', '暗黑护甲'],
        up: {
            stk: 33,
            def: -10,
            hp: 200
        }
    }
}

// 获取用户装备是满足在套装加成
const result = checkUpBuff(take, (obj) => {
    const dict = { stk: '攻击力', def: '防御力', hp: '血量' }
    console.log(`你目前激活了${obj.name},${Object.keys(obj.up).map(item => {
        return `${dict[item]}:${obj.up[item]}`
    }).join('、')}`);
})
console.log(result);

// 判断持有套装和总加成
function checkUpBuff(take, fn) {
    // 初始化总加成
    const upInfo = {}
    // 初始化套装内容
    const upSetName = []

    // 遍历套装数据
    Object.keys(upSet).forEach(item => {
        // 判断是否符合需求
        const isUse = upSet[item].have.every(up => {
            return take.includes(up)
        })
        // 如果符合 叠加对应加成
        if (isUse) {
            const upKey = Object.keys(upSet[item].up)
            upSetName.push(item)
            // 回调函数 用于每次触发响应 (达成成就的判断?)
            fn && fn({ name: item, up: upSet[item].up })
            upKey.forEach(up => {
                if (!upInfo[up]) upInfo[up] = 0
                upInfo[up] += upSet[item].up[up]
            })
        }
    })
    // 返回加成和加成套装名对象
    return { upSetName, upInfo }
}

效果截图

立刻获得加成信息,可以在每次必要场景时额外赋值

image

本地存储思路


由于大型游戏少不了存储本地数据的方法,实际上每次行动或者获得道具时候直接发起本地读写是比较耗时的。

很多游戏的存储方法是定时、存档点后存储,这种优化可以用于游戏开发中。

设置定时存档,初始化时直接提取所需的本地数据转为临时数据。并周期性将临时同步至本地数据中。以下是某个作者按这思路制作的 openword 的部分代码

  async function getStoreWordMap() {
    const result = await getOrCreateFile(path.join(ctx.baseDir, config.historyData), true);
    return JSON.parse(result);
  }
  async function setStoreWordMap(map) {
    const mapStrData = JSON.stringify(map);
    await setOrCreateFile(path.join(ctx.baseDir, config.historyData), mapStrData);
    return true
  }
  async function getStoreUserArea() {
    const result = await getOrCreateFile(path.join(ctx.baseDir, config.playerArea));
    return JSON.parse(result);
  }
  async function setStoreUserArea(userLive) {
    const userLiveStrData = JSON.stringify(userLive);
    await setOrCreateFile(path.join(ctx.baseDir, config.playerArea), userLiveStrData);
    return true
  }
  async function getStoreUserInfo() {
    const result = await getOrCreateFile(path.join(ctx.baseDir, config.userInfo));
    return JSON.parse(result)
  }
  async function setStoreUserInfo(userInfo) {
    const userLiveStrData = JSON.stringify(userInfo);
    await setOrCreateFile(path.join(ctx.baseDir, config.userInfo), userLiveStrData);
    return true
  }

  const word = new WorldMap(config.wordSize)

  // 本地存储方案
  const StoreFn = {
    getStoreWordMap,
    setStoreWordMap,
    getStoreUserArea,
    setStoreUserArea,
    getStoreUserInfo,
    setStoreUserInfo
  }

  // 本地化存储
  async function setStoreFn() {
    // 批处理
    await Promise.all([
      StoreFn.setStoreWordMap(word.map),
      StoreFn.setStoreUserInfo(userInfo),
      StoreFn.setStoreUserArea(word.userLive)
    ])
    console.log('完成本地存储');
  }

  ctx.setInterval(async () => {
    await setStoreFn()
  }, 600000)

效果截图

没有实际代码,提供一个相关日志吧。

词库的开关功能,是在每次启动时完成所有分群数据的载入。并周期性判断是否有更新的数据,如果有,抽出更新的数据,依次存储对应更新的数据至本地存储中。

image

业务区分思路


有人开发游戏,会在程序处理时,返回一段或多段文本。然后拿着这个文本以消息的方式发送,这个其实是不太合理的。

如果只是小游戏,这倒也没什么。真正应该 逻辑层 和 显示层 分离。方便后期维护和更新界面层样式。

逻辑层只做逻辑层的事情,返回一段信息参数。提供给界面层渲染样式,提供消息接口发送。

不仅游戏应该这样,其他业务也可以这样。方便更新样式或者更新逻辑。
同步更新/多人协作 都互不干扰;

下面提供某个开发者做的 photodisk 插件的部分代码

    async addAuthority(userInfo) {
      if (this.userInfo[userInfo.userId]) {
        return {
          msg: '权限用户已注册,无需再次注册!',
          result: false
        }
      }
      const userName = userInfo.userName ? userInfo.userName : `manager_${userInfo.userId.substring(userInfo.userId.length - 4)}`;

      this.userInfo[userInfo.userId] = { userName, userId: userInfo.userId, power: 2 }
      await this.updateStoreAuthorityData();
      return {
        msg: `注册权限成功,名字为 ${userName}`,
        result: true
      }
    },
    async delAuthority(userId) {
      if (!this.userInfo[userId]) {
        return {
          msg: '不存在该权限用户,删除失败',
          result: false
        }
      }

      if (this.userInfo[userId].power >= 3) {
        return {
          msg: `无法通过指令去删除最高管理员 ${this.userInfo[userId].userName} 的权限\n请在后台删除`,
          result: false
        }
      }

      const delName = this.userInfo[userId].userName;
      delete this.userInfo[userId];
      await this.updateStoreAuthorityData();
      return {
        msg: ` 删除 ${delName} 权限用户成功`,
        result: true
      }
    },
    async changeAuthorName(userId, userName) {
      this.userInfo[userId].userName = userName;
      await this.updateStoreAuthorityData();
      return {
        msg: `改名成功!你的权限账号的新别名为 ${this.userInfo[userId].userName}`,
        result: true
      }

可以看到他写了类似后端的代码,返回了个处理状态 code,和消息提示参数。

说明他其实已经在为后期界面翻新做准备,方便后面更新显示层,因为显示层明确知道传过来的是什么,只要思考对应样式展示即可。

2 个赞

方便策划暗改(

2 个赞