2019-10-07 14:46:26 sddxccj 阅读数 225
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2377 人正在学习 去看看 杨石兴

《楚留香》《逆水寒》《天涯明月刀》等一批武侠游戏都将捏脸系统作为了标配,并且开放了大量的参数给玩家,从而能够自由的发挥自己的想象力,捏出一堆鬼脸~在知乎(《Honey Select》)以及其他文章里对捏脸的原理进行了详细的分析,本文呢,主要记录基于骨骼的捏脸在Unreal4中的实现。

原理

基于调整骨骼进行捏脸的核心就是修改脸部骨骼的Scale、Rotation,Position,从而改变骨骼对应的蒙皮的顶点的位置,以达到捏脸的效果:

上图是在动画蓝图里添加一个内置的改变骨骼的节点(下图)来修改鼻子的x坐标的scale 的效果:
看起来捏脸也就这么回事了!但是呢,要想达到游戏中千人千面的效果,基于骨骼的捏脸有以下几点要求:
  1. 需要设计一套有足够表达能力的骨骼以及细致的脸部蒙皮
  2. 大量的骨骼对应的大量参数带来的自由度过高,不易调节,应便于用户调节
  3. 性能消耗相对较少
  4. 跟现有的动画系统以及基于blendshape的表情兼容
  5. 如果有AI能力,根据用户提供的照片自动生成对应的模型是最好不过的了
  6. 有一套对应的妆容方案

其中 1 主要由3D建模师操作,另外对于脸部的对称部分,设计其对应的骨骼为对称骨骼,从而方便调节;对于第二条,大部分的游戏会设计一套叫做controller的第二层骨骼,每个controller同时操纵多根骨骼的多个参数的不同组合来调节局部区域,controller1控制眼部的整体的大小,需要添加眼部骨骼到controller控制的骨骼的列表中,controller的示意图如下:
在这里插入图片描述
这样用户通过操纵controller的滑竿便可以一次性调节一个局部区域,实际上,通过二层骨骼我们降低了局部骨骼参数的自由度,从而方便用户精细的调整角色脸部的细节表情。举例:controller1通过控制三根骨骼的缩放参数来达到整体调节眼部大小的目的:
在这里插入图片描述
3暂且按下不表;接下来4的话会涉及到如何在unreal里实现捏脸,因此会展开详细记录。

Unreal实现

分为捏脸部分和与动画系统融合部分

捏脸部分

首先,开篇所述的直接用ModifyBone蓝图节点来修改每根骨骼的话,对于程序非常的不友好,为了捏脸的效果和充分的表达能力,SkeletalMesh中通常设置较多的骨骼,因此直接使用ModifyBone节点是不太方便的。
我们整体的逻辑应该是这样:

  1. 根据json文件解析出的controller生成所有的调节滑杆,并加载其默认值;
  2. 如果滑杆值发生变化,则对应线性插值或者样条插值该controller对应的所有的骨骼的对应的参数;
  3. 然后将变化的相对Transform更新到骨架的transform上;
  4. Rendering。

第一步和第二步实现比较简单,略去。对于第三步在Unreal中针对骨架有多套数据结构,从捏脸的方便性上来说,这里我们选择PoseableMesh来操作,查看PoseableMeshComponent.h的源码,可以看到以下函数:

class ENGINE_API UPoseableMeshComponent : public USkinnedMeshComponent
	{
	GENERATED_UCLASS_BODY()

	/** Temporary array of local-space (ie relative to parent bone) rotation/translation/scale for each bone. */
	TArray<FTransform> BoneSpaceTransforms;

	UFUNCTION(BlueprintCallable, Category="Components|PoseableMesh")
	void SetBoneTransformByName(FName BoneName, const FTransform& InTransform, EBoneSpaces::Type BoneSpace);

	UFUNCTION(BlueprintCallable, Category="Components|PoseableMesh") 
	FTransform GetBoneTransformByName(FName BoneName, EBoneSpaces::Type BoneSpace);

	UFUNCTION(BlueprintCallable, Category="Components|PoseableMesh")
	void ResetBoneTransformByName(FName BoneName);

	UFUNCTION(BlueprintCallable, Category="Components|PoseableMesh")
	void CopyPoseFromSkeletalComponent(const USkeletalMeshComponent* InComponentToCopy);
	};

