Unreal4 入门(Blueprints和C++)

发表于2015-10-07
评论0 1.3k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

继续研究Blueprint,这次主要看看C++和Blueprint的结合方法。官方文档依然没有太大用处,但在wiki上还是有很多值得学习的文章的。当中有个叫Rama的家伙贡献了相当多的教程,他还写了一个很不错的插件,而且将源代码和实现的思路都写在了一个wiki页面里,对社区的帮助很大,MVP应该是没得跑了,要是多一些这样的人UE4就能更快地成熟并推广了。

不过在动手写代码之前,我首先把那个忍无可忍的VS2013自带的Intellisense给关掉了,我是完全按照官方文档的指引配置了所有的参数,但实际用起来却奇慢无比,再小的工程敲几行代码后要等二十几秒自动完成才能蹦出来,右下角那个处理提示符就几乎没消失过。而且代码帮助还非常不稳定,时有时无,另外还经常给出一些错误的提示。忍不了了,还是装了Visual Assist,瞬间一切都那么顺手又顺眼了。

工具搞定后,先来看看如何在C++中创建可以在Blueprint中使用的全局函数:

  • 创建一个继承自UBlueprintFunctionLibrary的C++类即可。不知道为什么在Editor中不能直接创建基于UBlueprintFunctionLibrary的C++类,但我们可以在VS中自己修改一下基类
  • 继承自UBlueprintFunctionLibrary类中,凡是具备BlueprintCallable属性的UFUNTION即可在Blueprint中被调用
  • 如果UFUNCTION还带有BlueprintPure属性,那么意味着这个函数不会修改任何游戏状态,因此无需exec链的触发(在Blueprint中体现为没有白线输入),可以在任何时刻被调用获取其结果
  • 作为随时可被调用的全局函数,都需要被声明成static函数

下面是具体代码:

MyBPLibrary.h:

复制代码
 Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. 
#pragma once 
 #include "MyBPLibrary.generated.h" 
 UCLASS() 
 class UMyBPLibrary : public UBlueprintFunctionLibrary 
 
     GENERATED_UCLASS_BODY() 
     UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Test"
    static FString GetHappyMessage(); 17 18 UFUNCTION(BlueprintCallable, Category = "Test"
     static bool SaveToFile(FString Dir, FString Name, FString Text, bool Overwrite = false); 
    UFUNCTION(BlueprintCallable, Category = "Test")
     static bool MakeDir(FString Dir);
};
复制代码

MyBPLibrary.cpp:

复制代码
1 // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. 2 3 #include "HelloWorld.h" 4 #include "MyBPLibrary.h" 
 UMyBPLibrary::UMyBPLibrary(const class FPostConstructInitializeProperties& PCIP) 
