普通视图

发现新文章,点击刷新页面。
昨天 — 2026年4月18日崎径 其镜赵安琪的博客

力扣笔记

作者 Anqi Zhao
2026年4月6日 05:14

程序模板

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
#include<cstring>
using namespace std;



int main()
{

return 0;
}

数学模板

公约数、公倍数

1
2
3
4
5
6
7
8
9
10
int gcd(int a,int b)  // 最大公约数
{
if(b==0) return a;
else return gcd(b,a%b);
}

int lcm(int a,int b) // 最小公倍数
{
return a/gcd(a,b)*b;
}

素数

1
2
3
4
5
6
7
int prime(int b)      // 素数
{
for(int i=2;i<=(int)sqrt(b);i++) {
if(b%i==0) return 0;
}
return 1;
}

排列组合打表

1
2
3
4
5
6
7
8
9
10
int c[N][N];
void com() {
memset(c,0,sizeof(c));
for(int i=1;i<N;i++) {
c[i][0]=c[i][i]=1;
for(int j=1;j<i;j++) {
c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
}

快速幂

1
2
3
4
5
6
7
8
9
int Fast(int x,int n) {
int tem=x,ans=1;
while(n) {
if(n%2==1) ans*=tem;
tem*=tem;
n>>=1;
}
return ans;
}

快速幂取模

1
2
3
4
5
6
7
8
9
int pow(int a,int x) {
int ans=1,temp=a%p;
while(x) {
if(x&1) ans=((long long)ans*temp)%p;
temp=((long long)temp*temp)%p;
x>>=1;
}
return ans;
}

十进制转换其他进制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
string trans(int num,int base) {
string str;
while(num>0) {
if(num%base<10) {
str+=num%base+'0';
}
else {
str+=num%base-10+'A';
}
num=num/base;
}
reverse(str.begin(),str.end());
return str;
}

常用数学公式

1
2
3
4
5
6
7
8
等差数列求和公式:

Sn=n*a1+n(n-1)d/2
Sn=n(a1+an)/2

等比数列求和公式:

Sn=a1(1-q^n)/(1-q)

常用函数

1
2
sort(起始地址,终点地址,比较方法);
lower_bound(起始地址,终点地址,查找元素);
昨天以前崎径 其镜赵安琪的博客

从零配置 VS Code C++ 环境

作者 Anqi Zhao
2026年4月13日 06:32

本文档记录在 Windows 系统上从零配置 VS Code C++ 开发环境的全过程,包括编译、调试和运行。

前置条件

需要的工具

1. MSYS2

MSYS2 是一个 Windows 下的 Linux-like 环境,提供 g++ 编译器和 gdb 调试器。

2. VS Code 扩展

  • C/C++ 扩展:由 Microsoft 提供,支持 IntelliSense、调试等功能
    • 在 VS Code 中搜索安装:ms-vscode.cpptools

安装步骤

步骤 1:安装 MSYS2

  1. 下载 MSYS2 安装包并运行安装程序
  2. 选择安装路径为 C:\msys64
  3. 安装完成后,运行 MSYS2 UCRT64 终端(从开始菜单启动)

步骤 2:更新 MSYS2 并安装工具

在 MSYS2 UCRT64 终端中执行以下命令:

1
2
3
4
5
# 更新包数据库
pacman -Syu

# 安装 g++ 编译器和 gdb 调试器
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-gdb

步骤 3:安装 VS Code 扩展

  1. 打开 VS Code
  2. Ctrl+Shift+X 打开扩展面板
  3. 搜索 C/C++ 并安装 Microsoft 的 C/C++ 扩展

VS Code 配置

在你的 C++ 项目根目录(例如 C:\Dev\leetcode)创建 .vscode 文件夹,并添加以下配置文件:

1. c_cpp_properties.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"configurations": [
{
"name": "windows-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "C:\\msys64\\ucrt64\\bin\\gcc",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "windows-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

2. settings.json

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{
"C_Cpp.default.compilerPath": "c:\\msys64\\ucrt64\\bin\\gcc.exe",
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false,
"terminal.external.windowsExec": "C:\\Windows\\System32\\cmd.exe"
}

3. tasks.json

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
{
"version": "2.0.0",
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: g++.exe build active file",
"command": "C:\\msys64\\ucrt64\\bin\\g++.exe",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${workspaceFolder}\\build\\Debug\\outDebug.exe"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Compile current C++ file to build/Debug/outDebug.exe"
},
{
"type": "shell",
"label": "Run C++ Program",
"command": "cmd.exe",
"args": [
"/c",
"start \"\" cmd.exe /k \"${workspaceFolder}\\build\\Debug\\outDebug.exe\""
],
"dependsOn": [
"C/C++: g++.exe build active file"
],
"options": {
"shell": {
"executable": "C:\\Windows\\System32\\cmd.exe"
}
},
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared"
},
"problemMatcher": []
}
]
}

4. launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": false,
"cwd": "c:/Dev/leetcode",
"program": "${workspaceFolder}/build/Debug/outDebug.exe",
"MIMode": "gdb",
"miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

5. keybindings.json(全局配置)

在 VS Code 用户设置中添加快捷键绑定(Ctrl+Shift+P -> Preferences: Open Keyboard Shortcuts (JSON)):

1
2
3
4
5
6
7
8
9
10
[
{
"key": "f6",
"command": "workbench.action.tasks.runTask",
"args": {
"task": "Run C++ Program"
},
"when": "editorTextFocus"
}
]

使用方法

  1. 编译:按 Ctrl+Shift+B 或打开命令面板运行 “Tasks: Run Build Task”
  2. 调试:按 F5,会启动 gdb 调试器
  3. 运行:按 F6,会打开 cmd 黑框窗口运行程序

常见问题

1. 找不到 g++ 或 gdb

  • 确保 MSYS2 已正确安装并更新
  • 检查 PATH 环境变量是否包含 C:\msys64\ucrt64\bin

2. 编译失败

  • 检查代码语法错误
  • 确保头文件路径正确

3. 调试失败

  • 确保已编译生成 build/Debug/outDebug.exe
  • 检查 gdb 路径是否正确

4. 黑框不弹出

  • 重启 VS Code
  • 检查 terminal.external.windowsExec 设置

版本信息

  • MSYS2: 最新版本
  • g++: 15.2.0
  • gdb: 17.1
  • VS Code: 最新版本
  • C/C++ 扩展: 最新版本

参考资料

Unity 游戏的 Google Play 16 kb页面对齐处理

作者 Anqi Zhao
2026年4月4日 01:21

16 KB 内存页面大小的支持,是Google Play 新提出的要求。要在2026年5月31日之前,满足这一条件:

https://play.google.com/console/u/0/developers/9060101706093336387/app/4974577679306649072/policy-center/issues/4988764664083295045/details

Android 15 的 16KB page 检测主要看三件事:

  1. PT_LOAD >= 16 KB
  2. RELRO 必须在 segment 末尾(suffix)
  3. RELRO end 必须 16KB 对齐

对于aab 来说,上面的三个要求,一般只用关注PT_LOAD。因为aab 有压缩率,所以关于压缩的两项检验直接计算是不准确的。Google Play 在分发时会自动处理。

官方文档:

https://developer.android.com/guide/practices/page-sizes?utm_source=chatgpt.com&hl=zh-cn

检测方式

关于so 文件是否合规的检测,主要有以下几种方法:

方法一

可以解压出so文件,然后使用ndk 工具检测(一定使用PowerShell,cmd无法运行):

1
llvm-objdump.exe -p E:\Test\so\arm64-v8a\libanogs.so | Select-String -Pattern "LOAD"

运行结果可以看LOAD off 类型行尾是不是带有212、213。
低于2**14的so文件都不符合。

工具的路径在:

1
{YourNDKPath}\toolchains\llvm\prebuilt\windows-x86_64\bin

但是这里只检查了PT_LOAD。

方法二

Google 官方提供了检测工具:

https://cs.android.com/android/platform/superproject/main/+/main:system/extras/tools/check_elf_alignment.sh?hl=zh-cn

可以直接使用这个脚本进行检测。

1
check_elf_alignment.sh APK_NAME.apk

方法三

最后,如果将Android Studio 更到最新版本,即可使用Apk Analyze 功能进行检测。

前者报错为:

1
4 KB LOAD section alignment, but 16 KB is required

后者报错为:

1
RELRO is not a suffix and its end is not 16 KB aligned

适配方法

Unity 相关so

Unity相关的so,需要通过提升Unity 编辑器版本号的方式进行解决。

第三方SDK

第三方SDK 相关的so,需要联系相关提供商进行SDK 升级。

如果无法完成合规,应该考虑取消对该插件的接入。

自己编译的so

如果是自己使用NDK 编译的so,需要升级NDK工具并在编译中指定相关参数。

1
2
3
4
5
6
7
({NDK Path}/ndk-build ^
NDK_PROJECT_PATH=. ^
APP_BUILD_SCRIPT=Android.mk ^
NDK_APPLICATION_MK=Application.mk ^
APP_ABI="armeabi-v7a x86 arm64-v8a x86_64" ^
APP_LDFLAGS="-Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384" ^ // 新增
|| pause) && pause

对于Android.mk 文件和Application.mk 文件也有内容需要修改:

如果旧内容涉及cmd-strip,在打包时会报错。这是因为NDK r23+中,strip 工具路径变成 LLVM 版本。最简单的解决方式是删掉这一行,新版本不需要手动调用strip,会自动调用。

如果文件中存在:APP_STL := gnustl_static,也需要进行修改。
修改为:APP_STL := c++_static

为了兼容RELRO 必须在 segment 末尾(suffix)和 RELRO end 必须 16KB 对齐,需要在Android.mk 中增加:

1
2
LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384
LOCAL_LDFLAGS += -Wl,-z,common-page-size=16384

最后,在旧的代码中,如果使用了PAGE_SIZE / PAGE_MASK 宏,这会报错,在新版本中,NDK 不再提供。
需要增加以下内容:

1
2
3
4
5
6
7
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

#ifndef PAGE_MASK
#define PAGE_MASK (~(PAGE_SIZE - 1))
#endif

3. il2cpp处理方式

升级到新版Unity之后,如果不修改代码,可能不会让C++代码重新导出。

也可以删除Library目录下的Bee目录和所有的il2cpp_*目录的缓存,强制生成。

一文详解Hexo 博客搭建

作者 Anqi Zhao
2026年4月3日 22:06

前言

捡起了好久没有更新的博客。

在AI 的帮助下,我完成了整个环境的重新搭建。

由于我使用的hexo 框架和版本都比较老了,所以有些地方可能不适应于新版本的主题,在接入时还请注意。

参考视频: https://www.bilibili.com/video/BV1xTgTemEDU/

视频文档: https://xiamu-ssr.github.io/Hexo/2024/06/19/2024-H1/2024-06-19-12-31-52/

准备工作

使用Github Actions 服务,将hexo 部署到 Github Pages。

与参考内容不同的是,我使用了两个仓库来实现。

参考内容中,使用了同一个项目,两个分支。其中一个分支用于提交博客源文件,另一个分支用于存放生成的静态网页文件。

考虑到将博客源文件内容暴露出来会有一定的风险,于是我的方案是使用两个仓库来实现。其中,Private 的仓库用于存放博客源文件,Public 的仓库用于存放页面。

我之前是有hexo 环境的,会和从头开始实现有所区别,在接下来的内容中,我将会分别说明。

关于Github Actions 服务的用量,Public 仓库可以免费使用,但是Private 的仓库会有一定的限制。但是如果只是作为hexo的资源站,免费的额度也是可以覆盖的。

hexo准备

安装并初始化hexo,如果已有hexo环境,可跳过这一步。

1
2
3
4
npm install -g hexo-cli
hexo init blog
cd blog
npm install

如果是已有hexo环境,在其他地方做过备份的。可以删除node_modules目录和package-lock.json,然后重新安装:

1
2
cd blog
npm install

仓库准备

在github上新建两个仓库,一个Private的blog_base,一个Public 的username.github.io;

已有Github Pages 的,可以不用重新创建username.github.io,只创建博客源文件仓库。

Token准备

使用Github Actions ,需要生成Personal access tokens,而且至少要包含repo 权限。把这个Token 配置给Github Action, 它才有权限去执行自动部署。

首先是Token 的创建:

单击头像 -> Settings -> Developer Settings -> Personal access tokens -> Tokens (classic)

这里是所有Tokens的列表,下面我们继续创建:

Generate new token -> Generate new token (classic)

新打开的界面中,我们需要给这个token 命名,随便命名即可,比如blog_base。

在下面的权限列表中,勾选repo,workflow 两项。

记录token的有效期,有效期到期后需要给Github Actions 配置新的token。

完成创建后,去到blog_base 仓库进行配置。首先打开该仓库,点击上方的Settings -> Secrets and variables -> Actions

点击 New repository secret 创建,Name设置为:GH_TOKEN(可随意设置,后面保持一致即可),Secret 粘贴刚才生成出来的token内容,就是一串以ghp_为开头的字符串。

修改_config.yml

为了设置推送的地址,需要在_config.yml 中配置推送地址。这里选择Public 的Github pages 仓库地址:

1
2
3
4
deploy:
type: git
repo: https://github.com/yourusername/your-repo.git
branch: master

配置Github Actions 工作流

在blog_base 目录创建.github文件夹,再在.github文件夹下创建workflows文件夹,然后创建deploy.yml,内容如下:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
name: Deploy Hexo to GitHub Pages

on:
push:
branches:
- main # 当推送到 main 分支时触发

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
submodules: false # 禁用子模块检查

- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '12'

- name: Install Dependencies
run: npm install

- name: Install Hexo Git Deployer
run: |
npm install hexo-deployer-git --save
npm install hexo-cli -g

- name: Clean and Generate Static Files
run: |
hexo clean
hexo generate

- name: Configure Git
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'

- name: Deploy to GitHub Pages
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
cd public/
git init
git add -A
git commit -m "Create by workflows"
git branch -M master
git remote add origin https://${{ secrets.GH_TOKEN }}@github.com/yourusername/your-repo.git
git push -f origin master

这里最后一步是将生成的内容,推送到你配置的仓库的master 分支,我的Github Pages就是设置的这个分支。

配置里的node-version 这个参数需要注意。旧版的hexo 使用新版的node 可能会生成失败。我的主题使用的hexo 版本比较老,使用12版本是可以生成的,最新的24 反而无法生成,这里可以按需修改版本。

这个工作流的意思就是,使用ubuntu-latest作为基础环境,然后安装各种依赖,随后hexo generate生成博客网站静态文件夹,最后再推送到设定的仓库和分支。

配置Github Pages

到Github Pages 仓库,点击Settings,设置page来源的分支。

推送并查看是否能够生效。

在这里,介绍一下我的.gitignore文件:

1
2
3
4
5
6
7
.DS_Store
Thumbs.db
db.json
*.log
public/
.deploy*/
node_modules/

.DS_Store文件和Thumbs.db文件。这两个文件分别是Mac系统和Win系统生成的垃圾。

public 目录存放的是本地生成的静态页面,这个也不必上传。Github Actions 会自动生成。

node_modules 目录一定不能上传,需要排除掉。因为它太大了,不方便版本控制。而且不同平台需求的可能都不同。本地环境和运行Github Actions 的虚拟机环境不可能保证一致的。

其他注意事项

如果你之前是有Github Pages的,需要做好备份,防止文件丢失。

我就出现了CNAME文件和其他文件丢失的情况,这里需要注意。

自定义域名

如果嫌username.github.io 的网址不够优雅,可以注册域名,然后配置解析。

在购买域名后,进行以下设置:

Github 配置

进入Github Pages 仓库,单击Settings 切页。

在左侧边栏点击Pages。

Custom domain 配置为:

1
www.yourdomin.xxx

然后勾选Enforce HTTPS

域名解析

在域名解析中,做出以下设置:

1
2
3
4
5
6
7
8
www  CNAME  yourusername.github.io
@ AAAA 2606:50c0:8003::153
@ AAAA 2606:50c0:8002::153
@ AAAA 2606:50c0:8001::153
@ AAAA 2606:50c0:8000::153
@ A 185.199.111.153
@ A 185.199.110.153
@ A 185.199.108.153

这里参考Github的文档:https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site

创建CNAME

需要在Github Pages 网站的根目录创建CNAME文件,内容是在第一步中配置的www.yourdomin.xxx

一文详解Hexo 博客搭建

作者 Anqi Zhao
2026年4月3日 22:06

前言

捡起了好久没有更新的博客。

在AI 的帮助下,我完成了整个环境的重新搭建。

由于我使用的hexo 框架和版本都比较老了,所以有些地方可能不适应于新版本的主题,在接入时还请注意。

参考视频: https://www.bilibili.com/video/BV1xTgTemEDU/

视频文档: https://xiamu-ssr.github.io/Hexo/2024/06/19/2024-H1/2024-06-19-12-31-52/

准备工作

使用Github Action 服务,将hexo 部署到 Github Pages。

与参考内容不同的是,我使用了两个仓库来实现。

参考内容中,使用了同一个项目,两个分支。其中一个分支用于提交博客源文件,另一个分支用于存放生成的静态网页文件。

考虑到将博客源文件内容暴露出来会有一定的风险,于是我的方案是使用两个仓库来实现。其中,Private 的仓库用于存放博客源文件,Public 的仓库用于存放页面。

我之前是有hexo 环境的,会和从头开始实现有所区别,在接下来的内容中,我将会分别说明。

1. hexo准备

安装并初始化hexo,如果已有hexo环境,可跳过这一步。

1
2
3
4
npm install -g hexo-cli
hexo init blog
cd blog
npm install

如果是已有hexo环境,在其他地方做过备份的。可以删除node_modules目录和package-lock.json,然后重新安装:

1
2
cd blog
npm install

2. 仓库准备

在github上新建两个仓库,一个Private的blog_base,一个Public 的username.github.io;

已有Github Pages 的,可以不用重新创建username.github.io,只创建博客源文件仓库。

3. Token准备

使用Github Actions ,需要生成Personal access tokens,而且至少要包含repo 权限。把这个Token 配置给Github Action, 它才有权限去执行自动部署。

首先是Token 的创建:

单击头像 -> Settings -> Developer Settings -> Personal access tokens -> Tokens (classic)

这里是所有Tokens的列表,下面我们继续创建:

Generate new token -> Generate new token (classic)

新打开的界面中,我们需要给这个token 命名,随便命名即可,比如blog_base。

在下面的权限列表中,勾选repo,workflow 两项。

记录token的有效期,有效期到期后需要给Github Actions 配置新的token。

完成创建后,去到blog_base 仓库进行配置。首先打开该仓库,点击上方的Settings -> Secrets and variables -> Actions

点击 New repository secret 创建,Name设置为:GH_TOKEN(可随意设置,后面保持一致即可),Secret 粘贴刚才生成出来的token内容,就是一串以ghp_为开头的字符串。

4. 修改_config.yml

为了设置推送的地址,需要在_config.yml 中配置推送地址。这里选择Public 的Github pages 仓库地址:

1
2
3
4
deploy:
type: git
repo: https://github.com/yourusername/your-repo.git
branch: master

5. 配置Github Actions 工作流

在blog_base 目录创建.github文件夹,再在.github文件夹下创建workflows文件夹,然后创建deploy.yml,内容如下:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
name: Deploy Hexo to GitHub Pages

on:
push:
branches:
- main # 当推送到 main 分支时触发

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
submodules: false # 禁用子模块检查

- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '12'

- name: Install Dependencies
run: npm install

- name: Install Hexo Git Deployer
run: |
npm install hexo-deployer-git --save
npm install hexo-cli -g

- name: Clean and Generate Static Files
run: |
hexo clean
hexo generate

- name: Configure Git
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'

- name: Deploy to GitHub Pages
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
cd public/
git init
git add -A
git commit -m "Create by workflows"
git branch -M master
git remote add origin https://${{ secrets.GH_TOKEN }}@github.com/yourusername/your-repo.git
git push -f origin master

这里最后一步是将生成的内容,推送到你配置的仓库的master 分支,我的Github Pages就是设置的这个分支。

配置里的node-version 这个参数需要注意。旧版的hexo 使用新版的node 可能会生成失败。我的主题使用的hexo 版本比较老,使用12版本是可以生成的,最新的24 反而无法生成,这里可以按需修改版本。

这个工作流的意思就是,使用ubuntu-latest作为基础环境,然后安装各种依赖,随后hexo generate生成博客网站静态文件夹,最后再推送到设定的仓库和分支。

6. 配置Github Pages

到Github Pages 仓库,点击Settings,设置page来源的分支。

推送并查看是否能够生效。

7. 其他注意事项

如果你之前是有Github Pages的,需要做好备份,防止文件丢失。

我就出现了CNAME文件和其他文件丢失的情况,这里需要注意。

8. 自定义域名

如果嫌username.github.io 的网址不够优雅,可以注册域名,然后配置解析。

在购买域名后,进行以下设置:

8.1 Github 配置

进入Github Pages 仓库,单击Settings 切页。

在左侧边栏点击Pages。

Custom domain 配置为:

1
www.yourdomin.xxx

然后勾选Enforce HTTPS

8.2 域名解析

在域名解析中,做出以下设置:

1
2
3
4
5
6
7
8
www  CNAME  yourusername.github.io
@ AAAA 2606:50c0:8003::153
@ AAAA 2606:50c0:8002::153
@ AAAA 2606:50c0:8001::153
@ AAAA 2606:50c0:8000::153
@ A 185.199.111.153
@ A 185.199.110.153
@ A 185.199.108.153

这里参考Github的文档:https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site

8.3 创建CNAME

需要在Github Pages 网站的根目录创建CNAME文件,内容是在第一步中配置的www.yourdomin.xxx

xlua学习笔记

作者 Anqi Zhao
2021年5月19日 03:05

文件加载

使用LuaEnv中的DoString方法来执行Lua脚本。
因为创建一个LuaEnv相当于创建了一个lua虚拟机,所以一个游戏最好只有一个LuaEnv。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 执行lua代码
env.DoString("print(\"Hello World\")");

// 通过文件执行lua代码
TextAsset ta = Resources.Load<TextAsset>("Lua/helloworld.lua");
env.DoString(ta.text);

// 通过默认Loader执行lua代码
env.DoString("require 'Lua/helloworld'");

// 通过自定义Loader执行lua代码
// require实际上是调一个个的Loader去加载,有一个成功就不再往下尝试,全失败则报文件找不到。
private byte[] LuaLoader(ref string filePath)
{
string path = "Lua/" + filePath + ".lua";
print(path);
TextAsset ta = Resources.Load<TextAsset>(path);
return System.Text.Encoding.UTF8.GetBytes(ta.text);
}

env.AddLoader(LuaLoader);
env.DoString("require 'helloworld'");

官方建议的加载Lua脚本方式是:整个程序就一个DoString(“require ‘main’”),然后在main.lua加载其它脚本(类似lua脚本的命令行执行:lua main.lua)。

C#访问Lua普通全局变量

先加载lua脚本,再通过LuaEnv.Global.Get获取变量。

1
2
3
4
5
6
TextAsset ta = Resources.Load<TextAsset>("Lua/variable.lua");
env.DoString(ta.text);

int g_int = env.Global.Get<int>("g_int");
string g_str = env.Global.Get<string>("g_str");
bool g_bool = env.Global.Get<bool>("g_bool");

C#访问Lua table的四种方法

需要注意的是,在构建C#中对应的结构时,接口一定要是public修饰,否则会报错。类最好也是,但是经测试,没有报错。

映射到普通class或struct

table的属性可以多于或者少于class的属性,也就是不一定都需要做映射。这个过程是值拷贝,如果class比较复杂代价会比较大。而且修改class的字段值不会同步到table,反过来也不会。

1
2
3
4
5
6
7
public class Person
{
public string name;
public int age;
}

Person p = env.Global.Get<Person>("g_tbl");

映射到接口

一定要加[CSharpCallLua]和public,否则会报错。

1
2
3
4
5
6
7
g_int = 1
g_str = "Hello World Variable"
g_bool = false
g_tbl = {name = "Juhnny", age = 12}
g_tbl.say = function(this, str)
print(str)
end

1
2
3
4
5
6
7
8
9
10
[CSharpCallLua]
public interface IPerson
{
string name { get; set; }
int age { get; set; }
public void say(string str);
}

IPerson p = env.Global.Get<IPerson>("g_tbl");
p.say("Yahoo");

映射到Dictionary和List

这种方法较方法2更为轻量级,但是要求table下key和value类型一致。

1
2
g_dic = {apple = "iPhone", flower = "Huawei"}
g_list = {1, 3, 5, 7, 9}

1
2
3
4
5
6
7
8
9
10
11
Dictionary<string, string> dic = env.Global.Get<Dictionary<string, string>>("g_dic");
foreach (var child in dic)
{
print(string.Format("Key is {0}, Value is {1}", child.Key, child.Value));
}

List<int> list = env.Global.Get<List<int>>("g_list");
foreach (var v in list)
{
print(v);
}

映射到LuaTable

这种方法比较慢,而且没有类型检查,不推荐使用。

1
2
3
4
5
6
7
8
LuaTable tab = env.Global.Get<LuaTable>("g_tbl");
string name = tab.Get<string>("name");
int age = tab.Get<int>("age");
print(string.Format("Name is {0}, age is {1}", name, age));
foreach (string key in tab.GetKeys())
{
print(tab.Get<object>(key));
}

C#访问Lua全局函数

使用delegate映射

这种是建议的方式,性能好很多,而且类型安全,但是要生成代码。支持带返回值,多返回值,甚至返回值都可以是delegate。

1
2
3
4
5
6
7
8
g_func1 = function()
print("FF")
end

g_func2 = function(num)
print(num)
return num, "Yoshida", "SE"
end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[CSharpCallLua]
public delegate int Func2(int num, out string name, out string company);
// 声明委托时,一定要加public和[CSharpCallLua]

// 无返回
Action func1 = env.Global.Get<Action>("g_func1");
func1();
func1 = null;

// 单返回值
Func2 func2 = env.Global.Get<Func2>("g_func2");
int num = func2(14);
func2 = null

// 多返回值
Func2 func2 = env.Global.Get<Func2>("g_func2");
string name;
string company;
int num = func2(14, out name, out company);
func2 = null

在过去的一些版本中,需要在不使用后,将接收到的函数销毁,否则会在LuaEnv.Dispose的时候报错。

使用LuaFunction

与上一种方法优缺点相反,不推荐使用。

1
2
LuaFunction func = env.Global.Get<LuaFunction>("g_func2");
object[] res = func.Call(14);

Lua调用C

1
2
3
4
5
6
7
8
9
10
11
// 读静态属性
CS.UnityEngine.Time.deltaTime

// 写静态属性
CS.UnityEngine.Time.timeScale = 0.5

// 调用静态方法
CS.UnityEngine.GameObject.Find('helloworld')

// 读写成员属性类似,但是访问成员方法要使用:
testobj:DMFunc()

其它注意事项

方法的参数处理
Lua调用侧的参数处理规则:C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算,然后从左往右对应lua 调用侧的实参列表。

Lua调用侧的返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。

可变参数的处理

1
2
3
4
5
// 对于C#的如下方法:
void VariableParamsFunc(int a, params string[] strs)

// 可以在lua里头这样调用:
testobj:VariableParamsFunc(5, 'hello', 'john')

枚举

1
2
3
4
5
6
7
8
9
10
// 枚举值就像枚举类型下的静态属性一样。

testobj:EnumTestFunc(CS.Tutorial.TestEnum.E1)

上面的EnumTestFunc函数参数是Tutorial.TestEnum类型的。

枚举类支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换,例如:

CS.Tutorial.TestEnum.__CastFrom(1)
CS.Tutorial.TestEnum.__CastFrom('E1')

委托

1
2
3
4
5
6
7
8
-- 使用+-号来实现委托
-- 比如testobj里头有个事件定义是这样:public event Action TestEvent;

-- 增加事件回调
testobj:TestEvent('+', lua_event_callback)

--移除事件回调
testobj:TestEvent('-', lua_event_callback)

Lua新知

作者 Anqi Zhao
2021年5月10日 19:42

两年没有碰过Lua了,总觉得得要捡起来。现在从头到尾再过一遍,总结一些东西。之前根据菜鸟学的时候,使用了5.1.1的版本。与现在常用的版本相悖。因此这次我使用了:zerobrane studio,它可以方便切换版本,能实现很好的练习效果。

关于多行注释的安全问题

如果直接使用默认的–[[–]]来进行多行注释的时候,如果遇到table包table的情况会直接结束注释:

1
2
3
4
5
6
--[[
a = [];
b = [a];
a[b[1]]
a = 1
--]]

可以使用另一种方法来进行多行注释,避免这个问题:

1
2
3
4
5
6
--[=[
a = [];
b = [a];
a[b[1]]
a = 1
]=]

当然,最安全的方法还是使用IDE的快捷键进行注释.IDE会在每行的前面加单行注释,避免错误的发生.

多行字符串安全问题

多行字符串,也会出现类似于上面的情况:

1
2
3
4
5
6
7
8
9
10
str = 
[[
话说
这个东西
好像打印不出来
就是这个
table[table[idx]]
咋办啊
]]
print(str)

运算符相关

#运算符取的是最大索引的值,如果删除了中间的值,获得的结果不会改变:

1
2
3
4
5
tb_a = {1, 2, 3}
print (#tb_a)

tb_a[2] = nil
print (#tb_a)

但是这种情况仅限于Lua 5.1 ,Lua5.3和Lua5.4结果是1。

模拟三目运算符的and…or…,在第二个参数为false时,始终返回c,会出现问题:

1
2
a = true
print (a and false or true)

输出结果会是true 而不是三目运算符应该返回的false。
这时候,可以将三目运算符的后面两个部分转换成table,运算结束后再取第一个元素,即可实现:

1
2
3
4
5
6
7
a = true
b = (a and {false} or {true})[1]
if b then
print("true")
else
print("false")
end

自定义迭代器

自定义迭代器的格式:

1
2
3
for 变量列表 in 迭代函数, 状态变量, 控制变量 do
-- 循环体
end

自定义无状态迭代器

1
2
3
4
5
6
7
8
9
10
11
function square(iteratorMaxCount, currentNumber)
if currentNumber < iteratorMaxCount then
currentNumber = currentNumber + 1
return currentNumber, currentNumber * currentNumber
end
end

for i, n in square, 3, 0
do
print(i, n)
end

多状态迭代器:
多个状态可以存放在table,因此只需要一个参数即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
array = {"Google", "Runoob"}

function elementIterator (collection)
local index = 0
local count = #collection
-- 闭包函数
return function ()
index = index + 1
if index <= count then
-- 返回迭代器的当前元素
return collection[index]
end
end
end

for element in elementIterator(array) do
print(element)
end

循环时的goto语句

可以使用goto语句来实现continue:

1
2
3
4
5
6
7
8
9
for i=1, 3 do
if i <= 2 then
print(i, "yes continue")
goto continue
end
print(i, " no continue")
::continue::
print([[i'm end]])
end

元表

Lua可以通过设置元表,定义元方法的方式,改变table的一些行为:

index元方法,可以修改按索引取值的逻辑。
index内容是一个表的话,如果table里没有值,会从元表中对应的索引去取:

1
2
3
4
5
6
7
8
9
10
11
t_table = setmetatable({k1 = "v1"}, {__index = {k2 = "v2"}})
print(t_table["k1"])
print(t_table["k2"])
t_table["k2"] = "v3"
print(t_table["k2"])
--[[
输出是:
v1
v2
v3
--]]

若__index内容是一个函数,则可以定义返回逻辑:

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
t_table = setmetatable({}, {__index =
function(t_table, key)
if key == 1 then
return "true"
else
return "false"
end
end
})

print(t_table[1])
print(t_table[2])
print(t_table[3])

t_table = {1, 2, 3}

print(t_table[1])
print(t_table[2])
print(t_table[3])
--[[
输出是:
true
false
false
1
2
3
--]]

__newindex元方法,可以修改追加索引时的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t_table = setmetatable({k1 = "v1"}, {
__newindex =
function(t_table, key, value)
rawset(t_table, key, "\"v" .. value .. "\"")
end
})

t_table["k2"] = 2

print(t_table["k1"])
print(t_table["k2"])

--[[
输出是:
v1
"v2"
--]]

其它的规则:
|元表|运算符|
|:–:|:–:|
|add|+|
|
sub|-|
|mul|*|
|
div|/|
|mod|%|
|
unm|-(相反数)|
|concat|..|
|
eq|==|
|lt|<|
|
le|<=|

add和tostring:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
t_table = setmetatable({1, 2, 3}, {__add =
function(t_table, data)
t_table[#t_table + 1] = data
return t_table
end,
__tostring =
function(t_table)
local res = ""
for _, v in pairs(t_table) do
res = res .. v
end
return res
end
})
print(t_table)
print(t_table + 4)
--[[
输出:
123
1234
--]]

需要注意的是,关系运算符,比较的两端,元表必须相同。如果只有一方有元表,另一方没有,又或者是两方拥有不同的元表,会导致比较报错:

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
a_metatable =
{
__gt = function(a_table, b_table)
return a_table[1] > b_table[1]
end,
__lt = function(a_table, b_table)
return a_table[1] < b_table[1]
end
}
b_metatable =
{
__gt = function(a_table, b_table)
return b_table[1] > a_table[1]
end,
__lt = function(a_table, b_table)
return b_table[1] < a_table[1]
end
}
a_table = setmetatable({0}, a_metatable)
b_table = setmetatable({1}, b_metatable)
print(a_table<b_table)
--[[
报错内容是:
attempt to compare two table values
--]]

重写取反操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a_metatable =
{
__unm = function(a_table)
local _temp_table = {}
for i = #a_table, 1, -1 do
_temp_table[#a_table + 1 - i] = a_table[i]
end
return _temp_table
end
}
p_metatable =
{
__tostring =
function(a_table)
local res = ""
for _, v in pairs(a_table) do
res = res .. v
end
return res
end
}
a_table = setmetatable({1, 2, 3}, a_metatable)
b_table = setmetatable(-a_table, p_metatable)
print(b_table)

__call比较有意思,可以让table可以变得和函数一样可以执行。因此可以写成如下的极其过分的形式:

1
2
3
4
5
6
7
8
9
10
function_content =
{
__call = function(a_table, a_data)
print(a_data)
end
}


func_print = setmetatable({}, function_content)
b = func_print(4)

协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function sleep(n)
local t0 = os.clock()
while os.clock() - t0 <= n do end
end
-- 这里的sleep会占用大量资源 正常逻辑不能使用

test_coroutine = coroutine.create(
function()
for i = 1, 10, 1 do
print(i)
sleep(0.1)
coroutine.yield()
end
end
)

function do_cro(co)
while(coroutine.status(co) ~="dead") do
coroutine.resume(co)
end
end

do_cro(test_coroutine)

面向对象

用table实现了类与继承

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
-- 类
-- 类管理器
CClassManager = {}
function CClassManager.ctor(cls, ...)
local this = {}
setmetatable(this, cls)
cls.__index = cls -- 将元表的__index设为自身,访问表的属性不存在时会搜索元表
cls.init(this, ...)
return this
end

function CClassManager.VInit(self, ...)
end

function CClassManager.init(self, ...)
self:VInit(...)
end

function CClassManager.dtor(cls)
-- do sth
end


-- 通过继承实现的类的定义
ClassT = CClassManager:ctor()
function ClassT.VInit(self, word)
self.word = word
end

function ClassT.say(self)
print(self.word)
end

test = ClassT:ctor("B")
test:say()
test:dtor()
test:say()

使用贝塞尔曲线实现道具随机飞动效果

作者 Anqi Zhao
2020年12月13日 05:03

工作中,遇到了一个需求是要实现获得道具和货币的飞动效果:

  1. 根据道具的多少生成不同数目的道具
  2. 货币首先要有一个炸开的效果,然后向一个点汇集;道具从原位置沿直线飞过去
  3. balabala

这里只讨论货币飞行路线的问题。

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。PS中的钢笔工具就是使用贝塞尔曲线来绘制矢量曲线的。

这里使用简单的,四个点确定的贝塞尔曲线来实现飞行轨迹。首先p0和p3分别是移动的起点和终点。为了模拟爆炸效果,可以通过随机选取以p0为圆心,长度为R的圆上的点,调整曲线的第一次弯折位置实现。这一点定为P1。最后,根据P1点和P0点之间的关系,进行x轴或者y轴的修正,实现曲线平滑延申到p3,这一点是p2点。

p1点(爆炸点)的获取方式:

1
2
3
4
5
private static Vector3 GetRandomPosition(Vector3 vec)
{
float sita = UnityEngine.Random.Range(-Mathf.PI, Mathf.PI);
return new Vector3(vec.x + Mathf.Cos(sita) * Screen.width / 8, vec.y + Mathf.Sin(sita) * Screen.width / 8);
}

p2点(修正点)的获取方式:

1
2
3
4
private static Vector3 GetFixedPoint(Vector3 start, Vector3 end)
{
return new Vector3(start.x + 3*(end.x - start.x)/4, start.y + 3 * (end.y - start.y) / 4 + p1.y > start.y ? Screen.height / 15 : - Screen.height / 15);
}

根据已知的四个点,实现物体延贝塞尔曲线移动的效果。

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
/// <summary>
/// 延贝塞尔曲线移动
/// </summary>
private static IEnumerator MoveBezier(GComponent gcom, float time, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
for (float t = 0; t < time; t += Time.deltaTime)
{
gcom.position = CalculateCubicBezierPoint(t / time, p0, p1, p2, p3);
yield return 0;
}
gcom.position = p3;
gcom.Dispose();
}

/// <summary>
/// 计算贝塞尔曲线点的坐标
/// </summary>
private static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;

Vector3 p = uuu * p0;
p += 3 * uu * t * p1;
p += 3 * u * tt * p2;
p += ttt * p3;

return p;
}

震惊,JS不加分号会造成错误!?

作者 Anqi Zhao
2019年12月2日 08:12

在之前的工作中,我遇到了一个奇怪的问题。明明在语法上没有问题,找同事也看了,但是程序依旧会产生奇怪的错误。最后通过一步一步断点,定为了错误位置,才找到了造成这个错误的原因———在一个不需要分号的语言中,句末不加分号居然报错了。

不卖关子了,这个错误是多返回值函数造成了对上一句值的影响。下面举个例子:

1
2
3
4
5
6
7
8
9
10
function func() {
return [1, 2]
}

let b = 0
let c = 0
let a = 3
[b, c] = func();
console.log("a is :" + a)
console.log("b is :" + b + " c is :" + c)

运行的结果将会是这样:

a is :1,2
b is :0 c is :0

很容易看到,我们函数的返回值给了上一行的a。这是因为我们的编译器将代码认为了:

1
let a = 3 ,[b, c] = func();

避免这种情况,还是要多加分号吧——虽然它并不会报错。

Linux升级Python

作者 Anqi Zhao
2019年11月28日 20:12

11月底,腾讯云搞了一波双11返场活动,我买了三年的服务器。

和买了新的电脑或者做了新系统一样,得先把生产环境搞好。

距离Python2.x停止维护大概只有5个月了吧,所以第一要务是升级Python的版本。但是yum是依赖Python2的,所以升级还是会有一些顾虑的。下面是升级的过程:

下载、解压

1
2
wget https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tgz
tar -zxvf Python-3.8.0.tgz

安装编译环境

1
2
yum install make build-essential libssl-dev zlib1g-dev libbz2-dev
yum install zlib

编译、安装

1
2
3
4
5
6
yum install -y make build-essential libssl-dev zlib1g-dev libbz2-dev
yum -y install zlib

cd Python-3.8.0
./configure
make && make install

备份与配置

1
2
mv /usr/bin/python /usr/bin/python.bak
ln -s /usr/local/bin/python3 /usr/bin/python

保证yum可用

将下面文件中的配置修改为Python2.x版本的路径。

1
2
/usr/bin/yum
/usr/libexec/urlgrabber-ext-down

安装pip

pip需要依赖setuptools,所以要先安装setuptools。

1
2
3
4
5
6
7
8
9
10
11
wget https://files.pythonhosted.org/packages/ce/1d/96320b9784b04943c924a9f1c6fa49124a1542039ce098a5f9a369227bad/setuptools-42.0.1.zip
unzip setuptools-42.0.1.zip
cd setuptools-42.0.1
python setup.py build
python setup.py install

wget https://files.pythonhosted.org/packages/ce/ea/9b445176a65ae4ba22dce1d93e4b5fe182f953df71a145f557cffaffc1bf/pip-19.3.1.tar.gz
tar -zxvf pip-19.3.1.tar.gz
cd pip-19.3.1
python setup.py build
python setup.py install

参考资料

https://www.cnblogs.com/zhangym/p/6226435.html
https://www.cnblogs.com/fjping0606/p/9156344.html
https://www.cnblogs.com/fyly/p/11112169.html

Github图床工具

作者 Anqi Zhao
2019年11月18日 02:32

这是一个上传图片到github的工具,目前还不是很成熟,不过已经可以实现压缩并上传图片的目的了,对于写博客来说已经够用了。

使用步骤

  1. 创建todo目录
  2. 创建pic目录,在github上创建一个空项目,然后pull到这里
  3. 修改配置文件,将git_url修改为上一步新建的项目
  4. 将图片放到todo文件夹下
  5. 执行工具

需要注意的是,因为图床需要一个git目录,同时代码也需要。在使用部分git管理工具时会禁止这种目录的嵌套。因此最好将代码独立运行。可以自己打包,也可以使用我打好的可执行程序。

在这里需要提一句,一般如果只想要下载github上项目的一个目录或者一个文件,可以使用svn进行下载。将文件路径中的文件名/master替换为trunk即可使用下载。

经过配置之后,以后只需要将图片放到todo目录下,执行脚本即可。

编译时需要安装以下运行库

1
2
3
pip install pyyaml
pip install gitpython
pip install pillow

更新文档

Ver.0.2 2019-11-17 提交编译的可执行程序;增加配置文件

Ver.0.1 2019-11-12 提交项目

JS使用replace()函数全部替换

作者 Anqi Zhao
2019年11月17日 06:03

在处理爬虫爬取下来的数据时,遇到了在文字中出现了经过转义的换行符,在文中显示出了\n,很影响观赏效果。因此,我对内容做了处理。

但是在刷库的过程中,我发现,我总不能一次处理完所有的数据。后来发现是JavaScript的Replace函数的问题,这个函数默认只能替换第一个匹配到的项目。如果需要处理全部的,需要使用正则表达式:

1
string.replace(/\\n/g, "\n")

除此之外,下面是一些处理爬取内容常用操作,包含了html的转义:

1
2
3
4
5
6
7
8
9
10
string.replace(/&nbsp;/g, ' ')
string.replace(/&lt;/g, '<')
string.replace(/&gt;/g, '>')
string.replace(/&amp;/g, '&')
string.replace(/&quot;/g, '"')
string.replace(/&#x3D;/g, '=')
string.replace(/\[.*?\]/g,'')
string.replace("\\n","\n")
string.replace("\\t","")
string.replace("\\r","")

JS使用Splice()函数操作数组

作者 Anqi Zhao
2019年11月17日 05:59

在js的使用过程中,有一次需要对数组进行各种操作,一时间迫使我想要去使用链表。后来通过查阅资料,总结了下面的一些方法,主要使用了splice()函数。

下面的方法主要是使用下标进行操作。如果是用值的话,可以通过indexOf()函数来获取下标。若不存在则返回-1。

值交换

1
2
3
4
function swap_arr(a_list, index1, index2) {
a_list[index1] = a_list.splice(index2, 1, a_list[index1])[0];
return a_list;
}

置顶

1
2
3
4
5
function up_arr(a_list, index){
    if(index!=0 && index!=-1){
a_list[index] = a_list.splice(index-1, 1, a_list[index])[0];
    }
}

下移

1
2
3
4
5
function down_arr(a_list, index) {
    if(index!=a_list.length-1 && index!=-1){
a_list[index] = a_list.splice(index+1, 1, a_list[index])[0];
    }
}

插入

1
2
3
4
5
6
7
8
9
10
11
function ins_arr(a_list, index, a_data) {
if(index!=-1){
if (a_list.indexOf(a_data)==-1) {
a_list.splice(index+1, 0, a_data);
return true;
}
else {
return false;
}
}
}

在顶部插入

1
2
3
function topins_arr(a_list, a_data) {
a_list.splice(0, 0, a_data)
}

删除元素

1
2
3
4
5
6
7
8
function del_arr(a_list, a_data_list) {
for (const ele of a_data_list) {
let index = a_list.indexOf(ele);
if(index!=-1){
a_list.splice(index, 1);
}
}
}

当你的程序连接Mysql然后崩溃时

作者 Anqi Zhao
2019年11月17日 05:43

之前写过一个监控mysql数据库更新状态的预警程序,总是莫名其妙的报一个连接错误的错,然后程序死掉。后来在系统趋于稳定之后,我就没再继续维护这个工具了。

但是最近我在写另一个工具时,遇到了一个奇怪的问题,就是:tick总在27000多左右的时候崩溃。

我进行了一系列的猜测,比如tick的代码,或者是逻辑有问题,最后我把思路放在了之前遇到的这个错误上。查阅资料后发现,MySQL数据库在连接之后,如果超过一个设定的时间戳之后,会断开。这个值叫WAIT_TIMEOUT,默认值是28800,也就是说如果连上MySQL数据库之后,8小时内没有进行操作,这个连接便会断开。

网上很多连接MySQL数据库的代码没有处理过超时连接的问题,就连JS的官方代码好像也是在17年之后才更新的。以往这个问题,大家都是通过修改这个值来进行规避的。比如改成300天。修改有两种方式,一种是修改配置文件,这样在启动时便会使用这个配置;另一种是修改这个值,或者全局,或者当次生效。

下面是我在使用JavaScript语言链接MySQL数据库时,处理超时重连问题的代码:

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
this.config = {
"host": "x.x.x.x",
"port": xxxx,
"user": "root",
"password": "pass",
"database": "name""
}

async connect() {
let self = this;
console.log("connect mysql success with", JSON.stringify(this.config))
// 创建连接
this.db_mysql = mysql.createConnection(
this.config
);
// 连接数据库
await this.db_mysql.connect();
// 错误处理
this.db_mysql.on('error', function(err) {
if (err) {
if (err.code === 'PROTOCOL_CONNECTION_LOST') {
// 处理超时
console.warning("start reconnect mysql");
self.connect();
} else {
console.error(err.stack || err);
console.warning("start reconnect mysql");
self.connect();
}
}
});
}

安卓应用闪屏

作者 Anqi Zhao
2019年11月17日 05:19

去年在接入安卓SDK时,会有部分渠道有要求手写闪屏的情况,下面是当时的笔记,这只是最简单的一种方法。

参考资料:

很好的例子:

https://www.jianshu.com/p/a609f510b19a

https://blog.csdn.net/l799069596/article/details/47094731

安卓动画:https://blog.csdn.net/IO_Field/article/details/53101499

背景:

除去游戏本身的闪屏之外,有的渠道会要求,有额外的渠道闪屏。为了使用一套资源出不同渠道包,我们可以对接渠道的AS工程进行处理,单独设置闪屏。

首先,创建一个闪屏Activity,为你的主Activity,这样在游戏的一开始你就可以看到闪屏了。

这里需要注意的是,你原先的Activit也需要在Manifest中注册打开日志,否则在打包的时候会找不到,报错:

https://blog.csdn.net/qq_28301007/article/details/52265775

1
<activity android:name="...Activity"/>

下面是主Activity,也就是闪屏Activity的代码,需要根据AS的提示import缺少的部分。

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
39
40
41
42
43
44
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.WindowManager;
import android.widget.ImageView;

public class SplashActivity extends Activity {
private final int TimeAnimDurning = 2000;
private int displayDeviceWidth;
ImageView iv_splash;
private ObjectAnimator objAnim;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
displayDeviceWidth = getResources().getDisplayMetrics().widthPixels;
setContentView(R.layout.activity_splash);
iv_splash = (ImageView) findViewById(R.id.splash);
objAnim = ObjectAnimator.ofFloat(iv_splash,"alpha",1,0);
objAnim.setDuration(TimeAnimDurning);

new Handler().postDelayed(new Runnable() {
@Override
public void run() {
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if((int)animation.getAnimatedFraction() == 1){
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
startActivity(new Intent(SplashActivity.this , .YouActivity.class));
finish();
return ;
}
}
});
objAnim.start();
}

}, 2000);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context="com.unity3d.player.SplashActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">

<ImageView
android:id="@+id/splash"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitXY"
android:src="@drawable/splash" />

</LinearLayout>
</RelativeLayout>

在上面控制闪屏格式的style.xml中,可以看到闪屏的背景色设置为了白色。这里有一些常用的颜色xml:https://blog.csdn.net/sundaysunshine/article/details/53509854

除了这两处之外,还需要根据style.xml中的配置,放好闪屏图片,设置闪屏背景。

整个闪屏的原理就是创建一个动画,在动画播放完成之后,去执行一个新的activity。在补全报错的部分之后,还是有一些细节部分需要注意的。

首先是结束时间的判定。判定时机总共有两种,一种是获取动画的进度,就像这里的例子,使用(int)animation.getAnimatedFraction()进行获取一个从0~1的数,来表示目前的动画的播放进度。除此之外还可以获取播放的时间,这个函数是:getAnimatedValue(),它可以获取属性的当前值。使用这两个函数可以很方便地控制动画的时间和动作。

除此之外,在调起另一个Activity之后,我结束了这个Activity。这是因为如果使用默认的LaunchMode,在重新唤醒应用时,闪屏会再次启动,然后走完动画,应用重启。这就造成了应用无法关闭的状况,只能后台强制杀掉。解决办法就是让闪屏只执行一次。

U3D问题总结(六) 优化

作者 Anqi Zhao
2019年10月30日 07:00

请简述GC(垃圾回收)产生的原因,并描述如何避免(?

GC回收堆上的内存
避免:1.减少new产生对象的次数
2.使用公用的对象(静态成员)
3.将String换为StringBuilder

如何优化内存?

1.压缩自带类库;
2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;
3.释放AssetBundle占用的资源;
4.降低模型的片面数,降低模型的骨骼数量,降低贴图的大小;
5.使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)。
6.代码中少产生临时变量

UNITY3d在移动设备上的一些优化资源的方法

1.使用assetbundle,实现资源分离和共享,将内存控制到200m之内,同时也可以实现资源的在线更新
2.顶点数对渲染无论是cpu还是gpu都是压力最大的贡献者,降低顶点数到8万以下,fps稳定到了30帧左右
3.只使用一盏动态光,不是用阴影,不使用光照探头
粒子系统是cpu上的大头
4.剪裁粒子系统
5.合并同时出现的粒子系统
6.自己实现轻量级的粒子系统
animator也是一个效率奇差的地方
7.把不需要跟骨骼动画和动作过渡的地方全部使用animation,控制骨骼数量在30根以下
8.animator出视野不更新
9.删除无意义的animator
10.animator的初始化很耗时(粒子上能不能尽量不用animator)
11.除主角外都不要跟骨骼运动apply root motion
12.绝对禁止掉那些不带刚体带包围盒的物体(static collider )运动
NUGI的代码效率很差,基本上runtime的时候对cpu的贡献和render不相上下
13每帧递归的计算finalalpha改为只有初始化和变动时计算
14去掉法线计算
15不要每帧计算viewsize 和windowsize
16filldrawcall时构建顶点缓存使用array.copy
17.代码剪裁:使用strip level ,使用.net2.0 subset
18.尽量减少smooth group
19.给美术定一个严格的经过科学验证的美术标准,并在U3D里面配以相应的检查工具

场景优化

1.遮挡剔除(Occlusion Culling) 不显示被遮挡住的物体
2.LOD 根据相机距离远近显示不同精细程度的模型
3.大场景可以调节相机可视距离
4.小物体可以适当隐藏掉
5.使用光照贴图 避免动态实时的进行光照计算,提高效率

UI优化

1.将同一画面图片放到同一图集中
2.图片和文字尽量不要交叉,会产生多余drawcall(相同材质和纹理的UI元素是可以合并的)
3.UI层级尽量不要重叠太多
4.取消勾选不必要的射线检测RaycastTarget
5.将动态的UI元素和静态的UI元素放在不同的Canvas中,减少canvas网格重构频率

GC优化

1.字符串使用StringBuilder而不是string,stringBuilder在创建时会自动获取一个容量存储并逐渐扩充,string每一次改变都会创建一个新的对象。
2.访问物体tag的时候尽量使用Gameobject.CompareTag(),因为访问物体的tag属性会在堆上额外的分配空间
3.使用对象池缓存大量创建的物体
4.用for代替foreach,foreach每次迭代产生24字节垃圾内存

U3D问题总结(五) 渲染与光照

作者 Anqi Zhao
2019年10月30日 07:00

什么是渲染管道(?

是指在显示器上为了显示出图像而经过的一系列必要操作。 渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。主要步骤有:
本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化

对渲染管线的理解

渲染流水线流程:
1.应用阶段(由CPU负责,输出是渲染所需要的几何信息,即渲染图元。然后发起Draw Call,进入GPU流水线)
2.几何阶段(由GPU负责,处理渲染图元,这阶段中最重要的就是把顶点坐标变换屏幕空间中,交给光栅器处理
这阶段输出的是屏幕空间中二维顶点坐标、每个顶点对应的深度值、着色等相关信息)
3.光栅化阶段(由GPU,负责这一阶段会使用上个阶段传递的数据来产生屏幕上的像素,并渲染出最终的图像)
细节过程https://zhuanlan.zhihu.com/p/97498781

什么是DrawCall?DrawCall高了又什么影响?如何降低DrawCall?

Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。DrawCall越高对显卡的消耗就越大。降低DrawCall的方法:

  1. Dynamic Batching
  2. Static Batching
  3. 高级特性Shader降级为统一的低级特性的Shader。

什么是DrawCall,有什么方法可以减少DrawCall

CPU通过调用绘制命令来告诉GPU开始进行一个渲染过程(一次DrawCall)。
CPU方面减少DrawCall:
1、使用Draw Call Batching 、Dynamic Batching动态批处理
2、纹理打包成图集减少材质使用
3、少用反光、阴影
4、设置一个合适的Fixed Timestep
5、不要使用网格碰撞器(Mesh Collider)
6、大量或频繁的字符串连接操作一定要用StringBuilder
7、某些可能情况,使用结构体代替类
8、使用对象池重复利用空间
9、尽量不要用foreach,用for
10、不要直接访问GameObjcet的tag属性
11、不要频繁使用GetComponent,访问一次后保留其引用
12、使用OnBecameVisible()和OnBecameInVisible(),控制物体update()函数的执行减少开销
13、使用内建数组,如Vector3.zero而不是new Vector3(0,0,0)
14、使用ref关键字对方法的参数进行优化
15、关闭所有update中的log操作
16、不在update中调用GetComponent、SendMessage、FindWithTag等方法
17、不在update中使用临时变量

GPU方面减少DrawCall

1、使用纹理图集代替一系列单独小贴图
2、保持材质数目尽可能少
3、如果使用纹理图集和共享材质,用Renderer.sharedMaterial代替Renderer.material
4、使用光照纹理(lightmap)而非实时灯光
5、使用LOD
6、使用mobile版的shader
7、尽可能减少顶点数、背面删减
8、压缩图片,减少显存带宽压力

什么是material,什么是shader,二者有什么关系

材质系统定义了如何渲染物件表面信息。shader里面使用材质信息加自身操作,最终呈现物体渲染。shader是material一部分,是根据计算即时演算生成贴图的程序,叫着色器。常用处理无法用固定贴图表现的模型。material是模型的材质,包含贴图、shader、顶点、凹凸等信息。

如何在Unity3D中查看场景的面数,顶点数和Draw Call数?如何降低Draw Call数

在Game视图右上角点击Stats。降低Draw Call 的技术是Draw Call Batching
这个在5.0以后在window-》Profiler下面,快捷键是cmd + 7(ctl + 7

DrawCall和SetPass Call

DrawCall:meshes网格绘制应用批处理后的总数。请注意,在多次呈现对象(例如,由像素灯照明的对象),每个在一个单独的渲染结果绘制调用。

SetPass Call:渲染改变( passes)次数。每个改变 需要Unity运行时绑定一个新的渲染器(shader),它可能会引入 CPU 开销。

Unity3D Shader分哪几种,有什么区别

表面着色器的抽象层次比较高,它可以轻松地以简洁方式实现复杂着色。表面着色器可同时在前向渲染及延迟渲染模式下正常工作。
顶点片段着色器可以非常灵活地实现需要的效果,但是需要编写更多的代码,并且很难与Unity的渲染管线完美集成。
固定功能管线着色器可以作为前两种着色器的备用选择,当硬件无法运行那些酷炫Shader的时,还可以通过固定功能管线着色器来绘制出一些基本的内容。

有A和B两组物体,有什么办法能够保证A组物体永远比B组物体先渲染?

把A组物体的渲染队列大于B物体的渲染队列,通过shader里面的渲染队列来渲染

问一个Terrain,分别贴3张,4张,5张地表贴图,渲染速度有什么区别?为什么?

没有区别,因为不管几张贴图只渲染一次。

LOD是什么,优缺点(?

LOD(Level of detail)多层次细节,是最常用的游戏优化技术。它按照模型的位置和重要程度决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。缺点是增加了内存。

MipMap是什么,作用(?

MipMapping:在三维计算机图形的贴图渲染中有常用的技术,为加快渲染进度和减少图像锯齿,贴图被处理成由一系列被预先计算和优化过的图片组成的文件,这样的贴图被称为MipMap。

什么是LightMap

LightMap:就是指在三维软件里实现打好光,然后渲染把场景各表面的光照输出到贴图上,最后又通过引擎贴到场景上,这样就使物体有了光照的感觉。

alpha blend工作原理

Alpha Blend 实现透明效果,不过只能针对某块区域进行alpha操作,透明度可设。

alpha blend 用于做半透明效果。Color = (源颜色 源系数) OP ( 目标颜色 目标系数);其中OP(混合方式)有加,减,反减,取最小,取最大

Unity的Shader中,Blend SrcAlpha OneMinusSrcAlpha这句话是什么意思

作用就是Alpha混合。公式:最终颜色 = 源颜色 x 源透明值 + 目标颜色 x(1 - 源透明值)

alpha test在何时使用?能达到什么效果

Alpha Test ,中文就是透明度测试。简而言之就是V&F shader中最后fragment函数输出的该点颜色值(即上一讲frag的输出half4)的alpha值与固定值进行比较。AlphaTest语句通常于Pass{}中的起始位置。Alpha Test产生的效果也很极端,要么完全透明,即看不到,要么完全不透明。

Vertex Shader是什么,怎么计算

顶点着色器是一段执行在GPU上的程序,用来取代fixed pipeline中的transformation和lighting,Vertex Shader主要操作顶点。
Vertex Shader对输入顶点完成了从local space到homogeneous space(齐次空间)的变换过程,homogeneous space即projection space的下一个space。在这其间共有world transformation, view transformation和projection transformation及lighting几个过程。

写出光照计算中的diffuse(漫反射)的计算公式

diffuse = Kd x colorLight x max(N*L,0);
Kd 漫反射系数、colorLight 光的颜色、N 单位法线向量、L 由点指向光源的单位向量、其中N与L点乘,如果结果小于等于0,则漫反射为0。

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
答:漫反射光(diffuse)计算公式为:Idiffuse = Dintensity*Dcolor*N.L ; (Dintensity表示漫反射强度,Dcolor表示漫反射光颜色,N为该点的法向量,L为光源向量)

其他,3D渲染中,物体表面的光照计算公式为:

I = 环境光(Iambient) + 漫反射光(Idiffuse) + 镜面高光(Ispecular);

其中,环境光(ambient)计算公式为:

Iambient= Aintensity* Acolor; (Aintensity表示环境光强度,Acolor表示环境光颜色)

漫反射光(diffuse)计算公式为:

Idiffuse = Dintensity*Dcolor*N.L ; (Dintensity表示漫反射强度,Dcolor表示漫反射光颜色,N为该点的法向量,L为光源向量)

镜面光照(specular)计算公式为:

Ispecular = Sintensity*Scolor*(R.V)n; (Sintensity表示镜面光照强度,Scolor表示镜面光颜色,R为光的反射向量,V为观察者向量)

综上所得:整个光照公式为:

I = Aintensity* Acolor+ Dintensity*Dcolor*N.L + Sintensity*Scolor*(R.V)n ;

将一些值合并,并使用白色作为光照颜色,则上述公式可简化为:

I = A + D*N.L + (R.V)n

MeshRender中material和sharedmaterial的区别(?

修改sharedMaterial将改变所有物体使用这个材质的外观,并且也改变储存在工程里的材质设置。不推荐修改由sharedMaterial返回的材质。如果你想修改渲染器的材质,使用material替代。

简述水面倒影的渲染原理

原理就是对水面的贴图纹理进行扰动,以产生波光玲玲的效果。用shader可以通过GPU在像素级别作扰动,效果细腻,需要的顶点少,速度快

什么叫动态合批?跟静态合批有什么区别

如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的,并不需要你进行额外的操作。
区别:动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。

两种阴影判断的方法、工作原理。

本影:景物表面上那些没有被光源直接照射的区域(全黑的轮廓分明的区域)。
半影:景物表面上那些被某些特定光源直接照射但并非被所有特定光源直接照射的区域(半明半暗区域)
工作原理:从光源处向物体的所有可见面投射光线,将这些面投影到场景中得到投影面,再将这些投影面与场景中的其他平面求交得出阴影多边形,保存这些阴影多边形信息,然后再按视点位置对场景进行相应处理得到所要求的视图(利用空间换时间,每次只需依据视点位置进行一次阴影计算即可,省去了一次消隐过程)

Unity提供了几种光源,分别是什么

四种。
平行光:Directional Light
点光源:Point Light
聚光灯:Spot Light
区域光源:Area Light

实时点光源的优缺点是什么

可以有cookies – 带有 alpha通道的立方图(Cubemap )纹理。点光源是最耗费资源的。

GPU的工作原理

简而言之,GPU的图形(处理)流水线完成如下的工作:(并不一定是按照如下顺序) 顶点处理:这阶段GPU读取描述3D图形外观的顶点数据并根据顶点数据确定3D图形的形状及位置关系,建立起3D图形的骨架。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Vertex Shader(定点着色器)完成。 光栅化计算:显示器实际显示的图像是由像素组成的,我们需要将上面生成的图形上的点和线通过一定的算法转换到相应的像素点。把一个矢量图形转换为一系列像素点的过程就称为光栅化。例如,一条数学表示的斜线段,最终被转化成阶梯状的连续像素点。 纹理帖图:顶点单元生成的多边形只构成了3D物体的轮廓,而纹理映射(texture mapping)工作完成对多变形表面的帖图,通俗的说,就是将多边形的表面贴上相应的图片,从而生成“真实”的图形。TMU(Texture mapping unit)即是用来完成此项工作。 像素处理:这阶段(在对每个像素进行光栅化处理期间)GPU完成对像素的计算和处理,从而确定每个像素的最终属性。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Pixel Shader(像素着色器)完成。 最终输出:由ROP(光栅化引擎)最终完成像素的输出,1帧渲染完毕后,被送到显存帧缓冲区。
总结:GPU的工作通俗的来说就是完成3D图形的生成,将图形映射到相应的像素点上,对每个像素进行计算确定最终颜色并完成输出。

图形学

光照模型有哪些

3维模型组成

Mesh

Mesh下面有哪些字段

如向将文理贴在模型上

图片向格式有那些

U3D问题总结(四) 物理相关

作者 Anqi Zhao
2019年10月30日 07:00

射线检测碰撞物的原理是

射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射 。

Unity3d中的碰撞器和触发器的区别?

碰撞器是触发器的载体,触发器是碰撞器的属性
Is Trigger=false,碰撞器根据物理引擎引发碰撞,产生碰撞的效果
此时调用OnCollisionEnter/Stay/Exit函数
Is Trigger=true,碰撞器被物理引擎所忽略,没有碰撞效果
此时调用OnTriggerEnter/Stay/Exit函数

发生碰撞的必要条件

两个物体都必须带有碰撞器(Collider)
其中至少有一个物体带有刚体(Rigidbody)或者角色控制器(CharacController)
必须是运动的物体带有Rigidbody脚本才能检测到碰撞

物体发生碰撞的过程有几个阶段

1.OnCollisionEnter
2.OnCollisionStay
3.OnCollisionExit

当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?

穿透(碰撞检测失败)
1)增大细小物体的碰撞体(不建议这样做)
(2)使用射线检测,检测他们之间的距离
(3)FixedUpdate频率修改,可以physics time减小(同样不建议)
(4)改变物体的速度(废话)
(5)将检测方式改为连续检测,rigifdbody.collisionDetectionMode =CollisionDetectionMode.Continuous;
或者是动态连续检测(CollisionDetectionMode.ContinuousDynamic)
(6)代码限制,加大计算量 提前计算好下一个位置

Unity3d物理引擎中,有几种施加力的方式(?

rigidbody.AddForce/AddForceAtPosition,都在rigidbody系列函数中

CharacterController和Rigdibody的区别

Rigidbody:刚体组件、用于模拟真实的物理效果、可以受到重力和其他力的作用、这个力可以直接施加、也可以来自其他刚体的碰撞
CharacterController:角色控制组件,它自带一个胶囊控制器,能够受到重力的影响。移动时使用自身的Move()、SimpleMove()方法

Rigidbody具有完全真实物理的特性,Unity中物理系统最基本的一个组件,包含了常用的物理特性,而CharacterController可以说是受限的的Rigidbody,具有一定的物理效果但不是完全真实的,是Unity为了使开发者能方便的开发第一人称视角的游戏而封装的一个组件

什么叫做链条关节(?

Hinge Joint,可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。

❌
❌