Rust + Bevy Engine + egui 시작하기

2025. 7. 30. 10:25프로그래밍/Rust

반응형


길고도 짧았던 프로젝트 하나가 일단락되어 게시물을 작성할 여유가 생겼습니다.

 

Rust를 본격적으로 업무에 사용하기 시작한 것이 2019년도였습니다. 지금도 그렇지만 당시에도 Rust 진영에서 GUI 프로그램 개발을 하기에는 선택지가 많지 않은 상황이었습니다. 그나마 그때에는 간단한 펌웨어 업데이트 프로그램을 만들던 때라 Sciter( https://sciter.com/ )를 사용했었습니다. 지금이라면 Tauri를 선택했겠지만 Tauri가 1.0이 된 게 2022년도 여름에나 되어서였네요.

시간이 지나 다시 GUI 프로그램을 만들어야 하는 상황이 왔고, 그때에는 Tauri를 사용했습니다. 그때가 딱 Tauri가 1.0이 된 직후였네요. 차트가 필요해서 Chart.js를 사용해서 실시간으로 업데이트되는 데이터를 표시했습니다. 그리고 그때부터 성능에 한계를 느끼기 시작했어요.

 

또 이런저런 일들을 하다가 드디어 영상을 3차원 공간에 올려야 하는 일을 하게 되었습니다. 그것도 macOS에서요. 때가 되었구나 싶었습니다. 계속해서 GUI와 관련된 새로운 정보들을 탐색해 왔었고, 다음 개발에는 Bevy Engine을 써야겠다고 염두해 두고 있었거든요. 한편으로 egui도 생각하고 있었습니다. 복잡한 UI 요소들을 넣어야 하는데 Bevy Engine만으로는 불가능했거든요.

 

C#을 사용한 게 20년 정도 되었고, WPF를 사용한 것도 15년 정도가 되어가는 것 같습니다. 복잡한 UI 요소들을 직접 만들어서 사용해야 할 일들이 많았었는데 쉽고 빠르게 만들 수 있었어요. 지금 기억나는 건 NMEA에 있는 위성 위치와 신호 세기를 표시하는 컨트롤을 만들었던 것과 드론 센서 데이터를 표시하는 실시간 차트를 만들었던 겁니다. 한 화면에서 18개의 실시간 차트를 동시에 그렸더니 WPF의 선 그리기로는 성능이 도저히 안 나와서 결국 백그라운드에서 비트맵에 그린 후에 화면에 붙여 넣는 방법으로 구현을 했었습니다.( https://youtu.be/ozzC118UJTk ) 여전히 UI 요소들의 편집도 쉽고, 새로운 컨트롤을 만드는 것도 좋지만 윈도우를 벗어나서 사용하기에는 어려움이 있습니다.

 

Bevy Engine과 egui를 사용하기 시작한 게 이제 딱 1년이 되었습니다. 작업을 진행하면서 좋은 점도 있고, 아쉬운 부분들도 있었지만, 충분히 사용할 만하다고 생각하고 있습니다. 개인적으로는 꼭 C#과 WPF를 사용해야 하는 상황이 아니라면 GUI 프로그램을 만드는데 Bevy Engine과 egui를 사용하려 합니다. 물론 현재 가장 크리티컬 한 문제는 한국어 입력이 안 된다는 부분입니다. 이른 시일 내에 해결이 되면 좋겠네요. 개인적으로도 bevy_egui_ime ( https://github.com/8bitTD/bevy_egui_ime ) 라이브러리를 한국어 입력이 되도록 수정해 보고 있습니다. 충분히 가능성이 있어 보입니다.

 

C#을 처음 배울 때 좋았던 것이 GUI 프로그램을 너무 쉽게 만들 수 있다는 것이었습니다. "Hello World"를 윈도우 위에 출력하다니. 조금 건너뛰는 느낌이 있기는 하지만 Rust를 처음 접하시더라도 재미있게 무언가를 만들어 보실 수 있다면 좀 더 흥미를 느끼실 수 있지 않을까 생각하고 있습니다. 그런 의미에서 이 게시물을 시작했습니다.

 

이제 시작하겠습니다.

 

 

Rust 설치

Rust 홈페이지의 Getting started 문서를 참고하시기 바랍니다.

https://www.rust-lang.org/learn/get-started

 

macOS, Linux, WSL은 아래의 명령어를 사용하여 설치하실 수 있습니다.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

 


새로운 프로젝트 시작

개인적으로는 사용자 폴더 하위에 project 폴더를 만들고 그 아래에 프로젝트를 생성해서 사용하고 있습니다. 개인의 취향에 따르는 문제이니 원하는대로 사용하시면 됩니다.

새로운 Rust 프로젝트를 시작하는 명령은 아래와 같습니다.

 

Cargo new [프로젝트 이름]

 

cargo new bevy_egui_hello_world

 

폴더로 이동해서 생성된 파일을 확인하면 아래와 같은 파일들이 보입니다.

 

새로 생성한 프로젝트 폴더

 

 

Cargo.toml 파일은 프로젝트의 정보와 의존성 패키지 목록을 관리합니다. 내용을 살펴보면 아래와 같습니다.

 

Cargo.toml

 

 

의존성 추가

현재 프로젝트에 Bevy Engine과 egui 라이브러리를 추가해보겠습니다.

 

아래의 명령을 사용하여 Bevy Engine을 추가합니다.

cargo add bevy

 

다음으로 아래의 명령을 사용하여 bevy_egui를 추가합니다.

이 라이브러리는 Bevy Engine에서 egui를 사용하기 위해 필요한 라이브러리입니다.

cargo add bevy_egui

 

마지막으로 egui를 추가합니다.

cargo add egui

 


위의 명령을 묶어서 한 번에 설치하실 수도 있습니다.

cargo add bevy bevy_egui egui

 

 

VS Code 설치

소스 코드를 편집하는데 VS Code를 사용하겠습니다.

없으시다면 아래의 사이트를 방문하여 다운로드 받고 설치하시기 바랍니다.

https://code.visualstudio.com/

 

macOS에서는 압축 파일을 다운받게 되는데 압축 파일을 더블 클릭하셔서 압축을 해제하신 후에 생성된 'Visual Studio Code'를 클릭-드래그해서 Finder 좌측에 보이는 '응용 프로그램'에 끌어다 놓고 사용하시면 됩니다.

 

VS Code를 실행하시면 화면 좌측에 버튼들이 표시됩니다. 이 중에 네 개의 사각형이 그려진 버튼을 눌러 확장을 설치할 수 있는 화면으로 이동하시기 바랍니다.

 

확장

 

확장 화면 상단의 검색창을 사용해서 필요한 확장을 설치하실 수 있습니다.

Rust 언어를 사용하기 위해 기본적으로 필요한 확장은 다음과 같습니다.

  • CodeLLDB
  • Crates
  • Dependi
  • Error Lens
  • Even Better TOML
  • rust-analyzer

 

아래는 사용을 권해드리는 확장입니다. 필요에 따라 설치하시기 바랍니다.

  • Alphabetical Sorter
  • Code Spell Checker
  • Git Graph
  • Git History
  • Git History Diff
  • Indenticator
  • Bevy Color

 

터미널에서 VS Code를 실행하려면 아래에 설명한 단계를 따라하시기 바랍니다.

  1. Command + Shift + P 키를 누릅니다.
  2. 명령 팔레트에 shell을 입력합니다.
  3. Shell Command: Install 'code' command in PATH 항목을 선택합니다.
  4. 관리자 권한 요청이 뜨면 OK를 누르고 로그인 암호를 입력합니다.

 

명령 팔레트 화면

 


설치가 완료된 후에 터미널을 새로 열고 아래의 명령을 입력하면 현재 위치에서 VS Code를 바로 실행하실 수 있습니다.

code .

 

 

Cargo.toml 확인

각 라이브러리의 버전에 따라 아래의 main.rs에서 사용할 코드가 작동하지 않을 수도 있습니다. 버전을 확인해보시고 다르다면 아래와 같은 버전으로 변경해주시기 바랍니다.(아직도 Bevy Engine과 bevy_egui, egui의 핵심이 되는 API가 업데이트에 따라 바뀌는 부분들이 있고, 이 부분이 마이그레이션에 어려움을 주기도 합니다)

[package]
name = "bevy_egui_hello_world"
version = "0.1.0"
edition = "2024"

[dependencies]
bevy = "0.16.1"
bevy_egui = "0.35.0"
egui = "0.31.1"

 


main.rs 편집

src 폴더 아래에 main.rs 파일이 있습니다. 이 파일을 아래와 같이 수정합니다.

use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiPrimaryContextPass};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(EguiPlugin::default())
        .add_systems(Startup, setup_camera_system)
        .add_systems(EguiPrimaryContextPass, ui_example_system)
        .run();
}

