草庐IT

【UE4】将蓝图转为C++的方法实操示范

Alexander_420 2023-04-08 原文

动机

前面总结了官方教程
【UE4】蓝图转为C++官方教程部分笔记
这里我也按照我的理解,简单的将官方模板双摇杆射击游戏从蓝图转为C++供大家参考。
这种转换的方法并不一定很好,只是希望能够用上教程中的转换方法,巩固知识。

准备工作

1.创建工程


  • 我们最后是做一个C++的项目,但是一开始选模板的时候是选蓝图。

2. 添加C++类

  • 新建了工程,很容易就会发现和C++工程相比,没有编译这个选项。工程文件里面也没有source文件夹

  • 所以首先就需要新建C++类,把我们的这个工程转为C++项目。

  • 我们第一步先改写操作的这个飞船,飞船原本的父类是pawn类,所以就先创建一个Pawn。

3. 重设蓝图父项

  • 在我们的飞船找到重设蓝图父项这个选项。

  • 找到新建的pawn,选择之后,新建的C++类就变成我们当前蓝图的父项了。
  • 接下来的也就是将蓝图里面的逻辑迁移到类里面。

根据蓝图写C++

观察蓝图


  • 在事件图表中,很容易就可以看到事件tick后有很长一串的操作,同时有两个函数。我们可以先从替换函数入手。

方法一 :一一对应改写函数

  • 首先先看FireShot函数,他有一个输入,向量Direction,没有输出。
	UFUNCTION(BlueprintCallable)
	void FireShot(FVector Direction) ;
  • 那我们声明也尽量还原他的函数定义

  • 首先是第一部分,他注释成Check if the user can fire的代码块。
  • 首先我们要用到这个Can Fire的变量。但是他是在蓝图中定义的,在C++ 文件中没有。所以我们要在C++ 文件中创建。
	UPROPERTY(BlueprintReadWrite)
	bool CanFire;
  • 使用BlueprintReadWrite,蓝图就可以读写这个变量

  • 第一个挑战来自于向量长度这个API,通过FVector类的联想可以找到。


如果没法联想或者找不到。就在蓝图里面查看他属于什么库。

  • 进入C++ 源代码查找
  • 通过搜索,找到库里面的节点函数实现,这下就破案了。

void ANewPawn::FireShot(FVector Direction)
{
	if(!CanFire) return ;
	if(Direction.Size() <= 0) return; 
}
  • 那就把这个块的蓝图逻辑实现。(目前代码↑)

方法二 : 使用蓝图本地事件

  • 第二个块,其实逻辑也非常轻松,但是难办的点在于。生成Actor这个函数需要我们提供类。但是C++里面寻找类资产还是比较麻烦的。
	if(!CanFire) return ;
	if(Direction.Size() <= 0) return;
	FRotator ShipRotator = Direction.ToOrientationRotator() ;
	FVector SpawnPoint = GetActorLocation()+Direction.ToOrientationRotator().RotateVector(GunOffset);
	FTransform SpawnTransform = FTransform(ShipRotator, SpawnPoint, FVector(1.0f,1.0f,1.0f));
  • 将生成Actor之前的代码都做好了。但是生成Actor实在不会写那怎么办呢。
  • 这时候就需要搬出蓝图本地事件了。
	UFUNCTION(BlueprintCallable,BlueprintNativeEvent)
	void FireShot(FVector Direction) ;
  • 在定义的时候加入BlueprintNativeEvent的宏
void ANewPawn::FireShot_Implementation(FVector Direction)
{
	if(!CanFire) return ;
	if(Direction.Size() <= 0) return;
	FRotator ShipRotator = Direction.ToOrientationRotator() ;
	FVector SpawnPoint = GetActorLocation()+Direction.ToOrientationRotator().RotateVector(GunOffset);
	SpawnTransform = FTransform(ShipRotator, SpawnPoint, FVector(1.0f,1.0f,1.0f));
}

  • 实现里面的函数要加上FireShot的后缀。
  • 这里为了方便,我将生成的Transform创建了一个变量

  • 编译完之后原本的CanFire等变量都变成_0的形式,防止冲突。我们需要将他们一个个替换掉。
  • 不要忘了设置好C++变量的初始值

  • 添加重载函数,选择在C++里面实现的FireShot

  • 前半部分是C++实现的,所以也要调用父类函数,先执行完再执行剩下的步骤。
  • 也就是说,我们将这个函数改成了一半是C++实现的,一半是蓝图实现的。

  • 但是如果这样就会出现问题,问题在于:原本不是CanShot的时候,运行时直接停止的。但是在我的写法中,他会继续运行,并且访问生成Actor这个节点。
