Skip to main content

[Unreal Engine][C++] How to make a character brachiate on a ledge

[Unreal Engine][C++] How to make a character brachiate on a ledge

Custom Character Movement

Used Unreal Engine Version: 4.22

There are several ways how to implement custom movement in Unreal Engine. I show you one of them: Brachiating. It is a special movement and uses no physics. It will give you the knowledge to go and try to make your own movements.

As a base we use AThirdPersonCharacter which already supports movement types like walking, crouching, running, jumping, swimming and flying, but not - you guessed it - brachiating.

Implementation

One way to implement it, is extending UCharacterMovementComponent. I already showed you how to do this. But since this is rather for physics based movement, I decided to go the other way, by using the already existing delegate MovementModeChangedDelegate. This delegate broacasts events each time the movement mode changes.

So, what do we need? How do we trigger the new movement mode and how do we leave it? Here is a short overview how our brachiation should work:

  • when jumping on a ledge, our character should grab it and hang in there
  • when walking slowly over a ledge, our character should hold on it instead of falling down
  • when trying to move forward, our character should jump up over the ledge
  • when trying to move backwards, our character should let go and fall down
  • when trying to move over the end of a ledge, our character should fall down

Lots of rules, but it gives us a good idea about what to implement.

Keep in mind, that this is only one way how to do it and just a starting point for you. You’ll have to tweak values and positions to find the perfect fit for your character.

Triggers

We have to give our character the ability to know, when to start brachiating. For this I used various trigger volumes.


In the image you can see them. I added some opaque boxes so I can see what happens, when being in game. As soon as everything works fine, we’ll remove them.

The design of our brachiation trigger:

  • a box trigger to activate brachiation
  • a brachiation direction
  • a brachiation normal (the direction our character faces when brachiating)
  • threshold values to define a maximum velocity, above which brachiation will not be triggered (allows us to walk over an edge without dangling from it)

We’ll set this up in C++. Create a new class and name it BrachiationTrigger:

/* BrachiationTrigger.h */
#pragma once

#include "CoreMinimal.h"
#include "Engine/TriggerBox.h"
#include "BrachiationTrigger.generated.h"

// Forward Declarations
class UBoxComponent;

UCLASS()
class BEATEMDOWN_API ABrachiationTrigger : public AActor
{
	GENERATED_BODY()

public:
  // .............................................................. Properties
  /** Maximum falling velocity, at which player can get hold of the ledge. */
  UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Trigger")
  float FallThroughVelocityThreshold = 100.0f;

  /** Maximum velocity, at which player can get hold of the ledge, when
      jumping through it from beneath. */
  UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Trigger")
  float JumpThroughVelocityThreshold = 100.0f;

  /** Maximum velocity, at which player can get hold of the ledge, when
      jumping or running at it horizontally. */
  UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Trigger")
  float RunThroughVelocityThreshold = 100.0f;

  /** The axis along which the player can brachiate. */
  UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Geometry")
  FVector BrachiationAxis = FVector(1,0,0);

  /** The direction in which a brachiating player is looking. */
  UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Geometry")
  FVector BrachiationFaceDirection = FVector(0, -1, 0);

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
  USceneComponent* Root = nullptr;

  /** Shape of the ledge, where character may brachiate. */
  UPROPERTY(EditDefaultsOnly, BlueprintReadWrite/*, Category = "Collision"*/)
  UBoxComponent* BrachiationLedge = nullptr;

  /** Rotator to facing direction in world coordinates. */
  UFUNCTION(BlueprintCallable)
  FRotator GetFaceDirectionWorldRotation();
        
  /** Brachiation direction in world coordinates. */
  UFUNCTION(BlueprintCallable)
  FVector GetBrachiationWorldAxis();

  /** Character face direction while brachiating in world coordinates. */
  UFUNCTION(BlueprintCallable)
  FVector GetBrachiationWorldFaceDirection();

  // .............................................................. Constructor
  ABrachiationTrigger();

protected:
  virtual void BeginPlay() override;
	
public:
  virtual void Tick(float DeltaTime) override;
};

Now that we’ve defined everything in the header file, let’s implement it in the cpp file:

/* BrachiationTrigger.cpp */
#include "BrachiationTrigger.h"
#include "Components/BoxComponent.h"

FRotator ABrachiationTrigger::GetFaceDirectionWorldRotation()
{ return GetActorRotation() + BrachiationFaceDirection.ToOrientationRotator(); }

