本文介绍如何用Picgo+阿里oss搭建图床

可用于个人Markdown文档的图片外链

oss图床优点:速度快,安全;缺点:流量大的情况下收费很夸张

阿里oss

  1. 登录注册阿里云账户,选择对象存储oss服务

  1. 右侧创建新的创建Bucket,这个就是存储的仓库

  1. 各种参数根据需求设置,需要注意的是读写权限必须选择公共读,不然无法访问;地区选择离自己最近的地区

  1. 选择右上角自己头像,选择AccessKey管理

  1. 创建用户

  1. 选择权限管理-> 添加权限->选择并添加AliyunOSSFullAccess

  1. 创建AccessKey,记得保存AccessKeySecret,下次登录就看不到了,不过即使忘记了还可以继续创建新的AccessKey

到此oss端的操作已经完成

Picgo设置

  • KeyId:AccessKey
  • KeySecret:AccessKeySecret
  • 设定存储空间名:Bucket名称
  • 存储地区:Bucket的地区名称
  • 存储路径:Bucket内的文件夹路径

本文介绍如何用Picgo+GitHub搭建免费的图床

可用于个人Markdown文档的图片外链

Github图床优点:免费,安全;缺点:网络不稳定,偶尔上传失败

关于Picgo

这是一款图片上传的工具,目前支持七牛图床腾讯云阿里ossGitHub等图床,未来将支持更多图床。

所以解决问题的思路就是,将本地的文件,或者剪切板上面的截图发送图床,然后生成在线图片的链接,这样就可以随时随地使用图片外链了

准备工作

GitHub流程

  1. 新建一个Repository,建议命名成img之类的方便记忆

  2. 生成一个Token用于操作GitHub repository,流程如下

    Settings->Developer settings->Personnal access tokens->Generate new token

    注:创建成功后,会生成一串token,这串token之后不会再显示,所以第一次看到的时候,就要好好保存

配置Picgo

  • 仓库名:即你github仓库的名称
  • 设定分支:对应仓库的分支,默认是master
  • 设定Token:之前保存的Token
  • 指定存储路径:保存到仓库的路径
  • 设定自定义域名:见如下
自定义域名

默认不指定域名,这样生成的是github地址,部分情况会被墙掉,对于图片访问不是特别友善

有自己域名的同学强烈建议使用自定义域名

本文主要介绍Git软件和Github在Windows平台的简单使用方式

安装Git

安装包可以在官网下载,选择对应版本无脑下一步安装

初次设置

安装完成后,邮件菜单即可调用Git命令,点击进入后需要初始化账户名和邮箱

1
2
git config --global user.name "Your Name"
git config --global user.email "email@example.com"
生成SSH Key
1
ssh-keygen -t rsa -C "youremail@example.com"
  • 一路Enter键后会需要设置自己的账户和密码,注意正确输入
  • 然后在C:\Users\你的用户名\.ssh路径下可以找到如下3个文件

  • 后缀为pub的文件,这个是公钥,在后续会用的到,也可以放心的告诉别人
  • 后缀为rsa的是私钥,不要泄露出去
关于SSHKey

GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。

当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。

注册GitHub账户

  1. github官网自行注册并登录
  2. 右键点击右上角头像边的箭头,选择setting进入

  1. 找到左侧的SSH and GPG keys,点击NewSSHKey

  1. 找到之前生成的公钥文件,用记事本打开复制里面全部内容,然后粘贴到如下图key 内,点击AddSSHKey完成,

使用Git

创建本地仓库
  • 在桌面直接右键进入git
1
2
3
4
cd E:/     --进入E盘
mkdir GitTest --创建文件夹GitTest
cd ./GitTest --进入GitTest文件夹
git init --初始化

如上述代码所示,完成在E盘新建文件夹然后初始化的过程,其中前三步非每次必须,可以直接手动创建文件夹,然后通过git init初始化

