이 글은 아두이노와 언리얼(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/
삭제네^^
삭제