: Super(PCIP) 
 
 

 FString UMyBPLibrary::GetHappyMessage() {
     return FString("Hello, I'm Happy!"); 
 
bool UMyBPLibrary::MakeDir(FString Dir) { 
     return FPlatformFileManager::Get().GetPlatformFile().CreateDirectory(*Dir); 

bool UMyBPLibrary::SaveToFile(
 FString Dir, 
FString Name,
 FString Text, 
 bool Overwrite 
     IPlatformFile& F = FPlatformFileManager::Get().GetPlatformFile(); 
     if (!F.DirectoryExists(*Dir)) 
         
             F.CreateDirectoryTree(*Dir); 
             if (!F.DirectoryExists(*Dir)) 
             
                   return false
              
          
           Dir += "\"
           Dir += Name; 
      if (!Overwrite) 
     
            if (F.FileExists(*Dir))  
                    return false
      
       return FFileHelper::SaveStringToFile(Text, *Dir); 
}
复制代码

这个例子中实现了3个全局函数:GetHappyMessage,MakeDir以及SaveToFile,其中GetHappyMessage为无副作用的函数。它们在Blueprint中就可以这么用了:

在调试的过程中遇到的一些问题在这里说一下:

  • 在Rama的例子中,对文件的操作都是基于GFileManager,但在实际写代码时发现这个全局变量不存在,只能通过FPlatformFileManager来实现相关操作,可能是Rama写教程之后UE4的代码又发生过变化
  • 在Blueprint中,那些紫色的节点代表一个String类型的输入,但是这里需要注意:在输入框里不要将字符串包围在双引号之间了,也不要对特殊字符转义,系统会帮你处理。一开始不清楚在这里卡了好久
  • 如果你自己的类是继承自PlayerController,那么可以使用ClientMessage来输出一些调试信息;但如果不是(比如上面的这个例子),则可以使用UE_LOG宏,宏的用法和Rama教程里的用法也不太一样了,需要自己去研究一下

通过在C++中实现供Blueprint调用的全局函数,就实现了Blueprint和C++交互的一种常用途径。后面再写一些其他交互方式的实现方法。


C++触发Blueprints事件

如果想实现只供某一个类使用的Blueprint函数,方式是类似的,只是不要再继承UBlueprintFunctionLibrary类,同时函数也无需再声明成static即可。

虽然能够在Blueprint中调用一个C++实现的方法是很不错,但在实际中我们还会需要其他的交互方式,比如由C++代码去触发一系列的Blueprint动作,以及让Blueprint能够和C++类的某些属性变量直接进行交互。

我们先来看看如何将C++类中的某些属性变量暴露出去,让Blueprint(或Editor)能够看见、读或写这些变量,从而实现和C++的通信。

其实非常简单:只需要在C++类的头文件中这样声明一下就可以了:

1 2 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerMusicSkill") 3 int32 MusicSkillLevel;

只需要使用UPROPERTY宏,在加上一些枚举属性,就可以让一个变量以开发者希望的方式暴露给Blueprint,其中:

  • EditAnywhere表示该变量可以在Editor中任意进行修改,而VisibleAnywhere则表示Editor中只能看、但无法修改这个变量,还有几个其他的可选项供选择,可以自行研究代码
  • BlueprintReadWrite表示该变量可以在Blueprint中读或写,BlueprintReadOnly则表示在Blueprint中只能读
  • Category表示在Editor和Blueprint列表中这个变量归到那一分类,这主要是一个方便开发者寻找的功能,没有其他特别作用
  • 头部的注释不仅仅是代码中的注释,它也会作为这个参数的帮助提示显示在Editor的界面上

上面的代码在编译后,只需要在Editor中基于这个类创建一个Blueprint,然后就能够在它的Default属性界面看到下面的内容:

相当不错,而且简单。下面再来看看关键的:如何让C++去触发Blueprint,同时给Blueprint传递信息?

其实也非常简单,主要是使用UFUNTION+BlueprintImplementableEvent属性:

1 UFUNCTION(BlueprintImplementableEvent, meta = (FriendlyName = "Music skill is GOOD")) 2 virtual void MusicSkillGood(int32 CurrentSkill);

其中:

  • BlueprintImplementableEvent表示下面定义的函数会触发Blueprint里的一个事件,但事件触发后如何处理则由Blueprint自行实现,C++代码不负责,它只负责在适当的时候调用下面的函数并传递参数数据而已
  • meta相关内容和Category类似,主要是给用户提供一个更容易分辨的信息。在这里这个自定义事件在Blueprint中就会被显示为“Music skill is GOOD”,没有其他作用
  • MusicSkillGood就是用来在C++中触发Blueprint事件的函数,它一定要被定义为虚函数,而且返回值一定要为void,因为这个函数的实现不由C++来做,它只是提供一个触发的手段,且所有的数据都通过其参数传递给Blueprint

完整的类代码如下:

MyPlayerController.h:

复制代码
#pragma once
#include "GameFramework/PlayerController.h" 
 #include "MyPlayerController.generated.h"
 UCLASS() 
 class AMyPlayerController : public APlayerController 
 
       GENERATED_UCLASS_BODY() 
       UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerMusicSkill"
       int32 MusicSkillLevel;
       UFUNCTION(BlueprintImplementableEvent, meta = (FriendlyName = "Music skill is GOOD")) 
       virtual void MusicSkillGood(int32 CurrentSkill); 
       virtual void PlayerTick(float DeltaTime) OVERRIDE; 
};
复制代码

MyPlayerController.cpp:

复制代码
1 // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "HelloWorld.h" 
#include "MyPlayerController.h" 5
AMyPlayerController::AMyPlayerController(const class FPostConstructInitializeProperties& PCIP) 
: Super(PCIP) 
 

void AMyPlayerController::PlayerTick(float DeltaTime) 
      Super::PlayerTick(DeltaTime); 
      if (MusicSkillLevel > 50) 
       MusicSkillGood(MusicSkillLevel); 
       
 }
复制代码

上面代码的主要逻辑就是在每个Tick检查当前类实例的MusicSkillLevel变量值是否大于50,如果是,则对Blueprint触发MusicSkillGood事件,并将MusicSkillLevel的值传递过去。最终在Blueprint中可以这样用:

这个Blueprint的逻辑就是:

  • 每当用户按M键,就将MusicSkillLevel变量的值加1
  • 而如前所述,C++代码会在每个Tick检查MusicSkillLevel的值是否大于50,如果大于50了,那么就会在每个Tick都触发一次MusicSkillGood事件(由于meta的设置,这个事件被显示成“Music Skill is GOOD”)
  • 当用户按了足够多的M键导致MusicSkillLevel变量的值超过50时,在游戏中就能看到MusicSkillLevel的当前值在刷屏了

到此为止,Blueprint已经和C++代码实现了完全的交互:Blueprint能够主动调用C++中的函数,C++也能主动触发Blueprint的事件,而且双方还能通过暴露的变量进行交互。这样一来,整个游戏的底层平台模块完全可以用C++实现,然后给上层的Blueprint提供调用接口,由Blueprint来利用、组织这些模块来实现上层的完整游戏逻辑。这种结合方式既保留了C++的性能优势,又充分利用了Blueprint的易用性和灵活性来让游戏开发保持快速地迭代。这应该就是UE4所推崇的最佳开发模式。

原文链接

著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台