Announcement

Collapse
No announcement yet.

M.O.S. - Markable Objects System

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    M.O.S. - Markable Objects System


    Hi guys,

    time ago i discovered this usefull Easy Offscreen Indicator Blueprint and i decided to implement it in my project to make a (very very simple) Metal Gear Solid 5 style enemy marker, now my system is at a good point and i want to share it with you.

    It's a very simple stuff and has some bugs (specially when offscreen marker go at bottom of the screen), nothing to do with AAA style games but is the first complex work i achieved alone and i'm proud of it

    Any help and suggestions to improve the system are very appreciated! Especially for the C++ part because (as i said) is not mine.

    Here a short video on what i'm talking about (sorry for the horrid grammar error on the walls, i fixed it).


    In the example project you will find some NPC and Actors already marked to show you the various kind of marker, and one NPC not marked, to discover what kind of marker the NPC has you have to "scan" it, to do it look at it and press and hold Q.

    With E you can spawn custom checkpoints, at the moment the spawn location not work very well (if you look at the top of the wall and you press E the CP will be spawned too high and you cant reach it) because my next step is to create a minimap and spawn CP from minimap.

    M.O.S. work with both Third Person and First Person Player Characters.

    M.O.S. core it's composed by:

     
    Spoiler


    How to use M.O.S.?

    Use M.O.S it's very simple

    IMPORTANT STEP

    Create a Blueprint Function Library C++ class and copy and paste this code from Easy Offscreen Indicator Blueprint (or copy and paste from the example project file), remember to change names with yours.

    (Obvious don't do it into the example project, it has it).

    .h

    Code:
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "Kismet/BlueprintFunctionLibrary.h"
    #include "HUDBlueprintLibrary.generated.h"
    
    /**
     * 
     */
    UCLASS()
    class MOSEXAMPLE_API UHUDBlueprintLibrary : public UBlueprintFunctionLibrary
    {
    	GENERATED_BODY()
    	
    public:
    	/**
    	* Converts a world location to screen position for HUD drawing. This differs from the results of FSceneView::WorldToScreen in that it returns a position along the edge of the screen for offscreen locations
    	*
    	*   [MENTION=209895]param[/MENTION]		InLocation	- The world space location to be converted to screen space
    	*   [MENTION=209895]param[/MENTION]		EdgePercent - How close to the edge of the screen, 1.0 = at edge, 0.0 = at center of screen. .9 or .95 is usually desirable
    	* @outparam	OutScreenPosition - the screen coordinates for HUD drawing
    	* @outparam	OutRotationAngleDegrees - The angle to rotate a hud element if you want it pointing toward the offscreen indicator, 0° if onscreen
    	* @outparam	bIsOnScreen - True if the specified location is in the camera view (may be obstructed)
    	*/
    	UFUNCTION(BlueprintPure, meta = (WorldContext = "WorldContextObject", CallableWithoutWorldContext), Category = "HUD|Util")
    		static void FindScreenEdgeLocationForWorldLocation(UObject* WorldContextObject, const FVector& InLocation, const float EdgePercent, FVector2D& OutScreenPosition, float& OutRotationAngleDegrees, bool &bIsOnScreen);
    
    };
    .cpp

    Code:
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "MOSExample.h"
    #include "HUDBlueprintLibrary.h"
    
    void UHUDBlueprintLibrary::FindScreenEdgeLocationForWorldLocation(UObject* WorldContextObject, const FVector& InLocation, const float EdgePercent, FVector2D& OutScreenPosition, float& OutRotationAngleDegrees, bool &bIsOnScreen)
    {
    	bIsOnScreen = false;
    	OutRotationAngleDegrees = 0.f;	
    
    	if (!GEngine) return;
    
    	const FVector2D ViewportSize = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY());
    	const FVector2D  ViewportCenter = FVector2D(ViewportSize.X / 2, ViewportSize.Y / 2);
    
    	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject);
    
    	if (!World) return;
    
    	APlayerController* PlayerController = (WorldContextObject ? UGameplayStatics::GetPlayerController(WorldContextObject, 0) : NULL);
    
    	if (!PlayerController) return;
    
    	ACharacter* PlayerCharacter = Cast<ACharacter>(PlayerController->GetPawn());
    
    	if (!PlayerCharacter) return;
    
    	FVector Forward = PlayerCharacter->GetActorForwardVector();
    	FVector Offset = (InLocation - PlayerCharacter->GetActorLocation()).GetSafeNormal();
    
    	float DotProduct = FVector::DotProduct(Forward, Offset);
    	bool bLocationIsBehindCamera = (DotProduct < 0);
    
    	if (bLocationIsBehindCamera)
    	{
    		// For behind the camera situation, we cheat a little to put the
    		// marker at the bottom of the screen so that it moves smoothly
    		// as you turn around. Could stand some refinement, but results
    		// are decent enough for most purposes.
    
    		FVector DiffVector = InLocation - PlayerCharacter->GetActorLocation();
    		FVector Inverted = DiffVector * -1.f;
    		FVector NewInLocation = PlayerCharacter->GetActorLocation() * Inverted;
    
    		NewInLocation.Z -= 5000;
    
    		PlayerController->ProjectWorldLocationToScreen(NewInLocation, OutScreenPosition);
    		OutScreenPosition.Y = (EdgePercent * ViewportCenter.X) * 2.f;
    		OutScreenPosition.X = -ViewportCenter.X - OutScreenPosition.X;
    	}
    
    	PlayerController->ProjectWorldLocationToScreen(InLocation, OutScreenPosition);//*ScreenPosition);
    
    	// Check to see if it's on screen. If it is, ProjectWorldLocationToScreen is all we need, return it.	
    	if (OutScreenPosition.X >= 0.f && OutScreenPosition.X <= ViewportSize.X
    		&& OutScreenPosition.Y >= 0.f && OutScreenPosition.Y <= ViewportSize.Y)
    	{
    		
    		bIsOnScreen = true;
    		return;
    	}
    
    	OutScreenPosition -= ViewportCenter;
    
    	float AngleRadians = FMath::Atan2(OutScreenPosition.Y, OutScreenPosition.X);
    	AngleRadians -= FMath::DegreesToRadians(90.f);
    
    	OutRotationAngleDegrees = FMath::RadiansToDegrees(AngleRadians) + 180.f;
    
    	float Cos = cosf(AngleRadians);
    	float Sin = -sinf(AngleRadians);
    
    	OutScreenPosition = FVector2D(ViewportCenter.X + (Sin * 180.f), ViewportCenter.Y + Cos * 180.f);
    
    	float m = Cos / Sin;
    
    	FVector2D ScreenBounds = ViewportCenter * EdgePercent;
    
    	if (Cos > 0)
    	{		
    		OutScreenPosition = FVector2D(ScreenBounds.Y / m, ScreenBounds.Y);
    	}
    	else
    	{		
    		OutScreenPosition = FVector2D(-ScreenBounds.Y / m, -ScreenBounds.Y);
    	}
    
    	if (OutScreenPosition.X > ScreenBounds.X)
    	{		
    		OutScreenPosition = FVector2D(ScreenBounds.X, ScreenBounds.X*m);
    	}
    	else if (OutScreenPosition.X < -ScreenBounds.X)
    	{		
    		OutScreenPosition = FVector2D(-ScreenBounds.X, -ScreenBounds.X*m);
    	}
    	
    	OutScreenPosition += ViewportCenter;	
    
    }

    Step 1

    Open the Blueprint where you want to use M.O.S. (for example your NPC Blueprint), and in Class Settings add the BP_MOS interface

     
    Spoiler

    Step 2

    Add the BP_MarkerComponent

     
    Spoiler

    Step 3

    Implement the BP_MOS interface into your Blueprint Event Graph and call the BP_MarkerComponent OnMark event.

     
    Spoiler


    Step 4

    In the BP_MarkerComponent details tweak the M.O.S. properties as you want

     
    Spoiler


    TIPS: set "MaxDistance" as a negative number to make your marker "persistent" on distance.

    Step 5

    Send a BP_MOS interface OnMark message where you want, in my example i sent the message to the actor retrieved from a LineTraceByChannel hit into BP_MyPlayerController event graph.

     
    Spoiler


    Step 6

    Setup a Post Process volume into your level, in details set it "Unbound" and under "Post Process Settings -> Misc -> Blendables" add one element and use the PP_OutlineCustomDepthOcclusion_Inst

     
    Spoiler
    Last edited by ZioYuri78; 11-20-2016, 06:49 AM. Reason: Format
    Join the Unreal Engine community on Reddit! | Twitter: @ZioYuri78

    #2
    Reserved for FAQ
    Join the Unreal Engine community on Reddit! | Twitter: @ZioYuri78

    Comment


      #3
      Well this is super neat, good work
      CrashGame (Working title for my survival/exploration game) - YouTube

      Comment


        #4
        Cool stuff, ty for your work.
        OMG this forum is the hell for my freetime.
        So many interesting things around to test out and play with.
        My small game on IndieDB ****** Beams on Twitter ****** Beams on Steam ****** VideoStuff ****** PictureStuff
        UE brings Math back into my life or i am not sure.

        Comment


          #5
          Thats amazing man, Great job!

          Originally posted by Luftbauch View Post
          OMG this forum is the hell for my freetime.
          hahaha +1

          Comment


            #6
            Well done
            --------------------------------------------------------
            [New On Marketplace] Custom Character Creator
            --------------------------------------------------------
            [Marketplace]Multiplayer Melee Combat System - Animated Crossbow
            --------------------------------------------------------

            Comment


              #7
              Thank you guys

              I have a question for you, one user on Reddit have mention this memory leak problem into the code
              https://it.reddit.com/r/unrealengine...system/cu715mz

              I managed a little bit the original code, do you think now is better?

              Code:
              void UHUDBlueprintLibrary::FindScreenEdgeLocationForWorldLocation(UObject* WorldContextObject, const FVector& InLocation, const float EdgePercent, FVector2D& OutScreenPosition, float& OutRotationAngleDegrees, bool &bIsOnScreen)
              {
              	bIsOnScreen = false;
              	OutRotationAngleDegrees = 0.f;	
              
              	if (!GEngine) return;
              
              	const FVector2D ViewportSize = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY());
              	const FVector2D  ViewportCenter = FVector2D(ViewportSize.X / 2, ViewportSize.Y / 2);
              
              	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject);
              
              	if (!World) return;
              
              	APlayerController* PlayerController = (WorldContextObject ? UGameplayStatics::GetPlayerController(WorldContextObject, 0) : NULL);
              
              	if (!PlayerController) return;
              
              	ACharacter* PlayerCharacter = Cast<ACharacter>(PlayerController->GetPawn());
              
              	if (!PlayerCharacter) return;
              
              	FVector Forward = PlayerCharacter->GetActorForwardVector();
              	FVector Offset = (InLocation - PlayerCharacter->GetActorLocation()).GetSafeNormal();
              
              	float DotProduct = FVector::DotProduct(Forward, Offset);
              	bool bLocationIsBehindCamera = (DotProduct < 0);
              
              	if (bLocationIsBehindCamera)
              	{
              		// For behind the camera situation, we cheat a little to put the
              		// marker at the bottom of the screen so that it moves smoothly
              		// as you turn around. Could stand some refinement, but results
              		// are decent enough for most purposes.
              
              		FVector DiffVector = InLocation - PlayerCharacter->GetActorLocation();
              		FVector Inverted = DiffVector * -1.f;
              		FVector NewInLocation = PlayerCharacter->GetActorLocation() * Inverted;
              
              		NewInLocation.Z -= 5000;
              
              		PlayerController->ProjectWorldLocationToScreen(NewInLocation, OutScreenPosition);
              		OutScreenPosition.Y = (EdgePercent * ViewportCenter.X) * 2.f;
              		OutScreenPosition.X = -ViewportCenter.X - OutScreenPosition.X;
              	}
              
              	PlayerController->ProjectWorldLocationToScreen(InLocation, OutScreenPosition);//*ScreenPosition);
              
              	// Check to see if it's on screen. If it is, ProjectWorldLocationToScreen is all we need, return it.	
              	if (OutScreenPosition.X >= 0.f && OutScreenPosition.X <= ViewportSize.X
              		&& OutScreenPosition.Y >= 0.f && OutScreenPosition.Y <= ViewportSize.Y)
              	{
              		
              		bIsOnScreen = true;
              		return;
              	}
              
              	OutScreenPosition -= ViewportCenter;
              
              	float AngleRadians = FMath::Atan2(OutScreenPosition.Y, OutScreenPosition.X);
              	AngleRadians -= FMath::DegreesToRadians(90.f);
              
              	OutRotationAngleDegrees = FMath::RadiansToDegrees(AngleRadians) + 180.f;
              
              	float Cos = cosf(AngleRadians);
              	float Sin = -sinf(AngleRadians);
              
              	OutScreenPosition = FVector2D(ViewportCenter.X + (Sin * 180.f), ViewportCenter.Y + Cos * 180.f);
              
              	float m = Cos / Sin;
              
              	FVector2D ScreenBounds = ViewportCenter * EdgePercent;
              
              	if (Cos > 0)
              	{		
              		OutScreenPosition = FVector2D(ScreenBounds.Y / m, ScreenBounds.Y);
              	}
              	else
              	{		
              		OutScreenPosition = FVector2D(-ScreenBounds.Y / m, -ScreenBounds.Y);
              	}
              
              	if (OutScreenPosition.X > ScreenBounds.X)
              	{		
              		OutScreenPosition = FVector2D(ScreenBounds.X, ScreenBounds.X*m);
              	}
              	else if (OutScreenPosition.X < -ScreenBounds.X)
              	{		
              		OutScreenPosition = FVector2D(-ScreenBounds.X, -ScreenBounds.X*m);
              	}
              	
              	OutScreenPosition += ViewportCenter;	
              
              }
              Last edited by ZioYuri78; 08-19-2015, 03:32 PM.
              Join the Unreal Engine community on Reddit! | Twitter: @ZioYuri78

              Comment


                #8
                Nice work, Thanks for sharing mate!
                Game Developer @ LODZERO - My Twitter | tomlooman.com UE4 samples & tutorials | C++ Survival Game (Open-Source)

                Comment


                  #9
                  Thanks for sharing. What engine version do I need to be on? I tried opening in 4.8.1 and everything error'd out

                  Comment


                    #10
                    Originally posted by SDRLabs View Post
                    Thanks for sharing. What engine version do I need to be on? I tried opening in 4.8.1 and everything error'd out
                    I used the 4.8.3, try with it and if you still have errors tell me which for reference.
                    Join the Unreal Engine community on Reddit! | Twitter: @ZioYuri78

                    Comment


                      #11
                      Kind of sucks how stock ue4 is limited to one blendable at a time. Also how did you have to set some setting to get the blendable to work when the entire actor is covered up. I can't remember the name, but it makes shadows all funky. I tried getting it to work in my project, but in order to get the x-ray type outline to work at least 90% of the time, I had to set it pretty high.

                      Comment


                        #12
                        Originally posted by _cDub View Post
                        Kind of sucks how stock ue4 is limited to one blendable at a time. Also how did you have to set some setting to get the blendable to work when the entire actor is covered up. I can't remember the name, but it makes shadows all funky. I tried getting it to work in my project, but in order to get the x-ray type outline to work at least 90% of the time, I had to set it pretty high.
                        Is something i have to figure out, at the moment my system is very basic, the Markable Component fire a line trace from the owner actor to the player and start from the bottom of actor location

                        Click image for larger version

