当前位置: 首页 > news >正文

UE5学习笔记21-武器的射击功能

一、创建C++类

        创建武器子弹的类,创建生产武器子弹的类,创建弹壳的类,生产武器子弹的类的父类是武器的类

        创建后如图,ProjectileMyWeapon类(产生子弹的类)继承自weapon类,Projectile(子弹的类),Casing(弹壳声音的类)

         在子弹的类中添加如下代码

//头文件中添加
private:UPROPERTY(EditAnywhere)class UBoxComponent* CollisionBox;// 碰撞盒的类//构造中添加
CollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("CollisionBox"));
SetRootComponent(CollisionBox);
CollisionBox->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic); //设置自身的碰撞类型
CollisionBox->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); //启动碰撞,启动触发器
CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); //设置对其他类型的碰撞
/* 第一个参数对角色 第二个参数是对角色是哪种碰撞 */
CollisionBox->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block);
CollisionBox->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block);

 二、武器蒙太奇(开火动画),添加开火功能

        1.将动画设置为加性的(让动作连贯),将瞄准和不瞄准的动画找到设置如图

        2.创建武器蒙太奇动画,在对应动画右键->创建->创建动画蒙太奇 (我将创建好的蒙太奇动画放到了其他文件夹中)

        3.新建蒙太奇片段,添加插槽,将瞄准的动画拖拽到蒙太奇中

 将之前的default片段名删除

 

添加另一端动画

将之前默认跳转的动画清空变成单独的动画 

 

选择插槽

最后样子

 三、绑定开火按键

        1.编辑->项目设置->输入->操作映射->添加fire鼠标左键

        2.在角色类中添加绑定

//角色类头文件
/* 发射子弹函数 */
void FireButtonPressed();
void FireButtonRelease();
/* 发射子弹函数 *///角色类源文件
void ABlasterCharacter::FireButtonPressed()
{if (Combat){Combat->FireButtonPressed(true);}
}void ABlasterCharacter::FireButtonRelease()
{if (Combat){Combat->FireButtonPressed(false);}
}//函数SetupPlayerInputComponent中
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &ABlasterCharacter::FireButtonPressed);
PlayerInputComponent->BindAction("Fire", IE_Released, this, &ABlasterCharacter::FireButtonRelease);

         3.声明一个蒙太奇动画类指针

//角色头文件	
// 当前类指针在界面中赋值 EditAnywhere可编辑 Combat在细节中找到对应设置
UPROPERTY(EditAnywhere , Category = Combat)
class UAnimMontage* FireWeaponMontage; // 动画蒙太奇类

        4.编译后在角色蓝图中设置对应的蒙太奇动画

        5.定义播放动画的函数,FName中的名字是蒙太奇动画中的名字

//角色类头文件
void PlayFireMontage(bool bAiming);//角色类源文件
void ABlasterCharacter::PlayFireMontage(bool bAiming)
{if (Combat == nullptr || Combat->EquippedWeapon == nullptr) return;UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();if (AnimInstance && FireWeaponMontage){AnimInstance->Montage_Play(FireWeaponMontage);/* 找到播放哪段动画 名字是动画中新建蒙太奇片段的名字 */FName SectionName;SectionName = bAiming ? FName("RifleAim") : FName("RifleHip");/* 找到播放哪段动画 */AnimInstance->Montage_JumpToSection(SectionName);}
}

         6.动画蓝图中的改变,武器开火实在装备武器后才可以所以如图,动画蓝图类中添加

slot中右侧细节可以选择槽

 new一个姿势

         7.在aimoffset中使用6中的姿势(在动作偏移中使用对应姿势)

        8. 在战斗组件类中添加代码(class ABlasterCharacter* Character指针我在角色类中的PostInitializeComponents函数赋值),使用了RPC函数多播功能让动画在每个客户端都能看见

