Luyu Huang's Tech Blog

2019 Annual Summary

On September 15, 2017, the first day I joined the company, I told myself that I joined a good company which has a good treatment but I was not feeling happy at all. I had plenty of worry and confusion at that moment because I didn't know my direction. All I can do is working hard and learning as much as possible. At the beginning of 2019, I to...

Read more

如何优雅地实现一个新手引导系统

笔者最近要在项目中实现一个新手引导系统. 新手引导其实上是一个比较复杂的系统, 与许多具体的功能紧密相关, 其中涉及到的特殊处理也比较多. 这篇文章我想谈谈新手引导的设计思路, 尽量不涉及具体的引擎框架和实现. 事件驱动 整个新手引导的流程应是事件驱动的. 比如说当宠物功能开启时在宠物功能按钮上显示引导提示, 当点击 A 按钮时把引导提示移动到 B 按钮上. 对于特殊的事件, 我们可以在必要的地方单独处理; 但是对于一些非常通用的事件, 比如说 点击按钮, 打开一个界面, 切换场景等, 我们就应该充分利用引擎和框架, 提供通用的事件, 而不是为每个按钮, 每个界面单独作处理. 抛出事件应该带上必要的数据. 这里举几个笔者项目中的例子: 对于按钮点击事件, 根据 UI 组织结...

Read more

[翻译] Jekyll 手把手教学

本文由 Luyu Huang 翻译, 原文地址 https://jekyllrb.com/docs/step-by-step/01-setup/. 欢迎提 issue 来帮助我改进翻译 1. 配置 欢迎来到 Jekyll 的手把手教学. 这个教程的目标是让你从只有一点点 web 开发经验到能够构建一个 Jekyll 站点 – 不依赖于默认的主题. 现在就开始吧! 安装 Jekyll 是使用 Ruby 编写而成, 所以首先需要在你的机器上安装 Ruby. 请先阅读安装指南并按照操作系统的说明进行操作. 安装并配置完 Ruby 之后就可以开始安装 Jekyll 了. 打开终端并键入以下命令: gem install jekyll bundler 然后需要创建一个 Gemf...

Read more

安全地传递密码

前端要想安全地将密码传递给后端, 应该按照以下几步操作: 前端 将明文密码作 md5 哈希; 将哈希过的密码拼上一段随即生成的, 固定长度的盐; 将上一步的结果使用后端的公钥加密并传递给后端. 后端 将前端传递过来的密码用私钥解密; 去盐. 因为盐是固定长度的, 所以直接裁剪即可; 再次随机生成一段盐; 将第2步去盐后的密码拼上第3步生成的盐, 并作 md5 哈希; 将第4步的结果和第3步生成的盐存入数据库. 验证密码的时候, 后端只需将第2步去盐后的密码拼上从数据库中取得的盐, 作 md5 哈希后与数据库中的密码相比较即可. 这么做的原因 为什么前端要将明文密码作 md5 哈希并拼上盐再加密? 因为公钥是公开...

Read more

行为树及其实现

在笔者的项目中 NPC 要有自动化的行为, 例如怪物的巡逻, 寻敌和攻击, 宠物的跟随和战斗等. 完成这些需求最好的做法是使用行为树(Behavior Tree). 笔者查阅资料, 研究并实现了一个行为树, 可以满足游戏中相关的需求. 这里笔者简单作一些总结和分享, 推荐想要深入研究的同学去看文章最下面的参考资料, 这个一个非常好的行为树教程. 行为树的结构 顾名思义, 行为树首先是一棵树, 它有着标准的树状结构: 每个结点有零个或多个子结点, 没有父结点的结点称为根结点, 每一个非根结点有且只有一个父结点. 在行为树中, 每个节点都可以被执行, 并且返回 Success, Failure 或 Running, 分别表示成功, 失败或正在运行. 行为树会每隔一段时间执行一下根结点, ...

Read more

使用协程处理耗时过程

游戏服务器常常有一些耗时的操作, 比如说给全服玩家发放奖励. 如果直接写一个循环, 遍历全服玩家, 给每个玩家发放奖励, 那么整个过程可能持续几分钟, 十几分钟甚至几十分钟, 整个进程都阻塞在这个过程中了. 解决这个问题的一种做法是使用定时器, 比如说每处理完 50 个玩家, 就停 1 秒, 1 秒后继续处理, 就像这样: function deal(list) local p = 0 local function foo() local from, to = p + 1, math.min(p + 50, #list) for i = from, to do local id = list[i] ...

Read more

关于容错和断言的一些思考

实际项目中的代码总是多多少少会有一些问题的. 面对一些问题, 我们有两种做法: 一种是容错, 把错误自行消化掉, 让代码能够继续往下运行; 另一种是加断言, 让错误发生时抛出异常, 把错误暴露出来, 并且能中断当前过程, 避免后续行为未定义. 这两种策略中, 笔者更偏爱后者. 因为无脑的容错最后会导致问题的根源不被解决, 使问题堆积, 导致代码腐败. 毕竟解决问题的第一步是面对问题, 而不是回避它. 然而事情并没有这么简单. 无论是容错, 还是加断言, 都是要根据不同的场景进行思考. 无脑地容错和无脑地加断言都是不可取的. 我们来看两个例子: 1. 排行榜结算 游戏服务器中一个常见的需求就是结算排行榜, 给排行榜中的玩家发奖. 这样的代码通常是这样的: function sett...

Read more

RSA算法背后的数学原理

1. 引言 RSA算法(RSA algorithm)是一种非对称加密算法, 广泛应用在互联网和电子商务中. 它使用一对密钥进行加密和解密, 分别称为公钥(public key)和私钥(private key). 使用公钥加密的内容只能用私钥解密, 使用私钥加密的内容只能用公钥解密, 并且不能通过公钥在可行的时间内计算出私钥. 这使得加密通信不需要交换私钥, 保证了通信的安全. 那么它是怎么做到这一点的呢? 背后有哪些数学原理? 这篇文章我们来讨论这个问题. 本文会先介绍RSA算法中用到的数论概念和定理: 模算术, 最大公约数与贝祖定理, 线性同余方程, 中国余数定理, 费马小定理; 然后再介绍RSA算法的原理, 并证明其是有效的. 本文会假设你了解数论的基本概念, 如素数, 最大公约...

Read more