fn setup_camera_system(mut commands: Commands) {
    commands.spawn(Camera2d);
}

fn ui_example_system(mut contexts: EguiContexts) -> Result {
    egui::Window::new("Hello world").show(contexts.ctx_mut()?, |ui| {
        ui.label("Hello world");
    });
    Ok(())
}

 

실행

아래의 명령을 사용하여 지금까지 작업한 내용을 실행해보겠습니다.

cargo run

 

실행 화면

 

실행이 되셨나요? 축하드립니다. 하지만 여기에서 끝내기에는 여러모로 아쉬움이 남기 때문에 조금 더 작업을 진행해보겠습니다.

 

 

플러그인 구조로 변경

Bevy Engine은 크게 세 가지의 구성 요소가 있습니다.

  • Resource
  • Event
  • System

Resource는 읽거나 수정이 가능한 전역에서 접근 가능한 데이터로 생각하시면 됩니다. Event는 이벤트를 발생시키고, 발생한 이벤트를 읽어서 처리하는데 사용합니다. 하나의 이벤트를 여러 곳에서 쓸 수 있고, 또 여러 곳에서 읽어서 사용할 수 있습니다. System은 첫 번째 인자의 값에 따라 처음 프로그램이 시작될 때 한 번만 실행하거나, 매 프레임마다 호출되는 함수입니다. 여기에서 Resource의 값을 읽거나 변경하고, 이벤트를 발생 시키고, 읽어서 처리할 수 있습니다.

 