void ABlasterCharacter::PostInitializeComponents()
{Super::PostInitializeComponents();if (Combat){Combat->Character = this;}
}
FVector_NetQuantize是FVector的网络传输的序列化的结构,减少网络带宽
//战斗组件类头文件
// 开火函数 
void FireButtonPressed(bool bPressed);/* Server RPC函数 */
UFUNCTION(Server, Reliable)
void ServerFire(const FVector_NetQuantize& TraceHitTarget);
/* Server RPC函数 *//* 多播函数 */
UFUNCTION(NetMulticast , Reliable)
void MulticastFire(const FVector_NetQuantize& TraceHitTarget);
/* 多播函数 *//* 命中线 */
void TraceUnderCrosshairs(FHitResult& TraceHitResult);
/* 命中线 */class ABlasterCharacter* Character;
bool bFireButtonPressed;//战斗组件类源文件
void UCombatComponent::FireButtonPressed(bool bPressed)
{bFireButtonPressed = bPressed;if (bFireButtonPressed){FHitResult HitResult;TraceUnderCrosshairs(HitResult);ServerFire(HitResult.ImpactPoint);}
}void UCombatComponent::TraceUnderCrosshairs(FHitResult& TraceHitResult)
{/* 屏幕中心为瞄准点 *//* 获得视口大小 */FVector2D ViewportSize;if (GEngine && GEngine->GameViewport){GEngine->GameViewport->GetViewportSize(ViewportSize);}/* 获得屏幕中心坐标 */FVector2D CrosshaurLocation(ViewportSize.X / 2.f, ViewportSize.Y / 2.f);FVector CrosshairWorldPosition; //世界空间中的相应 3D 位置FVector CrosshairWorldDirection; //在给定的 2d 点处远离摄像机的世界空间方向矢量bool bScreenToWorld = UGameplayStatics::DeprojectScreenToWorld(UGameplayStatics::GetPlayerController(this, 0),CrosshaurLocation,CrosshairWorldPosition,CrosshairWorldDirection);if (bScreenToWorld){FVector Start = CrosshairWorldPosition;TRACE_LENGTH 我设置为8000 在世界坐标的长度可以理解成武器的射程FVector End = Start + CrosshairWorldDirection * TRACE_LENGTH;/** bool LineTraceSingleByChannel(FHitResult& OutHit,             // 输出的碰撞信息const FVector& Start,           // 射线的起点const FVector& End,             // 射线的终点ECollisionChannel TraceChannel, // 碰撞通道const FCollisionQueryParams& Params = FCollisionQueryParams::DefaultQueryParam, // 可选的额外查询参数const FCollisionResponseParams& ResponseParam = FCollisionResponseParams::DefaultResponseParam // 可选的碰撞响应参数);*///检查射线与场景中的物体是否有交点,并返回相关的碰撞信息GetWorld()->LineTraceSingleByChannel(TraceHitResult,Start,End,ECollisionChannel::ECC_Visibility);
#if 0if (!TraceHitResult.bBlockingHit){TraceHitResult.ImpactPoint = End;HitTarget = End;}else{HitTarget = TraceHitResult.ImpactPoint;/*DrawDebugSphere(const UWorld* World,     // 表示你要在哪个世界中绘制球体FVector Center,          // 球体的中心位置float Radius,            // 球体的半径int32 Segments,          // 球体的分段数,影响球体的平滑度FColor Color,            // 球体的颜色bool bPersistentLines,    // 是否为持久化的调试线条(场景切换后是否还存在)float LifeTime,          // 调试球体的生存时间,0 为永久存在uint8 DepthPriority,     // 渲染优先级(影响是否被遮挡)float Thickness          // 球体线条的厚度)*/DrawDebugSphere(GetWorld(),TraceHitResult.ImpactPoint,12.f, //半径12, //FColor::Red);} 
#endif}
}void UCombatComponent::ServerFire_Implementation(const FVector_NetQuantize& TraceHitTarget)
{MulticastFire(TraceHitTarget);
}void UCombatComponent::MulticastFire_Implementation(const FVector_NetQuantize& TraceHitTarget)
{if (EquippedWeapon == nullptr) return;if (Character){//UE_LOG(LogTemp, Warning, TEXT("FireButtonPressed"));Character->PlayFireMontage(bAiming);EquippedWeapon->Fire(TraceHitTarget);}
}

         9.武器类添加代码

//武器类头文件
/* 开火功能 */
virtual void Fire(const FVector& HitTaget);UPROPERTY(EditAnywhere , Category = "Weapon Properties")
class UAnimationAsset* FireAnimation; //动画资产类UPROPERTY(EditAnywhere)
TSubclassOf<class ACasing> CasingClass; // 监视类 -- 监视蛋壳弹出//武器类源文件
void AWeapon::Fire(const FVector& HitTaget)
{if (FireAnimation){WeapomMesh->PlayAnimation(FireAnimation, false);}if (CasingClass){const USkeletalMeshSocket* AmmoEjectSocket = WeapomMesh->GetSocketByName(FName("AmmoEject"));if (AmmoEjectSocket){FTransform SocketTransform = AmmoEjectSocket->GetSocketTransform(GetWeaponMesh());UWorld* World = GetWorld();if (World){World->SpawnActor<ACasing>(CasingClass,SocketTransform.GetLocation(),SocketTransform.GetRotation().Rotator());}}}
}

        10.创建子弹蓝图类

        11.设置蓝图

        1.打开蓝图设置对用的武器网格体,WeaponMesh细节中网格体的骨骼网格体资产选择对于你武器资产

         2.设置pickwidget(没有可以不用设置)细节中用户界面的空间选择屏幕空间类选择对饮蓝图类

        3.设置动画(武器开火动画)该蓝图的类是projectileweapon,它的父类是weapon,父类中有

    UPROPERTY(EditAnywhere , Category = "Weapon Properties")
    class UAnimationAsset* FireAnimation; //动画资产类

        所以一在细节中可以找到(C++类的继承)

        4.将11中的创建武器蓝图拖拽到地图中

        12. 摄像机的偏移(可选,若想看见角色前方的可以设置)

         13.生产子弹类中代码(GetSocketName中的名字是对应武器网格体中枪口的插槽的名字)

//子弹类头文件
public:virtual void Fire(const FVector& HitTaget) override;protected:UPROPERTY(EditAnywhere)TSubclassOf<class AProjectile> ProjectileClass;//子弹类源文件
void AProjectileMyWeapon::Fire(const FVector& HitTaget)
{Super::Fire(HitTaget);if (!HasAuthority()) return;APawn* InstigatorPawn = Cast<APawn>(GetOwner());const USkeletalMeshSocket* MuzzleFlashSocket = GetWeaponMesh()->GetSocketByName(FName("MuzzleFlash"));if (MuzzleFlashSocket){FTransform SocketTransform = MuzzleFlashSocket->GetSocketTransform(GetWeaponMesh());// 从枪口闪光插座到开火位置 获得尖端的位置FVector ToTarget = HitTaget - SocketTransform.GetLocation();FRotator TargetRotation = ToTarget.Rotation();if (ProjectileClass && InstigatorPawn){FActorSpawnParameters SpawnParams;SpawnParams.Owner = GetOwner();SpawnParams.Instigator = InstigatorPawn;UWorld* World = GetWorld();if (World){World->SpawnActor<AProjectile>(ProjectileClass,SocketTransform.GetLocation(),TargetRotation,SpawnParams);}}}
}

        14.创建子弹类蓝图

        15.打开子弹类蓝图设置(中间黄色的是碰撞盒)

        15.1 设置碰撞盒大小

         16.定义粒子特效类和声音类

//子弹类头文件virtual void Destroyed() override;protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;UFUNCTION()virtual void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);public:	private:UPROPERTY(VisibleAnywhere)class UProjectileMovementComponent* ProjectileMovementComponent; //子弹运动的类UPROPERTY(EditAnywhere)class UParticleSystem* Tracer; //粒子系统类class UParticleSystemComponent* TracerComponent;	//粒子系统组件类UPROPERTY(EditAnywhere)class UParticleSystem* ImpactParticals; //粒子系统类UPROPERTY(EditAnywhere)class USoundCue* ImpactSound;//声音提示类//子弹类源文件
//构造中添加
ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
ProjectileMovementComponent->bRotationFollowsVelocity = true; //如果为 true,则此射弹将在每一帧更新其旋转以匹配其速度方向
// Called when the game starts or when spawned
void AProjectile::BeginPlay()
{Super::BeginPlay();if (Tracer){//播放附加到指定组件并跟随指定组件的指定效果。当效果完成时,系统将消失。不复制TracerComponent = UGameplayStatics::SpawnEmitterAttached(Tracer,//粒子系统创建CollisionBox,//要附加到的组件。FName(),//AttachComponent 中的可选命名点,用于生成发射器GetActorLocation(),// 位置 -- 根据 LocationType 的值,这是与附加组件/点的相对偏移量,或者是将转换为相对偏移量的绝对世界位置(如果 LocationType 为 KeepWorldPosition)。GetActorRotation(),//旋转 -- 根据 LocationType 的值,这是与附加组件/点的相对偏移量,或者是将转换为相对偏移量的绝对世界旋转(如果 LocationType 为 KeepWorldPosition)EAttachLocation::KeepWorldPosition//根据 LocationType 的值,这是附加组件中的相对缩放,或者是将转换为相对缩放的绝对世界缩放(如果 LocationType 为 KeepWorldPosition)。//指定 Location 是相对偏移还是绝对世界位置//当粒子系统完成播放时,组件是否会自动销毁,或者是否可以重新激活//用于池化此组件的方法。默认为 none。//组件是否在创建时自动激活。	);}if (HasAuthority()){CollisionBox->OnComponentHit.AddDynamic(this,&AProjectile::OnHit);}
}void AProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{Destroy();
}void AProjectile::Destroyed()
{Super::Destroyed();if (ImpactParticals){UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticals, GetActorTransform());}if (ImpactSound){UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation());}
}

        17.设置声音和粒子特效设置速度

        18. 若想发射子弹时在其他客户端也可以显示在子弹类的构造中将bReplicates = true;即可

        19.弹壳类

//弹壳头文件UFUNCTION()virtual void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);#if 0
public:	// Called every framevirtual void Tick(float DeltaTime) override;
#endifprivate:UPROPERTY(VisibleAnywhere)UStaticMeshComponent* CasingMesh;//静态网格体类 武器开火时弹出的子弹的网格体UPROPERTY(EditAnywhere)float ShellEjectionImpulse;// 弹壳初速度UPROPERTY(EditAnywhere)class USoundCue* ShellSound;// 弹壳弹出时的声音//弹壳源文件
ACasing::ACasing()
{// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.PrimaryActorTick.bCanEverTick = false;CasingMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CasingMesh"));SetRootComponent(CasingMesh);CasingMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera,ECollisionResponse::ECR_Ignore);CasingMesh->SetSimulatePhysics(true); // 物理CasingMesh->SetEnableGravity(true); // 重力CasingMesh->SetNotifyRigidBodyCollision(true); //通知ShellEjectionImpulse = 10.f;
}// Called when the game starts or when spawned
void ACasing::BeginPlay()
{Super::BeginPlay();CasingMesh->OnComponentHit.AddDynamic(this, &ACasing::OnHit);// 给弹壳添加初始速度CasingMesh->AddImpulse(GetActorForwardVector() * ShellEjectionImpulse);
}
void ACasing::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{if (ShellSound){UGameplayStatics::PlaySoundAtLocation(this, ShellSound, GetActorLocation());}Destroy();
}
#if 0
// Called every frame
void ACasing::Tick(float DeltaTime)
{Super::Tick(DeltaTime);}
#endif

        20.弹壳类蓝图

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【小沐学OpenGL】Ubuntu环境下glew的安装和使用
  • 2.10鼠标事件
  • malloc中的mmap是如何分配内存的
  • Leetcode第414周赛第二题:3281. 范围内整数的最大得分
  • 两种常用损失函数:nn.CrossEntropyLoss 与 nn.TripletMarginLoss
  • ​Benvista PhotoZoom Pro 9.0.4新功能介绍
  • 解决 Python IDLE 横向显示文字的方法
  • JavaWeb笔记整理14——公共字段自动填充技术实现
  • 比特币网络和支付
  • Linux网络编程IO管理
  • 使用Docker快速启动Nacos集群
  • 微信小程序页面制作——个人信息
  • 探究:为什么JavaScript要在body标签尾部引入?
  • 韦季李输入法_屏幕键盘第二个选择
  • 社群空间站9.9付费入群系统二开源码 易支付版全套搭建教程
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • Akka系列(七):Actor持久化之Akka persistence
  • C++入门教程(10):for 语句
  • Centos6.8 使用rpm安装mysql5.7
  • CSS中外联样式表代表的含义
  • ES6 ...操作符
  • ES6 学习笔记(一)let,const和解构赋值
  • Hexo+码云+git快速搭建免费的静态Blog
  • Java比较器对数组,集合排序
  • Java-详解HashMap
  • MySQL主从复制读写分离及奇怪的问题
  • SpingCloudBus整合RabbitMQ
  • 从伪并行的 Python 多线程说起
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 前端设计模式
  • 跳前端坑前,先看看这个!!
  • 学习HTTP相关知识笔记
  • 正则表达式小结
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • #laravel 通过手动安装依赖PHPExcel#
  • #stm32整理(一)flash读写
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • #知识分享#笔记#学习方法
  • $.ajax()参数及用法
  • (12)Hive调优——count distinct去重优化
  • (8)STL算法之替换
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (Java入门)学生管理系统
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (补充):java各种进制、原码、反码、补码和文本、图像、音频在计算机中的存储方式
  • (十七)Flink 容错机制
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)JAVA中的堆栈
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .describe() python_Python-Win32com-Excel
  • .dwp和.webpart的区别
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .net core 管理用户机密