
Struct의 중첩 트리 구조
Model
├── player: Player
│ ├── position: Vec2
│ ├── size: f32
│ └── color: Rgb
│
├── particles: Vec<Particle>
│ └── [각 요소가 Particle]
│ ├── position: Vec2
│ ├── velocity: Vec2
│ └── color: Rgba
│
└── game_state: GameState
├── score: u32
└── is_paused: bool
Model 은 최상위 루트 구조체입니다.
Player, Vec<Particle>, GameState 는 Model의 필드로 포함됩니다.
Vec<Particle>는 동적 배열이므로, 그 안에 여러 개의 Particle 인스턴스가 들어갑니다.
- 각 하위 구조체(
Player, Particle, GameState)는 다시 기본 타입(Vec2, f32, u32, bool, Rgba 등)으로 구성됩니다.
📝 Rust Code
use nannou::prelude::*;
struct Model {
player: Player,
particles: Vec<Particle>,
game_state: GameState,
}
struct Player {
position: Vec2,
size: f32,
color: Rgb,
}
struct Particle {
position: Vec2,
velocity: Vec2,
color: Rgba,
}
struct GameState {
score: u32,
is_paused: bool,
}
fn main() {
nannou::app(model).update(update).event(event).run();
}
fn model(app: &App) -> Model {
let _window = app
.new_window()
.size(800, 600)
.view(view)
.key_pressed(key_pressed)
.build()
.unwrap();
let mut particles = Vec::new();
for _ in 0..10 {
particles.push(Particle {
position: vec2(
random_range(-300.0, 300.0),
random_range(-200.0, 200.0),
),
velocity: vec2(
random_range(-50.0, 50.0),
random_range(-50.0, 50.0),
),
color: rgba(random_f32(), random_f32(), random_f32(), 0.5),
});
}
Model {
player: Player {
position: vec2(0.0, 0.0),
size: 20.0,
color: rgb(1.0, 0.0, 0.0),
},
particles,
game_state: GameState {
score: 0,
is_paused: false,
},
}
}
fn update(app: &App, model: &mut Model, update: Update) {
if model.game_state.is_paused {
return;
}
let dt = update.since_last.as_secs_f32();
for particle in &mut model.particles {
particle.position += particle.velocity * dt;
let win = app.window_rect();
if particle.position.x < win.left() || particle.position.x > win.right() {
particle.velocity.x *= -1.0;
}
if particle.position.y < win.bottom() || particle.position.y > win.top() {
particle.velocity.y *= -1.0;
}
if particle.position.distance(model.player.position) < model.player.size + 10.0 {
model.game_state.score += 1;
}
}
}
fn event(_app: &App, model: &mut Model, event: Event) {
match event {
Event::WindowEvent {
simple: Some(window_event),
..
} => match window_event {
WindowEvent::KeyPressed(key) => key_pressed(_app, model, key),
_ => {}
},
_ => {}
}
}
fn key_pressed(_app: &App, model: &mut Model, key: Key) {
match key {
Key::Up => model.player.position.y += 10.0,
Key::Down => model.player.position.y -= 10.0,
Key::Left => model.player.position.x -= 10.0,
Key::Right => model.player.position.x += 10.0,
Key::Space => model.game_state.is_paused = !model.game_state.is_paused,
_ => {}
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
draw.ellipse()
.xy(model.player.position)
.radius(model.player.size)
.color(model.player.color);
for particle in &model.particles {
draw.ellipse()
.xy(particle.position)
.radius(10.0)
.color(particle.color);
}
draw.text(&format!("Score: {}", model.game_state.score))
.xy(vec2(0.0, app.window_rect().top() - 20.0))
.color(WHITE);
if model.game_state.is_paused {
draw.text("Paused")
.xy(vec2(0.0, 0.0))
.color(YELLOW)
.font_size(40);
}
draw.to_frame(app, &frame).unwrap();
}
use nannou::prelude::*;
struct Model {
player: Player,
particles: Vec<Particle>,
game_state: GameState,
}
struct Player {
position: Vec2,
size: f32,
color: Rgb,
}
struct Particle {
position: Vec2,
velocity: Vec2,
color: Rgba,
}
struct GameState {
score: u32,
is_paused: bool,
}
fn main() {
nannou::app(model).update(update).event(event).run();
}
fn model(app: &App) -> Model {
let _window = app
.new_window()
.size(800, 600)
.view(view)
.key_pressed(key_pressed)
.build()
.unwrap();
let mut particles = Vec::new();
for _ in 0..10 {
particles.push(Particle {
position: vec2(
random_range(-300.0, 300.0),
random_range(-200.0, 200.0),
),
velocity: vec2(
random_range(-50.0, 50.0),
random_range(-50.0, 50.0),
),
color: rgba(random_f32(), random_f32(), random_f32(), 0.5),
});
}
Model {
player: Player {
position: vec2(0.0, 0.0),
size: 20.0,
color: rgb(1.0, 0.0, 0.0),
},
particles,
game_state: GameState {
score: 0,
is_paused: false,
},
}
}
fn update(app: &App, model: &mut Model, update: Update) {
if model.game_state.is_paused {
return;
}
let dt = update.since_last.as_secs_f32();
for particle in &mut model.particles {
particle.position += particle.velocity * dt;
let win = app.window_rect();
if particle.position.x < win.left() || particle.position.x > win.right() {
particle.velocity.x *= -1.0;
}
if particle.position.y < win.bottom() || particle.position.y > win.top() {
particle.velocity.y *= -1.0;
}
if particle.position.distance(model.player.position) < model.player.size + 10.0 {
model.game_state.score += 1;
}
}
}
fn event(_app: &App, model: &mut Model, event: Event) {
match event {
Event::WindowEvent {
simple: Some(window_event),
..
} => match window_event {
WindowEvent::KeyPressed(key) => {
key_pressed(_app, model, key);
}
_ => {}
},
_ => {}
}
}
fn key_pressed(_app: &App, model: &mut Model, key: Key) {
match key {
Key::Up => model.player.position.y += 10.0,
Key::Down => model.player.position.y -= 10.0,
Key::Left => model.player.position.x -= 10.0,
Key::Right => model.player.position.x += 10.0,
Key::Space => {
model.game_state.is_paused = !model.game_state.is_paused;
}
_ => {}
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
draw.ellipse()
.xy(model.player.position)
.radius(model.player.size)
.color(model.player.color);
for particle in &model.particles {
draw.ellipse()
.xy(particle.position)
.radius(10.0)
.color(particle.color);
}
draw.text(&format!("Score: {}", model.game_state.score))
.xy(vec2(0.0, app.window_rect().top() - 20.0))
.color(WHITE);
if model.game_state.is_paused {
draw.text("Paused")
.xy(vec2(0.0, 0.0))
.color(YELLOW)
.font_size(40);
}
draw.to_frame(app, &frame).unwrap();
}