이러한 구성 요소들을 용도에 따라 또는 화면 단위로 묶어서 플러그인으로 만들 수 있습니다. 플러그인으로 만들면 코드 복잡도가 낮아져서 이해하고 관리하기가 쉬워집니다. 아래에서 제안하는 폴더 구조는 제 개인적인 의견이므로 사용에 익숙해지시면 원하는대로 변경해서 사용하시면 됩니다.

 

 

플러그인 구조로 변경하는 방법은 아래의 순서대로 따라하시기 바랍니다.

  • src 폴더 하위에 plugins 폴더를 추가합니다.
  • plugins 폴더에 mod.rs 파일을 추가하고 아래의 코드를 작성합니다.
pub mod hello_world;
  • plugins 폴더에 hello_world 폴더를 추가합니다.
  • hello_world 폴더에 mod.rs 파일을 추가하고 아래의 코드를 작성합니다.
pub mod plugin;
  • hello_world 폴더에 plugin.rs 파일을 추가하고 아래의 코드를 작성합니다.
use bevy::prelude::*;
use bevy_egui::{EguiContexts, EguiPrimaryContextPass};

pub struct HelloWorldPlugin;

impl Plugin for HelloWorldPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(EguiPrimaryContextPass, ui_example_system);
    }
}

fn ui_example_system(mut contexts: EguiContexts) -> Result {
    egui::Window::new("Hello").show(contexts.ctx_mut()?, |ui| {
        ui.label("world");
    });
    Ok(())
}

 

  • main.rs 파일을 아래와 같이 수정합니다.
mod plugins;

use bevy::prelude::*;
use bevy_egui::EguiPlugin;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(EguiPlugin::default())
        .add_systems(Startup, setup_camera_system)
        .add_plugins(plugins::hello_world::plugin::HelloWorldPlugin)
        .run();
}

fn setup_camera_system(mut commands: Commands) {
    commands.spawn(Camera2d);
}

 


변경이 완료된 폴더의 구조는 아래와 같습니다.

 

hello_world 플러그인


실행 결과는 변경 전과 동일합니다. 하지만 이걸로 끝내면 안되겠죠. 아래에서 조금 더 내용을 진행해보도록 하겠습니다.

 

 

현재 시간을 표시하는 프로그램 만들기

버튼을 누르면 버튼을 누른 시간이 텍스트 박스에 차례대로 추가되는 예제를 만들어보겠습니다. 이 기능을 사용하려면 시간 값이 들어있는 문자열을 어디엔가 저장해야하는데요. 이 때 Resource를 사용합니다. 아래의 설명을 차례대로 따라하시기 바랍니다.

  • 시간을 원하는 형태로 출력하려면 chrono 크레이트(라이브러리)가 필요합니다. 터미널에서 아래의 명령을 사용하여 라이브러리를 추가합니다.