可以看到利用PoseableMesh我们可以方便的操纵Transform,从而达到捏脸的目的。下面放两张Demo的截图,左侧为直接调节单根骨骼,右侧为调节controller:

在这里插入图片描述在这里插入图片描述

与动画系统的融合

PoseableMesh虽好,可不要贪杯哦(划掉),但是不支持动画,不支持Blendshape,换句话说,PoseableMesh就像专门的一套方便处理骨架transform的数据结构,其他的功能还是交由SkeletalMesh来做,那么问题就来了,如何将那捏脸的数据传到SkeletalMesh中,从而与动画以及BlendShape融合呢?这里我选择在AnimationBlueprint里实现一个自定义的AnimNode ModifyTransform来将PoseableMesh处理好的捏脸数据喂到SkeletalMesh的Render_Thread中,整个流程如下图所示:
在这里插入图片描述

  1. 因为我们有两套mesh来处理不同的数据,因此在蓝图中我们选择挂载俩mesh,将其中的PoseableMesh设为不可见:
    在这里插入图片描述
  2. 新建一个UAvatarAnimInstance继承自UAnimInstance,并添加以下数据:
UCLASS()
	class AVATAR_UE4_API UAvatarAnimInstance : public UAnimInstance
	{
		GENERATED_BODY()
		public:
		UAvatarAnimInstance(const FObjectInitializer& ObjectInitializer);

		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BoneTransform)
		TArray<FVector> BonesTranslation;

		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BoneTransform)
		TArray<FRotator> BonesRotation;

		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BoneTransform)
		TArray<FVector> BonesScale;

		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BoneTransform)
		TArray<FName> BonesName;
	};
  1. 在计算完捏脸滑杆逻辑即将捏脸的transform更新到PoseableMesh后,添加并调用以下函数将数据传入到AnimInstance中:
void UAutoPinch::TransformBoneData2AnimInstance()
	{
		if(Animation)
		{ 
			for (int i = 0; i < Animation->BonesName.Num(); i++)
			{
				Animation->BonesTranslation[i] = PoseableMesh->GetBoneLocationByName(Animation->BonesName[i], EBoneSpaces::ComponentSpace);
				Animation->BonesRotation[i] = PoseableMesh->GetBoneRotationByName(Animation->BonesName[i], EBoneSpaces::ComponentSpace);
				Animation->BonesScale[i] = PoseableMesh->GetBoneScaleByName(Animation->BonesName[i], EBoneSpaces::ComponentSpace);
			}
		}
	}
  1. 创建一个蓝图类继承自UAvatarAnimInstance,并将其指定为第一步中的skeletalMesh的AnimClass中的动画蓝图的父类。
  2. 接下来创建自定义动画蓝图节点,主要分为编辑器部分和runtime部分,编辑器部分的创建可参考其他文档,这里我们只记录如何创建自定义动画蓝图节点的runtime部分。
  3. 创建FAnimNode_ModifyTransform类继承自FAnimNode_SkeletalControlBase
USTRUCT()
	struct AVATAR_UE4_API FAnimNode_ModifyTransform :public FAnimNode_SkeletalControlBase
	{
	GENERATED_USTRUCT_BODY()
	public:
	
	/*New Transform to use*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Translation, meta = (PinShownByDefault))
		FBonesTransfroms BonesTransfroms;

	/** Whether and how to modify the translation of this bone. */
	UPROPERTY(EditAnywhere, Category = Translation)
		TEnumAsByte<EBoneModificationMode> TranslationMode;

	/** Whether and how to modify the translation of this bone. */
	UPROPERTY(EditAnywhere, Category = Rotation)
		TEnumAsByte<EBoneModificationMode> RotationMode;

	/** Whether and how to modify the translation of this bone. */
	UPROPERTY(EditAnywhere, Category = Scale)
		TEnumAsByte<EBoneModificationMode> ScaleMode;

	/** Reference frame to apply Translation in. */
	UPROPERTY(EditAnywhere, Category = Translation)
		TEnumAsByte<enum EBoneControlSpace> TranslationSpace;

	/** Reference frame to apply Rotation in. */
	UPROPERTY(EditAnywhere, Category = Rotation)
		TEnumAsByte<enum EBoneControlSpace> RotationSpace;

	/** Reference frame to apply Scale in. */
	UPROPERTY(EditAnywhere, Category = Scale)
		TEnumAsByte<enum EBoneControlSpace> ScaleSpace;
	FAnimNode_ModifyTransform();
	//  // FAnimNode_Base interface  
	virtual void GatherDebugData(FNodeDebugData& DebugData) override;
	//  // End of FAnimNode_Base interface  

		// FAnimNode_SkeletalControlBase interface  
	virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms) override;
	
	bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override;
	// End of FAnimNode_SkeletalControlBase interface  

	private:
	// FAnimNode_SkeletalControlBase interface  

	virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override;
	// End of FAnimNode_SkeletalControlBase interface  

	};
  1. 因为自定义蓝图节点不支持TArray做为输入,这里我们创建一个struct用来接收AnimInstance中传过来的数据:
