Show HN: Odyis: lunar lander (1979) clone written in Rust

3 weeks ago 1

---
title: "Odyis: lunar lander (1979) clone written in Rust"
url: https://ad301.org
---

Link to the repository on Codeberg.org

Reading the blog post and looking at the game or its source code you may notice things. Things, only experienced developers might know. Since my goal with this game is to learn and improve my (so far non-existant) Rust skill, please share any feedback you have either on the repository in the form of issues, as a comment below or if you want to get into a conversation send me an E-Mail at [email protected].


Seeing as Rust seems to be become an increasingly favoured programming language by many programmers, I wanted to see what the hype is about! After writing a “Hello, world” I was intrigued, the compiler seems to be quite nice to work with and the writing experience smooth - and this language is supposed to be as well suited for embedded programming as C?

However, just writing “Hello, world” to the console obviously does not give much of an insight into how good a programming language feels to work in or gets the job done, so I had to create a marginally larger application to really dip my toes in.

That idea eventually became Odyis, a clone of the classic lunar lander game (which I admittedly have lot played myself) that serves as an intro to the language for me.

My original idea for Odyis was not a lunar lander clone but rather an idea about trying something in Rust I considered hard in C: drawing something to the screen. 1

This was just an experiment, but googling “draw triangle rust” quickly led me to this library: Macroquad.rs… and man, that lib is nice!

Getting something rendering is as easy as this:

use macroquad::prelude::*; #[macroquad::main("BasicShapes")] async fn main() { loop { clear_background(RED); draw_line(40.0, 40.0, 100.0, 200.0, 15.0, BLUE); draw_rectangle(screen_width() / 2.0 - 60.0, 100.0, 120.0, 60.0, GREEN); draw_circle(screen_width() - 30.0, screen_height() - 30.0, 15.0, YELLOW); draw_text("HELLO", 20.0, 20.0, 20.0, DARKGRAY); next_frame().await } }

2

That’s it? That’s it! It displays as this:

 A few primary shapes on a red background Image displaying what the above minimal example would look like when rendered: A few primary shapes on a red background

Granted, that does not look very good (seriously, green on red?) but it’s amazing how easy that was! This felt even simpler than it would in Python.

From there it was honestly pretty straight forward, I wanted to display the ship and learned about how structs are done in Rust:

pub struct Ship { pub pos: Vec2, pub vel: Vec2, pub rot: f32, pub auto: bool, pub fuel: f32, }

That way we can store everything we need to. Gameplay actions like turning and burning (haha) are simply operations on these structs organized by area into files. For example ship.rs contains both the previous ship definition aswell as the code to burn and turn (haha, but the other way around):

pub fn burn(ship: &mut Ship) { if ship.fuel <= 0.0 { return; } let velocity = get_point_origin(BURN_SPEED, ship.rot); let text = format!("thr = ({:.2}, {:.2})", velocity.x, velocity.y); draw_text(&text, 20.0, 200.0, 30.0, GREEN); ship.vel = Vec2::new(ship.vel.x + velocity.y, ship.vel.y - velocity.x); ship.fuel -= 0.1; draw_thruster_flame(ship); } pub fn rotate_clockwise(ship: &mut Ship) { ship.rot += TURN_SPEED; } pub fn rotate_counter_clockwise(ship: &mut Ship) { ship.rot -= TURN_SPEED; }

I split the game into these parts:

main.rs Controls the game loop and calls all other components.
ship.rs Defines Ship aswell as most functions to alter its state.
terrain.rs Defines Platform aswell as the terrain generation (Vec<Vec2> where Vec2 is the custom Macroquad vector type).
gui.rs Draws all graphical elements.
math_utils.rs A bunch of usefull functions, i.e. generating points rotated around an origin for the graphics.
physics.rs Handles collision detection.

In most modern games the way to get a texture on the screen is to render a billboard rect with an image on it. I wanted to got more “hands on” for fun and rendered all of the graphics from code.

This meant having to create some helper functions to generate points of primitive shapes around the center of the ship. For example in math_utils.rs I created rotate_around() to rotate one point arount an origin for a certain angle.

In math_utils.rs:

pub fn rotate_around(origin: Vec2, point: Vec2, angle_deg: f32) -> Vec2 { let x = point.x * angle_deg.to_radians().cos() - point.y * angle_deg.to_radians().sin(); let y = point.y * angle_deg.to_radians().cos() + point.x * angle_deg.to_radians().sin(); return Vec2::new(x + origin.x, y + origin.y); }

This could then be used to draw a rotated polygon with 6 sides depending on the rotation of the player ship.

In gui.rs:

pub fn draw_ship(ship: &Ship) { let capsule_point = rotate_around(ship.pos, Vec2::new(0.0, -30.0), ship.rot); draw_poly_lines( capsule_point.x, capsule_point.y, 6, 20.0, ship.rot, 2.0, GREEN, ); // --- snip --- }

Comments

Leave a comment

Read Entire Article