This guide covers development workflows, code organization patterns, performance guidelines, and best practices for working with OmniShift. Following these practices will help you create maintainable, performant, and robust character systems.
Organize your project with this recommended structure:
YourProject/
├── Source/
│ └── YourProject/
│ ├── YourProject.Build.cs
│ ├── YourProject.cpp
│ └── Characters/
│ ├── Base/
│ │ ├── BaseCharacter.h
│ │ ├── BaseCharacter.cpp
│ │ └── Interfaces/
│ ├── Player/
│ │ ├── PlayerCharacter.h
│ │ ├── PlayerCharacter.cpp
│ │ └── PlayerController.h/.cpp
│ ├── NPC/
│ │ ├── NPCCharacter.h
│ │ ├── NPCCharacter.cpp
│ │ └── AI/
│ └── Enemies/
│ ├── EnemyCharacter.h
│ └── EnemyCharacter.cpp
├── Content/
│ ├── OmniShift/
│ │ ├── Blueprints/
│ │ ├── DataAssets/
│ │ │ ├── Characters/
│ │ │ ├── Camera/
│ │ │ ├── Animation/
│ │ │ ├── Input/
│ │ │ └── Sound/
│ │ └── Materials/
│ └── ThirdParty/
│ └── OmniShift/
├── Config/
│ ├── DefaultEditor.ini
│ ├── DefaultEngine.ini
│ └── DefaultGame.ini
└── .uproject
YourProject.Build.cs:
PublicDependencyModuleNames.AddRange(
new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput",
"GameplayAbilities",
"GameplayTasks",
"MotionWarping",
"NetCore",
"UMG",
"Slate",
"SlateCore"
}
);
PrivateDependencyModuleNames.AddRange(
new string[] {
"SlateCore",
"Slate",
"ToolMenus",
"EditorStyle",
"EditorWidgets",
"UnrealEd",
"SlateCoreEditor",
"EditorStyleSet",
"EditorWidgets",
"Slate",
"SlateCore",
"OmniShift"
}
);
.gitignore for OmniShift projects:
# Build artifacts
Binaries/
Intermediate/
*.aps
*.vcxproj.*
*.sln
*.xcodeproj
*.xcworkspace
# OmniShift specific (avoid committing generated data)
Content/OmniShift/Generated/
Content/OmniShift/.vscode/
Content/OmniShift/.development/
Create a base character class that extends GASPAC_CharacterBase:
// BaseCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GASPAC_CharacterBase.h"
#include "BaseCharacter.generated.h"
UCLASS()
class YOURPROJECT_API ABaseCharacter : public AGASPAC_CharacterBase
{
GENERATED_BODY()
public:
ABaseCharacter();
protected:
virtual void BeginPlay() override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual void Tick(float DeltaTime) override;
// Override key functions
virtual void PossessedBy(AController* NewController) override;
virtual void UnPossessed() override;
// Character lifecycle
UFUNCTION(BlueprintCallable, Category = "Character")
virtual void InitializeCharacter();
UFUNCTION(BlueprintCallable, Category = "Character")
virtual void CleanupCharacter();
// Configuration
UFUNCTION(BlueprintCallable, Category = "Character")
virtual void ApplyCharacterConfiguration();
// State management
UFUNCTION(BlueprintCallable, Category = "Character")
virtual void UpdateCharacterState();
// Health and damage
UFUNCTION(BlueprintCallable, Category = "Character")
virtual float TakeDamage(float DamageAmount, AActor* DamageInstigator);
protected:
// Components
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class UHealthComponent* HealthComponent;
// Configuration assets
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Configuration")
class UCharacterConfigDataAsset* CharacterConfig;
// State
UPROPERTY(BlueprintReadOnly, Category = "State")
bool bIsInitialized = false;
private:
// Internal initialization
void InitializeComponents();
void SetupInputMapping();
void LoadConfiguration();
};
Create Blueprint Class:
Configure Blueprint:
// In the Blueprint Editor
- Set Character Config in Details panel
- Configure starting values
- Add custom variables
- Override events as needed
Setup Input:
// In the character Blueprint event graph
Event Graph → BeginPlay
→ Call Parent: BeginPlay
→ Initialize Character (Custom)
→ Apply Configuration
// CharacterConfigDataAsset.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GASPAC_CharacterConfigDataAsset.h"
#include "CharacterConfigDataAsset.generated.h"
UCLASS(BlueprintType, Blueprintable)
class YOURPROJECT_API UCharacterConfigDataAsset : public UGASPAC_CharacterConfigDataAsset
{
GENERATED_BODY()
public:
UCharacterConfigDataAsset();
// Game-specific properties
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game Configuration")
EGameType GameType = EGameType::Action;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game Configuration")
EDifficultyLevel DefaultDifficulty = EDifficultyLevel::Normal;
// Movement tuning
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement", meta = (ClampMin = "0.0", ClampMax = "1000.0"))
float MovementSpeedMultiplier = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement", meta = (ClampMin = "0.0", ClampMax = "2.0"))
float JumpHeightMultiplier = 1.0f;
// Abilities
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Abilities")
TArray<TSubclassOf<UGameplayAbility>> DefaultAbilities;
// Audio
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audio")
class USoundBase* FootstepSound = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audio")
class USoundBase| ClassFilter = "SoundBase" > JumpSound = nullptr;
// Visual effects
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
class UParticleSystemComponent* FootstepEffect = nullptr;
// Debug
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Debug")
bool bShowDebugInfo = false;
};
// In your character class
void ABaseCharacter::LoadConfiguration()
{
if (CharacterConfig)
{
// Apply base configuration
Super::ApplyCharacterConfig();
// Apply game-specific configuration
UCharacterConfigDataAsset* GameConfig = Cast<UCharacterConfigDataAsset>(CharacterConfig);
if (GameConfig)
{
// Apply movement multipliers
WalkSpeed *= GameConfig->MovementSpeedMultiplier;
RunSpeed *= GameConfig->MovementSpeedMultiplier;
JumpZVelocity *= GameConfig->JumpHeightMultiplier;
// Grant default abilities
for (TSubclassOf<UGameplayAbility> AbilityClass : GameConfig->DefaultAbilities)
{
if (AbilityClass)
{
if (AbilitySystemComponent)
{
AbilitySystemComponent->GiveAbility(AbilityClass);
}
}
}
// Set up audio
if (AudioComponent && GameConfig->FootstepSound)
{
AudioComponent->SetSound(GameConfig->FootstepSound);
}
}
}
}
// Example header organization
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
// Forward declarations
class UCameraComponent;
class USpringArmComponent;
class UInputComponent;
class UAnimationInstance;
// Constants
namespace CharacterConstants
{
constexpr float DEFAULT_INTERPOLATION_SPEED = 5.0f;
constexpr int32 DEFAULT_MAX_IK_ITERATIONS = 10;
constexpr float DEFAULT_INTERACTION_RANGE = 200.0f;
}
UCLASS(BlueprintType, Blueprintable, ClassGroup = (YourGame))
class YOURPROJECT_API AYourCharacter : public AGASPAC_CharacterBase
{
GENERATED_BODY()
public:
// Constructor and lifecycle
AYourCharacter();
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
// Public interface
UFUNCTION(BlueprintCallable, Category = "Character")
void PerformAction(const FString& ActionName);
UFUNCTION(BlueprintPure, Category = "Character")
bool CanPerformAction(const FString& ActionName) const;
protected:
// Protected interface
virtual void InitializeCharacter();
virtual void UpdateCharacterState();
virtual void HandleMovement(float DeltaTime);
virtual void HandleRotation(float DeltaTime);
// Component setup
UFUNCTION(BlueprintCallable, Category = "Components")
void SetupComponents();
// Configuration
UFUNCTION(BlueprintCallable, Category = "Configuration")
void LoadConfiguration();
// Events
UFUNCTION(BlueprintImplementableEvent)
void OnActionStarted(const FString& ActionName);
UFUNCTION(BlueprintImplementableEvent)
void OnActionCompleted(const FString& ActionName, bool bSuccess);
private:
// Private interface
void SetupInputMapping();
void BindInputActions();
void UpdateAbilities();
// Components
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class UYourCharacterComponent* CustomComponent;
// Properties
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Configuration")
class UYourCharacterConfigDataAsset* CustomConfig;
// State variables
UPROPERTY(BlueprintReadOnly, Category = "State")
bool bIsPerformingAction = false;
// Constants
static constexpr float ACTION_COOLDOWN = 0.5f;
};
void AYourCharacter::PerformAction(const FString& ActionName)
{
// Validate input
if (ActionName.IsEmpty())
{
UE_LOG(LogTemp, Warning, TEXT("Attempted to perform empty action"));
return;
}
// Check if action can be performed
if (!CanPerformAction(ActionName))
{
UE_LOG(LogTemp, Warning, TEXT("Cannot perform action: %s"), *ActionName);
return;
}
// Check cooldown
if (IsActionOnCooldown(ActionName))
{
UE_LOG(LogTemp, Log, TEXT("Action on cooldown: %s"), *ActionName);
return;
}
// Perform action
bool bSuccess = InternalPerformAction(ActionName);
// Notify listeners
OnActionStarted(ActionName);
if (bSuccess)
{
SetActionCooldown(ActionName, ACTION_COOLDOWN);
OnActionCompleted(ActionName, true);
}
else
{
OnActionCompleted(ActionName, false);
}
}
void AYourCharacter::UpdateCharacterState()
{
// Early out if character is being destroyed
if (IsPendingKill() || !IsValidLowLevel())
{
return;
}
// Cache frequently accessed values
const float CurrentTime = GetWorld()->GetTimeSeconds();
const FVector CurrentVelocity = GetVelocity();
// Only update state if character is moving significantly
if (CurrentVelocity.SizeSquared() > FMath::Square(10.0f))
{
bIsMoving = true;
CurrentSpeed = CurrentVelocity.Size();
// Throttle expensive updates
if (CurrentTime - LastStateUpdateTime >= StateUpdateInterval)
{
UpdateComplexState();
LastStateUpdateTime = CurrentTime;
}
}
else
{
bIsMoving = false;
CurrentSpeed = 0.0f;
}
// Always update critical state
UpdateCriticalState();
}
// In your character constructor
AYourCharacter::AYourCharacter()
{
// Set update frequencies based on character type
SetCharacterType(ECharacterType::Player);
}
void AYourCharacter::SetCharacterType(ECharacterType Type)
{
switch (Type)
{
case ECharacterType::Player:
// High frequency updates for responsive player control
if (ProceduralRigComponent)
{
ProceduralRigComponent->SetUpdateFrequency(120.0f);
}
if (BodyProcessingComponent)
{
BodyProcessingComponent->SetUpdateFrequency(60.0f);
}
break;
case ECharacterType::NPC:
// Medium frequency updates for NPCs
if (ProceduralRigComponent)
{
ProceduralRigComponent->SetUpdateFrequency(60.0f);
}
if (BodyProcessingComponent)
{
BodyProcessingComponent->SetUpdateFrequency(30.0f);
}
break;
case ECharacterType::Enemy:
// Lower frequency updates for enemies
if (ProceduralRigComponent)
{
ProceduralRigComponent->SetUpdateFrequency(30.0f);
}
if (BodyProcessingComponent)
{
BodyProcessingComponent->SetUpdateFrequency(15.0f);
}
break;
}
}
void AYourCharacter::OptimizeBasedOnDistance()
{
const APlayerController* PC = GetController<APlayerController>();
if (!PC) return;
const APlayerCameraManager* CameraManager = PC->PlayerCameraManager;
if (!CameraManager) return;
const float DistanceToPlayer = FVector::Dist(GetActorLocation(), PC->GetPawn()->GetActorLocation());
// Dynamic quality adjustment
if (DistanceToPlayer < 500.0f)
{
SetQualityLevel(EQualityLevel::High);
}
else if (DistanceToPlayer < 2000.0f)
{
SetQualityLevel(EQualityLevel::Medium);
}
else
{
SetQualityLevel(EQualityLevel::Low);
}
}
void AYourCharacter::SetQualityLevel(EQualityLevel Quality)
{
switch (Quality)
{
case EQualityLevel::Ultra:
SetUpdateFrequency(144.0f);
EnableAllFeatures();
break;
case EQualityLevel::High:
SetUpdateFrequency(120.0f);
EnableEssentialFeatures();
break;
case EQualityLevel::Medium:
SetUpdateFrequency(60.0f);
EnableCoreFeatures();
DisableExpensiveFeatures();
break;
case EQualityLevel::Low:
SetUpdateFrequency(30.0f);
EnableMinimalFeatures();
DisableAllOptionalFeatures();
break;
}
}
// Use object pools for frequently allocated objects
template<typename T>
class TObjectPool
{
public:
TObjectPool(int32 InitialSize = 10)
{
Objects.Reserve(InitialSize);
for (int32 i = 0; i < InitialSize; ++i)
{
Objects.Add(NewObject<T>());
}
}
T* Get()
{
if (Objects.Num() > 0)
{
return Objects.Pop();
}
return NewObject<T>();
}
void Return(T* Object)
{
if (Object && Objects.Num() < MaxPoolSize)
{
Object->Reset();
Objects.Add(Object);
}
}
private:
TArray<T*> Objects;
static constexpr int32 MaxPoolSize = 50;
};
// Usage in your character class
class AYourCharacter : public AGASPAC_CharacterBase
{
private:
TObjectPool<FGASPAC_IKChain> IKChainPool;
TObjectPool<FGASPAC_BoneTransform> TransformPool;
};
// Create a debug configuration system
class OMNISHIFT_API UDebugConfig
{
public:
static bool IsDebugEnabled();
static bool IsDrawDebugEnabled();
static bool IsPerformanceDebugEnabled();
static void SetDebugEnabled(bool bEnabled);
static void SetDrawDebugEnabled(bool bEnabled);
static void SetPerformanceDebugEnabled(bool bEnabled);
private:
static bool bDebugEnabled;
static bool bDrawDebugEnabled;
static bool bPerformanceDebugEnabled;
};
// Use in your character class
void AYourCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (UDebugConfig::IsDebugEnabled())
{
DrawDebugInfo();
}
if (UDebugConfig::IsPerformanceDebugEnabled())
{
LogPerformanceMetrics();
}
}
// Test base class for character functionality
class OMNISHIFT_API UCharacterTestSuite
{
public:
static bool RunAllTests(AActor* TestActor);
static bool TestCharacterInitialization(AActor* TestActor);
static bool TestCharacterMovement(AActor* TestActor);
static bool TestCharacterInteraction(AActor* TestActor);
private:
static bool CompareVectors(const FString& TestName, const FVector& Expected, const FVector& Actual, float Tolerance = 1.0f);
static bool CompareRotators(const FString& TestName, const FRotator& Expected, const FRotator& Actual, float Tolerance = 1.0f);
static bool CompareFloats(const FString& TestName, float Expected, float Actual, float Tolerance = 0.1f);
};
bool UCharacterTestSuite::TestCharacterInitialization(AActor* TestActor)
{
AYourCharacter* Character = Cast<AYourCharacter>(TestActor);
if (!Character)
{
UE_LOG(LogTemp, Error, TEXT("TestCharacterInitialization: Actor is not a YourCharacter"));
return false;
}
// Test component initialization
if (!Character->GetAbilitySystemComponent())
{
UE_LOG(LogTemp, Error, TEXT("TestCharacterInitialization: Ability System Component not found"));
return false;
}
if (!Character->GetBodyProcessingComponent())
{
UE_LOG(LogTemp, Error, TEXT("TestCharacterInitialization: Body Processing Component not found"));
return false;
}
// Test initial state
if (Character->GetCharacterState() != EGASPAC_CharacterState::Idle)
{
UE_LOG(LogTemp, Error, TEXT("TestCharacterInitialization: Initial state is not Idle"));
return false;
}
return true;
}
// Automated test runner
class OMNISHIFT_API UAutomatedTestRunner
{
public:
static bool RunCharacterTests(const TArray<TSubclassOf<AYourCharacter>>& CharacterClasses);
static void GenerateTestReport();
private:
struct FTestResult
{
FString TestName;
bool bPassed;
float ExecutionTime;
FString ErrorMessage;
};
static TArray<FTestResult> TestResults;
static void RunTest(const FString& TestName, TFunction<bool(AActor*)> TestFunction, AActor* TestActor);
static void LogTestResult(const FTestResult& Result);
};
bool UAutomatedTestRunner::RunCharacterTests(const TArray<TSubclassOf<AYourCharacter>>& CharacterClasses)
{
TestResults.Empty();
for (TSubclass<AYourCharacter> CharacterClass : CharacterClasses)
{
AActor* TestCharacter = GetWorld()->SpawnActor(CharacterClass);
if (!TestCharacter)
{
UE_LOG(LogTemp, Error, TEXT("Failed to spawn test character: %s"), *CharacterClass->GetName());
continue;
}
// Run tests
RunTest(TEXT("CharacterInitialization"), [](AActor* Actor) {
return UCharacterTestSuite::TestCharacterInitialization(Actor);
}, TestCharacter);
RunTest(TEXT("CharacterMovement"), [](AActor* Actor) {
return UCharacterTestSuite::TestCharacterMovement(Actor);
}, TestCharacter);
RunTest(TEXT("CharacterInteraction"), [](AActor* Actor) {
return UCharacterTestSuite::TestCharacterInteraction(Actor);
}, TestCharacter);
// Clean up
TestCharacter->Destroy();
}
GenerateTestReport();
return TestResults.Num() > 0 && TestResults.Num() == TestResults.FilterByPredicate([]( const FTestResult& Result ) { return Result.bPassed; }).Num();
}
// Always check pointers before use
void AYourCharacter::SafeComponentAccess()
{
// Safe access pattern
if (AbilitySystemComponent && AbilitySystemComponent->IsValidLowLevel())
{
// Safe to use the component
AbilitySystemComponent->DoSomething();
}
// Null check with early return
if (!BodyProcessingComponent)
{
UE_LOG(LogTemp, Warning, TEXT("Body Processing Component is not valid"));
return;
}
// Use safe casts
AYourPlayerController* PC = Cast<AYourPlayerController>(GetController());
if (PC)
{
// Safe to use PC
PC->DoSomething();
}
}
// Use assertions in development builds
void AYourCharacter::ValidateConfiguration()
{
#if WITH_EDITOR
// Validate required components
check(ProceduralRigComponent && "Procedural Rig Component must be valid");
check(BodyProcessingComponent && "Body Processing Component must be valid");
check(CameraManagerComponent && "Camera Manager Component must be valid");
// Validate configuration data
if (CharacterConfig)
{
check(CharacterConfig->WalkSpeed > 0.0f && "Walk speed must be positive");
check(CharacterConfig->RunSpeed >= CharacterConfig->WalkSpeed && "Run speed must be >= walk speed");
}
// Validate mesh
if (GetMesh())
{
check(GetMesh()->IsValidLowLevel() && "Character mesh must be valid");
}
#endif
}
void AYourCharacter::InitializeWithFallback()
{
// Try to initialize with full configuration
if (!InitializeWithFullConfiguration())
{
UE_LOG(LogTemp, Warning, TEXT("Full configuration failed, using fallback"));
// Fallback to minimal configuration
InitializeWithMinimalConfiguration();
}
}
bool AYourCharacter::InitializeWithFullConfiguration()
{
try
{
InitializeComponents();
LoadConfiguration();
SetupInputMapping();
return true;
}
catch (const std::exception& e)
{
UE_LOG(LogTemp, Error, TEXT("Initialization failed: %s"), UTF8_TO_TCHAR(e.what()));
return false;
}
}
void AYourCharacter::InitializeWithMinimalConfiguration()
{
// Initialize only essential components
if (AbilitySystemComponent)
{
AbilitySystemComponent->SetIsReplicated(true);
}
// Disable expensive features
if (ProceduralRigComponent)
{
ProceduralRigComponent->SetComponentEnabled(false);
}
if (BodyProcessingComponent)
{
BodyProcessingComponent->SetIKSystemEnabled(EGASPAC_IKType::HeadIK, false);
BodyProcessingComponent->SetIKSystemEnabled(EGASPAC_IKType::HandIK, false);
}
}
// Class-level comment
/**
* Custom character class extending OmniShift base functionality.
*
* This class demonstrates best practices for:
* - Component initialization
* - Configuration management
* - Performance optimization
* - Error handling
*
* @author Your Name
* @since 1.0.0
*/
UCLASS()
class YOURPROJECT_API AYourCharacter : public AGASPAC_CharacterBase
{
GENERATED_BODY()
public:
/**
* Constructor
* Sets up component references and default values.
*/
AYourCharacter();
/**
* Performs an action with the character.
* @param ActionName The name of the action to perform
* @return True if the action was performed successfully
*/
UFUNCTION(BlueprintCallable, Category = "Character")
bool PerformAction(const FString& ActionName);
protected:
/**
* Internal method to perform the actual action logic.
* @param ActionName The name of the action to perform
* @return True if the action was successful
*/
bool InternalPerformAction(const FString& ActionName);
private:
/**
* Cooldown manager for character actions.
*/
UPROPERTY()
TMap<FString, float> ActionCooldowns;
/**
* Checks if an action is currently on cooldown.
* @param ActionName The action name to check
* @return True if the action is on cooldown
*/
bool IsActionOnCooldown(const FString& ActionName) const;
/**
* Sets a cooldown for an action.
* @param ActionName The action name to set cooldown for
* @param CooldownDuration The cooldown duration in seconds
*/
void SetActionCooldown(const FString& ActionName, float CooldownDuration);
};
/**
* Updates the character's state based on current conditions.
* This function should be called every frame to maintain character state.
*
* @param DeltaTime Time since last frame
*
* @see UpdateCharacterState() in GASPAC_CharacterBase
*/
void AYourCharacter::UpdateCharacterState(float DeltaTime)
{
// Implementation details
}
// Example: Creating a custom character with OmniShift integration
class OMNISHIFT_API ACustomCharacter : public AGASPAC_CharacterBase
{
public:
ACustomCharacter();
protected:
virtual void BeginPlay() override;
// Custom functionality
UFUNCTION(BlueprintCallable, Category = "Custom")
void StartCustomAbility();
UFUNCTION(BlueprintCallable, Category = "Custom")
void StopCustomAbility();
private:
// Custom components
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
class UCustomAbilityComponent* CustomAbilityComponent;
// Custom configuration
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
class UCustomConfigDataAsset* CustomConfig;
// Custom state
UPROPERTY(BlueprintReadOnly)
bool bIsCustomAbilityActive = false;
};
// Example usage in BeginPlay
void ACustomCharacter::BeginPlay()
{
Super::BeginPlay();
// Initialize custom systems
CustomAbilityComponent->Initialize();
LoadCustomConfiguration();
}
// Example: Implementing custom ability
void ACustomCharacter::StartCustomAbility()
{
if (!CustomAbilityComponent || bIsCustomAbilityActive)
{
return;
}
if (CustomAbilityComponent->CanActivateAbility())
{
bIsCustomAbilityActive = true;
CustomAbilityComponent->ActivateAbility();
// Notify listeners
OnCustomAbilityStarted.Broadcast();
}
}
// In your character header
UCLASS(BlueprintType, Blueprintable, Replicates = true)
class YOURPROJECT_API AYourCharacter : public AGASPAC_CharacterBase
{
GENERATED_BODY()
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
// Server functions
UFUNCTION(Server, Reliable, BlueprintCallable)
void Server_SetCharacterState(const FGASPAC_CharacterState& NewState);
UFUNCTION(Server, Reliable, BlueprintCallable)
void Server_ApplyDamage(float DamageAmount, AActor* DamageInstigator);
// Client functions
UFUNCTION(Client, Reliable)
void Client_OnCharacterStateChanged(const FGASPAC_CharacterState& NewState);
private:
// Replicated properties
UPROPERTY(Replicated)
float CustomStat;
UPROPERTY(Replicated)
bool bIsPerformingCustomAction;
// Replication settings
UPROPERTY()
float CharacterStateReplicationRate = 100.0f;
UPROPERTY()
float ReplicationFrequency = 60.0f;
};
// Client-side prediction for movement
void AYourCharacter::Client_MoveForward(float Value)
{
if (!IsLocallyControlled()) return;
// Apply movement locally immediately
AddMovementInput(FVector::ForwardVector, Value);
// Predict state change
PredictCharacterState();
// Request server validation
Server_RequestMoveForward(Value);
}
void AYourCharacter::PredictCharacterState()
{
// Predict what the character state will be
FGASPAC_CharacterState PredictedState;
PredictedState.bIsMoving = GetVelocity().Size() > KINDA_SMALL_NUMBER;
PredictedState.CurrentSpeed = GetVelocity().Size();
PredictedState.CurrentVelocity = GetVelocity();
// Apply predicted state
SetCharacterState(PredictedState);
}
// Blueprint function to create character configuration
UFUNCTION(BlueprintCallable, Category = "Asset Management")
static UCharacterConfigDataAsset* CreateCharacterConfig()
{
UCharacterConfigDataAsset* Config = NewObject<UCharacterConfigDataAsset>(GetTransientPackage(), TEXT("CharacterConfig"));
// Set default values
Config->CharacterName = TEXT("NewCharacter");
Config->WalkSpeed = 165.0f;
Config->RunSpeed = 375.0f;
Config->JumpZVelocity = 420.0f;
// Save asset
FString AssetPath = TEXT("/Game/Characters/DataAssets/DA_NewCharacterConfig");
UPackage* Package = CreatePackage(*AssetPath);
Config->MarkPackageDirty();
return Config;
}
// Data asset validation utility
class OMNISHIFT_API UAssetValidator
{
public:
static bool ValidateCharacterConfig(const UCharacterConfigDataAsset* Config);
static bool ValidateCameraProfile(const UGASPAC_CameraProfileDataAsset* Profile);
static bool ValidateRigConfig(const UGASPAC_RigConfigDataAsset* RigConfig);
static FString GetValidationErrorDescription(const UDataAsset* Asset);
private:
static bool ValidateBaseProperties(const UDataAsset* Asset);
static bool ValidateReferences(const UDataAsset* Asset);
};
bool UAssetValidator::ValidateCharacterConfig(const UCharacterConfigDataAsset* Config)
{
if (!Config)
{
return false;
}
// Validate properties
if (Config->WalkSpeed <= 0.0f)
{
return false;
}
if (Config->RunSpeed < Config->WalkSpeed)
{
return false;
}
if (Config->JumpZVelocity <= 0.0f)
{
return false;
}
return true;
}
// Validate input parameters
void AYourCharacter::PerformAction(const FString& ActionName)
{
// Validate input parameter
if (ActionName.IsEmpty())
{
UE_LOG(LogTemp, Warning, TEXT("Cannot perform empty action"));
return;
}
// Sanitize input
FString SanitizedName = ActionName.TrimStartAndEnd();
if (SanitizedName.Len() > MAX_ACTION_NAME_LENGTH)
{
UE_LOG(LogTemp, Warning, TEXT("Action name too long: %s"), *SanitizedName);
return;
}
// Validate action against allowed list
if (!IsActionAllowed(SanitizedName))
{
UE_LOG(LogTemp, Warning, TEXT("Action not allowed: %s"), *SanitizedName);
return;
}
// Perform validated action
InternalPerformAction(SanitizedName);
}
// Server-side validation for network actions
bool AYourCharacter::Server_ApplyDamage(float DamageAmount, AActor* DamageInstigator)
{
// Validate damage amount
if (DamageAmount < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("Invalid damage amount: %f"), DamageAmount);
return false;
}
// Validate damage instigator
if (!DamageInstigator || !DamageInstigator->IsValidLowLevel())
{
UE_LOG(LogTemp, Warning, TEXT("Invalid damage instigator"));
return false;
}
// Check damage rate limiting
if (IsDamageRateLimited())
{
UE_LOG(LogTemp, Warning, TEXT("Damage rate limited"));
return false;
}
// Apply damage
float ActualDamage = CalculateDamage(DamageAmount, DamageInstigator);
ApplyDamage_Internal(ActualDamage, DamageInstigator);
return true;
}
// Classes: PascalCase with project prefix
class YOURPROJECT_API AYourClassName;
// Functions: PascalCase, descriptive verbs
void PerformAction();
void CalculateMovement();
// Variables: camelCase for private/protected, PascalCase for public
float currentSpeed;
UFUNCTION(BlueprintCallable)
bool IsPerformingAction;
// Constants: Upper snake case
const float MAX_INTERACTION_RANGE = 200.0f;
// Enums: PascalCase with E prefix
enum class ECharacterState { Idle, Walking, Running };
// Structs: PascalCase with F prefix
struct FCharacterData
{
FVector Location;
FRotator Rotation;
};
/**
* Brief one-sentence description of the function.
*
* Detailed description explaining what the function does, its parameters,
* return value, and any important considerations.
*
* @param ParamName Description of the parameter
* @return Description of the return value
* @see RelatedFunction
* @since Version 1.0
*/
float CalculateDistance(const FVector& PointA, const FVector& PointB)
{
return FVector::Dist(PointA, PointB);
}
Type(scope): Brief description
More detailed explanation of the change.
- List of specific changes made
- Another change item
Closes #issue-number
Examples:
feat(characters): Add new custom ability system
- Add CustomAbilityComponent for ability management
- Implement StartCustomAbility and StopCustomAction functions
- Add ability cooldown system
- Add unit tests for new functionality
Closes #123
fix(camera): Resolve camera collision issues in tight spaces
- Improve collision detection algorithm
- Add configurable collision radius
- Fix camera stuttering when near walls
- Update collision debug visualization
Closes #456
# Feature branches
git checkout -b feature/custom-abilities
# Bugfix branches
git checkout -b fix/camera-collision
# Release branches
git checkout -b release/1.0.0
// Build configuration for distribution
void ConfigureForDistribution()
{
// Disable debug features
UDebugConfig::SetDebugEnabled(false);
UDebugConfig::SetDrawDebugEnabled(false);
UDebugConfig::SetPerformanceDebugEnabled(false);
// Optimize for shipping
SetQualityLevel(EQualityLevel::Medium);
DisableExpensiveFeatures();
// Validate configuration
ValidateConfiguration();
}
// Asset optimization pipeline
class OMNISHIFT_API UAssetOptimizer
{
public:
static void OptimizeForShipping();
static void CompressTextures();
static void OptimizeAudio();
static void GenerateMipmaps();
static void ReduceLODBias();
};
void UAssetOptimizer::OptimizeForShipping()
{
// Optimize character meshes
for (const auto& CharacterBlueprint : CharacterBlueprints)
{
OptimizeCharacterBlueprint(CharacterBlueprint);
}
// Optimize animations
for (const auto& Animation : CharacterAnimations)
{
OptimizeAnimation(Animation);
}
}
// Performance monitoring system
class OMNISHIFT_API UPerformanceMonitor
{
public:
static void StartMonitoring();
static void StopMonitoring();
static void LogMetrics();
static void ExportMetrics(const FString& Filename);
private:
struct FPerformanceMetrics
{
float AverageFrameTime;
float MaxFrameTime;
int32 FrameCount;
float MemoryUsage;
};
static FPerformanceMetrics Metrics;
static bool bIsMonitoring;
static FDateTime StartTime;
};
// Error reporting system
class OMNISHIFT_API UErrorReporter
{
public:
static void ReportError(const FString& ErrorType, const FString& Message, const FString& Context = "");
static void ReportWarning(const FString& Message, const FString& Context = "");
static void SetErrorReportingEnabled(bool bEnabled);
private:
static bool bErrorReportingEnabled;
static FString LogFileName;
static TMap<FString, int> ErrorCounts;
};
For complete mastery of OmniShift development:
Following these best practices will help you create maintainable, performant, and robust character systems with OmniShift.