Name:	pCRIpyy.png
Views:	1
Size:	103.7 KB
ID:	1084488

                        rise up the Z value could be a good temporary solution but if you are using the Third Person Character and the linetrace have a hit with a wall but your camera is rotated "behind" the wall you still see the Outline PP, a good solution could be change the end of the trace to the camera location instead of the player, when i will finish the minimap and his integration with MOS for the spawn checkpoint system i will go deep on this.
                        Last edited by ZioYuri78; 08-26-2015, 05:30 AM. Reason: typo
                        Join the Unreal Engine community on Reddit! | Twitter: @ZioYuri78

                        Comment


                          #13
                          How does this stop occlusion culling? :/

                          Comment


                            #14
                            Originally posted by _cDub View Post
                            How does this stop occlusion culling? :/
                            Mmm, probably i don't understand what you mean
                            Join the Unreal Engine community on Reddit! | Twitter: @ZioYuri78

                            Comment


                              #15
                              Originally posted by ZioYuri78 View Post
                              Mmm, probably i don't understand what you mean
                              Sorry, not a good explanation of what I was talking about, and it looks like I changed how I was going to phrase the sentence part way through. Lol

                              When I used that x-ray type blendable that you are using on that character model to see him through walls a while ago, I had trouble getting the actor to stay on screen. When I partially block him from my camera sight, I can see the portion of him that is blocked by a wall or something, but the second he is entirely blocked, he disappeared.
                              My guess was that occlusion culling kept him from being rendered and thus had nothing to outline. I tried setting the bounds scale setting, and that worked some of the time but would still culled at certain angles. I had to crank it as high as 13. This was on 4.7 though.

                              This is the one I am taking about http://www.tomlooman.com/ue4-evolves...e-post-effect/
                              It looks like you had it working fine in yours.

                              Comment

                              Working...
                              X