0%

注意: 本篇内容是在已完成windows本地部署的情况下做修改,达到linux服务器部署的效果

Nginx 部署

  1. apt-get 直接安装

    1
    apt-get install nginx
  2. 配置文件修改 /etc/nginx/nginx.conf

配置文件和local server的配置大致相同,监听端口修改为当前服务器开放的端口

指定端口后需要修改跨域配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 由root用户启动
user root;

server {
# 当前服务器开放47777端口用于提供live2d-api
listen 47777;
server_name live2d_api;

# 跨域配置修改,Origin指向当前域名
add_header Access-Control-Allow-Origin http://loolob.cn;

location ~ \.php$ {
# root改为当前系统内的live2d_api路径
root /root/loolob_blog/live2d_api;
}
}
  1. 启动nginx服务
    1
    service nginx start

PHP 部署

  1. 安装php

    1
    2
    apt-get isntall php
    apt-get install php-fpm
  2. php.ini的修改同local,/etc/php/7.2/fpm/pool.d/www.conf修改如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ; 由root运行
    user = root
    group = root

    listen = 127.0.0.1:9000

    listen.allowed_clients = 127.0.0.1

    pm.max_children = 50

    pm.max_requests = 500

    request_terminate_timeout = 0

    rlimit_files = 1024
  3. 启动php服务,由于改为由root启动,需要增加启动参数

    1
    php-fpm7.2 -R

liv2d-weight修改

apiPath 指向当前服务器开放的url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (screen.width >= 768) {
Promise.all([
loadExternalResource(live2d_path + "waifu.css", "css"),
loadExternalResource(live2d_path + "live2d.min.js", "js"),
loadExternalResource(live2d_path + "waifu-tips.js", "js")
]).then(() => {
initWidget({
waifuPath: live2d_path + "waifu-tips.json",
apiPath: "http://loolob.cn:47777/",
//apiPath: "https://live2d.fghrsh.net/api/",
// cdnPath: "https://cdn.jsdelivr.net/gh/fghrsh/live2d_api/"
});
});
}

1 EOT字体与NexT Icon

EOT字体是一种在WEB端显示的字体文件,next使用的大部分icon都封装在”themes/next/source/lib/font-awesome/fonts/“目录下的fontawesome-webfont.eot字体文件中

这里以next本身menu icon的使用为例

样式配置文件中代码如下:

_config.yml
1
2
menu:
home: / || home

编译样式时会调用”themes/next/layout/_partials/header/“下的”menu-item.swig”文件

1
2
3
4
5
6
7
/* menu-item.swig */
{%- set menuIcon = '' %}
{%- if theme.menu_settings.icons %}
{%- set menuIcon = '<i class="fa fa-fw fa-' + value.split('||')[1] | trim + '"></i>' %}
{%- endif %}
{%- set menuText = __('menu.' + name) | replace('menu.', '') %}
...

可以看出,结合_config.yml中的home字段,最终在界面中生成了如下样式

1
<i class="fa fa-fw fa-home"></i>

再对照next样式文件font-awesome.css中的内容

