LOL自走棋(TFT)网页版开发日志-第二周

607 阅读5分钟

第二周

主要实现了英雄的获取及拖拽展示逻辑等操作

实现目标

英雄的随机获取

比例按照腾讯官方给出的比例来(没有考虑7费拉克丝的出现情况)

接口

GET /api/heroes/getRealRandomHeroes

参数
名称 类型 其他
level number 当前用户等级
number number 当前获取的英雄数目
返回
{
  "code": 0,
  "data": [
    {
      "heroId": 163,
      "price": 1,
      "id": 97,
      "equipment": "4004,3026,3107",
      "hero_name": "塔莉垭",
      "hero_tittle": "岩雀",
      "imgUrl": "",
      "job": 17,
      "otherjob": 0,
      "otherrace": 0,
      "partner": "",
      "race": "22",
      "season_id": "2",
      "skill_introduce": "塔莉垭在拥有最多当前法力值的敌人脚下引发岩突,对其造成魔法伤害,并将其晕眩2秒。此外,这个技能还会将远程英雄拉近,或将近战英雄推离。",
      "skill_name": "岩突",
      "skill_num": "伤害:150/350/550",
      "skill_type": "主动",
      "special_desc": "",
      "special_heroid": "",
      "createdAt": "2019-12-18T03:46:12.000Z",
      "updatedAt": "2019-12-18T03:46:12.000Z",
      "info": {
        "CR": "25%",
        "Mana": "80",
        "StartingMana": "50",
        "armor": "20",
        "damage": "40",
        "dps": "26",
        "health": "500",
        "magic_res": "20",
        "name": "1",
        "range": "3",
        "speed": "0.65"
      },
      "leftHealth": "500",
      "leftMana": "50"
    },
    ...
  ],
  "msg": "OK"
}
算法

其实很简单的。

const getRateResult = (arr) => {
  if (arr.length === 1) {
    return 1
  }
  let leng = 0;
  let tmpArr = [];
  for(let i=0; i<arr.length; i++){
      leng+=arr[i];//获取总数
      tmpArr[i + 1] = arr[i]; //重新封装奖项,从1开始
  }
  tmpArr[0] = 0;
  for(let i=0; i<tmpArr.length; i++){
      if(i > 0) {
          tmpArr[i] += tmpArr[i - 1]; //计算每项中奖范围
      }
  }
  let random = parseInt(Math.random()*leng); //获取 0-总数 之间的一个随随机整数
  for(let i=1; i<tmpArr.length; i++){
      if(tmpArr[i - 1] <= random && random < tmpArr[i]) {
         return i; //返回中奖的项数(按概率的设置顺序)
      }
  }
}

利用这个方法,获取到应该是几费的英雄,之后利用lodashsample方法,从该费下所有英雄中抽一个出来即可。

英雄分类
const heroes = {
  hero1: _.filter(config.heroes, (hero) => hero.price === 1 && hero.season_id === '2'),
  hero2: _.filter(config.heroes, (hero) => hero.price === 2 && hero.season_id === '2'),
  hero3: _.filter(config.heroes, (hero) => hero.price === 3 && hero.season_id === '2'),
  hero4: _.filter(config.heroes, (hero) => hero.price === 4 && hero.season_id === '2'),
  hero5: _.filter(config.heroes, (hero) => hero.price === 5 && hero.season_id === '2') 
}
随机英雄
const getRealRandomHeroes = async (ctx, next) => {
  try {
    const pickResult = [];
    const { level, number } = ctx.query;
    const rateArr = _.values(config.pickRate[level]);
    for (let i = 0; i<number; i++) {
      const star = getRateResult(rateArr);
      pickResult.push(_.sample(heroes[`hero${star}`]))
    }
    ctx.response.body = resBeautiful.set(_.map(pickResult, (hero) => ({
      ..._.omit(hero, 'level'),
      info: hero.level[0],
      leftHealth: hero.level[0].health,
      leftMana: hero.level[0].StartingMana
    })));
  } catch (error) {
    ctx.app.emit('error', error, ctx);
  }
}
英雄出现概率