FVector ABrachiationTrigger::GetBrachiationWorldAxis()
{ return GetActorRotation().RotateVector(BrachiationAxis); }

FVector ABrachiationTrigger::GetBrachiationWorldFaceDirection()
{ return GetActorRotation().RotateVector(BrachiationFaceDirection); }

ABrachiationTrigger::ABrachiationTrigger()
{
  PrimaryActorTick.bCanEverTick = true;

  // Create a separate root component, so we can place the 
  // trigger volume relatively to it
  Root = CreateDefaultSubobject<USceneComponent>(FName("Root"));
  RootComponent = Root;
  
  // Create a box trigger and keep a reference, so we can edit it in editor
  BrachiationLedge = CreateDefaultSubobject<UBoxComponent>(
                     FName("Brachiation Ledge"));
  BrachiationLedge->InitBoxExtent(FVector(100, 5, 5));
  BrachiationLedge->SetGenerateOverlapEvents(true);
  BrachiationLedge->SetCollisionProfileName(FName("Trigger"));

  // Setup Hierarchy
  RootComponent->SetRelativeLocation(FVector::ZeroVector);
  BrachiationLedge->AttachToComponent(
    RootComponent, 
    FAttachmentTransformRules::KeepRelativeTransform);
  BrachiationLedge->SetRelativeLocation(FVector::ZeroVector);
}

void ABrachiationTrigger::BeginPlay()
{ Super::BeginPlay(); }

void ABrachiationTrigger::Tick(float DeltaTime) 
{ Super::Tick(DeltaTime); }

I know, it’s lots of stuff. But take your time and try to understand everything. The next step is to base a new Blueprint on this class and add some debugging shapes to it. So right click in editor and derive a new Blueprint class and name it BrachiationTrigger_BP.

Then add two new Arrow components. We’ll use them to visualize brachiation direction and facing normal:


We’ll always want our arrows to point in the directions of the both variables. So we set up their rotations in the construction script:


Congratulations! Our brachiation trigger is ready. You can add a box component and always set it’s extent and position to the trigger box, so you can see the trigger in game (see my first screenshot). Now it’s time to teach our character how to brachiate!

The Character

This one is a little bit more complex. I guess there are simpler ways to do it, but it’s a good starting point and you can try to make it simpler from there. We’ll base everything on the ThirdPersonCharacter Unreal provides us with in the third person game template (use the C++ template). Simply copy it and add some stuff. I am not allowed to show you Epic’s original code (see the contract when using Unreal Engine) but I show you what I added. Since we’re starting with the same code file, you should get it done.

First we need a new enumeration type ECustomMovementTypeEnum, to be able to add more custom movement modes. Add it to the top of the header file and add as many movement modes as you want. We’ll only implement one for now:

/* ThirdPersonCharacter.h */

UENUM(BlueprintType)
enum class ECustomMovementTypeEnum : uint8
{
 MOVE_None            UMETA(DisplayName = "none"),
 MOVE_ClimbingLadder  UMETA(DisplayName = "Climbing Ladder"),
 MOVE_Brachiating     UMETA(DisplayName = "Brachiating")
};

Add ABrachiationTrigger as forward declaration at the top of the header file:

/* ThirdPersonCharacter.h */

// Forward Declarations
class ABrachiationTrigger;

Then add some properties for us to keep track of our movement:

  • a boolean bBrachiating which is true, when the character is brachiating,
  • a boolean bDynamicallyBrachiating, which is true when the character is not just hanging on the ledge but additionally moving sideways,
  • a variable storing our current movement type,
  • two pointers to two box triggers we’ll add to our character, to overlap with our Brachiation Trigger:
/* ThirdPersonCharacter.h */

public:
  // .............................................................. Properties
  /** Set true, when colliding with a brachiation trigger and brachiating
      along an edge. */
  UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Movement Type")
  bool bBrachiating = false;

  /** Set true, when actively brachiating. False, when just hanging on
      the ledge. */
  UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Movement Type")
  bool bDynamicallyBrachiating = false;

  UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Movement Type")
  FVector CurrentBrachiationDirection = FVector::ZeroVector;

  UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Movement Type")
  ECustomMovementTypeEnum CurrentCustomMovementType = 
    ECustomMovementTypeEnum::MOVE_None;

  UPROPERTY(BlueprintReadWrite, EditAnywhere)
  UPrimitiveComponent* BrachiationColliderHold = nullptr;

  UPROPERTY(BlueprintReadWrite, EditAnywhere)
  UPrimitiveComponent* BrachiationColliderWalkOverLedge = nullptr;