提交本地文件
1
vim test.txt --新建文件

按键盘Insert键进入编辑模式,输入hello,然后按顺序ESC,:,wq退出

上述步骤完成了创建文件,写入退出的过程,到此生成了文件和内容如下图

对,一顿操作猛如虎,回头一想就是Linux那一套☠

  • 上述步骤完全可以自行创建和编辑内容,然后我们开始提交
1
2
git add test.txt //提交text.txt文件到待提交列表
git commit -m "第一次提交" //正式提交到仓库

至此我们已经提交到了本地仓库,可以通过命令git log 查询日志找到记录

添加远程仓库

到这一步我们得先创建一个远程仓库,来到我们github,通过Your respositories>New创建新的仓库,这里我们创建了名为Test 的仓库

  • 找到如下图所示,复制地址信息(推荐使用SSH,使用HTTPS经常需要输入账户)

然后运行git命令

1
git remote add origin "你的地址" //添加远程仓库地址
提交/拉取远程仓库
1
git add push origin -u master //master为当前分支,默认一般就是master,可以自行修改,下同

到这里就提交到了远程仓库,如下图所示

1
git pull origin master 

从远程仓库拉取master分支的内容

取消关联远程仓库
1
git remote rm origin
新建分支
1
2
3
4
5
6
7
8
9
10
11
// -b是base的意思,就是以当前分支为基础创建一个新分支 
git branch ${new_branch_name} -b
// 切换分支
git checkout ${new_branch_name}

// 也可以使用git checkout命令加上-b参数表示创建并切换
git checkout -b ${new_branch_name}

//相当于以下两条命令:
git branch ${new_branch_name}
git checkout ${new_branch_name}
Checkout的用法

checkout有三种用途:切换分支、移除修改、从历史版本中签出,后面会写到

1
2
3
4
5
//从远程的origin/level为基础创建本地分支
git checkout -b level origin/level
//上面的命令等同于下面两条
git checkout -b origin/level
git branch -m level
推送本地当前分支到远程分支
1
2
// 若远程不存在${new_branch_name}分支则会自动创建
git push ${remote_repo_name} ${new_branch_name}
合并分支
1
2
3
4
5
// 首先需要切换回需要合并到的分支,比如将dev分支合并到master分支
git branch master
// 将dev分支合并到当前分支
git merge dev
// 合并完毕后就可以删除dev分支
取消合并

如果本地与远程分支(或本地分支)有冲突可以取消合并,使本地分支回到合并之前的状态。

1
git merge --abort
解决冲突

在使用git协作开发的时候,冲突是经常会遇到的问题,也是对于新手来说非常棘手的问题

  • 首先我们要在本地处理好自己的文件
撤销修改
1
2
3
4
git checkout -- test.txt  //撤销对test.txt文件的修改
git checkout -- . //撤销本次所有文件的修改
git reset -- test.txt //如果已经add了test文件,需要先使用本条命令退出add状态
git reset -- . //撤销所有文件的add状态
解决拉取冲突

如果在拉取以后发生冲突了,在拉取日志里就有报错,通过git status命令也可以看到有红色部分,通过下列命令解决

如果我们要保留远程分支的test.txt可以执行以下命令:

1
2
// 保留远程版本的test.txt
$ git checkout --theirs test.txt

而保留本地分支的文件命令为:

1
$ git checkout --ours test.txt

然后再此执行如下命令,注意这会再一次生成提交记录

1
2
3
4
$ git add test.txt
//假如上面执行的是 --theirs
$ git commit -m "合并冲突,保留远程版本"

此时本地就与远程合并了,并且合并时保持了远程的文件版本。

回退版本

如果想要取消本地的某次提交${123456}的更改可以使用回退到历史版本,然后使用:

1
2
3
4
5
git reset --hard 123455
//将123456提交的文件签出到123455的提交中去
//相当于直接在123455的基础上做了123456中的文件修改
$ git checkout 123456 .
//然后在reset/checkout不想提交的文件即可
删除分支
1
2
3
4
5
6
7
8
// 删除本地分支
// 注意:删除分支之前要合并分支,不然会报错(或者使用-D强制删除)
// error: The branch 'dev' is not fully merged.
// If you are sure you want to delete it, run 'git branch -D dev'.
git branch -D dev

// 删除远程分支
git push ${repo_name} --delete ${remote_branch_name}
版本回退
1
2
3
git log --pretty=oneline  //查看所有提交记录
git reset --hard "版本号" //回退到某个版本,版本号为查询记录里黄色部分
git reset "版本号" "filename" //某个文件回退到某个版本
克隆远程仓库
1
2
3
4
// clone仓库所有分支
git clone "仓库地址"
// 只clone一个分支可以使用-b指定分支名字
git clone -b "分支名称" "仓库地址"
切换到远程分支

远程仓库 git clone 下来后,执行 git branch,只能看到

1
* master

并不会看到其他分支,即便远程仓库上有其他分支。

可以使用git branch -va首先列出本地/远程分支列表:

通过git checkout remotes/origin/master切换到远程分支

再进行如下操作

1
git checkout -b dev

最后得到如下结果

然后在本地修改dev分支的内容就可以提交到远程dev分支(此案例远程没有dev分支)

忽略不想提交的文件

有时我们不想要把所有的东西都提交到仓库中(比如一些编译生成的二进制文件),可以使用.gitignore来排除不需要提交的文件。
在Windows下我们不能够直接创建这个文件,因为是.开头没有文件名,所以需要在GitBash下使用命令创建:

1
touch .gitignore //创建文件

然后用记事本打开编辑里面的内容并保存

1
2
3
4
5
6
7
8
9
//排除当前目录下的public文件夹
public/
//排除所有的.exe文件
*.exe
//排除所有的txt文件
*.txt
Intermediate //排除Intermediate文件的提交
Saved
Content/Test/

其中在上述最后3条对于UE4的开发比较常用

关于忽略文件的更多内容,可以查看此文档

比较不同版本的差异

我们继续在test.txt文件加入一行world,然后运行

1
git diff

得到如下

其他命令

1
2
3
4
5
6
7
8
9
10
//查看尚未暂存的某个文件更新了哪些
$ git diff filename
//查看已经暂存起来的文件和上次提交的版本之间的差异
$ git diff –cached
//查看已经暂存起来的某个文件和上次提交的版本之间的差异
$ git diff –cached filename
//查看某两个版本之间的差异
git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9 b8e7b00c02b95b320f14b625663fdecf2d63e74c
//查看某两个版本的某个文件之间的差异
git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9:filename b8e7b00c02b95b320f14b625663fdecf2d63e74c:filename

两个分支的差异

1
2
3
git diff topic master (1)
git diff topic..master (2)
git diff topic...master (3)

用法1,直接跟两个使用空格分隔的分支名会直接将两个分支上最新的提交做diff,相当于diff了两个commit。
用法2,用两个点号分隔的分支名,作用同用法1(装酷利器)
用法3,用三个点号分隔的分支名会输出自topic与master分别开发以来,master分支上的change。
需要注意的是这里的..和…不能与git rev-list中的..和…混淆。

错误处理

如果使用git终端时出现以下错误:

1
2
3
git status
error: bad signature
fatal: index file corrupt

这是由于索引损坏造成的,可以通过下面的方式来处理:

1
2
rm -f .git/index
git reset
关闭Git的SSL验证
1
git config --global http.sslVerify false
私钥权限过于开放报错

使用git出现如下报错

1
2
3
4
5
6
7
8
9
10
11
12
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/root/.ssh/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/root/.ssh/id_rsa": bad permissions
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

这是提示你的Git私钥读写权限过于开放会有安全问题

