If you want to develop somewhat complex projects, it’s fundamental to understand what interfaces are and how they differ from blueprints. Essentially, one very distinct difference between blueprint actors and an interface is that, a Blueprint Actor holds information about something specific. This can be a gun blueprint that contains a physical location (transform), components (like meshes, lights, or collision), and specific logic (the “how it works”). The problem is that; if your player wants to interact with another Actor in your level; this can be an interactive chair, an enemy, or a note written on a piece of paper; the first/third person BP needs a “hard reference” to it.
So, if I press the "E" key, I should be able to check whether my line trace hit the chair, the enemy, or the note. While this can still be done in Blueprints using branch nodes (Did we hit the desk? → False → Did we hit the enemy? → False → Did we try to read the note? → True → Run logic), you can see how prone this method is to becoming spaghetti code, in addition to being extremely difficult to scale as you add more and more actors to your level. Additionally, creating a hard reference means you’re going to be loading all of these BP Actors into memory just to check whether you actually hit what you wanted. If we go back to the previous example, we loaded the chair into memory, but that’s not what we hit; yet it’s still in memory. Then we branch out and check if the line trace hit the enemy. No? Move on, but that one is now also in memory. Was it the paper that we hit? No? Move on. As you can see, we are ballooning up our memory with just a single line trace check. Your project will have other functionalities similar to this, and eventually this first/third person BP will become an FPS-breaking, unoptimized mess.
This is where interfaces come in. Interfaces can be seen as templates; sets of rules that multiple things can follow. For example, in our interface BP, we can define a function like OnInteract. So when we shoot the line trace, we can just call this OnInteract function and say, “Hey, we hit this thing; apply the logic and rules we set in the interface to this hit actor. It might sound a bit confusing at first, but with the example I’ll show you guys here, it’s very easy to understand.
Let's start by right-clicking in our Content Browser → Blueprints → Blueprint Interface. Name this interface BPI_Interactable. Open the interface, and you'll see a function named New Function or something similar; rename it to OnHit.
Let's give our function a name: OnHit
Optionally, we can add an input called ImpactLocation (Type: Vector) if we want the sound to play at a specific spot, but if you are going to use Play Sound 2D, this is not necessary.
Creating the vector input: ImpactLocation
Now we can open the Blueprint we want to hit (for example, a wooden crate). In the project I am working on, I have a BP called BP_Anchor; with three Geometry Collections (wood, door, chain) that break apart via Chaos physics when my bullet hits and creates a physical impact. So I’ll open this Blueprint. At the top part of the Blueprint, you’ll see a button named “Class Settings.” This is where we tell our BP to implement the Interface we just created whenever my line trace hits this actor:
"Class Settings" location
Click the “Add” button next to “Implemented Interfaces” in the Details panel and add your “BPI_Interactable” interface.
Adding My Interface to My BP_Anchor (Part 01)
Adding My Interface to My BP_Anchor (Part 012
Next, we can call the function we implemented in our interface, since this BP (BP_Anchor) can now use the functions in that interface. In your Event Graph, right-click and search for Event OnHit. Connect your Play Sound at Location node here.
Right Click > Event OnHit
Connecting the pins
At this stage, I'll go back to my FirstPersonCharacterBP, where I shoot my line trace, and whenever I shoot a line trace, I'll promote the "Out Hit Actor" output of the line trace to a variable and hold a reference to which actor we hit. If you are trying to interact with components, the same logic applies; you just need to use Out Hit Component. If you don't know what the difference is, an actor is the BP itself; for example, when I press "E", I hit the actor BP_Anchor (where my wood, chain, and door components are located). If I track the line trace component output, this will specifically return which component (wood geometry collection component, door geometry collection component, or chain geometry collection component) we hit within that specific (BP_Anchor) actor.
Promote the "Out Hit Actor" output of the line trace to a variable and call OnHit
That's it! We successfully implemented our interface. We can now focus on setting up a database so we play different sounds (wood, chain, or door) depending on which component we hit.
Above, I mentioned that my BP that I wanna hit with my line trace has three Geometry Collection components: wood, chain, and door. Depending on what I hit, I wanna play a different metal/door/wood sound. In the industry, to handle such systems, we rely on data-driven approaches, meaning we can create a Data Table so when we call the interface from the hit BP, it can look up which sound to play based on its material.
Right-click in Content Browser -> Blueprints -> Structure, and name it something like S_ImpactSounds.
Blueprint > Structure
Inside this structure, I need two inputs. One is the SurfaceName (Name), which defines which surface we hit (wood, door, or chain), and the other is an ImpactSound (Sound Base or Sound Cue), the sound that plays when we hit that component.
Add your variables (Part 01)
Add your variables (Part 02)
Now we have our structure (think of it as an empty table) that we need to fill with some data. Right-click in the Content Browser → Miscellaneous → Data Table to create the data table we need.
Miscellaneous → Data Table
Link the data table to the structure we just created: Pick S_ImpactSounds as the row structure. Name the table DT_ImpactSounds.
Pick S_ImpactSounds as the row structure. Name the table DT_ImpactSounds.
Click on the "Add" button to start adding your data.
Click on the "Add" button to start adding your data.
After adding all of your data to the datatable, don't forget to assign your sound cues to them: We don't need to play with the "SurfaceName" settings here.
Assigning sound cues to our data
Now we need to tell our BPI_Interactable (linked with our structure S_ImpactSounds, using the data table DT_ImpactSounds) to use the row names to determine which surface we hit and which sound to play. To do so, we can create a “RowName” (Type: Name) input.
Creating the "Name" input, titled "RowName"
We're almost done. Now we can go back to the actor that we hit (in my case, BP_Anchor, where my geometry collection components reside) and create a variable called SurfaceType (Type: Name). We will update this variable depending on what we hit, so instead of one sound, we tell the BP to "look up" what was hit and which sound to play. (You can leave the default value as "None".)
Creating the variable SurfaceType
From the Event OnHit, drag off the Row Name pin. Search for Get Data Table Row. Select your DT_ImpactSounds in the dropdown. Break the Out Row struct and plug the ImpactSound into Play Sound at Location.
Drag off the Row Name pin. Search for Get Data Table Row.
Connecting the pins in BP_Anchor where my breakable objects reside.
Now, lastly, we can update our FirstPersonBP to see which "Physical Material" we are hitting, since we want to play the sound based on the Physical Material (wood, chain, door) and it’s the most professional way to handle this type of matter. Right-click in the Content Browser -> Physics -> Physical Material. Select PhysicalMaterial as the parent. Since I have three objects made from different materials (wood, chain/metal, door/another type of wood), I'll create all three of these and name them: PM_Wood, PM_Chain, and PM_Door.
One more thing we can do if we want to stay organized: go to Project Settings → Physics → Physical Surface and name the Surface Types to match (Wood, Chain, Door). This links the assets to the engine's physics system. After that, just assign the physical materials to the objects that need them. Make sure to open your physical materials and ensure the surface type matches the desired material (Wood, Chain, Door, etc.). Now, whenever our line trace hits a mesh using this material, it knows exactly what surface it is.
If we go to our FirstPerson BP, we can now see that there is an input called RowName. (Again, we are getting this value from our interface.)
Creating our Physical Materials (Part: 01)
Creating our Physical Materials (Part: 02)
Creating our Physical Materials (Part: 03) - Project Settings → Physics → Physical Surface
Now let's do some problem solving. When I opened my project settings, I realized that I am already using "Wood" in my SurfaceType4 (this sound cue is played when the player is walking on a woody surface), but I also need to have another wood surface that creates "wood breaking" sounds when I shoot my line trace in my basement level. One very, very important thing about SurfaceType and data tables is that they are case-sensitive. To be able to link them properly, the names in both areas should match exactly.
Creating Our Physical Materials (Part 04) – Assigning the materials to my geometry collection components (I’ll dive right back into why case matching is so important here in a bit)
For now, let's just keep implementing our interface. All we have to do now is check which physical material we are hitting. To do this, we can go back to our FirstPersonBP and promote the output pin of Line Trace by Channel to a variable, so that every time we shoot a trace, it updates and shows what we hit:
Promoting "Out Hit Phys Mat" to a Variable (Part 01)
Promoting "Out Hit Phys Mat" to a Variable (Part 02)
Drag off the Out Hit Phys Mat variable that we just created, and search for Get Surface Type, so we know which surface (physical material type) the line trace is hitting:
Out Hit Phys Mat > Get Surface Type
Now the last, but very important part. We can drag off the Surface Type enum and use an Enum to Name node (in FirstPersonBP) since we already linked our physical materials in the project settings. Then, go back to our hit components BP (BP_Anchor in my case, where my wood, chain, and door are located) and make sure everything is connected like this:
Make sure every pin is connected in BP_Anchor (or whatever your BP actor is named).
And that's it! Now, if you combine this setup with some randomized sounds (tutorial here), your system is up and running.
Above, I mentioned that data structures and SurfaceType are case-sensitive, and it's very important that all the characters match. I had to change my physical material names for my basement-breaking wood, etc., since I already have another wood physical material that I use for footstep sounds. Changing the name broke the whole system due to case sensitivity and character mismatch. I need to make sure everything matches here:
My Physical Material Names
My PM_WoodBasement physical material’s surface type
My datatable's row names
My surface type names in the Project Settings > Physics.
I had to make sure that every name in these areas matches. A very important thing to know is: in Unreal Engine, when we use Get Surface Type (Line Trace → Out Hit Phys Mat → Surface Type in FPS_BP), it returns a specific name based on our Project Settings, not the name of the .uasset file (like PM_WoodBasement). Just to underline it again, we need to go to Edit → Project Settings, search for Physics, scroll down to Physical Surface, and we'll see a list (Surface1, Surface2, etc.). Type in your names there: WoodBasement, ChainBasement, etc. Then don't forget to open your PM_WoodBasement asset and set its Surface Type from dropdown to the WoodBasement you just created. Now that the engine recognizes the surface, your Data Table Row Names must match those Surface Types exactly. Again, to make sure, open your DT_ImpactSounds Data Table, right-click the row for your wood sound, and select Rename. Change it to WoodBasement (it must be spelled exactly like it is in the Project Settings).
I'll also teach you how to debug this kinda stuff. In your FirstPersonBP, right after the Get Surface Type node, Drag off the Surface Type pin and add a Print String, play the game, shoot the object. Look at the top-left of your screen. If it says "Default", then your Physical Material isn't assigned correctly to the Mesh. If it says "WoodBasement", then the issue is definitely the spelling in your Data Table. If it prints surface11, surface9 (if you check the Project settings > SurfaceType screenshots above, you can see that my PM_WoodBasement is for example sitting at SurfaceType9), it means that either the Engine didn't save the SurfaceType in the project settings properly, (I am not 100% sure if this is bug, but in version 5.7.1 SurfaceType doesn't sometimes register your input in the project settings, so it keeps returning the index (SurfaceType09 for example)) but doesn't play the sound due to mismatch issues. We can confirm this by gonig to our BP_Anchor (Our hit actor) and after EventOnHit, Get Data Table Row DT_ImpactSounds row not found. If it print strtings row not found, it means Data Table has the row named WoodBasement, but the Blueprint is searching for SurfaceType11, it returns "Row Not Found" (Image 4).
Instead of dealing with (In FirstPerson_BP) Out Hit Phys Mat > Surface Type > Enum to Name, we can just do Out Hit Phys Mat > Get Display Name. If this prints strings correctly and plays the audio, there is an issue with the enum-to-name conversion.
Out Hit Phys Mat > Get Display Name
Again, the reason your sound isn't playing yet might still be related to a name mismatch. But if the names already match 100% and the issue still persists, it could be an engine-related issue. Why "Get Display Name" works here is that, by default, the Enum to Name node looks at the internal index list (Surface1, Surface2). The Get Display Name node is smarter; it looks at the actual name of the asset or the label assigned to that enum. This is actually normal behavior in Unreal. Again, the Surface Type node returns an "Enum" (a list), and when you convert an enum directly to a name/string, Unreal often defaults to the internal name (like SurfaceType11). Get Display Name, however, is specifically designed to read the human-readable label we typed in the Project Settings.
I hope everything has been crystal clear up until this point, but if you have any questions or tutorial requests, you are always welcome to reach out to me on Patreon. I’d also appreciate it if you could support this website, so I can dedicate more time to it! Until then, happy developing!