这是一份Multiplayer RPG Template的说明文档,内容包含其中主要系统的配置和制作方法

此案例是基于UE4的ListerServer建立的局域网联机游戏demo,参考作品WOW

基于UE4版本4.25

demo尚且有未完善的地方,后续会跟进优化和修复

技能系统

如上图所示,此图为技能表的配置信息,下面简述其中重要的参数

  • Action: 这个变量代表这个行为的唯一ID,原则上跟表格的RowName相同,很多逻辑中需要通过这个ID查找关于这个技能的所有信息,或者通过这个ID执行这个技能等
  • Display(struct):3个参数用来显示这个技能一般信息,比如名字或者说明还有图标的显示,在UMG上有相关的体现,当然你也可以用来自定义
  • CastData(struct):这个是一个施法的结构体,主要设置的是施法的方式和条件
    • Type:比如默认的读条施法,持续施法,攻击叠加类的施法等方式
    • Time:施法所需要的时长,0意味着瞬发
    • Duration:如果是持续施法,那么这个参数意味着引导时间
    • KeepAttackStat:施法完以后会继续进行普通攻击
    • MotionCast:移动施法,设置为否的话必须站立施法,一旦移动就打断施法
    • NotTargetCast:不需要目标的施法,一般用于范围技能比如火雨之类
  • Mp:消耗魔法值,战士为怒气值
  • CD:冷却时间
  • MaxRange:最大释放范围,按照虚幻单位计算,0意味着无限制
  • LearnGold/Level:学习该技能的前置条件,0意味着无要求
  • RangeSelectRange:如果是范围选择的技能,此参数表示作用的半径
  • TargetType:目标类型,作用地方还是友方等
  • SkillClass:技能实例的类,无需手动创建
SkillRef

这个是通过表格配置的SkillClass创建的,无需手动创建,你只需要制作这个类的逻辑和效果

这个类是继承自BP_BaseSkill,里面有一些乱七八糟的蓝图逻辑,不过并不是全部都是必须使用的,这个类很多情况下你可以自由发挥,反正技能释放了以后就通过这个类来控制这个技能实例的运行

下面就通过IceBullet这个技能类来说明一下

IceBullet继承于ProjectileBase,如上图所示,这是这个类的基本参数类型

  • speed:飞行速度,依赖ProjectileComponent
  • StopDistance:停止的距离,因为这个类是追踪目标飞行的,所以会总有接近并且到达目标的时候,到达目标并停止以后会产生一段逻辑
  • Duration:持续时间,如果没有命中到达时间以后也会摧毁自己
  • Sound/FX:相关的特效配置,Start可以用于产生的时候播放,Hit可以用于打中目标以后播放,但都需要手动释放,
    • ProjectileBase已经实现了BeginPlay以后调用Start的效果
    • 在打中目标后已经实现播放Hit的效果
  • BuffData:如果这个技能有buff/debuff可以设置这个类,需要手动调用方法来创建并运行buff类,如下图

BuffRef

这是Buff的实例类,通过SkillRef内部调用CreateBuff方法创建

通过重写BuffInitBuffEnd方法来实现自定义的逻辑,如果是拥有持续效果,那么你可以重写BuffDelta 方法来制作持续调用的逻辑

装备系统

如上图所示,这个是装备信息的配置表,下面简单介绍其中主要参数的作用

  • BodyPart:装备对应的位置
  • Mesh/Mat:用于显示这个装备实体的模型,装备在人物上或者丢弃到世界中的显示
  • Property:如字面意思就是2种属性,其中Adv属性会随着Base的更改而变化

道具系统

道具系统类似于装备系统

  • EffectType:作用类型,本案例只制作了治疗效果和增加魔法的效果
  • Value: 可以设置作用效果的范围值,当然这个不是必须的,具体是下面的道具类内部实现
  • CanBeStacked:如果是否那么一个道具占据背包栏的一格,否则么可以堆放到一起
  • ActionClass:道具类实现逻辑的类,参考Skill的实例实现,具体逻辑可以写到这个类里面

掉落系统

如上图所示,掉落系统的配置放在DataSystem 这个类里面,为什么没有配置表格呢?O(∩_∩)O

当然也可以,你可以自己创建一个类似的表格然后替换其中的逻辑,这个很简单

