๐ŸŽ›๏ธ :: Nannou์˜ ์‚ฌ์šฉ์ž ์ •์˜ ๊ตฌ์กฐ์ฒด

BamgasiJMยท2025๋…„ 9์›” 27์ผ

Nannou <BASIC>

๋ชฉ๋ก ๋ณด๊ธฐ
8/41
post-thumbnail

Nannou ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ •์˜ํ•˜๋Š” ๊ตฌ์กฐ์ฒด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋ฉฐ, ๊ด€๋ก€์ ์œผ๋กœ Model์ด๋ผ๋Š” ์ด๋ฆ„์„ ์ž์ฃผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Nannou์˜ ๊ณต์‹ ๋ฌธ์„œ์™€ ์˜ˆ์ œ ์ฝ”๋“œ์—์„œ ํ”ํžˆ ๋ณด์ด๋Š” ํŒจํ„ด์œผ๋กœ, ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ์ฑ„ํƒ๋œ ์Šคํƒ€์ผ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” ๊ฐ•์ œ์ ์ธ ๊ทœ์น™์ด ์•„๋‹ˆ๋ผ ๋‹จ์ˆœํ•œ ๊ด€๋ก€์ผ ๋ฟ์ด๋ฏ€๋กœ, AppState, State, MyModel ๋“ฑ ๋‹ค๋ฅธ ์ด๋ฆ„์„ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ๊ฒƒ์€ ํ•ด๋‹น ๊ตฌ์กฐ์ฒด๊ฐ€ Nannou์˜ ํ•จ์ˆ˜๋“ค(model, update, view ๋“ฑ)์—์„œ ์ผ๊ด€๋˜๊ฒŒ ์ฐธ์กฐ๋˜๊ณ , ํƒ€์ž…์ด ๋งž์•„์•ผ ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

Model ๊ตฌ์กฐ์ฒด์— ๋Œ€ํ•˜์—ฌ

Model ๊ตฌ์กฐ์ฒด๋Š” Nannou ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ•ต์‹ฌ ์ƒํƒœ ๊ด€๋ฆฌ์ž ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Rust์˜ struct ํ‚ค์›Œ๋“œ๋กœ ์ •์˜๋˜๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ๋ณ€์ˆ˜, ๋ฐ์ดํ„ฐ, ๋˜๋Š” ์ƒํƒœ(์˜ˆ: ์œ„์น˜, ์ƒ‰์ƒ, ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ณ€์ˆ˜ ๋“ฑ)๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. Nannou์˜ ์ด๋ฒคํŠธ ๋ฃจํ”„(์˜ˆ: nannou::app(model).update(update).run())์—์„œ ์ด ๊ตฌ์กฐ์ฒด๊ฐ€ ์ค‘์‹ฌ์ด ๋˜๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค:

๐Ÿ•น๏ธ ์ดˆ๊ธฐํ™” (model ํ•จ์ˆ˜):

model ํ•จ์ˆ˜์—์„œ Model ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” App ์ฐธ์กฐ๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„ ์ฐฝ ์ƒ์„ฑ์ด๋‚˜ ์ดˆ๊ธฐ ์„ค์ •์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์˜ˆ: ์œ„์น˜๋‚˜ ์ƒ‰์ƒ ๊ฐ™์€ ํ•„๋“œ๋ฅผ ์ดˆ๊ธฐํ™”.

๐Ÿ•น๏ธ ์ƒํƒœ ๊ฐฑ์‹  (update ํ•จ์ˆ˜):

update ํ•จ์ˆ˜์—์„œ Model์˜ ํ•„๋“œ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. Update ๊ตฌ์กฐ์ฒด๋ฅผ ํ†ตํ•ด ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ๋กœ์ง(์˜ˆ: ์• ๋‹ˆ๋ฉ”์ด์…˜)์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์˜ˆ: ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์œ„์น˜๋ฅผ ์—…๋ฐ์ดํŠธ.