解决方法:对文件.ssh/id_rsa点右键属性-安全/高级,禁用继承,将文件所有者更改为你自己的账户,然后添加你自己的账户和SYSTEM完整的控制权,保存即可。

Git更新远程分支列表
1
git remote update origin --prune
submodule

如果再一个git仓库中又用到其他git仓库的,就可以使用git module来处理这个包含关系。

如果直接在git仓库A中把另一个仓库B clone下载作为A的子目录,在git add时会有下列类似的提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint: git submodule add <url> Plugins/VaRest
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint: git rm --cached Plugins/VaRest
hint:
hint: See "git help submodule" for more information.

目的就是告诉你你包含了另一个git的仓库。

正确的处理办法是,先把之前clone的B删除,然后使用git submodule来clone B:

1
2
3
4
5
6
7
8
9
10
11
$ git submodule add git@github.com:VJien/img.git
Cloning into 'E:/GitTest/img'...
remote: Enumerating objects: 123, done.
remote: Counting objects: 100% (123/123), done.
remote: Compressing objects: 100% (118/118), done.
remote: Total 123 (delta 39), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (123/123), 4.05 MiB | 466.00 KiB/s, done.
Resolving deltas: 100% (39/39), done.
warning: LF will be replaced by CRLF in .gitmodules.
The file will have its original line endings in your working directory

会把B clone下来,然后进到B的目录里面,将版本记录改为你想要的commit,然后再add/commit即可。

1
2
3
4
5
6
git status
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: Plugins/VaRest (new commits)

然后再push到远程仓库即可,在远程仓库中,A项目中的B目录就不是直接上传的文件,而是连接到真正的B的源仓库。

初始更新子模块
1
git submodule update --init --recursive
后续更新子模块
1
2
3
git submodule foreach git fetch
//或
git submoudule update --remote
删除子模块

删除一个以添加的子模块需要有以下几个步骤:

  1. 删除子模块的目录
  2. 删除.gitmodules中的关于该子模块的信息
  3. 删除.git/config中关于该子模块的信息
  4. 删除.git/modules下该子模块的目录

执行完上面几步操作之后就可以了,但是如果有报错问题可以删掉缓存:

1
git rm --cached 子模块名称
创建本地空仓库

个命令是创建一个空仓库,可以在其他的项目中将该仓库添加为远程仓库。

1
git --bare init

部分内容参考至本人好友查理同学的☛博客

2019年4月18日在steam发布了个人一款独立动作游戏《EndlessHell》,本文将借此记录发布steam游戏的流程

游戏链接

UE4版本4.21

SteamWork入门指南

文中内容前半部分为提交当时编辑,搬运至博客后更新后续部分

配置

SteamDLL

复制steam程序中的dll文件至本地引擎文件内*\UE_4.21\Engine\Binaries\ThirdParty\Steamworks\Steamv139

如下图所示

最终效果如下

Engine.ini

添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

[OnlineSubsystem]
DefaultPlatformService=Steam

