开始
- 学习UE过程中记录一些基础的流程,方便后续回顾,并且形成一个demo具体展示见B站:https://www.bilibili.com/video/BV1Mm4y147yW/?vd_source=015738998f609ed20f860258eed94865
- 分为以下几个主要的部分,主要是人物的基本移动,骨骼动画,网络RPC的简介以及武器开发与死亡竞赛开发,其中武器开发与死亡竞赛开发是重点。
1 人物基本移动
//.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MultiFPSTeach/Public/FPSTeachBaseCharacter.h"
// Sets default values
AFPSTeachBaseCharacter::AFPSTeachBaseCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
#pragma region Component
PlayerCamera = CreateAbstractDefaultSubobject(TEXT("PlayerCamera"));
if (PlayerCamera) {
PlayerCamera->SetupAttachment(RootComponent);
PlayerCamera->bUsePawnControlRotation = true;
}
FPArmsMesh = CreateAbstractDefaultSubobject(TEXT("FPArmsMesh"));
if (FPArmsMesh) {
FPArmsMesh->SetupAttachment(PlayerCamera);
FPArmsMesh->SetOnlyOwnerSee(true);
}
auto c =GetMesh();
c->SetOwnerNoSee(true);
c->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
c->SetCollisionObjectType(ECC_Pawn);
#pragma endregion
}
// Called when the game starts or when spawned
void AFPSTeachBaseCharacter::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AFPSTeachBaseCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
#pragma region Input
// Called to bind functionality to input
void AFPSTeachBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
InputComponent->BindAxis(TEXT("MoveRight"), this,&AFPSTeachBaseCharacter::MoveRight);
InputComponent->BindAxis(TEXT("MoveForward"), this, &AFPSTeachBaseCharacter::MoveForward);
InputComponent->BindAxis(TEXT("TURN"), this, &AFPSTeachBaseCharacter::AddControllerYawInput);
InputComponent->BindAxis(TEXT("LookUp"), this, &AFPSTeachBaseCharacter::AddControllerPitchInput);
InputComponent->BindAction(TEXT("Jump"), IE_Pressed,this,&AFPSTeachBaseCharacter::JumpAction);
InputComponent->BindAction(TEXT("Jump"), IE_Released, this, &AFPSTeachBaseCharacter::StopJumpAction);
}
void AFPSTeachBaseCharacter::MoveRight(float AxisValue)
{
AddMovementInput(GetActorRightVector(), AxisValue,false);
//GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, "MoveRight\n");
}
void AFPSTeachBaseCharacter::MoveForward(float AxisValue)
{
AddMovementInput(GetActorForwardVector(), AxisValue, false);
}
void AFPSTeachBaseCharacter::JumpAction()
{
Jump();
}
void AFPSTeachBaseCharacter::StopJumpAction()
{
StopJumping();
}
#pragma endregion
//--------------------------------------------------------------------------
//.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/Character.h"
#include "FPSTeachBaseCharacter.generated.h"
UCLASS()
class MULTIFPSTEACH_API AFPSTeachBaseCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AFPSTeachBaseCharacter();
private:
#pragma region Component
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Character , meta = (AllowPrivateAccess = "true"))
UCameraComponent* PlayerCamera;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Character, meta = (AllowPrivateAccess = "true"))
USkeletalMeshComponent* FPArmsMesh;
#pragma endregion
protected:
// Called when the game starts or when spawned
#pragma region Input
virtual void BeginPlay() override;
void MoveRight(float AxisValue);
void MoveForward(float AxisValue);
void JumpAction();
void StopJumpAction();
#pragma endregion
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};
1.1 挂载组件
PlayerCamera = CreateAbstractDefaultSubobject(TEXT("PlayerCamera"));
if (PlayerCamera) {
PlayerCamera->SetupAttachment(RootComponent);
PlayerCamera->bUsePawnControlRotation = true;
}
- 根据命名直接创建组件,并且设置它的父组件,之后可以直接使用组件的指针修改他具体的值而不用到蓝图里面修改。
1.2 设置输入
InputComponent->BindAxis(TEXT("MoveRight"), this,&AFPSTeachBaseCharacter::MoveRight);
void AFPSTeachBaseCharacter::MoveRight(float AxisValue)
{
AddMovementInput(GetActorRightVector(), AxisValue,false);
//GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, "MoveRight\n");
}
- 采用代理的方式,将设置的输入绑定到对应的输入函数中,其中源码的逻辑流程为AddMovementInput调用Internal_AddMovementInput最终获取一个ControlInputVector的值,这个值通过Internal_ConsumeMovementInputVector函数被ConsumeInputVector获取,再用到TickComponent函数中,最终获取了当前的inputvector,之后通过ControlledCharacterMove(InputVector, DeltaTime);的方式实现移动
InputComponent->BindAction(TEXT("Jump"), IE_Pressed,this,&AFPSTeachBaseCharacter::JumpAction);
InputComponent->BindAction(TEXT("Jump"), IE_Released, this, &AFPSTeachBaseCharacter::StopJumpAction);
void AFPSTeachBaseCharacter::JumpAction()
{
Jump();
}
void AFPSTeachBaseCharacter::StopJumpAction()
{
StopJumping();
}
- 至于jump类的源码,通过character的固有函数jump把bPressedJump设为true,之后再CheckJumpInput中,检测到bPressedJump会首先有一个条约次数的检测,用来检测二段跳的,之后一个canjump然后dojump
InputComponent->BindAxis(TEXT("TURN"), this, &AFPSTeachBaseCharacter::AddControllerYawInput);
InputComponent->BindAxis(TEXT("LookUp"), this, &AFPSTeachBaseCharacter::AddControllerPitchInput);
PlayerCamera->bUsePawnControlRotation = true;
- 摄像头旋转,直接绑定旋转到角色自身旋转,本质上是使control旋转,但是注意pawn只会use controller yaw,pawn跟随control一样左右转,但不会低头抬头,同时绑定摄像头旋转使用control旋转。
2 骨骼动画
- 直接导入动画初学者包,更换skeleton
- 制作一个BPanimation传给mesh,其中使用状态机,设置状态,并且从BPattacker获取变量即可。
- 制作一个混合动画,blend space,设置几个极限值,然后差值动画
3 网络同步RPC
- 设置静步shift操作时卡顿,作为客户端卡顿,作为服务器不卡顿
- 区分服务器端操作还是客户端操作的RPC,通俗来说本地的效果展示都放在客户端上做,服务端主要处理一些需要同步的事情,然后由服务端来调用客户端的RPC即可。另外多播操作通常用于一些对其他所有人均可见的效果,例如第三视角的动画展示,枪体的开火粒子动画等
4 武器开发
- 客户端武器开发,服务端武器开发,本质上你看到的别人的武器都是服务端上别人的武器,地上的武器也是服务器端的,本地的只有手上的事,所以本地的做动画,开火逻辑,服务器端的做拾取,做碰撞,并且ownernosee,一旦自己获取就看不见了,只能看见自己客户端的和不是自己的服务器端的
4.1 客户端武器开发 FP
- 目前啥也没有,装备的过程中就是当服务端武器发生碰撞事装到第一人称上
- 之后设置开火,通过character里面存的clientweapon获取到武器,然后调用武器的播放动画
- 动画montage,在slot中的动画优先播放
- DisaplayWeaponEffect播放一些特效,声音,闪光之类的
- 镜头抖动
- UI创造在controller里面而不是character里面因为死亡竞赛模式下面character可能会死
4.2 服务器端武器开发 TP
- 创建一个武器actor,添加碰撞,重力,并且启动模拟,放在场景中就会掉在地上
- 给TP角色添加一个右手的插槽,武器就放在这里
- 使用代理,碰撞检测,之后取消模拟与碰撞,然后设置owner,插到 skeleton 的对应插槽中,这都是第三人称的操作
- 设置出生自带,使用RPC的方式,在服务端获取authority的时候原地spawn生成一个服务端武器,然后设置这个武器复制到客户端,客户端发生碰撞之后会触发碰撞的流程装备上,服务端需要自己装备
- 服务器自己射击的时候也要播放声音与特效,这是给别的客户端看的TPS的效果
4.3 射击相关
- 射击子弹卸载服务端上,注意子弹减少要服务器上变客户端也跟着变
//weaponserver.h
UPROPERTY(EditAnywhere,Replicated)//服务器上改变,客户端也跟着变
int32 ClipCurrentAmmo;//弹匣剩余子弹
//weaponserver.cpp
void AWeaponBaseServer::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
{
DOREPLIFETIME_CONDITION(AWeaponBaseServer,ClipCurrentAmmo,COND_None);
}
- 创建UI,并且在开火时和出生时客户端调用修改UI,这个修改UI的函数是来自controller的,然后controller的函数在蓝图中实现,在蓝图中在直接调用UI蓝图的内部event,传参即可。
- 产生伤害,对应不同部位伤害不同,physics material设置成项目中的physics type,然后通过射线检测能直接拿到physics material再判断
- 增加血条UI并且设置UI动画
- 连续射击以及后坐力检测,射击的时候摇晃camera,camerashake并不会修改camera的数据,因此后坐力要单独做
4.3.1camerashake只会修改camera的 ViewTarget ,所以不会改变
- 后坐力使用curve float差值,每次开火成功之后给摄像机一个偏移
- 移动式偏移,射线检测的时候终点给一个偏移
4.3.2 reload相关
- 客户端播放手臂和枪的动画,枪自己播就行,手臂要一个montage
- 服务端播一个第三人称的montage
- 做一个延迟,动画播完才改武器子弹,然后修改UI
- 设置不能重复reload,并且开枪时不能reload,reload时不能开枪
4.4 其他武器开发
4.4.1 M4A1 MP7
- 设置混合动画,使用接口设置混合动画的blendpose
- 复用AK47即可,注意修改mesh,后坐力和屏幕抖动,音效,动画,以及mesh中的插槽即可
4.4.2 手枪 degel设计
- 设置成副武器,之后修改所有需要服务端武器的地方都是动态获取,玩家身上保留主武器和副武器两个变量,根据active的武器类型决定取哪个变量
- 后坐力设置每一次设计都增大,一段时间之后回复
4.4.3 狙击枪设计
- 主武器,射击时要等动画完全放完才能再次射击
- 开镜操作,设置UI,摄像机景深,隐藏枪和手臂
- 注意添加UMG相关内容要在项目CS文件夹下面添加UMG
5 DeathMatch开发
- 生成玩家,不在同一个位置,获取所有playerstart,然后存到数组,每生成一个索引+1,delay再生成
5.1 UI设计
- 增加排行榜UI,verticalbox里面用每个玩家的horizontalbox填充,之后每次变化更新。顶部增加计时UI
5.1.1 排行榜设计
- 在gamemode里面存一个map,包含所有controller以及对应的name和score,这个是最核心的数据
- 当有玩家加入的时候,先把自己的数据传到更新给gamemode,然后gamemode再下发给所有的controller他包含的所有人的name和score变量更新自己的UI
- 击杀,死亡加减分数,然后再刷新所有人的UI,蓝图不能设置结构体内部变量,因此删除再添加相同的key value
- 死亡之后删除pawn和附属的两把武器,然后再生成一个。
- TODO装备和卸载武器时播放动画montage再卸载或删除
- 初始化随机武器,并且设置计时
6 展示
- 基础移动展示,跑跳静步
- 不同武器第一视角展示,开火,声音,粒子特效,换弹,狙击枪开镜,后坐力,镜头摇晃,弹孔,是否连发等。
- 子弹UI以及血量UI展示。准星动画展示
- 死亡竞赛模式展示,击杀敌人,重生,排行榜加分,倒计时,倒计时结束回主界面
- 服务器及主界面展示,点击加入,pin之类的
- 思考
- rider比vs好用太多了,vs会莫名其妙添加头文件,打包时候报错注意去看看头文件
- 需要有良好的思维,写的代码要可以扩展,不要写死,比如主副武器设置,动态获取当前武器类型之类的,能动态获取尽量不要静态获取。为了给未来武器切换之类的留好接口
- 未来展望
- 阴影效果处理,第一人称看不见自己,
- 敌人AI设置,自动追逐玩家并射击,
- 搜寻第一人称动画,找到装备和卸载武器动画,做武器切换
- 游戏模式修改,基础的从重生自带武器到没有武器,地上捡武器,再到买武器
- 游戏UI的修改,倒计时结束全屏展示排行榜后再退回主界面