๐Ÿ•น๏ธ ๋ Œ๋”๋ง (view ํ•จ์ˆ˜):

view ํ•จ์ˆ˜์—์„œ Model์˜ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ทธ๋ž˜ํ”ฝ์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. Draw ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด ๋„ํ˜•์ด๋‚˜ ํ…์ŠคํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

  • ์˜ˆ: Model์˜ ์œ„์น˜์™€ ์ƒ‰์ƒ์„ ์‚ฌ์šฉํ•ด ์›์„ ๊ทธ๋ฆผ.

๐Ÿ•น๏ธ ๊ธฐํƒ€ ํ•จ์ˆ˜ (event ๋“ฑ):

ํ•„์š” ์‹œ event ํ•จ์ˆ˜์—์„œ ์ž…๋ ฅ ์ด๋ฒคํŠธ(ํ‚ค๋ณด๋“œ, ๋งˆ์šฐ์Šค)๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉฐ Model์„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Model์˜ ํ•„๋“œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์ž์œ ๋กญ๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ„๋‹จํ•œ 2D ์• ๋‹ˆ๋ฉ”์ด์…˜ ์•ฑ์ด๋ผ๋ฉด ๋ฒกํ„ฐ(Vec2), ์ƒ‰์ƒ(Rgb), ๋˜๋Š” ์นด์šดํ„ฐ ๊ฐ™์€ ํ•„๋“œ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ๊ธฐ๋ณธ์ ์ธ Model ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค:

use nannou::prelude::*;

struct Model {
    position: Vec2,  // ์œ„์น˜ ์ƒํƒœ
    velocity: Vec2,  // ์†๋„ ์ƒํƒœ
    color: Rgb,      // ์ƒ‰์ƒ ์ƒํƒœ
}

fn model(app: &App) -> Model {
    let _window = app.new_window().size(512, 512).view(view).build().unwrap();
    Model {
        position: vec2(0.0, 0.0),
        velocity: vec2(1.0, 1.0),
        color: rgb(1.0, 0.5, 0.0),
    }
}

fn update(_app: &App, model: &mut Model, _update: Update) {
    model.position += model.velocity;  // ์ƒํƒœ ์—…๋ฐ์ดํŠธ
}

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    draw.background().color(BLACK);
    draw.ellipse()
        .xy(model.position)
        .radius(20.0)
        .color(model.color);
    draw.to_frame(app, &frame).unwrap();
}

์ด ๊ตฌ์กฐ์ฒด๋Š” Nannou์˜ ๋ผ์ดํ”„์‚ฌ์ดํด๊ณผ ๋ฐ€์ ‘ํ•˜๊ฒŒ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์–ด, ์ƒํƒœ๊ฐ€ ์˜์†์ ์œผ๋กœ ์œ ์ง€๋˜๋ฉด์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.


์—ฌ๋Ÿฌ ๊ฐœ์˜ ์‚ฌ์šฉ์ž ์ •์˜ ๊ตฌ์กฐ์ฒด ์‚ฌ์šฉํ•˜๊ธฐ

Nannou์˜ ๊ธฐ๋ณธ ํŒจํ„ด์€ ํ•˜๋‚˜์˜ ์ฃผ์š” ์ƒํƒœ ๊ตฌ์กฐ์ฒด(์˜ˆ: Model)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด์ง€๋งŒ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ณต์žกํ•ด์ง€๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์‚ฌ์šฉ์ž ์ •์˜ ๊ตฌ์กฐ์ฒด๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ฝ”๋“œ์˜ ๋ชจ๋“ˆํ™”, ์žฌ์‚ฌ์šฉ์„ฑ, ๋˜๋Š” ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. Nannou ์ž์ฒด๊ฐ€ ์ด๋ฅผ ๊ฐ•์ œํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, Rust์˜ ๊ตฌ์กฐ์ฒด ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด ์ž์œ ๋กญ๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ์ฃผ์š” ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค:

๐Ÿงฐ Nested Structures (์ค‘์ฒฉ ๊ตฌ์กฐ์ฒด):