[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480//测试ID,正式包用自己的ApplicationID
Achievement_0_Id=ACH_WIN_ONE_GAME //添加成就,正式包用自己的成就
[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"

UE4配置

开启子系统,如下图

至此进入游戏后按shift+tab可以唤出steam窗口,记住需要使用==独立窗口模式运行==

  • 如需使用steamapi,在target.cs中开启如下选项
1
bUsesSteam = true;
  • 同时在build.cs中如下模块
1
2
3
4
5
6
7
PublicDependencyModuleNames.AddRange(new string[] {
"OnlineSubsystem",
"OnlineSubsystemUtils",
"Steamworks"
});

DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam");

SteamWork

  • 首先需要注册一个stemawork账号
  • 发布steam游戏需要申请一个ID,本作ID是1055000(此数字+1为depot的ID),以下用的此数字均为游戏ID,另外需要一次性付费100美刀(当时价格),当销售额度达到1000美刀后返还
  • 另外需要提供个人银行账户,每月月初当上个月份税后金额达到100刀以上会打款到账户
  • 在进入下面之前需要先下载steamSDK,如steamwork首页右下角位置,如图

应用管理

应用程序

通用

设置正确的游戏名字和平台

steam输入

提供的输入方式,如手柄,xbox等

SteamPipe

提交版本

这个是版本生成器,用于提交版本和选择当前在商店下载的游戏版本

提交版本可以使用SDK包中的工具提交,在开始提交之前需要设置如下内容

  1. 修改 sdk\tools\ContentBuilder\scripts\app_build_*.vdf文件为app_build_1055000.vdf,同时修改内容为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"appbuild"
{
"appid" "1055000"
"desc" "Your build description here" // description for this build
"buildoutput" "..\output\" // build output folder for .log, .csm & .csd files, relative to location of this file
"contentroot" "..\content\" // root content folder, relative to location of this file
"setlive" "" // branch to set live after successful build, non if empty
"preview" "0" // to enable preview builds
"local" "" // set to flie path of local content server

"depots"
{
"1055001" "depot_build_1055001.vdf"
}
}

必须修改的是appid和depots的内容, 对应的ID修改成自己的

  1. depot_build_1055001.vdf文件同样修改内容为
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
"DepotBuildConfig"
{
// Set your assigned depot ID here
"DepotID" "1055001"

// Set a root for all content.
// All relative paths specified below (LocalPath in FileMapping entries, and FileExclusion paths)
// will be resolved relative to this root.
// If you don't define ContentRoot, then it will be assumed to be
// the location of this script file, which probably isn't what you want
"ContentRoot" "E:\Download\steamworks_sdk_144\sdk\tools\ContentBuilder\content"

// include all files recursivley
"FileMapping"
{
// This can be a full path, or a path relative to ContentRoot
"LocalPath" "*"

// This is a path relative to the install folder of your game
"DepotPath" "."

// If LocalPath contains wildcards, setting this means that all
// matching files within subdirectories of LocalPath will also
// be included.
"recursive" "1"
}

// but exclude all symbol files
// This can be a full path, or a path relative to ContentRoot
"FileExclusion" "*.pdb"
}

主要修改depotsid和content的路径

  1. 运行 \sdk\tools\ContentBuilder\builder\steamcmd.exe准备提交内容

  2. ```\sdk\tools\ContentBuilder\content``内是游戏包的内容,请确保exe启动名字与==[安装>通用安装>启动选项>可执行文件]==名字一样

  3. 更新>登录>运行如下脚本提交

    1
    run_app_build E:\Download\steamworks_sdk_144\sdk\tools\ContentBuilder\scripts\app_build_1055000.vdf

    路径为app_build文件的路径

  4. 提交后看到如下图的新的分支,修改default为当前展示和下载的分支

depot设置

此页面比较简单,需要正确设置程序支持的语言平台

安装

通用安装
安装文件夹

下载安装以后的文件夹名字

启动选项
  • 可执行文件:启动程序,一般就是引擎打包以后那个exe文件
  • 其他正确设置就可以
可再发行文件

Steam 可以自动安装您的许多常见可再发行文件

客户端图像

根据要求规格上传各种icon文件

统计与成就

统计

设置统计的类型和名称

成就

设置名称、描述和icon

本地化

成就名称和描述的本地化,可以下载文档再提交更新

管理序列号

申请序列号,注意其中不同的序列号的用途

在首页有下载选项

发布

所有修改完后都需要进入发布选项进行发布到商店,系统会提示差异

成就API

  • 很容易被忘记的一点,ini文件利必须设置与网站成就对应的字符串,如下图

  • 蓝图api

读写成就之前必须先缓存成功

目前版本对于成就进度条的读取和设置只有0或者1,所以只要写入的参数大于0成就即完成,不知后续更新能不能完善

蓝图方法DrawDebugLine(以及其他图形)的方法只能在开发者模式中使用

下文为了得到发布版的DrawLine之类的功能

LineBatchComponent

  • 在UWorld中看到如下声明
1
2
3
4
5
6
7
8
9
10
11
/** Line Batchers. All lines to be drawn in the world. */
UPROPERTY(Transient)
class ULineBatchComponent* LineBatcher;

/** Persistent Line Batchers. They don't get flushed every frame. */
UPROPERTY(Transient)
class ULineBatchComponent* PersistentLineBatcher;

/** Foreground Line Batchers. This can't be Persistent. */
UPROPERTY(Transient)
class ULineBatchComponent* ForegroundLineBatcher;
  • 然后创建并注册组件
1
2
3
4
5
6
7
8
9
10
11
12
*****
if(!LineBatcher)
{
LineBatcher = NewObject<ULineBatchComponent>();
LineBatcher->bCalculateAccurateBounds = false;
}

if(!LineBatcher->IsRegistered())
{
LineBatcher->RegisterComponentWithWorld(this);
}
****
  • 再到组件LineBatchComponent.h

  • 先关注组件内部申明的类FLineBatcherSceneProxy : public FPrimitiveSceneProxy

    • 这个代理继承自FPrimitiveSceneProxy,这个类可以得到重要的FPrimitiveDrawInterface,通过这个类可以画出需要的图形(其他各类方法基本都是通过PDI来画出图形),代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//重写的方法,不需要自己调用
void FLineBatcherSceneProxy::GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const
{
QUICK_SCOPE_CYCLE_COUNTER( STAT_LineBatcherSceneProxy_GetDynamicMeshElements );

for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
if (VisibilityMap & (1 << ViewIndex))
{const FSceneView* View = Views[ViewIndex];
//得到PDI
FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);

for (int32 i = 0; i < Lines.Num(); i++)
{
//通过PDI画线,Lines是TArray<FBatchedLine>私有成员
PDI->DrawLine(Lines[i].Start, Lines[i].End, Lines[i].Color, Lines[i].DepthPriority, Lines[i].Thickness);
}

for (int32 i = 0; i < Points.Num(); i++)
{
//同理画点
PDI->DrawPoint(Points[i].Position, Points[i].Color, Points[i].PointSize, Points[i].DepthPriority);
}
  • 代理构造的时候提供ULineBatchComponent
  • 目的是把ULineBatchComponent的数据传给此代理
1
2
3
4
5
6
FLineBatcherSceneProxy::FLineBatcherSceneProxy(const ULineBatchComponent* InComponent) :
FPrimitiveSceneProxy(InComponent), Lines(InComponent->BatchedLines),
Points(InComponent->BatchedPoints), Meshes(InComponent->BatchedMeshes)
{
bWillEverBeLit = false;
}
  • 下面到ULineBatchComponent
  • ::DrawLine方法把FBatchedLine添加到数组,然后通过Tick递减时间并删除
1
2
3
4
5
6
7
8
void ULineBatchComponent::DrawLine(const FVector& Start, const FVector& End, const FLinearColor& Color, uint8 DepthPriority, const float Thickness, const float LifeTime)
{
//构造一个FBatchedLine成员
new(BatchedLines) FBatchedLine(Start, End, Color, LifeTime, Thickness, DepthPriority);

// LineBatcher and PersistentLineBatcher components will be updated at the end of UWorld::Tick
MarkRenderStateDirty();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void ULineBatchComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
bool bDirty = false;
// Update the life time of batched lines, removing the lines which have expired.
for(int32 LineIndex=0; LineIndex < BatchedLines.Num(); LineIndex++)
{
FBatchedLine& Line = BatchedLines[LineIndex];
if (Line.RemainingLifeTime > 0.0f)
{
Line.RemainingLifeTime -= DeltaTime;
if(Line.RemainingLifeTime <= 0.0f)
{
// The line has expired, remove it.
BatchedLines.RemoveAtSwap(LineIndex--);
bDirty = true;
}
}
}
*************************

DrawDebug版本

通过world得到相应的LineBatcher然后调用DrawLine方法

1
2
3
4
5
6
7
8
9
if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer)
{
// this means foreground lines can't be persistent
ULineBatchComponent* const LineBatcher = GetDebugLineBatcher( InWorld, bPersistentLines, LifeTime, (DepthPriority == SDPG_Foreground) );
if(LineBatcher != NULL)
{
float const LineLifeTime = GetDebugLineLifeTime(LineBatcher, LifeTime, bPersistentLines);
LineBatcher->DrawLine(LineStart, LineEnd, Color, DepthPriority, Thickness, LineLifeTime);

封装蓝图函数库

FlibDrawGraphics.h
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
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "FlibDrawGraphics.generated.h"

/**
*
*/
class ULineBatchComponent;
UCLASS()
class GWORLD_API UFlibDrawGraphics : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
private:
static ULineBatchComponent* GetDebugLineBatcher(const UWorld* InWorld, bool bPersistentLines, float LifeTime, bool bDepthIsForeground);

public:
UFUNCTION(BlueprintCallable)
static void G_DrawLine(UObject* context, FVector Start, FVector End, FLinearColor Color, uint8 DepthPriority, float Thickness, float Duration, bool bPersistent = false);
UFUNCTION(BlueprintCallable)
static void G_DrawBox(UObject* context, FVector Center, FVector Box, FLinearColor Color, uint8 DepthPriority, float Thickness, float Duration, bool bPersistent = false);
UFUNCTION(BlueprintCallable, meta = (context = "WorldContextObject"))
static void G_DrawSoildBox(UObject* context, FVector Center, FVector Extent, FLinearColor Color, uint8 DepthPriority, float Duration, bool bPersistent = false);


};

FlibDrawGraphics.cpp
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include "FlibDrawGraphics.h"
#include "Components/LineBatchComponent.h"
#include "Engine/World.h"

ULineBatchComponent* ULibGameFunc::GetDebugLineBatcher(const UWorld* InWorld, bool bPersistentLines, float LifeTime, bool bDepthIsForeground)
{

return (InWorld ? (bDepthIsForeground ? InWorld->ForegroundLineBatcher : ((bPersistentLines || (LifeTime > 0.f)) ? InWorld->PersistentLineBatcher : InWorld->LineBatcher)) : NULL);

}


//画线
void ULibGameFunc::BP_DrawLine(UObject* context, FVector Start, FVector End, FLinearColor Color, uint8 DepthPriority, float Thickness, float Duration, bool bPersistent /*= false*/)
{
UWorld* world = context->GetWorld();
if (world)
{
//通过优先级得到对应的组件
ULineBatchComponent* const LineBatcher = GetDebugLineBatcher(world, bPersistent, Duration, DepthPriority == SDPG_Foreground);
//如果是永久的设置时间-1
float const ActualLifetime = bPersistent ? -1.0f : ((Duration > 0.f) ? Duration : LineBatcher->DefaultLifeTime);
LineBatcher->DrawLine(Start, End, Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
}
}


//画盒子边框,通过8个点然后DrawLine得到
void ULibGameFunc::BP_DrawBox(UObject* context, FVector Center, FVector Box, FLinearColor Color, uint8 DepthPriority, float Thickness, float Duration, bool bPersistent /*= false*/)
{

UWorld* world = context->GetWorld();
if (world)
{

ULineBatchComponent* const LineBatcher = GetDebugLineBatcher(world, bPersistent, Duration, DepthPriority == SDPG_Foreground);
float const ActualLifetime = bPersistent ? -1.0f : ((Duration > 0.f) ? Duration : LineBatcher->DefaultLifeTime);
if (LineBatcher)
{ LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, Box.Z), Center + FVector(Box.X, -Box.Y, Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, -Box.Y, Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, Box.Y, Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, Box.Z), Center + FVector(Box.X, Box.Y, Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, -Box.Z), Center + FVector(Box.X, -Box.Y, -Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, -Box.Z), Center + FVector(-Box.X, -Box.Y, -Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, -Box.Z), Center + FVector(-Box.X, Box.Y, -Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, -Box.Z), Center + FVector(Box.X, Box.Y, -Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, Box.Z), Center + FVector(Box.X, Box.Y, -Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, Box.Z), Center + FVector(Box.X, -Box.Y, -Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, -Box.Y, -Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);
LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, Box.Z), Center + FVector(-Box.X, Box.Y, -Box.Z), Color.ToFColor(true), DepthPriority, Thickness, ActualLifetime);

}
}

}

//画实体的盒子
void ULibGameFunc::DrawSoildBox(UObject* context, FVector Center, FVector Extent, FLinearColor Color, uint8 DepthPriority, float Duration, bool bPersistent/*=false*/)
{
FBox Box = FBox::BuildAABB(Center, Extent);

if (context)
{
UWorld* World = context->GetWorld();
if (World)
{

ULineBatchComponent* const LineBatcher = GetDebugLineBatcher(World, bPersistent, Duration, DepthPriority == SDPG_Foreground);
if (LineBatcher != NULL)
{

float const ActualLifetime = bPersistent ? -1.0f : ((Duration > 0.f) ? Duration : LineBatcher->DefaultLifeTime);
LineBatcher->DrawSolidBox(Box, FTransform::Identity, Color.ToFColor(true), DepthPriority, ActualLifetime);
}
}

}
}

蓝图测试

前言

本文介绍从max到UE4顶点动画实现流程

使用的方式是贴图记录顶点位置和法线信息通过材质球使静态模型模拟动画的播放

Alt text

脚本下载

阅读全文 »

开发环境配置流程

MXSPyCom
  1. 下载GitHub的MXSPyComRelease包,链接
  2. 解压,并将安装包内的MXSPyCOM.exe文件复制到自定义目录
  3. initialize_COM_server.ms脚本放到max对应版本的startup目录中,默认路径是%localappdata%\autodesk\3dsmax\scripts\startup
VsCode
  1. 下载LanguageMaxScript插件

  2. 设置工作区

  3. 创建/配置task.json,可以ctrl+e搜索框中输入>task搜索到Configure Default Build Task,在文件内设置如下代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "version": "2.0.0",
    "tasks": [
    {
    "label": "Execute in Max",
    "type": "process",
    "command": "C:/MXSPyCOM.exe", //MXSPyCom.exe的路径
    "args": ["-f", "${file}"],
    "presentation": {
    "reveal": "always",
    "panel": "new"
    }
    }
    ]
    }

  4. 更改快捷键配置,方便调试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [
    {
    "key": "ctrl+e",
    "command": "workbench.action.tasks.runTask",
    "args": "Execute in Max" //对应任务的名称
    },
    {
    "key": "shift+e",
    "command": "workbench.action.quickOpen" //原来的ctrl+e功能
    },
    {
    "key": "shift+e",
    "command": "workbench.action.quickOpenNavigateNextInFilePicker",
    "when": "inFilesPicker && inQuickOpen"
    }
    ]


测试脚本
  1. 在工作目录创建后缀为 ms格式的脚本文件
  2. 启动max
  3. 复制如下代码到脚本文件内,按 ctrl+e 调试
1
2
3
4
5
6
7
8
9
10
11
12
13
rollout rename_rollout "Enter New Base Name"
(
edittext base_name ""
button rename_them "RENAME"
On rename_them pressed do
(
if base_name.text!="" do (
for i in selection do i.name=uniqueName base_name.text
)
)
)
CreateDialog rename_rollout 250 50

  1. max生成如下窗口