Line Trace Instanced Static Mesh

Hey,

I spawn about 200x200 instanced cube’s. Now, when the player clicks one it should change it’s color.

Question 1.) is that even possible that instances have different textures / materials?

Question 2.) If yes, how to I perform line traces?
Currently my line trace gets me a “StaticMeshActor” but when I try to cast it to my class (InstancedCell : public StaticMeshActor), the cast fails.

.h


UCLASS()
class CROCHET_API AInstancedCell : public AStaticMeshActor
{
	GENERATED_BODY()
	
public:

	UPROPERTY()
	class UMaterialInstance* OrangeMaterial;

	UPROPERTY()
	UInstancedStaticMeshComponent* InstancedStaticMeshComponent;

	UPROPERTY()
	UStaticMesh* SMAsset_Cube;
	
public:
	AInstancedCell(const FObjectInitializer& ObjectInitializer);


	UFUNCTION(BlueprintCallable, Category = "Cell")
		void onClicked();
	
};

.cpp


AInstancedCell::AInstancedCell(const FObjectInitializer& ObjectInitializer)
{
	struct FConstructorStatics
	{
		ConstructorHelpers::FObjectFinderOptional<UStaticMesh> PlaneMesh;
		ConstructorHelpers::FObjectFinderOptional<UMaterialInstance> BlueMaterial;
		ConstructorHelpers::FObjectFinderOptional<UMaterialInstance> OrangeMaterial;
		FConstructorStatics()
			: PlaneMesh(TEXT("/Game/StarterContent/Shapes/Shape_Plane.Shape_Plane"))
			, BlueMaterial(TEXT("/Engine/TemplateResources/MI_Template_BaseBlue.MI_Template_BaseBlue"))
			, OrangeMaterial(TEXT("/Engine/TemplateResources/MI_Template_BaseOrange.MI_Template_BaseOrange"))
		{
		}
	};
	static FConstructorStatics ConstructorStatics;
	//Mesh
	static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMeshOb_torus(TEXT("StaticMesh'/Engine/EngineMeshes/Cube.Cube'"));
	SMAsset_Cube = StaticMeshOb_torus.Object;
	//create new object
	InstancedStaticMeshComponent = ObjectInitializer.CreateDefaultSubobject < UInstancedStaticMeshComponent >(this, TEXT("InstancedStaticMeshComponentCOMP"));
	InstancedStaticMeshComponent->AttachTo(RootComponent);

	//Not Made some reason?
	if (!InstancedStaticMeshComponent) return;
	//~~~~~~~~~~~~~~~~~~~~~~~~

	//Set to Asset
	InstancedStaticMeshComponent->SetStaticMesh(SMAsset_Cube);

	InstancedStaticMeshComponent->bOwnerNoSee = false;
	InstancedStaticMeshComponent->bCastDynamicShadow = false;
	InstancedStaticMeshComponent->CastShadow = false;
	InstancedStaticMeshComponent->BodyInstance.SetObjectType(ECC_WorldDynamic);

	//Visibility
	InstancedStaticMeshComponent->SetHiddenInGame(false);

	//Mobility
	InstancedStaticMeshComponent->SetMobility(EComponentMobility::Movable);

	//Collision
	InstancedStaticMeshComponent->BodyInstance.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	InstancedStaticMeshComponent->BodyInstance.SetObjectType(ECC_WorldDynamic);
	InstancedStaticMeshComponent->BodyInstance.SetResponseToAllChannels(ECR_Ignore);
	InstancedStaticMeshComponent->BodyInstance.SetResponseToChannel(ECC_WorldStatic, ECR_Block);
	InstancedStaticMeshComponent->BodyInstance.SetResponseToChannel(ECC_WorldDynamic, ECR_Block);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


	if (!StaticMeshComponent) return;

	//Mobility
	StaticMeshComponent->SetMobility(EComponentMobility::Movable);

	//Collision
	GetStaticMeshComponent()->BodyInstance.SetCollisionEnabled(ECollisionEnabled::Type::QueryAndPhysics);
	GetStaticMeshComponent()->BodyInstance.SetObjectType(ECC_WorldDynamic);
	GetStaticMeshComponent()->BodyInstance.SetResponseToChannel(ECC_WorldStatic, ECR_Block);
	GetStaticMeshComponent()->BodyInstance.SetResponseToChannel(ECC_WorldDynamic, ECR_Block);
	GetStaticMeshComponent()->BodyInstance.SetResponseToAllChannels(ECR_MAX);

	OrangeMaterial = ConstructorStatics.OrangeMaterial.Get();
}



