1use ponko::*;
14
15const TILE: u32 = 16;
16const WATER: u16 = 0;
18const SAND: u16 = 1;
19const GRASS: u16 = 2;
20const FOREST: u16 = 3;
21const STONE: u16 = 4;
22
23fn tileset(ctx: &mut Ctx) -> SpriteSheet {
25 let colors: [[u8; 3]; 5] = [
26 [38, 84, 160],
27 [222, 196, 132],
28 [86, 160, 70],
29 [40, 102, 48],
30 [128, 128, 136],
31 ];
32 let (w, h) = (TILE * colors.len() as u32, TILE);
33 let mut rng = Rng::new(1);
34 let mut rgba = Vec::with_capacity((w * h * 4) as usize);
35 for _y in 0..h {
36 for x in 0..w {
37 let c = colors[(x / TILE) as usize];
38 let shade = 0.88 + rng.f32() * 0.24;
39 rgba.extend(c.iter().map(|&v| (v as f32 * shade).min(255.0) as u8));
40 rgba.push(255);
41 }
42 }
43 let tex = ctx.create_texture(w, h, &rgba);
44 ctx.set_texture_filter(&tex, TextureFilter::Nearest);
45 SpriteSheet::new(tex, TILE, TILE)
46}
47
48fn creature(ctx: &mut Ctx) -> SpriteSheet {
50 let (frames, size) = (8u32, 16u32);
51 let mut rgba = vec![0u8; (frames * size * size * 4) as usize];
52 for f in 0..frames {
53 let phase = f as f32 / frames as f32 * std::f32::consts::TAU;
54 let (rx, ry) = (5.0 + phase.sin() * 1.5, 5.0 - phase.sin() * 1.5);
55 for y in 0..size {
56 for x in 0..size {
57 let d = vec2((x as f32 + 0.5 - 8.0) / rx, (y as f32 + 0.5 - 9.0) / ry);
58 if d.length() <= 1.0 {
59 let i = ((y * frames * size + f * size + x) * 4) as usize;
60 rgba[i..i + 4].copy_from_slice(&[240, 110, 80, 255]);
61 }
62 }
63 }
64 }
65 let tex = ctx.create_texture(frames * size, size, &rgba);
66 ctx.set_texture_filter(&tex, TextureFilter::Nearest);
67 SpriteSheet::new(tex, size, size)
68}
69
70fn terrain(x: f32, y: f32) -> u16 {
72 let h = (x * 0.031).sin()
73 + (y * 0.027).cos()
74 + (x * 0.011 + y * 0.013).sin() * 1.5
75 + (x * 0.093 - y * 0.071).sin() * 0.3;
76 match h {
77 h if h < -0.6 => WATER,
78 h if h < -0.35 => SAND,
79 h if h < 0.8 => GRASS,
80 h if h < 1.6 => FOREST,
81 _ => STONE,
82 }
83}
84
85struct Wander(Vec2);
86
87fn setup(ctx: &mut Ctx, side: u32) {
89 let tiles = tileset(ctx);
90 let mut map = TileMap::new(tiles, side, side);
91 for y in 0..side {
92 for x in 0..side {
93 map.set(x, y, Some(terrain(x as f32, y as f32)));
94 }
95 }
96 ctx.world.spawn((Transform2D::default(), map));
97
98 let blob = creature(ctx);
99 let mut rng = Rng::new(7);
100 let world = (side * TILE) as f32;
101 ctx.world.spawn_batch((0..20_000).map(|_| {
102 let at = vec2(rng.range(0.0, world), rng.range(0.0, world));
103 let mut walk = Animation::new(blob, 0..8, rng.range(6.0, 12.0));
104 walk.time = rng.f32(); (
106 Transform2D::at(at),
107 blob.sprite(0),
108 walk,
109 Wander(rng.unit_vec2() * 20.0),
110 )
111 }));
112 ctx.camera.zoom = 2.0;
113 ctx.camera.look_at(vec2(world, world) / 2.0);
114}
115fn wander(ctx: &mut Ctx) {
118 let dt = ctx.dt();
119 ctx.world
120 .query::<(&mut Transform2D, &Wander)>()
121 .par_for_each(|(t, w)| t.position += w.0 * dt);
122}
123
124fn paint(ctx: &mut Ctx) {
126 if !ctx.mouse_down(MouseButton::Left) || ctx.mouse_over_ui() {
127 return;
128 }
129 let at = ctx.mouse_world();
130 ctx.world.query::<(&Transform2D, &mut TileMap)>().for_each(|(t, map)| {
131 if let Some(tile) = map.tile_at(t.position, at) {
132 map.set(tile.x, tile.y, Some(STONE));
133 }
134 });
135}
136fn hud(ctx: &mut Ctx) {
139 let r = ctx.render_stats();
140 let text = format!(
141 "{} sprites drawn ({} draw calls)\nzoom {:.2}x, {:.0} FPS\nleft mouse: paint, right drag: pan, wheel: zoom",
142 r.sprites,
143 r.draw_calls,
144 ctx.camera.zoom,
145 ctx.fps()
146 );
147 let size = ctx.measure_text(&text, ctx.ui_style.text_size) + 2.0 * ctx.ui_style.padding;
148 let panel = Rect::new(12.0, 12.0, size.x, size.y);
149 ctx.ui_panel(panel);
150 ctx.ui_label(panel.min + ctx.ui_style.padding, &text);
151}
152
153fn main() {
154 let side = std::env::var("MAP").ok().and_then(|v| v.parse().ok()).unwrap_or(1000);
155 Game::new("Crabby Engine: tiles", 1280, 720)
156 .vsync(false)
157 .system(camera_controls)
158 .system(wander)
159 .system(paint)
160 .system(hud)
161 .run(move |ctx| setup(ctx, side));
162}