์ฃผ์š” Model ์•ˆ์— ์„œ๋ธŒ ๊ตฌ์กฐ์ฒด๋ฅผ ํฌํ•จ์‹œ์ผœ ์ƒํƒœ๋ฅผ ๊ณ„์ธต์ ์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ƒํƒœ๊ฐ€ ๋งŽ์•„์งˆ ๋•Œ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

  • ์˜ˆ: ๊ฒŒ์ž„ ์•ฑ์—์„œ ํ”Œ๋ ˆ์ด์–ด ์ƒํƒœ์™€ ์  ์ƒํƒœ๋ฅผ ๋ณ„๋„์˜ ๊ตฌ์กฐ์ฒด๋กœ ๋ถ„๋ฆฌ.
  • ์žฅ์ : ์ฃผ์š” Model์ด ์ „์ฒด ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด์„œ, ์„œ๋ธŒ ๊ตฌ์กฐ์ฒด๋กœ ์„ธ๋ถ€ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
struct Player {
    position: Vec2,
    health: f32,
}

struct Enemy {
    position: Vec2,
    speed: f32,
}

struct Model {
    player: Player,  // ์„œ๋ธŒ ๊ตฌ์กฐ์ฒด
    enemies: Vec<Enemy>,  // ์—ฌ๋Ÿฌ ์ ์„ ๋ฒกํ„ฐ๋กœ ๊ด€๋ฆฌ
}

fn model(app: &App) -> Model {
    Model {
        player: Player { position: vec2(0.0, 0.0), health: 100.0 },
        enemies: vec![Enemy { position: vec2(100.0, 100.0), speed: 2.0 }],
    }
}

fn update(_app: &App, model: &mut Model, _update: Update) {
    model.player.position.x += 1.0;  // ํ”Œ๋ ˆ์ด์–ด ์ƒํƒœ ์—…๋ฐ์ดํŠธ
    for enemy in &mut model.enemies {
        enemy.position.y -= enemy.speed;  // ์  ์ƒํƒœ ์—…๋ฐ์ดํŠธ
    }
}

๐Ÿงฐ Multiple Windows or Modules (๋‹ค์ค‘ ์œˆ๋„์šฐ ๋˜๋Š” ๋ชจ๋“ˆ):

Nannou์—์„œ ์—ฌ๋Ÿฌ ์ฐฝ์„ ์ƒ์„ฑํ•  ๋•Œ, ๊ฐ ์ฐฝ๋งˆ๋‹ค ๋ณ„๋„์˜ ์ƒํƒœ ๊ตฌ์กฐ์ฒด๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ์ฐฝ์˜ model ํ•จ์ˆ˜์—์„œ ๋…๋ฆฝ์ ์ธ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

  • ์˜ˆ: ๋ฉ”์ธ ์ฐฝ๊ณผ ์„ค์ • ์ฐฝ์„ ๋ณ„๋„๋กœ ๊ด€๋ฆฌ.
  • Nannou์˜ WindowBuilder๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ ์ฐฝ์— ๋…๋ฆฝ์ ์ธ model, update, view๋ฅผ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
struct MainModel {
    // ๋ฉ”์ธ ์ƒํƒœ
    counter: u32,
}

struct SettingsModel {
    // ์„ค์ • ์ƒํƒœ
    theme: String,
}

fn main() {
    nannou::app(main_model).update(main_update).run();
    // ๋ณ„๋„์˜ ์ฐฝ ์ƒ์„ฑ ์‹œ ๋‹ค๋ฅธ model ํ•จ์ˆ˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
}

fn main_model(app: &App) -> MainModel {
    let _main_window = app.new_window().size(800, 600).view(main_view).build().unwrap();
    MainModel { counter: 0 }
}

// ๋‹ค๋ฅธ ์ฐฝ์„ ์œ„ํ•œ ๋ณ„๋„ ํ•จ์ˆ˜์™€ ๊ตฌ์กฐ์ฒด ์ •์˜ ๊ฐ€๋Šฅ