拖拽逻辑

这里没有使用对应的拖拽插件什么的,感觉用起来不自由,使用的是H5Drag系列API,感觉还挺好用的,理解起来也不是很费力。
这里是菜鸟教程的文档,W3school的文档不是很容易理解,推荐菜鸟的。
需要的函数有四个:

dragStart   开始拖拽
dragging    正在拖拽(鼠标点击了但是没有松开的时候)
allowDrop   是否允许放置(感觉没啥用)
drop        放置之后的回调

React中属性有五个(回调对应上面的函数):

onDrop={this.drop()}                        -> drop
onDragOver={this.allowDrop.bind(this)}      -> allowDrop
onDragStart={this.dragStart()}              -> dragStart
onDrag={this.dragging()}                    -> dragging
draggable="true"                            -> 当前元素是否可以拖动,可以设置为只能放置不能拖动

在本次的代码中,allowDropdragging其实没有用到,但是目测没法不填,没有e.preventDefault()无法正常拖拽。
而且,还有一个点需要注意的就是在拖拽过程中可以附带一些参数,e.dataTransfer.setData(name, content)即可为设置参数,然后使用e.dataTransfer.getData(name)获取参数,当然了,这里有个弊端,就是只能设置String类型的内容,其他类型的值需要使用JSON.stringify来转换一下。
示例代码如下:

heroTableItem.jsx
/**
 * @description: 允许drop定义方法
 * @param {DropEvent} event 拖拽事件
 * @return: void
 */
allowDrop(event) {
  event.preventDefault();
}

/**
 * @description: 拖拽放置方法
 * 拿到传递过来的hero, index, from参数
 * 如果来自table,去掉旧的hero,新增新的hero
 * 否则去掉waitting中hero,table新增hero
 * @param {DropEvent} e 拖拽事件
 * @return: void
 */
drop = () => e => {
  e.preventDefault()
  const { hero, index, from } = JSON.parse(e.dataTransfer.getData('info'));
  ...
}

/**
 * @description: 开始拖拽
 * 如果当前没有hero,直接return掉
 * 如果有hero,封装hero, index, from参数进拖拽事件
 * @param {DropEvent} e 拖拽事件
 * @return: void
 */
dragStart = () => e => {
  const hero = this.props.hero;
  if (!hero) {
    return e.preventDefault();
  }
  e.dataTransfer.setData('info', JSON.stringify({
    hero: this.props.hero,
    index: this.props.index,
    from: 'table'
  }));
}

/**
 * @description: 拖动中
 * @param {DropEvent} e 拖拽事件
 * @return: void
 */
dragging = () => e => {
  e.preventDefault()
}

大概齐就是这个意思了,使用原生API还有一个好处就是可以跨组件操作,也不用做任何操作,十分方便,甚至在拖动中可以自定义图片的内容,不过好像样式不好调节,不过问题不大。

遇到问题

React获取e

e非彼e,如果按照正常的操作,我们是这么拿到e的:

绑定:
onDrop={(e) => this.drop(e)}
获取:
drop (e) {}

这种情况下的e其实是默认的事件,里面的内容也与本次拖拽毫无关系,要想拿到真正的e,得这么操作

绑定:
onDrop={this.drop()}
获取:
drop = () => e => {}

如此便能拿到drop真正的e了,而且也可以顺利获取到dataTransfer中带有的数据了,使用之前的方法压根就没有dataTransfer这个属性。

拖拽的逻辑修改

本来是从HeroList中直接拖拽到HeroWaitting上,后来发现这样的操作有些问题,所以改成了双击HeroList元素自动填充到HeroWaitting中,之后可以从HeroWaitting中拖拽到HeroTable中,当从HeroWaitting中拖动到HeroList上时,直接售卖掉这个Hero。之后从HeroTable中也可以拖动到HeroWaitting中。
主要就是这样的一个逻辑。

小结

这周的工作其实也还好,抽奖逻辑感觉还有些问题,但是问题不大,日后有时间再做改进,先实现主要功能再说