bool ANewPawn::FireShot_Implementation(FVector Direction)
{
	if(!CanFire) return false;
	if(Direction.Size() <= 0) return false;
	FRotator ShipRotator = Direction.ToOrientationRotator() ;
	FVector SpawnPoint = GetActorLocation()+Direction.ToOrientationRotator().RotateVector(GunOffset);
	SpawnTransform = FTransform(ShipRotator, SpawnPoint, FVector(1.0f,1.0f,1.0f));
	return true ;
}
  • 修改一下CPP代码,改为有bool判断的函数。

  • 重载之后其他的就用原来的蓝图节点。

  • 测试:可以正常射出

方法三 :蓝图可实现事件

  • 如果你觉得,类似于图上设置场景旋转等操作需要访问骨骼网络体,在C++中不好实现,同样可以将他们留在蓝图中。
	UFUNCTION(BlueprintCallable,BlueprintImplementableEvent)
	void HandleRotation(FRotator Rotation,FVector NewLocation) ;
  • 在这里我们将后面的代码都放在一个蓝图可实现事件里面(宏:BlueprintImplementableEvent)
  • 声明好了之后直接编译(这和纯虚函数类似,并不需要在父类实现)

  • 回到子类蓝图,创建重载

  • 就可以把原来的函数分割开了。(后来修改时发现还得要加上一个Vector输入,图片没改)

改写Tick

  • 看了前面那些可能就会有疑惑,这样费劲心思的改写函数,又是改C++的,又是用蓝图本地事件,蓝图可实现事件的意义是什么呢。
  • 答案:在我们这个飞船中,事件Tick就类似于平时的main函数。利用上面的方法改写后的函数可以在C++中调用,改写Tick就变得非常容易了。

  • 现在的Tick逻辑就只有三步:1.计算旋转 2. 调用函数HandleRotaion 3.调用函数FireShot
void ANewPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	FVector MoveVector = FVector(GetInputAxisValue(FName("MoveForward")),GetInputAxisValue(FName("MoveRight")),0.0f);
	MoveVector = MoveVector.GetClampedToMaxSize(1.0f);
	MoveVector = MovementSpeed * DeltaTime * MoveVector ;
	if(MoveVector.Size() <= 0) return ;
	FRotator XRotator  = MoveVector.ToOrientationRotator();
	//调用蓝图可实现事件HandleRotation
	HandleRotation(XRotator,MoveVector);
	//调用蓝图本地时间FireVector
	FVector FireVector = FVector(GetInputAxisValue(FName("FireForward")),GetInputAxisValue(FName("FireRight")),0.0f);
	FireShot(FireVector);
}

// Called to bind functionality to input
void ANewPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	PlayerInputComponent->BindAxis(FName("MoveForward"));
	PlayerInputComponent->BindAxis(FName("MoveRight"));
	PlayerInputComponent->BindAxis(FName("FireForward"));
	PlayerInputComponent->BindAxis(FName("FireRight"));
	//不执行这个绑定的话就无法获取输入
}
  • 要添加的代码,也就很轻松得把上面的计算逻辑实现了。然后调用前面准备好的函数就完成了。
  • 如此一来,飞船就完全改完了。

提示

  1. 这只是一个个人向的练习,所以对于写法的选择并不一定很科学规范,仅提供一种思路
  2. 如果想不到蓝图节点怎么对应C++可以创建一个这个模板的C++版本参考学习(但是官方的C++实现也不见得最好)
  3. 多做多积累~

有关【UE4】将蓝图转为C++的方法实操示范的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  5. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  7. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  8. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  9. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

  10. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

随机推荐