这个配置信息是一个Map键值对,键对应的是角色的类,值即是掉落信息数组

  • Action:选择装备/道具等的Action名称
  • Type:选择是装备/道具,当然你要是想掉落技能的话理论上也可以,请自由发挥
  • Amount:掉落数量
  • Probability:概率,这个概率计算是在角色创建的时候就完成的,非击杀的时候

任务系统

如上图所示,这是任务表格的配置信息

  • Titile:任务的标题,会显示在任务栏内的抬头标题部分
  • Target:任务的目标
  • DynamicTargetStat:这个会动态的刷新,具体实现的逻辑在下面的TaskClass内
  • Descripion:任务描述
  • Reward:奖励
TaskRef

如上图所示,每接受一个任务以后就会产生一个任务类来作为该任务的观察者(代理)

具体这个任务是干什么的你可以通过实现这个类内的逻辑来管理,同时设置任务目标里的动态信息

其他

怪物刷新

怪物刷新是在场景里放置一个BP_AIGenerator管理类来完成的

  • MaxNum:刷新的最大数量
  • AIClass:设置刷新角色的类,超过1个的时候就随机从中刷新
  • Range:以该管理类的位置为中心的半径内刷新怪物

This is Multiplayer RPG Template Documentation,its about configuration and production method of the main system

This case is based on UE4’s ListerServer built-in LAN online game demo, refer to the work WOW

Based on UE4 version 4.25

There are still some imperfections in the demo, which will be followed up for optimization and repair

Skill system

As shown in the above figure, this figure is the configuration information of the skill table. The important parameters are briefly described below

  • Action: This variable represents the unique ID of this behavior. In principle, it is the same as the RowName of the table. In many logics, you need to use the ID to find all information about the skill, or use the ID to execute the skill
  • Display(struct):3 parameters are used to display the general information of this skill, such as the display of the name or description and icons, which are related to the UMG, of course, you can also customize
  • CastData(struct):This is a spell-casting structure, which mainly sets the method and conditions of the spell-casting
    • Type:For example, the default reading spell casting, continuous casting, attack superimposing casting and other methods
    • Time:The time it takes to cast, 0 means instant
    • Duration:If it is a continuous cast, then this parameter means the lead time
    • KeepAttackStat:After the cast is finished, the normal attack will continue
    • MotionCast:Move cast, if set to No, you must stand cast, once you move, interrupt cast
    • NotTargetCast:Does not require target casting, and is generally used for range skills such as Fire and Rain
  • Mp:Mana cost, Warrior Rage
  • CD:Cooling time
  • MaxRange:Maximum release range, calculated in Unreal Units, 0 means unlimited
  • LearnGold/Level:Prerequisites for learning this skill, 0 means no requirement
  • RangeSelectRange:If it is a skill selected by range, this parameter indicates the radius of the effect
  • TargetType:Target type, place of action or friend
  • SkillClass:Skill class, no need to create manually
SkillRef

This is created by the SkillClass configured in the table, there is no need to manually create it, you only need to make the logic and effects of this class

This class is inherited from BP_BaseSkill, which has some messy blueprint logic, but not all are necessary to use. In this case, you can freely play in many cases. Anyway, after the skill is released, you can control this skill through this class Running the instance

Let’s explain it through the skill class of IceBullet

IceBulletInherit fromProjectileBase,As shown in the figure above, this is the basic parameter type of this class

  • speed: flight speed, depends on ProjectileComponent
  • StopDistance: the distance to stop, because this class is to track the target flight, so there will always be when approaching and reaching the target, a logic will be generated after reaching the target and stopping
  • Duration: Duration, if there is no hit, the time will destroy yourself
  • Sound / FX: related special effects configuration, Start can be used to play when it is generated, Hit can be used to play after hitting the target, but all need to be manually released
       - The effect of calling Start after ProjectileBase has realized BeginPlay
       - The Hit effect has been achieved after hitting the target
  • BuffData: If this skill has buff / debuff, you can set this class, you need to manually call the method to create and run the buff class, as shown below

BuffRef

This is an instance class of Buff, created by internally calling CreateBuff method of SkillRef

By rewriting the BuffInit and BuffEnd methods to achieve custom logic, if it has a continuous effect, then you can rewrite the BuffDelta method to make the logic of continuous calls

