이 글은 아두이노와 언리얼(Unreal) 게임엔진을 연결하는 간단한 방법을 설명한다. 이 글은 실시간으로 시리얼 통신에서 얻은 센서값을 언리얼의 액터 메쉬의 머터리얼(재질) 색상에 직접 설정하는 방법을 포함한다. 센서와 언리얼 연결 방법을 보여주기 위해, 별도 상용 언리얼 플러그인을 사용하지 않고, C++에서 통신 모듈을 간단히 개발해 사용한다. 이 글에서 설명하는 같은 방식으로 텍스처, 광원, 메쉬 위치 등을 센서 값에 따라 실시간으로 변경할 수 있다.
이 글의 개발 결과는 다음과 같다. 화면에서 각 큐브는 물리적 센서에서 얻은 데이터값을 색상으로 표시한다. 이를 잘 이용하면, 언리얼을 이용해 IoT와 연결된 디지털트윈(Digital Twin)이나 메타버스(Metaverse)를 쉽게 개발할 수 있다.
개발 환경
언리얼과 아두이노 IDE를 각각 설치한다.
개발 순서
간단한 구현을 위해, 센서는 아두이노에서 얻은 값을 사용하고, RS232 시리얼 통신을 이용한다.
센서 - 아두이노 보드 - RS232 시리얼 통신 - 언리얼 - 데이터 가시화
주요 개발 순서는 다음과 같다.
1. 아두이노 IDE에서 센서 데이터 획득하는 회로와 프로그램 작성. 아두이노 A0핀에 광센서 등 사용해 시그널을 A0에 입력. LED는 9번핀에 연결.
const int analogInPin = A0;  // Analog input pin that the potentiometer is attached to
const int analogOutPin = 9; // Analog output pin that the LED is attached to
int sensorValue = 0;        // value read from the pot
int outputValue = 0;        // value output to the PWM (analog out)
void setup() {
  Serial.begin(9600);
}
void loop() {
  // read the analog in value:
  sensorValue = analogRead(analogInPin);
  // map it to the range of the analog out:
  outputValue = map(sensorValue, 0, 500, 0, 255);
  analogWrite(analogOutPin, outputValue);
  // print the results to the Serial Monitor:
  Serial.write(outputValue);
  delay(50);
}
2. 언리얼 실행. 빈 프로젝트를 C++ 형식으로 생성
3. 블루프린트 작성. 기본 클래스는 Actor에서 파생받음. 이름은 ColoredCube로 설정. 블루프린트에서 Cube 메쉬 객체 설정
4. 액터의 메쉬 객체에 적용할 재질 작성 및 설정. 재질 이름은 Color로 설정하고 다음과 같이 재질 작성
6. Visual Studio에서 Serial Port로 부터 센서 데이터를 얻는 모듈을 작성. 헤더 파일은 다음과 같음. 소스 코드는 여기를 참고.
#pragma once
#include "CoreMinimal.h"
#include "CoreTypes.h"
/**
 * 
 */
class IOT6_API SerialPort
{
public:
	SerialPort();
	~SerialPort();
	bool open(const TCHAR* sPort, int nBaud = 9600);
	void close();
	int write(TArray<uint8>& Buffer);
	int read(TArray<uint8>& Buffer);
private:
	void* m_PortHandle;
};
7. Visual Studio에서 액터 소스 파일 편집. Serial Port에서 얻은 센서 데이터로 액터 메쉬의 머터리얼을 얻어 생상을 설정함. 주요 코드는 다음과 같음.
#include "ColoredCube.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Components/StaticMeshComponent.h"
#include "Materials/MaterialInterface.h"
#pragma optimize("", off)
class SerialPortInstance
{
public:
	SerialPortInstance();
	~SerialPortInstance();
	SerialPort _port;
};
SerialPortInstance::SerialPortInstance()
{
	_port.open(_T("COM3"));  // 이 부분은 각자 시리얼 통신 포트 이름으로 변경해야 함
}
SerialPortInstance::~SerialPortInstance()
{
	_port.close();
}
SerialPortInstance _serial;
// Sets default values
AColoredCube::AColoredCube()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AColoredCube::BeginPlay() // 최초 액터 생성 시 실행
{
	Super::BeginPlay();
	auto Cube = FindComponentByClass<UStaticMeshComponent>();
	auto Material = Cube->GetMaterial(0);
	_DynamicMaterial = UMaterialInstanceDynamic::Create(Material, NULL);
	Cube->SetMaterial(0, _DynamicMaterial);
	_randomColor = FMath::Rand() % 64;
}
// Called every frame
void AColoredCube::Tick(float DeltaTime)  // 프레임 렌더링 전에 호출됨
{
	Super::Tick(DeltaTime);
	TArray<uint8> Buffer;
	Buffer.Add(0);
	int len = _serial._port.read(Buffer);
	if (len)
	{
		FString string = FString::Printf(TEXT("Data = %d"), Buffer[0]);
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, string);
		float blend = ((float)Buffer[0] + _randomColor) / (255.0);
		// float blend = 0.5f + FMath::Cos(GetWorld()->TimeSeconds) / 2;
		_DynamicMaterial->SetScalarParameterValue(TEXT("Blend"), blend);
	}
}
#pragma optimize("", on)
구현 결과
이 결과는 다음과 같다. 센서 값에 따라 큐브 색상에 실시간으로 변화하는 것을 확인할 수 있다.
실제 구현 코드는 아래와 같다.
- Sensor colored cube for Unreal IoT (github)
 
마무리
이 방법을 응용하면 IoT와 같은 객체에서 얻은 센서값을 언리얼과 연결할 수 있다. 이렇게 연결하여, 물리세계를 게임엔진 기반 3차원 가상세계로 맵핑하여 가시화하거나 그 반대로 액추에이터를 제어하는 앱을 개발할 수 있다. 
레퍼런스


와우 팟케스트에서 처음 뵙고 여기서도 보네요! ^^!
답글삭제이메일로 진로사항에 대해 질문 했었었는데... 지금은 서울예대 디지털아트과에 입학해서 2학년1학기를 다니고 있습니다~ ㅎㅎ 제 홈페이지에요 놀러오세요! https://www.instagram.com/yannenchemin/
삭제네^^
삭제