USTRUCT(BlueprintType)
	struct FBonesTransfroms
	{
	GENERATED_BODY()
		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BonesTransfroms")
		TArray<FName> BonesName;
		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BonesTransfroms")
		TArray<FVector> BonesTranslation;
		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BonesTransfroms")
		TArray<FVector> BonesScale;
		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BonesTransfroms")
		TArray<FRotator> BonesRotation;
	};
  1. 然后实现FAnimNode_ModifyTransform中的虚函数,其中最重要的就是EvaluateSkeletalControl_AnyThread:
void FAnimNode_ModifyTransform::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext & Output, TArray<FBoneTransform>& OutBoneTransforms)
	{
	check(OutBoneTransforms.Num() == 0);
	// the way we apply transform is same as FMatrix or FTransform
	// we apply scale first, and rotation, and translation
	// if you'd like to translate first, you'll need two nodes that first node does translate and second nodes to rotate.
	const FBoneContainer& RequiredBones = Output.AnimInstanceProxy->GetRequiredBones();
	const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
	for (int i=0;i<BonesTransfroms.BonesName.Num();i++)
	{
		auto name = BonesTransfroms.BonesName[i];
		FBoneReference MyBoneToModify(name);
		auto ret = MyBoneToModify.Initialize(RequiredBones);
	
		FCompactPoseBoneIndex CompactPoseBoneToModify = MyBoneToModify.GetCompactPoseIndex(BoneContainer);
		FTransform NewBoneTM = Output.Pose.GetComponentSpaceTransform(CompactPoseBoneToModify);
		FTransform ComponentTransform = Output.AnimInstanceProxy->GetComponentTransform();
	
		FVector Scale = BonesTransfroms.BonesScale[i];
		FVector Translation = BonesTransfroms.BonesTranslation[i];
		FQuat Rotation(BonesTransfroms.BonesRotation[i]);
		
		if (ScaleMode != BMM_Ignore)
		{
			// Convert to Bone Space.
			FAnimationRuntime::ConvertCSTransformToBoneSpace(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, ScaleSpace);

			if (ScaleMode == BMM_Additive)
			{
				NewBoneTM.SetScale3D(NewBoneTM.GetScale3D() * Scale);
			}
			else
			{
				NewBoneTM.SetScale3D(Scale);
			}

			// Convert back to Component Space.
			FAnimationRuntime::ConvertBoneSpaceTransformToCS(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, ScaleSpace);
		}
		if (RotationMode != BMM_Ignore)
		{
			// Convert to Bone Space.
			FAnimationRuntime::ConvertCSTransformToBoneSpace(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, RotationSpace);

			const FQuat BoneQuat(Rotation);
			if (RotationMode == BMM_Additive)
			{
				NewBoneTM.SetRotation(BoneQuat * NewBoneTM.GetRotation());
			}
			else
			{
				NewBoneTM.SetRotation(BoneQuat);
			}

			// Convert back to Component Space.
			FAnimationRuntime::ConvertBoneSpaceTransformToCS(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, RotationSpace);
		}
		if (TranslationMode != BMM_Ignore)
		{
			// Convert to Bone Space.
			FAnimationRuntime::ConvertCSTransformToBoneSpace(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, TranslationSpace);

			if (TranslationMode == BMM_Additive)
			{
				NewBoneTM.AddToTranslation(Translation);
			}
			else
			{
				NewBoneTM.SetTranslation(Translation);
			}

			// Convert back to Component Space.
			FAnimationRuntime::ConvertBoneSpaceTransformToCS(ComponentTransform, Output.Pose, NewBoneTM, CompactPoseBoneToModify, TranslationSpace);
		}

		OutBoneTransforms.Add(FBoneTransform(MyBoneToModify.GetCompactPoseIndex(BoneContainer), NewBoneTM));
	}
	}
  1. 最后在skeletalMesh的动画蓝图中添加以下节点:在这里插入图片描述
  2. 这样整个数据流就跑通了,从捏脸的数据到最后的rendering。
    链接: https://buaaccj.github.io/.
    谢谢。
