[C++]: Select a player with a mouse click.

Hi guys,

I’m trying to figure out the best way of selecting any character in my game with a mouse click. But i’m a little stuck. The basic gist of it is:

  • On mouse click - do raycast [done]
  • Deselect anything currently selected [done - will need to change this later so that it only does it if no players are hit.]
  • If raycast hits, check if it’s a playerpawn. [done]
  • If it’s a playerpawn, run a check to see which team they’re on. [done]
  • Now we know which team they’re on, find their number in the player array. [stuck here]
  • Assign this number to the SelectedEnemy or SelectedFriendly ints for future use [can do after above step is complete]
  • Apply fresnel effect based on selected player, only to selected player, and only visible on client end. [again, can do after above step]

I’ve pasted the relevant functions into this pastie: http://pastie.org/private/apqx0s8x9jj9pdkcnxbisa

Anybody have any ideas on the cleanest way to do this?

There’s actually a function you can use to get this easily; returns -1 if the instance is not in the array, otherwise returns the index.

Not sure i understand your problem but in a effort to help.
If you have done the trace and it returns the Pawn/ Character get the controller of the pawn.
In the Player State of the controller is the member UniqueId and PlayerId.

See doc for more info.
GetPlayerControllerFromNetId
APlayerState::
APawn::GetController
AController::GetPawn

Hope it helps. :slight_smile:

Ah okay, so it looks like this may be correct then?


void ASmashyCharacter::SelectPlayer(AController* SelectedPC)
{
	//is the selected enemy friendly or enemy?
	bool bIsEnemy = IsEnemy(SelectedPC);

	//now find the player number in the array.
	uint32 SelectedPlayer = SelectedPC->PlayerState->GetUniqueID();

	if (bIsEnemy)
	{
		SelectedEnemy = SelectedPlayer;
	}
	else
	{
		SelectedFriendly = SelectedPlayer;
	}

	UpdateTargetColorsAllMIDs();
}




Yes thats seems correct to me, can`t test it atm.
But now SelectedPlayer has the ID of the controller.
So you can do what ever you want with it now.

Glad i could help out :slight_smile:

Thanks for your help WCode. This is the better way to do it btw:



APlayerState* SelectedPlayer = SelectedPC->PlayerState;

	if (bIsEnemy)
	{
		SelectedEnemy = SelectedPlayer;
	}
	else
	{
		SelectedFriendly = SelectedPlayer;
	}


This stores the actual playerstate object rather than player ID, which apparently can change.

Thats intresting and very good to know, do you have a link or something to any documentation on that?

No real documentation as such i’m afraid, i was just going by the comments from people in #unrealengine IRC channel.

So here’s the current state of my initial function for getting a target:



void ASmashyPlayerController::GetPlayerTarget()
{
	//cast our own player pawn for reference.
	ASmashyPlayerState* Player = Cast<ASmashyPlayerState>(PlayerState);
	ASmashyCharacter* PlayerPawn = Cast<ASmashyCharacter>(Player);
	//deselect anything already selected.
	PlayerPawn->Deselect();

	//Trace to see if anything is behind the mouse cursor
	FHitResult Hit;
	GetHitResultUnderCursor(ECC_Pawn, false, Hit);

	if (Hit.bBlockingHit)
	{
		//it is a player, check what team they're on.
		ASmashyCharacter* TestPawn = Cast<ASmashyCharacter>(Hit.GetActor()); /// works until this point.
		AController* TestPlayer = TestPawn->GetController();
		//select player as enemy target
		PlayerPawn->SelectPlayer(TestPlayer);
	}
}


Maybe i’m doing this wrong, i was under the assumption that this:


GetHitResultUnderCursor(ECC_Pawn, false, Hit);

would run a line trace for what is clicked under my cursor, and will check to see if it was a pawn. however the game breaks and TestPawn = NULL. any ideas?

Am not sure about that method, GetHitResultUnderCursor()

Looking at the sample code in the doc it dont look like its doing the trace in 3D space at all.


bool APlayerController::GetHitResultUnderCursor(ECollisionChannel TraceChannel, bool bTraceComplex, FHitResult& HitResult) const
{
    ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player);
    bool bHit = false;
    if (LocalPlayer)
    {
        FVector2D MousePosition;
        if (LocalPlayer->ViewportClient->GetMousePosition(MousePosition))
        {
            bHit = GetHitResultAtScreenPosition(MousePosition, TraceChannel, bTraceComplex, HitResult);
        }
    }
 
    if(!bHit) //If there was no hit we reset the results. This is redundent but helps Blueprint users
    {
        HitResult = FHitResult();
    }
 
    return bHit;
}

I think you have to convert the screen space to world space and then do the trace.
Found a AnswerHub post on this here.

Hmmm… that’s interesting. GetHitResultUnderCursor() is used in the top down example game, that’s where i got it from.

Looks like i may need to set up a custom trace, which is annoying.

I’m using the following function on my pawn and is working for me:


void AMyRTS_Pawn::OnLeftMousePress(){
	bLeftMouseDown = true;

	FHitResult Hit;
	Controller->CastToPlayerController()->GetHitResultUnderCursor(ECC_Pawn, false, Hit);

	if (Hit.bBlockingHit){
		if (Hit.Actor != NULL){
			Selected = Cast<ACharacter>(Hit.GetActor());
			GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Magenta, FString::Printf(TEXT("Character Selected!")));
		}
	}
}

You are correct about the ECC_Pawn, it can get both actors and ground location.
Try checking if your Hit found an actor before casting, this will make sure its not a failed cast.