Now let’s create the two new triggers in the cpp-File and have a look at them in the derived Blueprint Character:

/* ThirdPersonCharacter.cpp */

AThirdPersonCharacter::AThirdPersonCharacter
(const FObjectInitializer& ObjectInitializer)
  : Super(ObjectInitializer)
{
  /* Already existing Unreal Code. Do not remove it. */
  // ...
 
  // Create a trigger in front of our characters head, which 
  // triggers brachiation when overlapping a ledge
  auto BrachiationBox = CreateDefaultSubobject<UBoxComponent>(
                        FName("Brachiation Collider Hands"));
  BrachiationBox->SetBoxExtent(FVector(10, 20, 10));
  BrachiationBox->AttachToComponent(
    RootComponent, 
    FAttachmentTransformRules::KeepRelativeTransform);
  BrachiationBox->SetRelativeLocation(FVector(15, 0, 50));
  BrachiationBox->SetGenerateOverlapEvents(true);
  BrachiationBox->SetCollisionProfileName(FName("Trigger"));
  BrachiationColliderHold = BrachiationBox;

  // Create a second one at it's back to trigger brachiation
  // when slowly walking over a ledge.
  BrachiationBox = CreateDefaultSubobject<UBoxComponent>(
                   FName("BrachiationColliderWalkOverLedge"));
  BrachiationBox->SetBoxExtent(FVector(10, 20, 10));
  BrachiationBox->AttachToComponent(
    RootComponent, 
    FAttachmentTransformRules::KeepRelativeTransform);
  BrachiationBox->SetRelativeLocation(FVector(-15, 0, -10));
  BrachiationBox->SetGenerateOverlapEvents(true);
  BrachiationBox->SetCollisionProfileName(FName("Trigger"));
  BrachiationColliderWalkOverLedge = BrachiationBox;
}

In our derived Blueprint it should lool like this (if you have none, create a new Blueprint class, base it on ThirdPersonCharacter and provide your character’s mesh):


Now we have to add some behaviour to those trigger boxes. We’ll implement our event callback functions. In there we check if the overlapping actor is a ABrachiationTrigger and our own overlapping component is one of our character brachiation triggers and then we initialize the brachiation:

Define the function in the header:

/* ThirdPersonCharacter.h */

/** Delegate for Shape's overlap begin event. */
UFUNCTION()
void OnTriggerComponentOverlapBegin
(
  UPrimitiveComponent* OverlappedComponent,
  AActor* OtherActor,
  UPrimitiveComponent* OtherComp,
  int32 OtherBodyIndex,
  bool bFromSweep, const
);

/** Delegate for Shape's overlap end event. */
UFUNCTION()
void OnTriggerComponentOverlapEnd
(
  UPrimitiveComponent* OverlappedComp,
  AActor* OtherActor,
  UPrimitiveComponent* OtherComp,
  int32 OtherBodyIndex
);

UFUNCTION()
void OnChangedMovementMode
(
  class ACharacter* Character, 
  EMovementMode PrevMovementMode, 
  uint8 PreviousCustomMode
);

and implement them:

/* ThirdPersonCharacter.cpp */

void AThirdPersonCharacter::OnTriggerComponentOverlapBegin
(
  UPrimitiveComponent* OverlappedComp, 
  AActor* OtherActor, 
  UPrimitiveComponent* OtherComp, 
  int32 OtherBodyIndex, 
  bool bFromSweep, 
  const FHitResult& SweepResult
){
  // Prevent self collision
  if (!OtherActor || (OtherActor == this)) { return; }
	
  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Brachiation Trigger
  if (OtherActor->GetClass()->IsChildOf(ABrachiationTrigger::StaticClass()))
  {
    // Check if overlapping component is BrachiationLedge
    auto Trigger = Cast<ABrachiationTrigger>(OtherActor);

    if ((OverlappedComp == BrachiationColliderHold || 
         OverlappedComp == BrachiationColliderWalkOverLedge) && 
         OtherComp == Trigger->BrachiationLedge)
    { StartBrachiating(Trigger, SweepResult); }
  }
}