cargo add chrono
  • hello_world 폴더에 resources.rs 파일을 추가하고 아래의 코드를 넣습니다.
use bevy::prelude::*;

#[derive(Resource, Default)]
pub struct PluginState {
    pub time_string: String,
}
  • resouces.rs 파일이 hello_world 폴더에 있다는 것을 알리기 위해 hello_world 폴더의 mod.rs 파일을 수정합니다.
pub mod plugin;
pub mod resources;
  • hello_world 폴더의 plugin.rs 파일을 아래와 같이 수정합니다.
use super::resources::PluginState;
use bevy::prelude::*;
use bevy_egui::{EguiContexts, EguiPrimaryContextPass};
use chrono::Local;

pub struct HelloWorldPlugin;

impl Plugin for HelloWorldPlugin {
    fn build(&self, app: &mut App) {
        app.insert_resource(PluginState::default());
        app.add_systems(EguiPrimaryContextPass, ui_example_system);
    }
}

fn ui_example_system(mut contexts: EguiContexts, mut state: ResMut<PluginState>) -> Result {
    egui::Window::new("Hello").show(contexts.ctx_mut()?, |ui| {
        ui.text_edit_multiline(&mut state.time_string);
        if ui.button("Update Time").clicked() {
            state.time_string += &format!("{}\n", Local::now().format("%Y-%m-%d %H:%M:%S"));
        }
        if ui.button("Reset").clicked() {
            state.time_string = String::new();
        }
    });
    Ok(())
}


작업이 완료된 후 아래의 명령을 사용해서 프로그램을 실행해보겠습니다.

cargo run


'Update Time' 버튼을 눌러보았습니다.

 

프로그램 실행 화면

 


뜯어보면 어려운 내용들이 있기는한데 여기서부터 천천히 알아보면 되지 않을까요?

 

전체 코드는 아래의 저장소에 올려두었습니다.

https://github.com/cellaxon/bevy_egui_hello_world

 

저장소의 코드를 내려받으려면 아래의 명령을 입력하시면 됩니다.

git clone https://github.com/cellaxon/bevy_egui_hello_world


아마도 git은 설치되어 있을거예요. 그렇죠?

 

이전에는 Rust를 선뜻 추천하기 어려웠는데 AI 도입으로 격변의 시기를 거치면서 이런 도구들의 도움을 받으면 문법에 조금 약해도 충분히 도전할 만한 상황이 되었습니다. 모르면 원하는 코드를 작성해달라고 하면 잘해줍니다. 코드 설명도 잘해주고요. 틀린 코드 수정은 지금 환경에서는 한 번에 안 될 때가 많지만 이 정도만 해줘도 어디인가 싶네요. 사용자의 의도와 맞지 않을 때도 있지만 상당히 많은 부분을 자동 완성해 주기도 하고요.

 

Github Copilot을 연간 구독으로 사용하는 중에 소문을 듣고 Windsurf( http://windsurf.com/ )와 Cursor( https://cursor.com/ )를 차례로 월 결제해서 써봤는데 돈이 아깝지 않았습니다. 오히려 너무 빨리 토큰을 다 써버려서 추가 결제를 해야 하나 망설이게 될 정도였어요. 물론 너무 큰 기대를 하고 계시면 실망하실 수도 있겠지만 맨땅에 헤딩했던 경험이 있으신 분들이라면 충분히 만족하고 쓰실 수 있을 겁니다. 결제가 부담되시거든 무료 버전이라도 써보세요. 꼭.

 

길고 긴 Bevy Engine과 egui 입문 게시물이 끝났습니다. 여기까지 작성하는 데 4시간이 걸렸네요. 아마도 이다음 게시물은 Event에 대한 설명이 될 것 같습니다. 적절한 예제를 찾기 위해 노력하겠습니다.

 

감사합니다.

반응형
사업자 정보 표시
주식회사 셀엑손 (CELLAXON Inc. | 이상효 | 경기도 화성시 동탄감배산로 143, 202동 2409호 | 사업자 등록번호 : 304-81-34245 | TEL : 031-8043-3215 | Mail : ryan@cellaxon.com | 통신판매신고번호 : 2022-화성동탄-0844호 | 사이버몰의 이용약관 바로가기