2016-11-27 19:03:44 or7rccl 阅读数 4901
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2377 人正在学习 去看看 杨石兴


















Projectsetting-》input=》Mobil选项里。。剩下的自己翻译去研究吧



2014-07-31 18:37:23 u010153703 阅读数 1187
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2377 人正在学习 去看看 杨石兴
Unreal里的Input被我分为两类,(1)只控制Possesed Pawn的ActionEvent和AxisEvents,需要配合设置Project Settings里的Input。(2)可以控制整个场景的任何物体。用的比较多的是KeyEvents和MouseEvents,当然,如果你用摇杆外设,用的比较多的是GamepadEvents。

(1) ActionEvent和AxisEvents
在ProjectSetting的 Input里定义一堆键盘输入来做Action和Axis的Mapping。
在ProjectSetting里的Input有两种:
Action Events和Axis Events。
AxisEvent提供对应按键的Axis Value。


Action Event则不提供额外信息,只是触发事件。



(2) KeyEvents和MouseEvents
其实,还有很有用的Input种类是:KeyEvents和MouseEvents。


Action Events和Axis Events,按键消息只被Possessed的Pawn接收,也就是你只能控制PlayerPawn/PlayerCharacter。
但KeyEvents和MouseEvents的按键消息可以被场景中任何物体接收。

噢噢噢,有了KeyEvents,用来测试多么方便多么方便呀~ 


2012-02-21 17:07:00 aigao1992 阅读数 1
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2377 人正在学习 去看看 杨石兴

类似于Java,UScript同样有属于一个自己的VM(VirtualMachine,虚拟机),下面就看看需要注意吸收理解的几点:

转自UDN

Unreal虚拟机

Unreal虚拟机由以下几部分组成: 服务器、客户端、渲染引擎及引擎支持代码。

Unreal控制着所有的玩家和物体间的游戏性和交互。在单玩家游戏中,Unreal客户端和Unreal服务器在同一台机器上运行;在网络游戏中,有一个机器用于专用服务器;所有连接到这个机器上的玩家是客户端。

所有的游戏播放都发生在一个"关卡"中,它是一个包含着几何体和actors的独立环境。尽管UnrealServer可以同时运行多个关卡,但每个关卡独立运作并且彼此屏蔽: 物体(actors)不能在不同关卡间穿行,而且一个关卡中的物体不能和另一个关卡中的物体进行通信。

地图中的每个actor可以由玩家控制(在网络游戏中可以有很多玩家)或者由脚本控制。当actor在脚本的控制下时,那么该脚本完全地定义了该actor如何移动及如何与其它actors进行交互。

对于世界中所有这些到处跑动的actors、执行的脚本及发生的事件,你也许会问它是如何能理解UnrealScript的执行流程哪。答案如下所示:

为了管理时间,Unreal将游戏运行的每秒钟分隔为"Ticks"。一个Tick是关卡中所有actors更新所使用的最小时间单位。一个tick一般是一秒钟的1/100到 1/10。tick时间仅受到CPU功率的限制,机器速度越快,tick持续时间越短。

在UnrealScript中的某些命令的执行只需要使用零tick的时间(也就是:它们的执行没有占有任何游戏时间),也有些命令需要占用很多ticks。需要占用游戏时间的函数称为"latent functions(潜伏的函数)"。一些latent functions函数的例子包括 Sleep , FinishAnimMoveTo 。UnrealScript中的Latent functions仅可以从在一个状态的代码中进行调用(所以也称作"state code(状态代码)"),而不能从一个函数的代码中(包括在一个状态中定义的函数)进行调用。