font-awesome.css
1
2
3
4
5
6
7
8
9
10
11
12
13
...
@font-face {
font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
...
.fa-home:before {
content: "\f015";
}
...

最终从fontawesome-webfont.eot字体文件中取出了015号文字来作为菜单栏上home选项的icon

EOT文件查看方式

(3D小游戏相关) 在update中计算出沿着圆移动的路径

问题:从点A移动到点B,要求移动的路径为圆弧形式

思路:做一个圆,使AB都处于这个圆上,沿着圆移动即可走出圆弧路径。AB相连做直线,取直线AB的垂线C,所需圆的圆心位于直线C上。然后根据具体需求,取出所需要的圆即可

举例:已知AB坐标,获取A到B的圆弧路径。要求AB路线的弧度所对应的角度为45度

注:以下算法仅针对游戏中的具体需求,提出一种思路和解,算法步骤并不完美,可根据实际问题优化

Laya + TypeScript 代码实现:

已知点A点B坐标和圆弧路径角度

1
2
3
const A = new Laya.Vector3(0, 0, 0)
const B = new Laya.Vector3(9, 9, 9)
const Angle = 45

计算出AB所走圆的圆心与半径

注意:
由于AB点处于三维空间,AB两点无法确定一个平面,而计算出路径是在平面之中,所以需要第3个点,或着一个方向,来帮助确定路径所在的平面。具体确定平面的方法,根据具体需求来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let center = null
let radius = null

// 点A、AB中点、圆心所成直角三角形的b边长度
let b = Laya.Vector3.distance(A, B) / 2

// 点A、AB中点、圆心所成直角三角形的c边长度
let c = b / Math.sin(Angle)

// 点A、AB中点、圆心所成直角三角形的a边长度
let a = Math.sqrt(c * c - b * b)

// 计算点A点B的中点C
let C = new Laya.Vector3(
(A.x + B.x) / 2,
(A.y + B.y) / 2,
(A.z + B.z) / 2
)

/**
* 注意,这里需要引入第3个点,或者一个方向来帮助确定路径所在的平面。
* 假设A向B的运动是一个简单的从原点向上跃起的动作,路径位于A点B点Y轴所形成的平面中,
* 则可引入一个向上的方向向量(0, 1, 0)
*/

// 引入一个沿着Y轴向上的方向向量
let up = new Laya.Vector3(0, 1, 0)

// 得到圆心位置和半径
center = new Laya.Vector3
Laya.Vector3.scale(up, -a, up) // 因为求向上跃起的路径,则圆心应该在AB下方,所以取-a
Laya.Vector3.add(C, up, center)
radius = c

取得路径圆的圆心和半径之后,可以开始计算圆的切线方向。在每一帧移动中,使移动方向为切线方向,即可走出圆弧路径。

以下代码计算圆的切线方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let cur = sp3.transform.position // 当前位置

// 计算AB的方向向量 ab
let dir_a_b = new Laya.Vector3
Laya.Vector3.subtract(B, A, dir_a_b)
Laya.Vector3.normalize(dir_a_b, dir_a_b)

// 计算当前位置到圆心的方向向量 cc
let dir_cur_c = new Laya.Vector3
Laya.Vector3.subtract(c, cur, dir_cur_c)
Laya.Vector3.normalize(dir_cur_c, dir_cur_c)

// 求出ab方向向量和圆心方向向量cc的垂向量v1
let dir_v1 = new Laya.Vector3
Laya.Vector3.cross(dir_a_b, dir_cur_c, dir_v1)
Laya.Vector3.normalize(dir_v1, dir_v1)

// 求出圆心方向向量cc和垂向量v1的垂向量v2,v2即使圆切线的方向向量
let dir_v2 = new Laya.Vector3
Laya.Vector3.cross(dir_cur_c, dir_v1, dir_v2)
Laya.Vector3.normalize(dir_v2, dir_v2)

每一帧的移动都沿着圆的切线方向进行

1
2
3
4
let dis = speed * Laya.timer.delta // 每一帧要移动的距离
let tag = new Laya.Vector3 // 每一帧移动到的目标位置
Laya.Vector3.scale(dir_v2, dis, tag)
Laya.Vector3.add(cur, tag, tag)

取得每一帧的目标位置后,还需要对目标位置做一个修正。防止跳帧或移动距离过大的情况下,走出的路径偏离圆弧

1
2
3
4
5
6
7
8
9
10
11
// 目标点与圆心距离大与半径了
if (Laya.Vector3.distance(tag, center) > radius) {
// 重新计算tag的位置
let dir = new Laya.Vector3
Laya.Vector3.subtract(tag, center, dir)
Laya.Vector3.normalize(dir, dir)
Laya.Vector3.scale(dir, radius, dir)
Laya.Vector3.add(center, dir, tag)
// 这里只是简单的将圆心与tag相连,然后得出连线上位于圆上的点
// 可以保证每次移动都在圆上,但不能保证每次移动的距离都是dis
}

以上算法思路在游戏中实现了:

  1. 人物荡秋千路径
  2. 向前跳跃落地路径
  3. 45度圆角转弯寻路

(线性代数相关) 递归取全排列

  1. 两个数排列,两两交换
1
2
3
4
5
swap(1, 2) => \begin{cases}
1, 2 \\
\\
2, 1
\end{cases}
  1. 三个数排列,三三交换(提取一个数,剩下的两两交换,然后与提取出的数合成新排列)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    swap(1, 2, 3) => \begin{cases}
    1 + swap(2, 3) => 1 + \begin{cases}
    2, 3 => 1, 2, 3 \\
    \\
    3, 2 = > 1, 3, 2
    \end{cases}\\
    \\
    2 + swap(1, 3) => 2 + \begin{cases}
    1, 3 => 2, 1, 3 \\
    \\
    3, 1 = > 2, 3, 1
    \end{cases}\\
    \\
    3 + swap(1, 2) => 3 + \begin{cases}
    1, 2 => 3, 1, 2\\
    \\
    2, 1 => 3, 2, 1
    \end{cases}\\
    \end{cases}
  2. 四个数排列,四四交换(提取一个数,剩下的三三交换,然后与提取出的数合成新排列)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    swap(1, 2, 3, 4) =>  \begin{cases}
    1 + swap(2, 3, 4)\\
    \\
    2 + swap(1, 3, 4)\\
    \\
    3 + swap(1, 2, 4)\\
    \\
    4 + swap(1, 2, 3)\\
    \end{cases}
  3. 依次类推,有N个数排列时,提取1个,剩下的(N-1)个数进行交换,然后与提取出的数合成新排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
swap(1, 2, 3 ... N) =>  \begin{cases}
1 + swap(2, 3 ... N)\\
\\
2 + swap(1, 3 ... N)\\
\\
3 + swap(1, 2 ... N)\\
.
\\
.
\\
.
\\
N + swap(1, 2, 3 ... N-1)\\
\end{cases}
  1. 借助递归思想,有N个数时,可以逐层展开,直到剩下两个数互相交换后返回

python实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 递归取全排列,输入为数组
def permutation(array):
if len(array) < 2:
return [array]
elif len(array) == 2:
# 当输入的数组仅有两个数时,返回原数组和两两交换后的数组
return [array, [array[1], array[0]]]
else:
# 当输入的数组长度为n时,提取1个数,剩下的(n-1)个数组成新数组再次调用permutation
allArray = []
for index in range(0, len(array), 1):
for arrayList in permutation(array[0:index] + array[index+1:]):
allArray.append([array[index]] + arrayList)
return allArray

Live2D 官方资料

Live2D Cubism 4 Editor

Cubism Web Framework

Cubism SDK Manual

Live2D + Hexo 本地化配置流程

Hexo切换为Next样式

使用live2d-widget需要将Hexo切换为Next样式,具体切换方式可参考Next官网

添加live2d-widget模块

live2d-widget 基本配置

直接下载其release版的源码,解压后丢到Hexo里的source目录中即可

修改部分配置

  1. 修改source/live2d-widget/autoload.js

    1
    2
    // live2d-widget解压后放在source目录中,配置autoload.js使其指向本地的live2d-widget目录
    const live2d_path = "/live2d-widget/";
  2. 修改themes/next/layout/_layout.swig

    1
    2
    3
    4
    <head>
    <!-- 在head添加如下内容 -->
    <script src="/live2d-widget/autoload.js"></script>
    </head>
  3. 修改_config.yml,在末尾添加

    1
    2
    live2d:
    enable: true
  4. 发布

    1
    hexo g d

完成以上步骤后打开本地Blog地址,可以看到live2d-widget模块已经嵌入界面,但仅有live2d-widget对话框,并无model加载,model的加载请查看后续步骤。

live2d-widget 进阶配置

翻看autoload.js以及README.md文件,可以知道live2d-widget只提供了在hexo上显示live2d的功能,并不提供live2d的模型。

autoload.js代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 加载 waifu.css live2d.min.js waifu-tips.js
if (screen.width >= 768) {
Promise.all([
loadExternalResource(live2d_path + "waifu.css", "css"),
loadExternalResource(live2d_path + "live2d.min.js", "js"),
loadExternalResource(live2d_path + "waifu-tips.js", "js")
]).then(() => {
initWidget({
waifuPath: live2d_path + "waifu-tips.json",
// apiPath: "https://live2d.fghrsh.net/api/",
cdnPath: "https://cdn.jsdelivr.net/gh/fghrsh/live2d_api/"
});
});
}

可以看到live2d-widget指定了一个获取资源的url,当live2d-widget加载时,会通过这个url去加载model文件。该url可能已经废弃

继续查看README.md,作者提到了提供API的网址,以及API的github地址

于是考虑建立本地后台提供api给本地live2d-widget调用

搭建live2d_api

查看live2d_api项目的内容,发现该项目内提供了live2d的模型文件以及php代码,于是需要在本地搭建一个php server。这里决定采用nginx + php的简单搭配,先跑起api再说。

nginx安装以及配置

下载LTS版本解压后放在本地目录即可,注意路径不要中文

配置nginx/conf/nginx.conf文件,一下仅展示几项比较重要的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
server {
# 这里配置nginx监听的端口号
listen 7777;

# !!!注意 由于是在本地跑api提供文件给hexo用,需要开启跨域,Access-Control-Allow-Origin为本地hexo的url
# #设置跨域配置 Start
set $cors_origin "";
if ($http_origin ~* "^http://localhost$"){
set $cors_origin $http_origin;
}

# add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Origin http://localhost:4000;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,x-auth-token always;
add_header Access-Control-Max-Age 1728000 always;

# 预检请求处理
if ($request_method = OPTIONS) {
return 204;
}
# #设置跨域配置 End

# 这里指定live2d_api项目的根目录,让nginx的请求都指向该目录
location / {
root D:\Code\NodeJsProject\loolob_blog\live2d_api;
}

# 这里配置对php的支持,注意fastcgi_pass的端口号,后面启动php服务器时要用到此端口号
location ~ \.php$ {
root D:\Code\NodeJsProject\loolob_blog\live2d_api;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

PHP安装以及配置

由于是在windows上跑后端,所以下载windows支持版的php,下载源码后解压即可

复制php根目录中的php.ini-development文件并改名为php.ini后修改

1
2
3
4
5
; 将extension_dir指向本地php目录中的ext文件夹
extension_dir = "D:\Code\NodeJsProject\php\ext"

; fix_pathinfo的值改为1
cgi.fix_pathinfo=1

live2d-widget 调用本地接口所需的修改

  1. 修改autoload.js文件,使其的url指向本地

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (screen.width >= 768) {
    Promise.all([
    loadExternalResource(live2d_path + "waifu.css", "css"),
    loadExternalResource(live2d_path + "live2d.min.js", "js"),
    loadExternalResource(live2d_path + "waifu-tips.js", "js")
    ]).then(() => {
    initWidget({
    waifuPath: live2d_path + "waifu-tips.json",
    apiPath: "http://localhost:7777/",
    });
    });
    }
  2. 修改waifu-tips.js文件,使其默认加载的第一个live2d model文件是1-1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    (function initModel() {
    let modelId = localStorage.getItem("modelId"),
    modelTexturesId = localStorage.getItem("modelTexturesId");
    if (modelId === null) {
    // 首次访问加载 指定模型 的 指定材质
    modelId = 1; // 模型 ID
    modelTexturesId = 1; // 材质 ID
    }
    ......
    )

启动hexo以及live2d api

  1. 启动php服务,注意端口号就是给本地nginx使用端口号

    1
    .\php-cgi.exe -b 127.0.0.1:9000 -c .\php.ini
  2. 启动nginx服务

    1
    start nginx
  3. 启动hexo server

    1
    hexo s --debug

启动完以上服务后,通过localhost访问本地hexo主页应该就能看到live2d模型了

Hexo 命令执行分析

尝试通过分析hexo node代码,追踪到index.html生成,以修改web icon

generate

1
$ hexo g

命令指向文件 ./node_modules/.bin/hexo

hexo文件指向 ./node_modules/hexo/bin/hexo

hexo文件指向 ./node_modules/hexo-cli/bin/hexo

hexo文件指向 ./node_modules/hexo-cli/lib/hexo.js

hexo.js

代码用了类反射写法和大量异步语法,只能通过log一步一步追踪,过于复杂催眠力太强,放弃分析😔

修改theme配置文件

默认的生成样式配置文件为**_config.landscape.yml**

将ico放入source目录下

配置文件**_config.landscape.yml**中加入如下代码

1
2
3
favicon:
small: favicon.ico
medium: favicon.png

在hexo根目录执行如下代码,即可重新生成并发布

generate

1
$ hexo g d