同步系列7-混合人称的材质设置和同步(躯干透明有影子)


由于我想做的效果是玩家可以在第一人称和第三人称中来回切换,但是由于我的第一人称视角摄像机是附加在角色身上的,当角色进行大幅度动作或视角向最下方看时摄像机很容易和人物模型发生碰撞,从而穿模,极大的影响游戏体验。对此我做了两种解决方案:

(1)给角色添加一个碰撞阻挡,让摄像机无法穿过角色模型,这样就不会穿模,但是我感觉这样会让手感变差,毕竟移鼠标突然移不动了可能会很不爽。

(2)给玩家的躯干部分设置透明材质,这样就算第一人称的摄像机穿进了角色模型,由于是透明材质所以不会发生影响。这样做需要解决两个问题,一是设置仅躯干部分的透明,因为玩家还要看到手部的换弹动作,技能释放动画之类的。第二是玩家切换到第三人称的时候,应设置回原本角色的材质,毕竟第三人称看着一个只有双手其余部位透明的角色太离谱了。第三,不管是第几人称,其他玩家看到你都应该是完整的角色,正常的材质而不是透明的。

接下来我们来看第二种如何实现:

首先是代码部分的实现。先看输入绑定,我这里用V键切换人称视角,正如所见,设置材质和相关同步的代码我是放在切换人称的功能里一起实现的。

1
2
//按V切换视角
EnhancedInputComponent->BindAction(IA_ChangeView, ETriggerEvent::Triggered, this, &ABlasterCharacter::InputShiftView);

这里是处理同步的逻辑,为什么这样写都写在注释里了。本篇所有的代码都写在角色类里的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
private:	
UPROPERTY(EditAnywhere, Category = "EffectMaterials")
UMaterialInterface* TransparentMaterial;//透明材质

UPROPERTY(EditAnywhere, Category = "EffectMaterials")
UMaterialInterface* NormalMaterialUp;//上半身正常材质

UPROPERTY(EditAnywhere, Category = "EffectMaterials")
UMaterialInterface* NormalMaterialDown;//下半身正常材质


void ABlasterCharacter::InputShiftView(const FInputActionValue& InputValue)
{
//如果是服务端执行,就相当于调用一次本地客户端,然后直接return
if(HasAuthority())
{
//服务端的本地就是服务端自己的客户端
ClientChangeView();
return;
}
//如果是客户端执行,让服务端去改变,再传递给各个客户端来完成同步的效果
ServerChangeView();
}


void ABlasterCharacter::ServerChangeView_Implementation()
{
//需要修改服务器上的版本,因为服务器上的版本是用来同步给别的客户端的,别的客户端不与正在执行动作的客户端通信,别的客户端拿的是服务端的数据
ChangeCameraView();
//再去让正在执行动作的客户端去真正实现功能

ClientChangeView();//注释此函数后,客户端角色能同步旋转但是不能改变视角,原因是没有收到Server发来的ClientChangeView()所以改变不了视角,但是和角色移动旋转相关的属性是默认实现网络同步的,是否旋转属性在上面Server端被修改的时候就被同步到客户端了,因此不需要Server发来的ClientChangeView()也改变了旋转。
}

void ABlasterCharacter::ClientChangeView_Implementation()
{
ChangeCameraView();
}

void ABlasterCharacter::ClientSetMaterial_Implementation()
{
// 获取当前材质插槽
int32 MaterialSlotIndex0 = GetMesh()->GetMaterialIndex("18 - Default");//下半身
int32 MaterialSlotIndex1 = GetMesh()->GetMaterialIndex("12 - Default");//上半身
if (IsLocallyControlled())//因为是Sevrer发起的调用,这里判断如果是本机,就本地设为透明,因为第一人称自己看自己是需要透明的
{
GetMesh()->SetMaterial(MaterialSlotIndex0, TransparentMaterial);
GetMesh()->SetMaterial(MaterialSlotIndex1, TransparentMaterial);
}
else//如果不是本机,就相当于设置其他人看自己,那么用正常的材质填充就好
{
// 设置为正常的材质
GetMesh()->SetMaterial(MaterialSlotIndex0, NormalMaterialDown);
GetMesh()->SetMaterial(MaterialSlotIndex1, NormalMaterialUp);
}
}