当一个actor在执行一个latent函数,那个actor的状态执行不会继续直到latent函数执行完毕。然而,其它的actor或者VM可能会调用该actor内部的函数。最终的结果是所有的UnrealScript的函数可以在任何时间被调用,甚至在latent 函数没有执行完毕的情况下。

按照传统的编程术语来说,UnrealScript就像在关卡中的每个actor 有它们自己的执行"thread(线程)"一样工作。在内部,Unreal不使用Windows线程,因为那将是非常低效的(Windows 95和Windows NT不能高效地处理同时发生的成千上万的线程)。然而,UnrealScript 模拟线程。虽然这个事实对于UnrealScript代码是透明的,但当您书写和UnrealScript交互的C++代码时则变得非常显然的。

所有的UnrealScripts将彼此独立地执行。如果有100个怪物正在关卡中走动,那么所有的这100个怪物的脚本在每个"Tick"中都正在同时地且独立地执行着。

UnrealScript的实现

要想获得更多的关于UnrealScript在底层是如何实现的信息 – 从编译过程执行到最终的字节代码的呈现 – 请查看UnrealScript实现页面。

UnrealScript的二进制兼容问题

UnrealScript的设计可以在不破坏二进制兼容性的情况下使包文件中的类随着时间不断地扩展。 这里所说的二进制数据兼容性是指“所依赖的二进制文件可以无误地进行加载并连接”;而您所修改的代码是否向您设计的那样工作是一个单独的问题。 需要特别说明的是,可以安全地进行修改的种类如下所示:

  • 在包中的.uc脚本文件可以在不破坏二进制兼容性的情况下重新编译。
  • 增加新的类到包中。
  • 增加新的函数到类中。
  • 增加新的状态到类中。
  • 增加新的变量到类中。
  • 从类中删除私有变量。

其它的改变一般都是不安全的,包括(但不限于):

  • 添加新的成员到struct中。
  • 从一个包中移除一个类。
  • 改变任何变量、函数参数或者返回值的类型。
  • 改变函数中参数的个数。

技术注意事项

垃圾回收

Unreal中的所有objects和actors都是用一个类似于Java VM的树形结构遍历的垃圾回收器进行垃圾回收。Unreal垃圾回收器使用UObject类的序列化函数来递归地确定每个活动的对象在引用哪些其它的对象。因此,不必显示地删除对象,因为当它们不被引用时,垃圾回收器最终会对它们进行回收。尽管这个方法具有隐藏删除未引用的对象的负面影响,但是它的效率远远地高于把那种很少发生删除的情况在内的引用算在内。请参照垃圾回收页面获得更多信息。


UnrealScript是基于字节码的

UnrealScript代码编译成为一系列类似于p-code(移植码)或Java字节代码的字节码。这使UnrealScript具有平台独立性,这可以使Unreal的客户端和服务器端组件直接地移植到其它的平台上,也就是Macintosh或Unix,并且所有的版本通过执行相同的脚本都可以很容易地进行交互操作。

Unreal作为虚拟机

虚幻引擎可以被看成一个3D游戏方面的虚拟机,就像Java语言和它的内置 Java类层次结构为网页开发脚本定义了一个虚拟机一样。Unreal虚拟机天生具有可移植性(由于在不同的模块中分离出了所有的平台依赖的代码)和可扩展性(由于可扩展的类的层次结构)。然而,目前我们没有想过要把Unreal VM文档化到那种足以让其它人可以用来创建独立的但兼容的实现的程度。

UnrealScript 编译器执行三遍

和C++不同, UnrealScript编译要进行独立的三遍。第一遍,对变量、struct、枚举型、常量、状态及函数声明进行分析和记忆;构建了每个类的概要。 在第二遍,将脚本代码编译为字节代码。这使得在这两遍中对带有循环依赖的复杂的脚本层次进行完全地编译和连接,没有独立的连接阶段。 第三个阶段使用在.uc文件的 defaultproperties 语句块指定的值来分析并导入类的默认属性.

持久的 actor 状态

在Unreal中值得重点注意的是,因为用户可以在任何时间内保存游戏,所有actors的状态,包括它们的脚本执行状态仅能在当所有的actors都处在UnrealScript栈的最低的层次时才能进行保存。这个持久的要求是latent函数仅能从状态代码中进行调用的背后的原因: 状态代码在栈中尽可能低的层次上执行,因此可以非常容易被序列化。函数代码可以存在于任何栈级别,并在可以使(比如)C++ 的native函数在栈中处于它的下面,这显然不能保存到硬盘并在稍后进行恢复。