๐Ÿงฐ Traits or Generics for Reusability (ํŠธ๋ ˆ์ดํŠธ ๋˜๋Š” ์ œ๋„ค๋ฆญ ํ™œ์šฉ):

๋ณต์žกํ•œ ์•ฑ์—์„œ ์—ฌ๋Ÿฌ ๊ตฌ์กฐ์ฒด๊ฐ€ ๊ณตํ†ต ๋ฉ”์„œ๋“œ๋ฅผ ๊ณต์œ ํ•˜๋„๋ก ํŠธ๋ ˆ์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๊ตฌ์กฐ์ฒด๋ฅผ ๋” ์œ ์—ฐํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ์—ฌ๋Ÿฌ ๊ตฌ์กฐ์ฒด๋ฅผ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ์˜ˆ: ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ๊ฐ€ Updateable ํŠธ๋ ˆ์ดํŠธ๋ฅผ ๊ตฌํ˜„.
trait Updateable {
    fn update(&mut self, delta_time: f32);
}

struct Ball {
    position: Vec2,
}

impl Updateable for Ball {
    fn update(&mut self, delta_time: f32) {
        self.position.x += 10.0 * delta_time;
    }
}

struct Model {
    balls: Vec<Ball>,  // ์—ฌ๋Ÿฌ ๊ตฌ์กฐ์ฒด ์ธ์Šคํ„ด์Šค
}

fn update(_app: &App, model: &mut Model, update: Update) {
    let dt = update.since_last.as_secs_f32();
    for ball in &mut model.balls {
        ball.update(dt);  // ํŠธ๋ ˆ์ดํŠธ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
    }
}

โœ… ์ฃผ์˜ํ•  ์ :

์—ฌ๋Ÿฌ ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋„ Nannou์˜ ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜(์˜ˆ: fn update(_app: &App, model: &mut Model, _update: Update))๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ์š” ์ƒํƒœ๋Š” ํ•˜๋‚˜์˜ ๋ฃจํŠธ ๊ตฌ์กฐ์ฒด(์˜ˆ: Model)๋กœ ํ†ตํ•ฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
๋ณต์žก๋„๊ฐ€ ์ฆ๊ฐ€ํ•˜๋ฉด ECS(Entity-Component-System) ํŒจํ„ด์ด๋‚˜ ๋ณ„๋„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(์˜ˆ: specs)๋ฅผ ๋„์ž…ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, Nannou์˜ ๊ธฐ๋ณธ ์ฒ ํ•™์€ ๊ฐ„๋‹จํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๊ฐ•์กฐํ•ฉ๋‹ˆ๋‹ค.
๊ด€๋ก€์ ์œผ๋กœ Model์„ ์ฃผ์š” ๊ตฌ์กฐ์ฒด๋กœ ์‚ฌ์šฉํ•˜๋˜, ์„œ๋ธŒ ๊ตฌ์กฐ์ฒด์—๋Š” ์˜๋ฏธ ์žˆ๋Š” ์ด๋ฆ„(์˜ˆ: Particle, UIState)์„ ๋ถ™์—ฌ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ๋†’์ด์„ธ์š”.


โœ… โš™๏ธ๐Ÿ› ๏ธ๐Ÿ“„๐Ÿ“š๐ŸŽจ๐Ÿ–ผ๏ธ๐Ÿงฉ๐Ÿง ๐Ÿงฎ๐Ÿ•น๏ธ๐Ÿงฟโณ๐ŸŽ›๏ธ๐Ÿ”ฎ๐ŸŒŒ๐Ÿชฉ๐Ÿ—‚๏ธ๐Ÿ“๐Ÿงฐ๐Ÿ”’

์•„๋ณด์นด๋„
Sample
Important
Highlighting
๋น„์ƒ‰
์ ํ™ฉ์ƒ‰
purple
hot pink
yellow
์‚ด๊ตฌ์ƒ‰
orange
green
blue

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€