赞
踩
前言:本项目为siki学院发布于哔哩哔哩的开源项目,本文真理了我在复刻项目时遇上的问题,并且记录了开发过程。
通过拖拽将项目资源文件夹拖拽如content文件夹下,之后资源文件夹便会在内容文件夹下显示
1.在内容下新建maps文件夹,在文件夹内保存当前关卡,并命名为mainmap
2.打开项目设置,并将当前地图设置为默认开始和结束地图
3. 在内容文件夹下创建蓝图文件夹
4.搭建背景导入材质
1.添加新项中添加C++类,其中父类选择Pawn(可以接受用户输入)创建后选择共有
说明:创建后的c++类会生成两个文件,其中.h文件用于声明组件,.cpp用于具体实现
1. 在.h文件proteted下用UStaticMeshComponent创建静态网格体组件,用于显示飞船
2. 声明一个球体组件用于检测碰撞,并且用UPOPERTY()为组件声明一些属性,让此组件在蓝图中可见,Category的意义为类别,在此指为此组件指定为component类别
备注1:因为此时修饰的是组件,因此设定为visibleAnywhere就可在蓝图中修改。若是在修饰常量属性,则需定义为editoranywhere,否则修饰的这个属性在蓝图中只能看见不能修改
备注2:当声明UStaticMeshComponent时可能报错,因为没有引入头文件,可以先在最开始class
UStaticMeshComponent 声明一下这个类,在.cpp中统一引入头文件,减少编译时间
底层:
(1)静态网格体组件(StaticMeshComponent) 可创建 的实例。静态网格体(Static Mesh) 是一个由多个静态多边形构成的几何体,是虚幻引擎 4 的基本场景构建单位。除用于构建场景,静态网格体还能用于创建运动对象(如门或电梯)、刚体物理对象、植物和地形装饰物、程序化创建的建筑、游戏目标和许多其他的视觉元素
(2) USphereComponent通常用于简单碰撞的球体。边界在编辑器中呈现为线条。
创建碰撞组件对象:对象名=CreateDefaultSubobject<类型名>(TEXT("蓝图中显示名称")),并将其设定为根组件,后续将飞机组件绑定在根组件(碰撞组件)上。
备注:UStaticMeshComponent需要引入头文件Components/StaticMeshComponent.h
USphereComponent需要引入头文件Components/SphereComponent.h
底层:
(1)FObjectInitializer::CreateDefaultSubobject :用于创建组件或子对象的类
语法:template<class TReturnType>
TReturnType * CreateDefaultSubobject
(
UObject * Outer,
FName SubobjectName,
bool bTransient
) const
(2)这里的创建对象本质与
template<class NameType, class AgeType>
class Person
p1=Person<string,int>(“孙悟空”,999) 一样都是c++创建类模板对象
(3) 绑定组件用对象中自带的SetupAttachment(绑定目标组件名)的方式
示例:ShipSM->SetupAttachment(RootComponent); //将飞船绑定到根组件上
1.1 .基于上面编写的c++类,创建基于它的蓝图类
1.2 在ShipSM中添加飞机模型作为静态网格体的显示材质
1.3 由于我们单独声明创建了一个CollisionComp用于碰撞检测,所以关闭飞机自带的碰撞检测函数
备注: UStaticMeshComponent会有碰撞预设
1.4 设置飞机为主角
方法1:搜索栏搜索poss将飞机设置为player0
方法2:
1.新建gamemodebase蓝图类
2.项目设置中修改设置
3.在项目中删除飞机,飞机将会在gameplayer上生成
2.1 在c++类中创建摄像机组件
在.h文件中定义camera组件,注意现在最开始声明class UCameraComponent
在.cpp文件中实例化相机组件,并绑定到根组件下
编译后即可在蓝图中看到相机组件
调整镜头参数
注意:
UCameraComponent需要引入头文件#include"Camera/CameraComponent.h"
在.h文件中
声明LookAtCursor()函数用于使飞机面向鼠标
声明APlayerController类型变量,因为该变量内含有显示鼠标的属性供项目使用
在.cpp文件的BeginPlay()函数下获取pc,并将APlayerController类型下,bShowMouseCursor属性设置为true用于显示鼠标
编写LookAtCursor()函数
设计思路:获取鼠标位置(用DeprojectMousePositionToWorld())
存储目的位置鼠标的x,y和角色的z用(可以直接.出来)
用UKismetMathLibrary::FindLookAtRotation()获取旋转角度
改变飞机朝向
在Tick函数中调用LookAtCursor()函数
注意:
因为将pc定义为APlayerController类型,在此需要用Cast<>()将获得到的GetController进行强制类型转换
底层:
1.APlayerController类型--PlayerControllers are used by human players to control Pawns.
类型说明: PlayerController 实现了从玩家获取输入数据并将其转换为动作的功能,例如移动、使用物品、 发射武器等。
其下bShowMouseCursor属性用于显示鼠标,为bool型变量
APlayerController头文件:#include"GameFramework/PlayerController.h"
2.APlayerController::DeprojectMousePositionToWorld(A, B)
说明:将当前鼠标2D位置转换为世界空间3D位置和方向。将位置存储在自定义变量A中,将方向存储在自定义变量B中。其中A和B都是FVector类型变量,如果无法确定值,则返回 false。
3.Cast<a>(b)---可用于将b强转成a类型
4.FVector类型变量:具有浮点精度的分量(X、Y、Z)组成的 3-D 空间中的矢量。
5.FRotator类型变量:实现用于旋转信息的容器。所有旋转值都以度为单位存储。
6.UKismetMathLibrary::FindLookAtRotation(A,B)
说明:获得从A位置到B位置的角度,其中A,B类型为FVector
UKismetMathLibrary头文件:#include"Kismet/KismetMathLibrary.h"
7.几个方法:
(1)SetActorRotation():旋转角色角度
(2)GetActorLocation():获得角色位置
摄像机随着飞船旋转的原因:摄像机是飞船的子组件
解决方方:利用弹簧臂组件
声明弹簧臂组件
实例化弹簧臂
弹簧臂头文件:#include"GameFramework/SpringArmComponent.h"
- SpringArmComp=CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
- SpringArmComp->SetupAttachment(RootComponent);
将摄像机组件换绑到弹簧臂下
在蓝图中调整弹簧臂角度
在弹簧臂蓝图中关闭摄像机选装
在飞机视口更改飞机朝向
注意:ue4中根组件无法更改朝向,因此无法直接在飞机视口旋转,应先将碰撞组件更改为跟组件
接下来在蓝图中修改调整朝向即可
声明两个函数,使其可以在x和y方向移动
tip:点击灯泡可以快速在.cpp中创建定义
两个函数的具体实现:
Move()函数的编写
底层:
APawn::AddMovementInput
沿给定的世界方向矢量(通常归一化)添加按“ScaleValue”缩放的运动输入。
作用:获得用户输入所对应的向量值
语法:virtual void AddMovementInput
(
FVector WorldDirection, //世界方向 在世界空间中应用输入的方向
float ScaleValue, //移动尺度 缩放以应用于输入。这可以用于模拟输入,即0.5的值应用正常值的一半,而-1.0将反转方向。
bool bForce //如果为 true,则始终添加输入,忽略 IsMoveInputIgnored() 的结果。
)
将函数与用户输入绑定
在声明中找到对应函数
跳转到对应函数定义进行轴映射绑定
底层:
常用两种输入映射绑定Axis Mappings轴映射和动作映射Action Mappings
Axis Mappings轴映射:通常用于持续性的输入,需要传递float ScaleValue,如用户按住wasd进行移动
Action Mappings动作映射:通常用于低频的输入,不需要传递float ScaleValue,如用户按一下就能跳跃
BindAxis参数含义:
PlayerInputComponent::BindAxis(绑定名称,作用在哪个action下面,调用哪个函数)
在项目设置的输入中绑定轴映射按键
由于本项目继承的是pawn类,并不会自动应用移动
解决pawn移动问题
声明移动函数并定义,随后在每一帧调用移动函数
Move函数作用:AddMovementInput已经获得了用户每一次输入对应的向量值,接下来需要再每一帧进行调用,先用ConsumeMovementInputVector()获得用户每一次输入对应的向量值,再用AddActorLocalOffset(),让角色移动这个向量值对应的量即可
出现问题:飞机的ad移动正常,ws移动变成了z轴方向的上下移动,修改代码后恢复为前后移动
总结:FVector(1,0,0)对应Fvector::ForwardVector 为掌管y轴的方向
FVector(0,1,0)对应Fvector::RightVector 为掌管x轴的方向
· FVector(0,0,1)对应Fvector::UpVector为掌管z轴的方向
AActor::AddActorLocalOffset
将增量添加到此组件在其本地参考系中的位置。(将向量幻化成切 实的移动)
APawn::ConsumeMovementInputVector
返回挂起的用户输入的方向向量并将其重置为零。(即用户输入一次,获取一次对应向量)
调整飞机移动速度
方法1:
声明参数speed,并在构造函数中赋初值
在控制移动函数中使ConsumeMovementInputVector()乘上速度
结果发现飞机移动速度还是很快,原因是速度值设置太大,除了减小速度值,还可以用乘以 DeltaTime(每一帧调用的时间)的方法解决。故修改Move函数,传入DeltaTime
方法2:
直接获取DeltaTime
引入头文件:#include "Public/Misc/App.h" 调用FApp::GetDltaTime()
作如下修改
这样就不用再向move里传参了
方法3:
直接改
创建子弹类声明子弹网格体,并定义
注意引入 UStaticMeshComponent头文件 #include "Components/StaticMeshComponent.h"
创建空组件
创建子弹的蓝图类后发现子弹朝上,因为子弹是根组件,不能直接旋转,为了旋转,新建一个他的父类组件并设置为根组件进行辅助。
旋转子弹
在飞船的.h文件中声明bullet,并将其与Bullet了.cpp中的子弹相关联,设置为可在蓝图内编辑,因为仅用于蓝图赋值,所以不用实例化出来
在飞机中创建开火口空组件 ,并绑定到飞机组件上
声明函数,完成开火工作
用getWorld()->spawnActor<生成类型>() 将子弹实例化出来,其中生成角色函数需要传入生成角色,生成位置,生成时旋转角度,SpawnParameters(传递给 SpawnActor 函数的可选参数的结构)四个参数
注意:这里要先判断bullet是否存在,否则程序会崩溃
对开火进行操作映射,并将函数绑定在按下鼠标左键开火
注意:因为在spaceship中调用了bullet,所以需要在头文件中引入#include"Bullet.h"
在蓝图中赋值bullet,因为定义时用了TSubclassOf<>,因此只能赋值为bullet或者bullet的子类,
这里选择bp_bullet因为需要生成的是蓝图中的模型
在飞机视口中旋转调整开火位置
1.TSubclassOf<>
TSubclassOf 是提供 UClass 类型安全性的模板类。例如您在创建一个投射物类,允许设计者指定伤害类型。您可只创建一个 UClass 类型的 UPROPERTY,让设计者指定派生自 UDamageType 的类;或者您可使用 TsubclassOf 模板强制要求此选择。以下示例代码展示了不同之处:
- /** type of damage */
- UPROPERTY(EditDefaultsOnly, Category=Damage)
- UClass* DamageType;
- /** type of damage */
- UPROPERTY(EditDefaultsOnly, Category=Damage)
- TSubclassOf<UDamageType> DamageType;
上图两个声明中,第二种效率更高,因为模板类告知编辑器的属性窗口只列出派生自 UDamageType 的类(作为属性选择)
说人话:限制了选择范围
例子:在本项目中两种定义方式对比如下
1.用uclass* Bullet定义
2.用TSubclassOf<ABullet> Bullet定义
2.GetWorld()->SpawnActor()
用于创建一个新的Actor生成 Actors 的过程是使用 UWorld::SpawnActor()
函数完成的。该函数创建指定类的一个新实例 并返回到那个新创建的 Actor 的指针。
参数列表:生成 Actors | 虚幻引擎文档 (unrealengine.com)
3.UsceneComponent组件
带有3D变化(位置、缩放、朝向)的ActorComponent。
UsceneComponent能够形成一定的成绩结构。Actor的位置、缩放、朝向取决于作为根组件Root的UsceneComponent。
Actor的根组件必须是UsceneComponent,UsceneComponent是有3D变化(位置、缩放、朝向)数据的。
声明并定义抛射运动组件
注意抛射运动组件使用时需要引入头文件
#include "GameFramework/ProjectileMovementComponent.h"
在组件视口去掉重力并且赋值速度
控制子弹连续发射(利用计时器)
声明函数控制开火的开始与结束
定义函数
修改输入绑定
1.计时器需要引入头文件 #include"TimerManager.h"
2.GetWorldTimerManager().SetTimer(时间句柄(FTimerHandle类型),作用对象,调用方法(fun),调用时间间隔(float),是否循环调用(bool),第一次调用等待多久(float))
3.GetWorldTimerManager().ClearTimer(句柄(FTimerHandle类型))
4.FTimerHandle类型一个声明句柄的类型,可以理解为一个唯一标识符
7.1创建敌人
创建Enemy的C++类
7.2敌人追逐主角
创建MoveTowardsPlayer()函数控制敌人向主角移动
因为需要控制敌人向主角移动所以要在敌人的.h文件中声明一个主角,便于获取主角
在.cpp文件中利用UGameplayStatics::GetPlayerPawn获取主角,并且在将Pawn类型强制转换为ASpaceShip类型后赋值给SpaceShip
注意:调用这个方法需引入头文件#include "Kismet/KismetMathLibrary.h"
用spaceship的向量位置减去敌人飞机的向量位置即可获得由敌人指向spaceship的方向向量,再利用AddActorLocalOffset()让敌人的飞机沿着这个方向移动, 并且调用 FindLookAtRotation每次让步敌人的飞机朝向面向玩家飞机
底层:
1.UGameplayStatics:GetPlayerPawn(WorldContextObject,PlayerIndex)
解释:返回指定玩家索引处的玩家控制器的 pawn。
2.UKismetMathLibrary::FindLookAtRotation(FVector WorldDirection,float ScaleValue,
bool bForce)
解释:沿给定的世界方向矢量(通常归一化)添加按“ScaleValue”缩放的运动输入。
7.2设置敌人颜色随机生成
采用蓝图的事件图表模式开发
敌人飞机现有材质信息
在Enemy.h下声明函数并将其设为BlueprintImplementableEvent(蓝色可实施事件)
在敌人蓝图的事件图标中搜索插入该函数,设定两个整形变量BCIndex和WCIndex,并将其进行随机数设定
生成设置飞机这个材质的动态材质实例,并且将生成的两个参数机身(color1)和机翼(color2)对应的rgb传递给材质的color1和color2参数,两者再将这两个参数c1和c2返还给材质实例,最后将实例的材质参数赋值给飞机的材质
注意:在ShipSM的UPROPERTY中加入BlueprintReadOnly,否则会因没有权限而报错
7.3敌人出生时变大
利用时间轴完成
新建时间轴,双击进行编辑
注释:play相当于调用,update相当于tick每一帧调用,finished为时间轴结束后调用
添加浮点类型轨迹,创建变化曲线,变化通过右键关键帧,添加用户平整切线实现
添加Set Actor Scale 3d,并且将scale的值赋值给它
7.4创建敌人生成器并找到敌人生成位置
新建c++类,命名为EnemySpawner,在.h中获取敌人,并且利用UBoxComponent组件框出一片生成敌人的区域
注意:UBoxComponent头文件:#include"Components/BoxComponent.h"
生成蓝图拖入场景并且调整位置大小,使其覆盖整个游戏区域
获取玩家飞机,声明函数用来找到适合生成的位置,避免敌人离玩家太近
实例化得到飞机
注意:GetPlayerPawn()头文件 #include"Kismet/GameplayStatics.h"
获得位置函数的实现
总结:
1.(A,B).Size()获得A和B两个位置之间的距离
2.UKismetMathLibrary::RandomPointInBoundingBox(生成中心,范围)
解释:用来在方形组件内随机找到个点
3.UBoxComponent* A->Bounds.Origin ------容器中心点
4.UBoxComponent* A->Bounds.BoxExtent -------容器整个范围
7.5 生成敌人
思路:同控制飞机发射子弹,用GetWorld()->SpawnActor生成敌人,用定时器GetWorldTimerManager().SetTimer()控制敌人已一定时间间隔生成
控制敌人生成的函数
注意:1.头文件:#include"Engine/World.h"
2.旋转角度设为0旋转角度FRotator::ZeroRotator
3.声明并传入角色参数FActorSpawnParameters SpawnParameters;
控制敌人已一定间隔生成
注意:1.GetWorldTimerManager().SetTimer()的头文件---#include"TimerManager.h"
2.声明并填入时间句柄 FTimerHandle TimerHandle_Spawn
3.SpawnInterval为自行设置的时间间隔
8.1 设置边界
调整飞船碰撞预设,利用碰撞组件将场地框起来
8.2 子弹碰撞
思路:通过复写bullet的Super::NotifyActorBeginOverlap()方法进行碰撞检测
注意:1. if(Enemy)这种写法用来做非空判断
2.想用这个方法传入的角色的碰撞检测必须设定为overlap
3.Destroy()这个方法可以直接调用,在角色的作用域下
4.对NotifyActorBeginOverlap覆写时要先进行父类调用
底层:
1.Super::NotifyActorBeginOverlap()用于碰撞
语法:
virtual void NotifyActorBeginOverlap
(
AActor * OtherActor
)
8.3 主角死亡
思路:与子弹碰撞一样在发生敌人与飞机的碰撞检测后调用Destory()销毁敌人。
代码:
注意:
1.如果直接调用Destroy销毁主角会导致程序崩溃
2.注意修改调整飞机,墙体,敌人,子弹之间的碰撞预设,调整原则是让NotifyActorBeginOverlap()接到的角色是Overlap类型,其他之间的关系设为阻挡,通过自定义碰撞预设,调整类型及检测实现
3.通过UE_LOG();打印日志确定碰撞生效。
8.4 重新开始
思路:设计OnDeath()函数,当玩家飞机碰撞到敌人时调用。函数实现思路为先让飞机隐身(坠毁效果),之后调用计时器,在2秒后调用RestarLevel()函数重启关卡
OnDeath函数
RestarLevel函数
在碰撞后调用
细节优化:
设计bool类型bDead记录飞机生存状态,在之前功能中做判断,只有当玩家存货时才进行部分操作
其中在控制敌人在玩家死后不移动需要将飞机的bDead属性传出实现如下,单列出一个函数用于获取bDead私有属性
总结:
1.重新加载地图
利用UGameplayStatics::OpenLevel()实现,需要引入头文件#include"Kismet/GameplayStatics.h"
2.让物体消失
直接找到蓝图中根组件类型,将其类型设置为不可见,并将这个设置传到它的所有子类中 CollisionComp->SetVisibility()
3.在声明中直接定义函数
在函数前加FORCEINLINE关键字
8.5 控制敌人最大数量
思路:在spawner下设置最大敌人数量和当前敌人数量,在生成敌人前做判断,并且对外提供一个函数使得可以让子弹碰撞到敌人时减少当前敌人数量
声明最大敌人数量和当前敌人数量并在构造函数中初始化
在生成敌人前增加判断并且使当前敌人数量增加
提供对外函数使得可以减少当前敌人
在bullet内引入spawner,并且在游戏开始时获取EnemySpawner类
获取 EnemySpawner静态类,声明一个EnemySpawnArray数组,将静态类存储在数组里并且将其强转成AEnemySpawner类型并存储在先前定义好的EnemySpawner里,最后在子弹碰撞敌人时调用EnemySpawner静态类的DecreaseEnemyCount()函数减少计数器
总结:
1.将一个类传递给别的类:
方法需要头文件#include"Kismet/GameplayStatics.h"
在接收端用UGameplayStatics::GetAllActorsOfClass()查找指定类的世界中的所有 Actor,并将其存入指定的TArray<AActor*>数组当中
2.在控制台打印定义的参数
UE_LOG(LogTemp,Warning,TEXT("%s"),*FString::SanitizeFloat(参数名))
8.6 获取分数
思路:把分数保存在gamemode里。通过新建一个ShipGameModeBase的C++类,在类中加入分数增加的方法,之后让蓝图中默认执行的BP_gamemode成为这个类的子类
新建C++类选择GameModeBase为父类
创造构造函数以及分数Score,并对外提供增加分数的接口
在敌人中创建OnDeath()处理敌人死亡的后续操作,修改前面代码逻辑,使子弹触碰到敌人时调用OnDeath函数,在OnDeath函数中处理敌人的销毁,个数的减少以及分数的增加
让敌人持有游戏模式
在Enemy的BeginPlay中中通过UGameplayStatics::GetGameMode()获取当前世界的gamemode,之后才能调用ShipGameModeBase中的IncreaseScore方法
总结:
1.获得gamemode的方法
UGameplayStatics::GetGameMode() 头文件#include"Kismet/GameplayStatics.h"
2.所有要保存在主角里的参数都可以保存在GameMode(游戏规则)里
3.在ue中在.h中引入自带的类型用U开头,引入自己定义的类型用A开头
例:AShipGameModeBase* MyGameMode;
USphereComponent* CollisionComp;
8.7 分数显示
思路:1.新建控件蓝图(在用户界面里),编辑画布面板
2.获取分数就要获取shipgamemode,在画布的事件图标中设计事件构造,使其获得shipgamemode并将其升为常量
3.在test中创建文本内容绑定,并更改颜色位置
4.在bp_spaceship的事件图标中编辑在游戏开始时创建控件并使其显示在视口上
在控件蓝图的设计器面板中编辑画布结构
编辑画布事件图标获取shipgamemode:获取游戏模式->将获得的游戏模式强转成定义的游戏模式->将其升级为一个变量(这样就能在游戏开始时获得定义的gamemode)
创建text的文本绑定并修改名字(上步后此时可以直接拖入game mode)
拖入game mode —> 获得game mode下的Score参数将获取的score参数和"Score:"字符串进行拼接(搜append选择字符串拼接) ---> 返回给GetCurrentScore函数
在bp_spaceship的事件图标中编辑在游戏开始时创建控件并使其显示在视口上
8.8 声音播放
导入音效,背景bgm直接拖入场景即可,开火音效与爆炸音效需要与c++类相结合,利用USoundCue类型引入声音,利用UGameplayStatics::PlaySoundAtLocation()方法调用声音;
在spaceship中创建USoundCue类型引入声音
设置在开火死亡时播放音效
生成两个音效对应的cue类型文件
在飞机蓝图中导入音效
总结:
1.声音类型 ------ USoundCue
需要头文件(#include"Sound/SoundCue.h" )
2.调用声音方法 ----UGameplayStatics::PlaySoundAtLocation()
需要头文件 (#include"Kismet/GameplayStatics.h")
8.9 粒子系统
创建一个新的粒子系统
设置粒子材质,并且设定为球体
将粒子绑定到主角尾部
在spaceship.h中声明粒子组件
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。