Unrealfiles(Unreal文件)是Unreal的native二进制文件

Unrealfiles包括一个索引、特定Unreal包内objects的序列化存储。Unrealfiles类似于DLL文件,因为它们可以包含任何到其它Unrealfiles存储的其它objects的引用。这个方法使在因特网上通过预先确定的包来发布Unreal内容成为可能,从而节约了下载时间(通过永远只能对一个特定的包进行一次下载进行限制)。

为什么UnrealScript不支持静态变量

C++支持静态变量的很好的原因是忠实于低层次语言的根源,Java支持静态变量的原因似乎并没有彻底地想好,在UnrealScript中不支持静态变量是由于它们的作用范围在序列化、继承及多个关卡方面的含糊性: 静态变量是否应该具有全局性,意味着所有的静态变量的值在所有活动的Unreal关卡中是一样的? 它们是否应该在每一个包中? 它们是否应该在每个关卡中? 如果这样,那么如何对它们进行序列化 – 使用在它的.u文件中的类还是使用它.unr文件中的关卡? 它们在每个基类中都是唯一的或者子类是否有它们自己的静态变量的值? 在UnrealScript中,我们通过不定义静态变量作为一个语言功能来回避了这个问题,把它留给了程序员来管理,程序员可以通过创建包含它们的类并在一个真实的对象中暴露它们来管理这些类似于静态变量和全局变量的变量。如果您想使用一些基于每个关卡访问的变量,那么您可以创建一个包含这些变量的新类,并确保它们随同关卡进行序列化。这样就不会有歧义性。要想查看用于这种用途的类的示例,请参照LevelInfo和GameInfo。


转载于:https://www.cnblogs.com/Zephyroal/archive/2012/02/21/2361621.html

2014-09-19 22:24:29 shangguanwaner 阅读数 9899
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2377 人正在学习 去看看 杨石兴

默认的,UE4,会以AGameMode中设置DefaultPawnClass对象中摄影机作为默认摄影机。有时候我们想要一个全局的摄影机对场景进行观察,这就需要重新创建一个摄影机,并在这两个摄影机直接切换。下面看下如何实现。

在APlayerController类中,有一个SetViewTarget方法,参数是一个AActor指针,就是实现此功能的。因为参数必须是AActor,即必须是AActor中包含的Camera组件才有效,所有,先创建一个AActor对象。

FreeCameraActor.h

UCLASS()
class NANTOPDOWN_API AFreeCameraActor : public AActor
{
GENERATED_UCLASS_BODY()


public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
TSubobjectPtr<class UCameraComponent> TopDownCameraComponent;


UFUNCTION(BlueprintCallable, Category = "View Target")
void ChangeViewTarget();


virtual void BeginPlay() override;

private:
AActor* OldActor;
};


ChangeViewTarget()方法提供给Blueprint调用,用于在Character的Camera和FreeCamera之间切换。


FreeCameraActor.cpp


AFreeCameraActor::AFreeCameraActor(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
TopDownCameraComponent = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("TopDownCamera"));
this->RootComponent = TopDownCameraComponent;
OldActor = NULL;
}


void AFreeCameraActor::ChangeViewTarget()
{
AActor* pActor = this->GetWorld()->GetFirstPlayerController()->GetViewTarget();
if (pActor != OldActor)
{
this->GetWorld()->GetFirstPlayerController()->SetViewTarget(OldActor);
}
else
{
this->GetWorld()->GetFirstPlayerController()->SetViewTarget(this);
}
}


void AFreeCameraActor::BeginPlay()
{
Super::BeginPlay();


OldActor = this->GetWorld()->GetFirstPlayerController()->GetViewTarget();


this->GetWorld()->GetFirstPlayerController()->SetViewTarget(this);
}


在Editor中创建一个从AFreeCameraActor继承的Actor,并拖放到场景中去。可以看到一个包含了摄影机的对象。然后编辑Level Blueprint,加入脚本代码



Play场景,按下1键,就能看到摄影机在character和刚才新建的摄影机间进行切换了。

Unreal里的动画

阅读数 1623

没有更多推荐了,返回首页