EquipSystem

As shown in the figure above, this is the configuration table of the equipment information. The following briefly introduces the role of the main parameters

  • BodyPart: the corresponding position of the equipment
  • Mesh/Mat: Used to display the model of this equipment entity, equipped on the character or discarded into the world
  • Property: literally means 2 kinds of properties, of which Adv property will change with the change of Base

PropSystem

The prop system is similar to the equipment system

  • EffectType:Effect type, this case only made the healing effect and the effect of increasing magic
  • Value: You can set the range value of the effect, of course, this is not necessary, specifically the internal implementation of the following prop class
  • CanBeStacked:If one item occupies one space in the backpack bar, otherwise it can be stacked together
  • ActionClass:Prop class implement logic class, refer to Skill instance implementation, specific logic can be written into this class

DropSystem

As shown in the figure above, the configuration of the drop system is placed in the DataSystem class. Why is there no configuration table? O (∩_∩) O

Of course, you can create a similar table yourself and replace the logic in it. This is very simple.

This configuration information is a Map key-value pair, the key corresponds to the role class, and the value is the drop information array

  • Action:Select the action name of equipment / props, etc.
  • Type:Choose equipment / props. Of course, if you want to drop skills, you can theoretically do it. Please play freely.
  • Amount:number of drops
  • Probability:Probability, this probability calculation is done when the character is created, when not kill

TaskSystem

As shown in the figure above, this is the configuration information of the task table

  • Titile:the title of the task, which will be displayed in the header section of the task bar
  • Target:the target of the task
  • DynamicTargetStat:This will be refreshed dynamically, the specific implementation logic is in the following TaskClass
TaskRef

As shown in the figure above, each time a task is accepted, a task class will be generated as an observer (agent) of the task

Specifically what the task is for, you can manage it by implementing the logic in this class, while setting the dynamic information in the task goal

Other

Monster refresh

Monster refresh is done by placing a BP_AIGenerator management class in the scene

  • MaxNum: the maximum number of refreshes
  • AIClass: Set the class for refreshing the character, if more than one, it will be randomly refreshed from it
  • Range: refresh monsters within a radius centered on the position of the management class

笔者之前制作了一个游戏Demo蓝图工程上架了虚幻商城,本文记录虚幻商城上传蓝图工程的注意事项

准备工作

  1. 当然是制作一份自己的工程,不要轻易白嫖别人的工程/资源,很容易被逮到,当然也有例外,下文会提到
  2. 注册并登录Epic账号,到商城卖家页面准备提交内容
    1. 需要一个公开邮箱,用于跟官方以及买家的交流,包括退款或者错误问题交流
    2. 需要提供一个Paypal账户(目前不支持支付宝)来接受汇款
    3. Epic在每月月底统计销售额,需要大于100美元才会在45日之内汇款,略慢
  3. 准备一套图片,分辨率为1920x1080,这些贴图会提交为商品预览图片
    1. 这里其中一个图片需要设置为商城浏览时的预览图片,分辨率为284*284
    2. 还有一张图片会设置为主页上上的图片,分辨率为894x488

产品信息

序号 信息 备注
1 发布者名称,产品标题和标签不包含任何版权或商标名称 我上架的时候因为是一个MMORPG的案例,加了标签**”WOW”**,结果违反了这一条规定
2 发布者名称,产品标题和标签不包含Epic的任何商标或属性 不要添加跟Epic有关系的内容
2 发布者名称,产品标题和标签不包含令人反感或不适当的语言 显而易见
3 产品标题不包含主观语言 这个不太清楚意思
4 产品标题,说明文字和标签可准确反映产品内容 字面意思
5 类别与产品的内容和功能有关 这个也容易犯错,可能为了更容易被搜索到添加了无关的分类
6 产品标题,说明文字和标签为英文,包含正确的拼写和正确的语法 不要写中文
7 所有技术信息模板字段均填充有适当的信息 把信息写满就可以
8 提交处于“待批准”状态 这个是废话,正常走流程就可以

媒体资源

序号 信息 备注
1 图片不包含任何令人反感的图片 *
2 图像清晰且与产品的内容和功能相关 *
3 图片不会显示任何未经许可的第三方版权材料 推荐自己引擎里截图就可以,不要加有些明显版权的图片什么的
4 显示产品视觉内容的图像在虚幻引擎4中渲染 推荐引擎里截图

