Making Games in Rust - Part 2 - Drawing Stuff and Cameras
Tags: rust, bevy, rapier, tutorial
Published: 2021-12-10 13:33:35

## Drawing a Square

Our game will be composed of sprites. All our world will be built with squares of equal width and height.

This means that the first thing we want to do is draw a square on the screen.

Let's create a new system called `spawn_player`:

```rs
// Player component
struct Player;

// spawn player system
fn spawn_player(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
    commands
        .spawn_bundle(SpriteBundle {
            material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()),
            sprite: Sprite::new(Vec2::new(10.0, 10.0)),
            ..Default::default()
        })
        .insert(Player);
}
```

and register this system with our Bevy application:

```rs
App::build()
        .insert_resource(WindowDescriptor {
            title: "Platformer!".to_string(),
            width: 640.0,
            height: 400.0,
            vsync: true,
            ..Default::default()
        })
        .insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04)))
        .add_startup_stage("player_setup", SystemStage::single(spawn_player.system())) // line to add
        .add_plugins(DefaultPlugins)
        .run();
```

Now let's run `cargo run`. The window will open and we should see: well, nothing.

We drew a square in our Bevy scene, but it is not yet visible since we did not add a camera to the scene...

## Adding a Camera

To add a camera, we will create a new `setup` system.

```rs
fn setup(mut commands: Commands) {
    commands.spawn_bundle(OrthographicCameraBundle::new_2d());
}
```

and register it with our application:

```rs
// ...
+ .add_startup_system(setup.system())
.add_startup_stage("player_setup", SystemStage::single(spawn_player.system()))
// ...
```

This new system adds an orthographic camera to the scene. There are 2 main types of camera that you need to know about when making computer games: orthographic and perspective. 

Perspective camera are mostly used in 3D games, when we need to simulate a real world view. This type of camera is more representative of the way we see things in real life.

Orthographic cameras are used when we don't need a concept of depth. An orthographic camera does not modify the size of our objects as they are moved further from our point of view (camera). In our case, we are building a 2D platformer, so this is what we are going to use.

Let's run `cargo run`:

🥳🥳🥳
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjhxklwky0149w54x5ak.png)
🥳🥳🥳

## Camera Adjustments

If the window is resized, the visible area gets bigger... In a usual platformer, the width of the visible area rarely changes. The camera will follow our player, but the visible width of our screen should remain constant.

Looking at the code of `OrthographicCameraBundle::new_2d()` (the source, not our code), we can see that it adds a resource called `OrthographicProjection` which has a parameter called `scaling_mode`. This parameter is an `enum` that can take 4 different values. The one we are interested in is the `FixedHorizontal` option.

Let's try it:

```rs
// src/camera.rs
use bevy::{prelude::OrthographicCameraBundle, render::camera::{OrthographicProjection, DepthCalculation, ScalingMode}, math::Vec3};

pub fn new_camera_2d() -> OrthographicCameraBundle {
  let far = 1000.0;
  let mut camera = OrthographicCameraBundle::new_2d();
  camera.orthographic_projection = OrthographicProjection {
    far,
    depth_calculation: DepthCalculation::ZDifference,
    scaling_mode: ScalingMode::FixedHorizontal,
    ..Default::default()
  };
  camera.transform.scale = Vec3::new(10., 10., 1.);
  return camera;
}
```

The line `camera.transform.scale = Vec3::new(10., 10., 1.);` is to scale up our camera, otherwise, the visible area goes from -1 to +1 on the y and x axis in our world's units.

```rs
// src/main.rs
mod camera;
pub use camera::*;
// ...
+ commands.spawn_bundle(new_camera_2d()); 
- commands.spawn_bundle(OrthographicCameraBundle::new_2d());
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwy8t9sslwke8n0exjyq.png)

It works, but our player is way too huge. Let's scale it down:

```rs
// src/main.rs spawn_player method
.spawn_bundle(SpriteBundle {
    material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()),
    sprite: Sprite::new(Vec2::new(1.0, 1.0)),
    ..Default::default()
})
```

🥳🥳🥳
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gbwyoqbs9bar2yq1dp9v.png)
🥳🥳🥳

It's all for today, in the next part, we will discuss physics.

All code available [here](https://github.com/belzile/platformer/tree/part-2).

Part 1: {% post https://dev.to/sbelzile/rust-platformer-part-1-bevy-and-ecs-2pci %}