void AInstancedCell::onClicked(){
	InstancedStaticMeshComponent->SetMaterial(0, OrangeMaterial);
}

Spawning


ACrochetBlockGrid::ACrochetBlockGrid()
{
	// Create dummy root scene component
	DummyRoot = CreateDefaultSubobject<USceneComponent>(TEXT("Dummy0"));
	RootComponent = DummyRoot;

	// Create static mesh component
	ScoreText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("ScoreText0"));
	ScoreText->SetRelativeLocation(FVector(200.f,0.f,0.f));
	ScoreText->SetRelativeRotation(FRotator(90.f,0.f,0.f));
	ScoreText->SetText(FText::Format(LOCTEXT("ScoreFmt", "Score: {0}"), FText::AsNumber(0)));
	ScoreText->AttachTo(DummyRoot);
	static ConstructorHelpers::FObjectFinder<UStaticMesh> staticMesh(TEXT("StaticMesh'/Game/StarterContent/Shapes/Shape_Plane.Shape_Plane'"));
	mesh = staticMesh.Object;
	// Set defaults
	Size = 300;
	BlockSpacing = 110.f;
}


void ACrochetBlockGrid::BeginPlay()
{
	Super::BeginPlay();

	FActorSpawnParameters SpawnInfo;

	//SET NO COLLISION FAIL TO TRUE
	SpawnInfo.bNoCollisionFail = true;

	SpawnInfo.Owner = this;

	//meshComp->SetMaterial(0, Material.Object);


	AInstancedCell* NewVertex =
		GetWorld()->SpawnActor<AInstancedCell>(
		AInstancedCell::StaticClass(),
		FVector(0,0,0),
		FRotator::ZeroRotator,
		SpawnInfo
		);
//	if (!NewVertex)                             return NULL;
	//if (!NewVertex->InstancedStaticMeshComponent)     return NULL;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	//Mesh
	NewVertex->InstancedStaticMeshComponent->SetStaticMesh(mesh);

	//Add Core Instance
	FTransform newT = NewVertex->GetTransform();
	newT.SetLocation(FVector(0, 0, 0));
	NewVertex->InstancedStaticMeshComponent->AddInstance(newT);

	//Scale
	//NewVertex->SetActorRelativeScale3D(CurrentVerticiesScale);

	// Number of blocks
	const int32 NumBlocks = Size * Size;

	// Loop to spawn each block
	for(int32 BlockIndex=0; BlockIndex<NumBlocks; BlockIndex++)
	{
		const float XOffset = (BlockIndex/Size) * BlockSpacing; // Divide by dimension
		const float YOffset = (BlockIndex%Size) * BlockSpacing; // Modulo gives remainder

		// Make postion vector, offset from Grid location
		const FVector BlockLocation = FVector(XOffset, YOffset, 0.f) + GetActorLocation();

		// Spawn a block
		FTransform newT = NewVertex->GetTransform();
		newT.SetLocation(BlockLocation);
		NewVertex->InstancedStaticMeshComponent->AddInstance(newT);
		//AInstancedCell* NewBlock = GetWorld()->SpawnActor<AInstancedCell>(BlockLocation, FRotator(0, 0, 0));

		
	}
}

Instances can not have different materials. You can customize their appearance only throudh a random number, that every instance receive upon creation. Sadly it is really random, you can only set a seed, but not actual number itself.

If every instance should have a different material you are out of luck. If you have groups of instances that would use the same material, what you can do is make multiple InstancedStaticMeshComponents. Suppose you have red, blue and orange cubes. You would have have an InstancedStaticMeshComponent with a red material and ones with a blue/orange. Making a cube change color would then be a case of removing one instance and creating an identically placed instance on another ISM.

Line tracing will give you the actor and component that were hit. You should be able to cast the actor to your InstancedCell actor, otherwise it must be another actor that was hit (try logging HitActor->GetDisplayName()). The line trace will not give you the instance that was hit. You can estimate that yourself by comparing the hit location to each instance’s world position, which will work accurately unless instances are placed really close to each other. I believe the instance data is stored in UInstancedStaticMeshComponent->PerInstanceSMData. Once you determine which instance was hit you know its index, thus you can remove it and add the same instance elsewhere.

1 Like