void AThirdPersonCharacter::OnTriggerComponentOverlapEnd
(
  UPrimitiveComponent* OverlappedComp, 
  AActor* OtherActor, 
  UPrimitiveComponent* OtherComp, 
  int32 OtherBodyIndex
){
  // Prevent self collision
  if (!OtherActor || (OtherActor == this)) { return; }

  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Brachiation Trigger
  if (OtherActor->GetClass()->IsChildOf(ABrachiationTrigger::StaticClass()))
  {
    // Check if overlapping component is BrachiationLedge
    auto Trigger = Cast<ABrachiationTrigger>(OtherActor);

    if (OverlappedComp == BrachiationColliderHold && 
        OtherComp == Trigger->BrachiationLedge)
    { EndBrachiating(false); }
  }
}

void AThirdPersonCharacter::OnChangedMovementMode
(
  ACharacter* Character, 
  EMovementMode PrevMovementMode, 
  uint8 PreviousCustomMode
){
  auto CharacterMovementComp 
    = FindComponentByClass<UCharacterMovementComponent>();
  auto CurrentMovementMode = CharacterMovementComp->MovementMode;
  auto CurrentCustomMovementMode = CharacterMovementComp->CustomMovementMode;

  switch ((ECustomMovementTypeEnum)CurrentCustomMovementMode)
  {
  case ECustomMovementTypeEnum::MOVE_Brachiating:
    UE_LOG(LogTemp, Warning, 
      TEXT("Movement Mode changed to BRACHIATING"));
    break;
  case ECustomMovementTypeEnum::MOVE_ClimbingLadder:
    UE_LOG(LogTemp, Warning, 
      TEXT("Movement Mode changed to CLIMBING LADDER"));
    break;
  default:
    break;
  }
}

Now let’s implement our brachiation functions StartBrachiating(...), EndBrachiating() and AnimateBrachiation(...):

/* ThirdPersonCharacter.h */

public:
  void AnimateStartBrachiating(float DeltaTime);
  void StartBrachiating(ABrachiationTrigger* Trigger, const FHitResult& Hit);
  void EndBrachiating(bool JumpUp);

private:
  float BrachiationStartTime = 0.0f;
  float BrachiationMinTime = 0.3f;
  FVector BrachiationStartPosition = FVector::ZeroVector;
  bool bBrachiationStartAnimationRunning = false;
  bool bAnimating = false;

In StartBrachiating(...) we check if all preconditions are met. Is the character slow enough to brachiate? Are all components there? Then we set some booleans to tell other functions they should not interfere with brachiation. At the start we move our character smoothly into our starting position and block everything else for a while. As soon as this has finished, our character will be able to move along the ledge or leave it again. For that we need to remember the starting time, to keep a minimum duration of brachiating.
Then we calculate the closest point on the ledge to the characters hands brachiation trigger and center the character at this points, to make sure his hands are exactly on the ledge:

/* ThirdPersonCharacter.cpp */

