Native iOS Game Development w/ Rust
Tags: rust, ios, gamedev, beginners
Published: 2022-09-01 22:15:58

My documented journey of exploring cross platform game development using purely Rust. 

> Seriously. It's 100% Rust!

Next article: [Handling tap events](https://dev.to/wadecodez/bevy-native-ios-touch-events-49p3)

---

## Why I Chose Rust
Rust is a popular topic of discussion when it comes to performance and modular design, but that is not why I chose this language. I chose Rust because I wanted a cross-platform system programming language.

> #### Hindsight: Things I Like About Rust
> After digging through Rust for a few weeks, I discovered some of most dreaded features turned out enjoyable. Albeit there is a learning curve, but in hindsight, I really appreciate the following features of Rust.
> * No OOP
> * Modular Code
> * Documentation Ecosystem
> * Build System
> * Package Management

---

## My Goals for Exploring Rust
My goal is to find a language which allows me to write cross platform code without needing to jump through a bunch of hoops. As a hobbyist, I don't have a lot of time to pick up new languages or dig through the framework of the week. 

To deem my exploration a success, I will continue Rust development until I publish an app on the App Store. Why? Because Swift/Obj-C are my most dreaded languages, but I can tolerate Java. So if Rust allows me to at least cut out Swift/Obj-C then I will be happy.


---

## 0. Project Environment Setup
Going into this project, I know that I will frequently modify environment variables and build scripts. So before I even start with Rust, I will add a script to my `.bashrc` which makes it easy to swap out large changes to my dev environment. 

The following script allows me to extend my `.bashrc` from any project directory in my `~/code` folder by traversing upward until it finds a folder called `.devenv`.  From there I can source a per-project bash script.

```bash
# get the first match from `find` while traversing upwards
function find_above {
    old_pwd="$PWD"
    while [[ "$PWD" == $HOME/code/* ]] ; do
        new_pwd=`find "$PWD"/ -maxdepth 1 "$@"`
        
        if [[ "$new_pwd" ]]; then
            break
        fi
        
        cd ..
    done

    echo "$new_pwd"
    cd "$old_pwd"
    old_pwd=""
    new_pwd=""
}
```

Here is the part where I use the method to source the local `.bashrc`. I have to re-source my environment every time I switch projects, but that is not a concern.

```bash
# see code above

ENVDIR=$(find_above -type d -name ".devenv")
if [[ "$ENVDIR" ]]; then
    source "$ENVDIR/.bashrc"
fi
```

### Creating Aliases
Now that my machine can handle per-project bash environments, I setup a few commands so I could quickly jump around my code.

```bash
# ~/code/tictactoe/.devenv/.bashrc
echo "detected local env: $PWD"

# src directory
export SRCDIR=$(find_above -name "src")

# root directory
export ROOTDIR=$(dirname $SRCDIR)

# build directory
export BUILDDIR="$ROOTDIR/target"
mkdir -p "$BUILDDIR"

# lib directory
export LIBDIR="$ROOTDIR/src"
mkdir -p "$LIBDIR"

# bin directory
export BINDIR="$ROOTDIR/.devenv/bin"
mkdir -p "$BINDIR"
export PATH="$BINDIR:$PATH"

export RUST_LOG="warn,handmade=debug"
export RUST_BACKTRACE=1

# aliases
alias c="cargo build"
alias clean="clean"
alias s="source $HOME/.zshrc"
alias root="cd $ROOTDIR"
alias r="cargo run"
alias t="cargo test"
alias i="run-ios-sim.sh"
alias cr="c && r"
```

---

## 1. Hello World Rust/Bevy
When I first started exploring weeks ago, I wanted to start at the lowest level possible.  I wanted to write my own graphics render, and game engine.  However, after about 3 days of zero progress, I gave up that dream and looked for a Rust game engine.

### Installing Bevy
Of course, I downloaded the first game engine I could find. Which turned out to be a pretty nice framework. Bevy is a game engine which is designed around apps which should be highly modular.  I have used the library for a few weeks, I love it still!

```
cargo add bevy
```

### Writing Code using Bevy's Paradigm 

Here is Hello world using Bevy on Mac OS:
```rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_system(hello_world_system)
        .run();
}

fn hello_world_system() {
    println!("Hello Rust");
}
```

Notice how I am not directly calling `println`. This is because Bevy provides a framework which is very similar to the routing system in `ExpressJS`. On bigger projects you can extract capabilities in modules/plugins and each plugin has access to the context for registering more systems.

### Running Bevy Hello World

```
cargo run
```

### Where is the GUI?
Bevy isolated all of its components into plugins so if you want to ditch the whole "game engine" thing you are free to do so! You can also run Bevy in headless mode! Although for the sake of this tutorial, you'll want to enable the default plugins.

```rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // ...
        .run();
}

// ...

```

---

## 2. Building for iOS (Cross Platform Upfront)
I want to immediately try to compile `hello world` for iOS. This saves me a lot of time upfront because if I can't get iOS to work with Rust then the project is a failure.  Plus nothing is more difficult than writing a project then trying to port it to another system after the fact.

> #### Hindsight: Caveats Lead to Discoveries
> It took me much longer than I care to admit to successfully compile.  Originally, I thought I needed to create a header bridge using `extern "C"`, but that is not the case. In fact you do not even need to touch XCode!

### Bundling
There is a build tool for cargo which let's you compile and bundle for iOS without jumping through any hoops. First install the build tool:

```
cargo install cargo-bundle
```

#### Adding Compilation Targets
Then you need to list out all the possible build targets that Rust supports. Since I am targeting iOS, I ran the following command:

```
rustup target list | grep ios
```

If you are missing iOS as a target, then you need to add them based on your environment.

```bash
# for production
rustup target add aarch64-apple-ios

# for development
rustup target add aarch64-apple-ios-sim
```

### Packaging for iOS Simulator
Once you have the compilation targets available on your System **and you are on a Mac**, you can use the script I wrote or copy and paste the following commands to inject your app into the simulator.  Most of the commands come with XCode.

The script below uses [dasel](https://github.com/TomWright/dasel) so it can query the name and bundle identifier from the projects `Cargo.toml` file.

```
brew install dasel
```

Then I use a series of commands provided by XCode to work with the iPhone simulator. More information about how the commands in this script work can be found using this [cheat sheet](https://nshipster.com/simctl/).

```bash
#/usr/bin/env bash

APP_NAME="$(cat Cargo.toml | dasel -r toml '.package.name')"
BUNDLE_ID="$(cat Cargo.toml | dasel -r toml '.package.metadata.bundle.identifier')"

cargo bundle --target aarch64-apple-ios-sim
xcrun simctl boot "iPhone 12 mini"  
open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app 
xcrun simctl install booted "target/aarch64-apple-ios-sim/debug/bundle/ios/$APP_NAME.app"
xcrun simctl launch --console booted "$BUNDLE_ID"
```

---

## 3. Project Success!
If everything worked correctly, you should be left with an iOS App which spams "Hello Rust" to your terminal and meets the following criteria:
* Written entirely in Rust
* No bridging code
* No XCode
* Modular Design
* Flexible Development Environment


![Rust running on iPhone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dr57p9j9e615v38h4jor.png)

---

## To Be Continued
If you would like to document the rest of the project as a series or share the source code please leave a like, comment, or other type of reaction!  It helps me stay motivated

Next article: [Handling tap events](https://dev.to/wadecodez/bevy-native-ios-touch-events-49p3)

## Conclusion
It is possible to write a game entirely in Rust and compile to iOS natively without XCode.