项目文件

基础
序号 信息 备注
1 每个项目文件链接仅承载一个具有适当文件夹结构的UE4项目或插件文件夹 你发给官方的链接也就是买家会下载的工程链接是只包含一个工程,这个一般也不会犯错
2 提供的项目与列出的支持的引擎版本匹配 版本不要搞错
3 分发方法适合产品的内容和功能 *
一般
序号 信息 备注
1 Content文件夹包含一个以项目命名的Pack文件夹 这个比较容易犯错,文件目录需要严格按照类似以下这种结构
MyProject
├── Config
├── Content
│ └── MyProject
├── MyProject.uproject |
2 Pack文件夹中的第一层文件夹是根据资产类型或特定资产命名的 你自己的文件夹里的第一个文件夹是资产类文件夹,推荐命名成Assets
3 所有资产类型都在各自的文件夹内 比如动画放到Animation文件夹,贴图放到Texture文件夹里,但是笔者并没有严格按照这个分类也没有被打道回府
4 项目不包含未使用的文件夹或资产 这个非常头大,你做项目的时候要谨慎添加资源比如帕拉共的资源,提交之前要删除所有未使用的资源,鉴于虚幻引擎清理资源的速度,可能这会花你几个晚上的时间;ps:笔者因为几张未使用的贴图被打回一次
5 所有重定向器均已清理 制作的时候注意清理/修复路径就可以了
6 命名约定为英语,字母数字,在整个项目中保持一致,并描述资产的含义 不要用中文,所有地方都别用就没事了
7 如果需要,发布者可以提供链接的或编辑者的文档/教程 笔者提供了一个油管简介视频,没有提供文档也通过了,不过还是推荐做一个文档
8 .uproject已禁用未使用的插件 关闭不适用的插件,系统默认开启的不用理会
质量
序号 信息 备注
1 内容主要不包括易于复制的资产 不清楚,笔者没有被打回
2 资产不包含视觉缺陷 字面意思
3 资产运作不影响绩效 这个翻译的不太对,应该是不影响性能之类的意思
4 所有资产均已完成且功能正常 这个笔者被打回了若干次,项目不能有明显的错误,不仅仅是编译运行错误,你游戏逻辑有错误也不行,笔者的demo里释放一个技能的时候有弹道错误也被打回来了,测试员还是蛮仔细的
5 产品包含总体良好的设计和概念 屁话
法律
序号 信息 备注
1 发布者拥有分发包含在产品中或由产品依赖的所有内容的合法权利 重头戏,资源的版权问题,如果你的美术资源都是自己做的就忽略本条,否则见下
1.Epic免费资源可以随意使用
2.其他第三方免费资源包括月限免的都不能使用
3.音效比较难定义,除非太过明显;BGM之类的不行,音乐是容易界定版权的
4.动画其实不容易界定版权,但是商城痕迹太明显的肯定不行
5.图片和模型资源版权容易界定,不要使用商城禁止的资源
2 不包含版权或商标内容 参考第一条,第一条过了这里基本也过了
3 Epic Games的示例内容或源代码的大部分内容仅用于显示/示例 意思应该就是字面意思,不要用示例工程的资源就没事了
4 未经修改的公共领域内容仅限于帮助演示,并且在描述中引用了来源 遵循第一条
5 产品不包含可能令人反感的资产 *
依赖问题
  • 这个工程不要依赖于其他工程
地图
序号 信息 备注
1 必须要有地图来展示内容 要有正式地图,建议启动地图就是正式地图
2 所有地图均已建立照明 容易被忽略的一条,记得提交之前构造光照
3 地图不会产生任何错误或相应的警告 提交之前修复所有报错信息,容易犯错的是资源引用错误
4 地图没有Z形战斗或重叠的多边形 啥翻译!!不要有重面就可以

其他问题

  1. 提交的版本需要支持当前最新版本,笔者在第一次提交的时候还是4.24版本,后面被打回后UE很不合时宜的升级到了4.25,结果工程必须升级到4.25进行重新测试,当中还遇到了很多崩溃问题,这个比较头痛

本文介绍如何用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生成如下窗口