void ABlasterCharacter::ServerSetMaterial_Implementation()
{
//别人看都正常,别人看不能是透明的
// 获取当前材质插槽
int32 MaterialSlotIndex0 = GetMesh()->GetMaterialIndex("18 - Default");//下半身
int32 MaterialSlotIndex1 = GetMesh()->GetMaterialIndex("12 - Default");//上半身

if(IsLocallyControlled())//如果是本机就本地设为透明,但下面的Client还是正常
{
GetMesh()->SetMaterial(MaterialSlotIndex0, TransparentMaterial);
GetMesh()->SetMaterial(MaterialSlotIndex1, TransparentMaterial);
}
else
{
// 设置为正常的材质
GetMesh()->SetMaterial(MaterialSlotIndex0, NormalMaterialDown);
GetMesh()->SetMaterial(MaterialSlotIndex1, NormalMaterialUp);
}
ClientSetMaterial();//再调客户端RPC把所有的客户端也设置一遍

}

下面就是角色的材质插槽,在修改自己的角色的时候改成自己角色对应的材质插槽就行。

image-20230420231315642

下面来看怎么切换材质的,构造函数已删除无关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
ABlasterCharacter::ABlasterCharacter()
{
// 获取当前材质插槽的数量
int32 MaterialSlotIndex0 = GetMesh()->GetMaterialIndex("18 - Default");
int32 MaterialSlotIndex1 = GetMesh()->GetMaterialIndex("12 - Default");
// 设置为透明的材质
GetMesh()->SetMaterial(MaterialSlotIndex0, NormalMaterialDown);
GetMesh()->SetMaterial(MaterialSlotIndex1, NormalMaterialUp);
}

void ABlasterCharacter::BeginPlay()
{
Super::BeginPlay();
ServerSetMaterial();//在这里相当于初始化并同步设置所有的客户端,你看到的我应该是完整的。
}

void ABlasterCharacter::ChangeCameraView()
{
// 判断当前激活的摄像机,并切换到另一个摄像机
if (FollowCamera->IsActive())//切换到第三人称
{
FollowCamera->SetActive(false);
FollowCamera->Deactivate();
TPSCamera->SetActive(true);
TPSCamera->Activate();
GetCharacterMovement()->bOrientRotationToMovement = true;
bUseControllerRotationYaw = false;
if(IsLocallyControlled())
{
// 获取当前材质插槽的数量
int32 MaterialSlotIndex0 = GetMesh()->GetMaterialIndex("18 - Default");//下半身
int32 MaterialSlotIndex1 = GetMesh()->GetMaterialIndex("12 - Default");//上半身
// 设置为正常的材质
GetMesh()->SetMaterial(MaterialSlotIndex0, NormalMaterialDown);
GetMesh()->SetMaterial(MaterialSlotIndex1, NormalMaterialUp);
}

}
else//切换到第一人称
{
TPSCamera->SetActive(false);
TPSCamera->Deactivate();
FollowCamera->SetActive(true);
FollowCamera->Activate();
GetCharacterMovement()->bOrientRotationToMovement = false;
bUseControllerRotationYaw = true;
if (IsLocallyControlled())
{
// 获取当前材质插槽的数量
int32 MaterialSlotIndex0 = GetMesh()->GetMaterialIndex("18 - Default");//下半身
int32 MaterialSlotIndex1 = GetMesh()->GetMaterialIndex("12 - Default");//上半身
// 设置为透明的材质
GetMesh()->SetMaterial(MaterialSlotIndex0, TransparentMaterial);
GetMesh()->SetMaterial(MaterialSlotIndex1, TransparentMaterial);
}
}
}

最后看一下透明材质怎么做的

image-20230420231735150

最后记得将动态阴影投射为遮罩和不透明蒙版剪切值改为0,不然默认的是0.33,那么当你设置不透明度低于0.33时就不会产生阴影了。

image-20230420231800716

最后测试一下结果,以监听服务器模式打开三个端,即一个Server两个Client

image-20230420232231871

可以看到所有的端的人物都有完整的阴影,也可以看到Client1玩家往下看时只能看到手而没有身体部分,但是自身的阴影确实是完整的。


  目录