Skip to content

Commit 9798313

Browse files
committed
Large update
Optimized and made several changes.
1 parent 1c97a6b commit 9798313

13 files changed

Lines changed: 328 additions & 153 deletions
1.6 KB
Binary file not shown.

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
Widget supports being on multiple screens as well across multiple players. Usage is simple Drag and Drop into the widget blueprint.
22

3-
DOES NOT CURRENTLY SUPPORT BRUSHES. The Brush in the pen settings is for future use. At the moment it is only lines of different colors.
3+
For documentation please see the [Wiki](https://github.com/Cutter-H/ReplicatedDrawingWidgetPlugin/wiki)
44

5+
If you wish for further information on the plugin's C++ related content, please see the header files, as they are thoroughly commented.
56

6-
### How does it replicate?
7+
You can find a settings data asset in the plugin's content folder. This has 2 separate variables you can use for optimization purposes. Moving or renaming the DataAsset will cause issues and it will no longer work.
78

8-
The plugin comes with a subsystem that spawns a replicated manager actor. It also listense for actor spawns and adds a lightweight component to player states when they are spawned. The player state component allows the widget to send information to the server. The manager actor holds all data and only 1 is created. Widgets are bound to a delegate on the replicated manager that sends out broadcasts on lines added. The widgets can also erase the data as well as refresh to match the manager to ensure no errors are present. The widgets are identified by the BoardName in the widget as well as the player drawing.
9+
DOES NOT CURRENTLY SUPPORT BRUSHES. At the moment it is only lines of different colors.
910

10-
There are overridable functions in the widget that allow for conditional line draws (such as if you wish to disable a player or players) and an override to get the drawer/player's name (the default is a GetPlayerName on the player state).
11+
Any changes to pen data will immediately affect new lines. There are overridable functions in the widget that allow for conditional line draws (such as if you wish to disable a player or players) and an override to get the drawer/player's name (the default is a GetPlayerName on the player state).
12+
13+
14+
## How does it replicate?
15+
16+
Since widgets do not replicate, the plugin comes with a subsystem that spawns a replicated manager actor. It also listense for actor spawns and adds a lightweight component to player states when they are spawned. The player state component allows the widget to send information to the server. The manager actor holds all data and only 1 is created. Widgets are bound to a delegate on the replicated manager that sends out broadcasts on lines added. The widgets can also erase the data as well as refresh to match the manager to ensure no errors are present. The widgets are identified by the BoardName in the widget as well as the player drawing.
1117

1218
Please see the video below for an example of the usage.
1319

14-
This is a plugin that was done in an afternoon and is not finished, but it is in a usable state.
15-
Documentation and fixes/polish is planned for tomorrow.
20+
1621

1722

1823

Source/ReplicatedDrawingWidgetPlugin/Private/Canvas/ReplicatedCanvasWidgetBase.cpp

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,59 @@
22

33

44
#include "ReplicatedCanvasWidgetBase.h"
5-
6-
#include "EngineUtils.h"
7-
#include "Blueprint/WidgetBlueprintLibrary.h"
8-
#include "Blueprint/WidgetTree.h"
95
#include "CanvasHelpers/CanvasPlayerStateHelper.h"
106
#include "CanvasHelpers/ReplicatedCanvasManager.h"
117
#include "CanvasHelpers/WorldCanvasSubsystem.h"
8+
9+
#include "Blueprint/WidgetBlueprintLibrary.h"
10+
#include "Blueprint/WidgetTree.h"
11+
1212
#include "Components/Border.h"
1313
#include "Components/Overlay.h"
1414
#include "Components/OverlaySlot.h"
15+
#include "Debug/ReporterGraph.h"
16+
1517
#include "GameFramework/PlayerState.h"
1618
#include "Kismet/GameplayStatics.h"
1719

1820

1921
void UReplicatedCanvasWidgetBase::DrawLines(FPaintContext& context) const{
20-
for (const FCanvasLineData& line : LineData) {
22+
for (const FCanvasLineData& line : LineData) {
2123
if(ShouldDrawLine(line)) {
22-
FLinearColor color(line.Tint);
23-
color *= Tint;
24-
const float lineLife = line.GetLineLife(GetWorld());
24+
FLinearColor color(line.PenData.Tint);
25+
const float lineLife = line.GetLineLifetime(GetWorld());
26+
float size = line.PenData.Size;
2527
if (FadeDuration > 0 && EraseTime > 0 && lineLife > (EraseTime - FadeDuration)) {
26-
const float fadeOpacity = (EraseTime - lineLife) / FadeDuration;
27-
color.A *= FMath::Clamp(fadeOpacity, 0.f, 1.f);
28+
const float fadeAmount = ( (EraseTime - lineLife) / FadeDuration);
29+
30+
/* Opacity fade */ {
31+
const auto opacityFadeType = line.PenData.OpacityFadeType;
32+
if (opacityFadeType == EPenDataFadeType::PENFADETYPE_Linear) {
33+
color.A *= fadeAmount;
34+
}
35+
if (opacityFadeType == EPenDataFadeType::PENFADETYPE_Curve) {
36+
if (line.PenData.OpacityFadeCurve) {
37+
color.A *= FMath::Abs(line.PenData.OpacityFadeCurve->GetFloatValue(1-fadeAmount));
38+
}
39+
}
40+
41+
}
42+
43+
/* Size Fade */ {
44+
const auto sizeFadeType = line.PenData.SizeFadeType;
45+
if (sizeFadeType == EPenDataFadeType::PENFADETYPE_Linear) {
46+
size *= fadeAmount;
47+
}
48+
if (sizeFadeType == EPenDataFadeType::PENFADETYPE_Curve) {
49+
if (line.PenData.SizeFadeCurve) {
50+
size *= FMath::Abs(line.PenData.SizeFadeCurve->GetFloatValue(1-fadeAmount));
51+
}
52+
}
53+
54+
}
2855
}
29-
UWidgetBlueprintLibrary::DrawSpline(context, line.StartingPoint, line.StartingDirection, line.StoppingPoint, line.StoppingDirection, color, line.Size);
56+
UWidgetBlueprintLibrary::DrawSpline(context, line.StartingPoint, line.StartingDirection, line.StoppingPoint, line.StoppingDirection, color, size);
57+
3058
}
3159
}
3260
}
@@ -35,23 +63,20 @@ void UReplicatedCanvasWidgetBase::CleanupLines(){
3563
if (EraseTime <= 0) {
3664
return;
3765
}
38-
while (LineData.Num() > 0 && LineData[0].GetLineLife(GetWorld()) > EraseTime) {
66+
while (LineData.Num() > 0 && LineData[0].GetLineLifetime(GetWorld()) > EraseTime) {
3967
LineData.RemoveAt(0);
4068
}
4169
}
4270

43-
// UReplicatedCanvasWidgetBase::UReplicatedCanvasWidgetBase(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {
44-
//
45-
// }
46-
47-
void UReplicatedCanvasWidgetBase::TrySetupReplicator() {
48-
if (Replicator) { return; }
71+
void UReplicatedCanvasWidgetBase::TrySetupCanvasManager() {
72+
if (CanvasManager) { return; }
4973

5074
if (const auto subsys = GetWorld()->GetSubsystem<UWorldCanvasSubsystem>()) {
5175

52-
Replicator = subsys->Replicator;
53-
if (Replicator) {
54-
Replicator->OnLineAdded.AddDynamic(this, &UReplicatedCanvasWidgetBase::OnLineAddedToBoard);
76+
CanvasManager = subsys->CanvasManager;
77+
if (CanvasManager) {
78+
CanvasManager->OnLineAdded.AddDynamic(this, &UReplicatedCanvasWidgetBase::OnLineAddedToBoard);
79+
RefreshFromManager();
5580
}
5681
}
5782
}
@@ -64,7 +89,7 @@ void UReplicatedCanvasWidgetBase::NativeConstruct() {
6489
UOverlay* root = WidgetTree->ConstructWidget<UOverlay>(UOverlay::StaticClass(), TEXT("OverlayRoot"));
6590
WidgetTree->RootWidget = root;
6691

67-
DrawingPad = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("DrawingPad"));
92+
UBorder* DrawingPad = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("DrawingPad"));
6893
DrawingPad->SetVisibility(ESlateVisibility::Visible);
6994
DrawingPad->SetBrushColor(FLinearColor(0, 0, 0, 0));
7095

@@ -76,12 +101,15 @@ void UReplicatedCanvasWidgetBase::NativeConstruct() {
76101
SetVisibility(ESlateVisibility::Visible);
77102
SetIsEnabled(true);
78103
SetIsFocusable(true);
79-
104+
105+
if (!IsDesignTime() && BoardID == 0) {
106+
BoardID = GetUniqueID();
107+
}
80108
}
81109

82110
void UReplicatedCanvasWidgetBase::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) {
83111
Super::NativeTick(MyGeometry, InDeltaTime);
84-
TrySetupReplicator();
112+
TrySetupCanvasManager();
85113
CleanupLines();
86114
}
87115

@@ -90,9 +118,6 @@ int32 UReplicatedCanvasWidgetBase::NativePaint(const FPaintArgs& Args, const FGe
90118

91119
FPaintContext context(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
92120
DrawLines(context);
93-
if (ShouldDraw()) {
94-
95-
}
96121
return Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
97122
}
98123

@@ -118,7 +143,7 @@ FReply UReplicatedCanvasWidgetBase::NativeOnMouseMove(const FGeometry& InGeometr
118143
currentDirection = InMouseEvent.GetGestureDelta();
119144
}
120145
if (bStartedDrawing) {
121-
const FCanvasLineData line(GetDrawerName(), UGameplayStatics::GetTimeSeconds(GetWorld()), LastPosition, LastDirection, currentPosition, currentDirection, PenData.Size, PenData.Tint);
146+
const FCanvasLineData line(GetDrawerName(), BoardID, UGameplayStatics::GetTimeSeconds(GetWorld()), LastPosition, LastDirection, currentPosition, currentDirection, PenData);
122147
LineData.Add(line);
123148
AddLineToState(line);
124149
}
@@ -145,28 +170,25 @@ void UReplicatedCanvasWidgetBase::NativeOnMouseLeave(const FPointerEvent& InMous
145170
#pragma endregion Mouse Overrides
146171

147172
void UReplicatedCanvasWidgetBase::OnLineAddedToBoard(FName board, const FCanvasLineData& line) {
148-
UKismetSystemLibrary::PrintString(this, "Func Hit");
149-
150173
if (board != BoardName) {
151174
return;
152175
}
153-
if (true){//line.DrawingPlayer != GetDrawerName()) {
176+
if (line.DrawingPlayer != GetDrawerName() && BoardID != line.BoardID) {
154177
LineData.Add(line);
155-
UKismetSystemLibrary::PrintString(this, "Line Added");
156178
}
157179
}
158180

159-
void UReplicatedCanvasWidgetBase::RefreshFromReplicator() {
181+
void UReplicatedCanvasWidgetBase::RefreshFromManager() {
160182
LineData.Empty();
161-
if (Replicator) {
162-
LineData = Replicator->GetCanvasLines(BoardName);
183+
if (CanvasManager) {
184+
LineData = CanvasManager->GetCanvasLines(BoardName);
163185
}
164186
}
165187

166188
void UReplicatedCanvasWidgetBase::CleanBoard() {
167189
LineData.Empty();
168-
if (Replicator) {
169-
Replicator->CleanBoard(BoardName);
190+
if (GetPlayerStateHelper()) {
191+
GetPlayerStateHelper()->CleanBoard(BoardName);
170192
}
171193
}
172194

@@ -185,7 +207,9 @@ void UReplicatedCanvasWidgetBase::AddLineToState(const FCanvasLineData& line) {
185207

186208
FName UReplicatedCanvasWidgetBase::GetDrawerName_Implementation() {
187209
if (DrawerName == FName()) {
188-
DrawerName = FName(GetOwningPlayerState(true)->GetPlayerName());
210+
if (GetOwningPlayerState()) {
211+
DrawerName = FName(GetOwningPlayerState()->GetPlayerName());
212+
}
189213
}
190214
return DrawerName;
191215
}

Source/ReplicatedDrawingWidgetPlugin/Private/Canvas/ReplicatedCanvasWidgetBase.h

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,77 +18,112 @@ UCLASS()
1818
class REPLICATEDDRAWINGWIDGETPLUGIN_API UReplicatedCanvasWidgetBase : public UUserWidget {
1919
GENERATED_BODY()
2020

21+
// Basically a tick function that draws lines.
2122
void DrawLines(FPaintContext& context) const;
23+
// Tick function that deletes unused lines outside the EraseTime.
2224
void CleanupLines();
23-
25+
/* Tick function that attempts to set up maanger. If it's already set this does nothing. ONLY ONE CANVAS MANAGER SHOULD BE USED PER MAP. */
26+
void TrySetupCanvasManager();
27+
2428
bool bWantsToDraw = false;
2529
bool bStartedDrawing = false;
2630
TArray<FCanvasLineData> LineData;
2731
FName DrawerName = FName();
2832
FVector2D LastPosition = FVector2D::ZeroVector;
2933
FVector2D LastDirection = FVector2D::ZeroVector;
3034

31-
bool LastPointValid() const { return (LastPosition + LastDirection).Length() > 0; }
32-
33-
void TrySetupReplicator();
3435

35-
AReplicatedCanvasManager* Replicator;
36-
UCanvasPlayerStateHelper* StateHelper;
37-
protected:
36+
3837
UPROPERTY()
39-
USizeBox* RootBox;
38+
AReplicatedCanvasManager* CanvasManager;
4039
UPROPERTY()
41-
UBorder* DrawingPad;
42-
//UReplicatedCanvasWidgetBase(const FObjectInitializer& ObjectInitializer);
43-
40+
UCanvasPlayerStateHelper* StateHelper;
41+
protected:
42+
/* Buncha overrides to the parent class for alotta thangs. */
4443
virtual void NativeConstruct() override;
4544
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
4645
virtual int32 NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override;
47-
4846
virtual FReply NativeOnMouseButtonDown( const FGeometry& InGeometry, const FPointerEvent& InMouseEvent ) override;
4947
virtual FReply NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
5048
virtual FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
5149
virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
5250

51+
/* Sends a line to the player state helper. */
5352
UFUNCTION()
5453
void AddLineToState(const FCanvasLineData& line);
55-
54+
55+
/* Override to change how the drawer is identified. By default, GetPlayerName from the player state is used, but this is included if other means should be used. */
5656
UFUNCTION(BlueprintNativeEvent)
5757
FName GetDrawerName();
58+
virtual FName GetDrawerName_Implementation();
59+
60+
/* Override for custom logic to let the user draw. If this is false then no drawing occurs. */
5861
UFUNCTION(BlueprintNativeEvent)
5962
bool ShouldDraw() const;
60-
63+
virtual bool ShouldDraw_Implementation() const;
64+
65+
/* Override for custom logic to check if a line should be drawn. */
6166
UFUNCTION(BlueprintNativeEvent)
6267
bool ShouldDrawLine(const FCanvasLineData& line) const;
63-
bool ShouldDrawLine_Implementation(const FCanvasLineData& line) const {return true;}
68+
virtual bool ShouldDrawLine_Implementation(const FCanvasLineData& line) const {return true;}
6469

70+
/* Used to bind to the Canvas Manager to receive newly drawn lines from other players. */
6571
UFUNCTION()
6672
void OnLineAddedToBoard(FName board, const FCanvasLineData& line);
6773

74+
/* This is the data the widget currently has of all the lines. These are ordered by time drawn. */
6875
UFUNCTION(BlueprintCallable)
6976
TArray<FCanvasLineData> GetLineData() const { return LineData; }
7077

78+
79+
/* Blueprint Getter for the Canvas Manager. This is used to receive replicated data.
80+
*
81+
* How the Replication works.
82+
*
83+
* CanvasManager <------PlayerStateHelper <-- Server Level
84+
* | \
85+
* | |
86+
* \-------> Widget--> PlayerStateHelper---/ Client Level
87+
*/
88+
UFUNCTION(BlueprintCallable)
89+
AReplicatedCanvasManager* GetManagerActor() const {return CanvasManager;}
90+
91+
/* Blueprint Getter for the Player State component. This is used to send requests to the server.
92+
*
93+
* How the Replication works.
94+
*
95+
* CanvasManager <------PlayerStateHelper <-- Server Level
96+
* | \
97+
* | |
98+
* \-------> Widget--> PlayerStateHelper---/ Client Level
99+
*/
71100
UFUNCTION(BlueprintCallable)
72-
AReplicatedCanvasManager* GetReplicatorActor() const {return Replicator;}
101+
UCanvasPlayerStateHelper* GetPlayerStateHelper() const {return StateHelper;}
73102

103+
/* Rebuilds the board to match the lines found in the Canvas Manager. */
74104
UFUNCTION(BlueprintCallable)
75-
void RefreshFromReplicator();
105+
void RefreshFromManager();
76106

107+
/* Erases all lines on the board. */
77108
UFUNCTION(BlueprintCallable)
78109
void CleanBoard();
79110

80111

81112
public:
113+
/* Used to identify this board between players. */
82114
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Drawing", meta=(ExposeOnSpawn = true))
83115
FName BoardName;
84-
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Drawing", meta=(ExposeOnSpawn = true))
85-
FLinearColor Tint = FLinearColor::White;
116+
/* Used to identify this board between instances on the same player. If left as 0 it will be given a unique id. */
117+
UPROPERTY(BlueprintReadOnly, Category="Drawing", meta=(ExposeOnSpawn = true))
118+
int BoardID = 0;
119+
/* This is used as parameters to draw lines. */
86120
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Drawing")
87121
FCanvasPenData PenData;
88-
122+
/* Amount of time in seconds before lines disappear. */
89123
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Drawing")
90124
float EraseTime = 0.f;
91-
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Drawing")
125+
/* Amount of time prior to being erased where the lines will fade. */
126+
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Drawing", meta=(EditCondition))
92127
float FadeDuration = 0.f;
93128
};
94129

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Fill out your copyright notice in the Description page of Project Settings.
2+
3+
#pragma once
4+
5+
#include "CoreMinimal.h"
6+
#include "Engine/DataAsset.h"
7+
#include "CanvasManagerSettings.generated.h"
8+
9+
/**
10+
*
11+
*/
12+
UCLASS(NotBlueprintType, NotBlueprintable)
13+
class REPLICATEDDRAWINGWIDGETPLUGIN_API UCanvasManagerSettings : public UDataAsset {
14+
GENERATED_BODY()
15+
16+
public:
17+
/* If a board exceeds this number of lines, the oldest will be removed from the manager. Boards will still keep this line. */
18+
UPROPERTY(EditAnywhere, Category = "Canvas Settings")
19+
int MaxNumberOfLines = 0;
20+
/* If a line's duration (in seconds) on a board exceeds this, it will be removed from the manager. Boards will still keep this line. */
21+
UPROPERTY(EditAnywhere, Category = "Canvas Settings")
22+
float MaxLineDuration = 0.f;
23+
};

0 commit comments

Comments
 (0)