void AThirdPersonCharacter::StartBrachiating
(ABrachiationTrigger* Trigger, const FHitResult& Hit)
{
  // Check, if velocity matches the constrictions
  // If Walking over the ledge
  float Z = GetVelocity().Z;
  
  // Get a absolute version of the velocity vector
  FVector AbsoluteVelocity = FVector(
    FMath::Abs(GetVelocity().X), 
    FMath::Abs(GetVelocity().Y), 
    FMath::Abs(GetVelocity().Z));
    
  // Extract the vertical velocity
  float VerticalVelocity = FMath::Abs(Z);

  // Extract the horizontal velocity
  float HorizontalVelocity 
    = FVector(AbsoluteVelocity.X, AbsoluteVelocity.Y, 0).Size();

  // Check our velocity thresholds
  if (VerticalVelocity > HorizontalVelocity) {      
    if (Z > 0 && VerticalVelocity > Trigger->JumpThroughVelocityThreshold) 
      { return; }
    if (Z < 0 && VerticalVelocity > Trigger->FallThroughVelocityThreshold) 
      { return; } }
  else { 
    if (       HorizontalVelocity > Trigger->RunThroughVelocityThreshold)  
      { return; } }

  if (!Trigger || !GetCharacterMovement()) { return; }

  /* If we came here, everything is fine and brachiating can start */

  // Stop Character by setting velocity to zero
  GetCharacterMovement()->Launch(FVector(0, 0, 0));

  bAnimating = true;			     // switch to animation status
  bBrachiationStartAnimationRunning = true;  // set it to start animation
  // remember, when we started brachiating
  BrachiationStartTime = GetWorld()->GetTimeSeconds();  

  // Set status to: BRACHIATING
  bBrachiating = true;

  // Rotate Character to ledges facing direction
  SetActorRotation(Trigger->GetFaceDirectionWorldRotation());
  CurrentBrachiationDirection = Trigger->GetBrachiationWorldAxis();

  // Set actor location so that players hands are centered on the ledge
  float CapsuleRadius = GetCapsuleComponent()->GetScaledCapsuleRadius();

  FVector ClosestPointOnLedge = UKismetMathLibrary::FindClosestPointOnLine(
    GetActorLocation(), 
    Trigger->GetActorLocation(), 
    Trigger->GetBrachiationWorldAxis());
    
  BrachiationStartPosition = FVector
  (
    ClosestPointOnLedge.X - Trigger->GetBrachiationWorldFaceDirection().X 
    * (BrachiationColliderHold->RelativeLocation.X * 1.5f),
    ClosestPointOnLedge.Y - Trigger->GetBrachiationWorldFaceDirection().Y 
    * (BrachiationColliderHold->RelativeLocation.X * 1.5f),
    ClosestPointOnLedge.Z - BrachiationColliderHold->RelativeLocation.Z
  );

  GetCharacterMovement()->SetMovementMode(
    EMovementMode::MOVE_Custom, 
    (uint8)ECustomMovementTypeEnum::MOVE_Brachiating);
}

To move our character smoothly into place, when starting the new movement, we have AnimateStartBrachiating(...) which is called from our tick method:

/* ThirdPersonCharacter.cpp */
void AThirdPersonCharacter::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);
  // Smooth Animations
  if (bBrachiating) { AnimateStartBrachiating(DeltaTime); }
}

void AThirdPersonCharacter::AnimateStartBrachiating(float DeltaTime)
{
  if (bBrachiationStartAnimationRunning)
  {
    FVector DestinationPosition = FMath::Lerp<FVector>(
      GetActorLocation(), 
      BrachiationStartPosition, DeltaTime*20);

    if ((BrachiationStartPosition - DestinationPosition).Size() < 0.01f)
    {
      bBrachiationStartAnimationRunning = false;	// Close enough
      bAnimating = false;
      DestinationPosition = BrachiationStartPosition;
    }
    SetActorLocation(DestinationPosition);
  }
}

In EndBrachiating(...) we stop all animations, leave the ledge and change the movement mode back to falling or jumping:

/* ThirdPersonCharacter.cpp */

void AThirdPersonCharacter::EndBrachiating(bool JumpUp)
{
  if (!bBrachiating) { return; }
  
  bAnimating = false;
  bBrachiationStartAnimationRunning = false;
  bBrachiating = false;
  bDynamicallyBrachiating = false;
  GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Falling, 0);

  if (JumpUp) // Jump up from Ledge
  { GetCharacterMovement()->Launch(FVector(0, 0, 1000)); }
  else        // Fall down from ledge
  { GetCharacterMovement()->Launch(FVector(0, 0, -100));    }
}

Last but not least we need to add our movement function itself:

/* ThirdPersonCharacter.h */

protected:
  void Brachiate(float Value);

And call it in the MoveRight(...) function:

/* ThirdPersonCharacter.cpp */

void AThirdPersonCharacter::MoveRight(float Value)
{
  if (bAnimating) { return; } // Block input while animating

  if (bBrachiating)
  {
    Brachiate(Value);
    return;
  }

  // Existing code ...
}

void AThirdPersonCharacter::Brachiate(float Value)
{
  if (bAnimating) { return; } // Block input while animating

  if (FMath::Abs(Value) > 0.001f) { bDynamicallyBrachiating = true;  }
  else                            { bDynamicallyBrachiating = false; }

  if ((Controller != NULL) && (Value != 0.0f))
  {
  // find out which way is right
  const FRotator Rotation = Controller->GetControlRotation();
  const FRotator YawRotation(9, Rotation.Yaw, 0);
  // get right vector
  const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
  // add movement in this direction
  SetActorLocation(GetActorLocation() + Value*100
    * CurrentBrachiationDirection 
    * GetWorld()->GetDeltaSeconds());
  }
}

But how do we end our brachiation actively? By navigating for- or backwards:

/* ThirdPersonCharacter.cpp */

void AThirdPersonCharacter::MoveForward(float Value)
{
  if (bAnimating) { return; } // Block input while animating

  // No moving forward on brachiation
  if (bBrachiating) 
  { 
    // Allow leaving brachiation only after minimal brachiation time
    if (GetWorld()->GetTimeSeconds() - BrachiationStartTime < BrachiationMinTime) 
      { return; }
    if (Value > 0.5f)       { EndBrachiating(true); }
    else if (Value < -0.5f) { EndBrachiating(false); }
    return; 
  }

  // Existing code ...
}

This is pretty much it. All we have to do now, is to entangle our animation in the animation blueprint. I’ll give you a screenshot and hope you could follow.

Animation Blueprint











Now simply place your brachiation triggers in the world and jump at them. Play with the threshold values and the character trigger positions.
Hope you had fun and learnt something :)

Written with StackEdit.

Unreal Engine C++ Tutorial Series

Want more? Have a look into the following tutorials!

Comments

Popular posts from this blog

Ubuntu 16.04 USB-Stick - "Das Ziel ist schreibgeschützt" lösen

Es gibt Dinge, die dürfen in einem nutzerfreundlichen Betriebssystem einfach nicht passieren. Vor allem dürfen Sie aber nicht monatelang bestehen bleiben. Mit Ubuntu 16.04 kann ich Freunden und Bekannten Ubuntu einfach nicht mehr empfehlen, wenn selbst ich an einfachsten Aufgaben scheitere. Gemeint ist hier das Kopieren von Dateien auf USB-Sticks. Trotz jahrelanger Ubuntu/Linux-Erfahrung gelang es mir erst nach gründlicher Recherche das Problem zu beheben. Ein Laie hat hier keine Chance. Damit ihr nicht lange suchen müsst, hier das Problem samt Lösung: Problem Datei oder Ordner auf Fat32-USB-Stick kopieren oder anlegen schlägt fehl mit der Meldung "Das Ziel ist schreibgeschützt". Lösung das Paket fuse-posixovl installieren und Ubuntu neu starten sudo apt-get install fuse-posixovl Viel Erfolg

[Unreal Engine][C++] How to create a simple trigger actor

[Unreal Engine][C++] How to create a simple trigger actor A Simple Trigger Volume in C++ Used Unreal Engine Version: 4.22 This is the first post of a small series of Unreal Engine C++ Tutorials. Keep in mind, that Unreal’s API changes rapidly and often. I still hope, this may be of some use to others. Coming from Unity, programming in C++ for Unreal is rather painful. I hope to give you some assistance and make life a little bit easier. Whay, would you say, should we make our own trigger actor? There is ATriggerVolume , right? Yes, there is, but inheriting from it is difficult and rather undocumented. I tried and failed. Yes, we have to give up some of ATriggerVolume 's functionality, but we learn a lot and at least we know exactly what it’s doing. First, we’ll create a new C++ class, inheriting from Actor , called SimpleTriggerVolume . Let’s add a protected property to hold a reference to our trigger component: /** Shape of the trigger volume componen

Der beste Weg um NintendoDS Schultertasten zu reparieren

Reparatur der NintendoDS Schultertasten ohne Löten! Selbst wenn man seinen NintendoDS wie ein rohes Ei behandelt kommt es doch immer wieder vor, dass nach einiger Zeit eine oder beide der Schultertasten nicht mehr wie gewohnt reagieren. Vor allem der NintendoDS Lite scheint häufig von diesem Problem betroffen zu sein. Wenn man im Internet nach diesem Problem sucht stößt man häufig auf diesen SoftMod: "Lippen über die Taste stülpen und hinein blasen." Diese Lösung funktioniert anfangs relativ gut. Aber der Erfolg ist nur von kurzer Dauer und die Methode von Mal zu Mal weniger erfolgreich. Jetzt bleiben drei Optionen. Bei Nintendo für 59€ ein Austauschgerät holen, eine neue Schultertaste einlöten ODER folgendes (Tipp von meinem Vater): Das wird benötigt: Tri-Wing Schraubenzieher, Elektronisches Reinigungsspray / Kontaktspray Man schraubt den NintendoDS auf (nur machen falls die Garantie abgelaufen ist) und nimmt, nachdem man die Batterie und Batterieklappe entfernt hat, die Rüc