over 3 years ago

## Part 5: Gameplay

In this part we will shape the basic parts of our game - we will add another tank for an enemy (without a real AI, for now) as well as projectiles, which will have the possibility of impacting and killing our tank or the enemy tank. We will allow ourselves, for now, to have ugly code - the point of this part is how to use nalgebra and ncollide.

## Streamlining the Object struct

First thing we should do is streamline the object struct. So far, it has several deficiencies. One such deficiency is that we keep our position and rotation "unwrapped". To fix this, we will use another library, nalgebra:

Extern it in main.rs:

We're going to do the use statement in object.rs

We will create a struct to contain our position, rotation and scale

A container for its combination with a sprite, and its implementation including rendering:

Now let's temporarily update our Object struct:

I honestly feel dirty having written that code; the issue is, we have to make hull and turret public because at the moment we have to set the sprite from our on_load function:

Our changes in Object::rot have also fixed turret rotation to be relative to the body, rather than to the world.

Phew.

Take a breath, we're not done.

Next library we need (unless we want to complicate our code beyond the scope of this tutorial) - ncollide

And let's do some uses:

Now, let's start making the actual gameplay. First, we will create an Object trait:

We're going to rename our old Object struct to Tank, and do the changes required; the above functions will go into impl Object for Tank, while the rest will go into impl Tank. Additionally, we will add collision information to the Tank struct, as well as information for whether it is destroyed or not:

We will also create a new struct called Bullet:

In the Tank impl, we will also add a function for checking collision with a bullet:

Additionally, we will add a function for firing a bullet in front of our cannon:

Now, let's move to main.rs:

We will add another player (we will call him player2); for now we won't control this player, and leave him somewhere to the right of player1. Additionally, we will add a vector of bullets:

Of course, also load set the same sprite for player2, and change all uses of player to player1. Additionally, let us position player2 to the right:

We've also created two sprites for the hull and turret, when they are destroyed. We load them and store them for easy cloning. For more info see the finished code for this part. Additionally, we have added a simple sprite for a bullet.

Let us move to the on_update function. We will check to see if any bullet touches a tank, and if so set its is_destroyed bool to true and its sprites to the ones we have created:

Aaaaaaaaaaaaaaand.. FIRE!

Result:

http://i.imgur.com/DRe446D.gifv

Code available here

over 3 years ago

## Part 4: The mouse (and various improvements)

Today we will do a couple of of things:

• Make movement sane
• Make our tank be represented by multiple sprites (turret + hull, independently rotated)
• Use mouse movement for it

## Movement and Turret

We'll add a current rotation value for our object:

We'll add another function to help us move in the direction we're pointing (or in reverse):

Finally, we make sure the center of the sprite is in the proper place, and represent the rotation:

We also need to load a separate sprite for the turret and hull. I split E-100_strip2.png into two images named E-100_Base.png and E-100_Turret.png.

## Mouse movement

I've added a use statement for Pi:

From now on we'll keep the center of our screen:

Now let's catch the mouse movement:

And call the update function:

As one last thing, I've scaled down both the turret and body to about a quarter the size:

End result:

You can find the source code for this part here

over 3 years ago

## Part 3: From a square to a tank

We're going to jump straight into the action this time around. In our src folder, we're going to make a new file named object.rs, containing a new struct - object - in preparation for having multiple game objects:

We include the object module in main.rs:

We remove the x and y members of Game, as they are now redundant and replace it with an Object named player; we'll also modify our on_update and on_draw functions accordingly:

We've also removed the constant rotation.

Now let's get a sprite for our tank. For that purpose, I found this. In our project folder, we will make a directory named assets and put a sprite of our preference (I chose E-100_preview.png) in it.

At this point we need to bring in some new crates; find_folder to find the assets folder, and a couple of gfx related libraries (Warning: with version matching the versions required by piston_window) to allow us to deal with the sprites; I've also updated piston_window to a more recent version:

We also need to include them:

And in object.rs we will add the following:

We'll add a sprite attribute to our object struct:

We can now display the sprite, if it is set; first we have to modify the definition of the render() function slightly:

Now let's display the sprite:

Finally, let's set that up in main.rs. We'll add an on_load() function to load our sprite:

And we're done! Building and running our program should look like this:

The source code for this part can be found here.

over 3 years ago

## Part 2: Moving Square

Last time we had a simple example - our spinning square - but now we want to go one more step and move it with our keyboard. Before we do that though, we should reorganize our code a little.

First, let's create a struct containing the information of our game:

Now let's create an implementation for it. We're going to move our game and rendering logic into an on_update function and an on_draw function, with some modifications:

We have also added a function to easily create instances of Game, essentially a constructor.

The event loop should now look like this:

One new thing you will notice is Event::Render. This is an event that piston uses to tell us it is time to draw a new frame; it also allows for better decoupling of the render loop from the event loop, such that we don't bog down keyboard input with rendering. It also gives us some more values to work with, such as the current window width and height - which we have used in the modified on_draw code above. We also now capture the Event::Update(UpdateArgs) member, rather than only the delta time; this is to streamline our event function calls.

### The moving the square part

Capturing input is similar to other events in piston:

Before we write on_input, we need some boolean values so we can do movement based on dt; we'll also add some variables for the x and y of our square:

Of course, in Game::new() we have to add default values:

Time to write on_input:

As you can see, we take the Input event argument and match it through one of the possible events - two of which, the ones we use, being Input::Press and Input:Release, representing a button press or release, respectively. We set our *_d variables accordingly.

The last two things we need to do is add movement logic to the update function:

And change the rendering of the square accordingly:

You can find the source code for this part here

Next time we'll load and display a tank sprite instead of a square. For that purpose, we will also start keeping object information in a separate struct, and its code in a separate file.

over 3 years ago

## Part 1: Hello, Piston!

Last time, we set up a workspace for rust and cargo. In this part, we are going to go from nothing to a window with a spinning square in the middle.

## Creating the project

In a terminal, type the following command:

This creates the folder we are going to work with, as well as an initial configuration for cargo.

Inside the folder, you will find it has created a file, Cargo.toml and a directory, src. Inside src you will find the main.rs file, with the basic rust "Hello World" program inside it.

Cargo.toml is the configuration file of our project. Open it up - you will find something similar to the following inside:

As you can tell, this section describes the basic infromation of our package - its name, the version it is on and who wrote it (the authors part will be different for you, of course).

Add the following lines at the end of the file:

This tells Cargo that our project will depend on piston_window, a package which serves as a convenience package for a couple of other packages for windowing, input and graphics.

Now open up src/main.rs and paste in the following code over the old one:

Then build it with cargo build or (on atom) Ctrl+Alt+B (WARNING: On Atom I have had issues with build and linter-rust, as the latter also uses cargo and it results in issues as cargo cannot have multiple instances building the same project at the same time). You should get a warning such as unused variable: 'e' - ignore this for now. Run your program by typing, in a terminal, ./target/debug/piston-tutorial (or double clicking it on windows). You should see something like this:

If the window is "transparent" - don't worry, we will fix that in a second. Otherwise, hurray! We have our first window. Close it by pressing esc.

### What's going on?

Most of the code is pretty intuitive - we include the crate piston_window, bring its contents into the global namespace, create a window with the title "piston-tutorial" and 600 by 600 pixels and tell it to close when we press esc, as well as some boilerplate for displaying the window and retrieving it from our WindowSettings struct.

What might be a little bit confusing is the line for e in window - it looks like an event loop, but we are iterating over the window?!

The reality is that it's quite simple - by iterating on the window, we get an event (in e.event) but we also get a copy of the window's state, rather than having issues with window becoming a moved value. It's also technically correct to write for e in window.events(), but working with that has some differences.

### The Square

Now we want to draw our square. To do this, we will call, inside the event loop, a member function of PistonWindow called draw_2d. It takes, as its only argument, a lambda function with two arguments - the graphical context and the graphical instance.

First, let's clear the screen in a nice black:

Now, let's get a transform for the center of the screen and describe the size and colour of the square:

Finally, we can display the square:

Build and run; you should get something like this:

Hurray! One last thing, let's make it spin. We will now use e.event to get the deltaT and do updates on the update event. Your event loop should now look like this:

And that's it! Running it will have your square spinning around:

You can find the finished source code for this tutorial here.

For the next part, where we will deal with keyboard input, go here

over 3 years ago

## Introduction

Welcome to the "first" part of this tutorial!

Before jumping into Piston and making a game, we must first have a workspace with the required tools for building rust programs, as well as know enough rust to use piston.

## The workspace

You will need to install the rust compiler and cargo, as I will use both to build rust and download libraries I use. On most linux distributions they are generally available in their respective package managers, but on Windows and Mac OS you might have to download the binaries or build the source code; I will not cover this topic in this tutorial

Additionally, you might want a couple of tools to edit your program - I personally use atom to edit my code, as well as rust-racer, which integrates with atom and other IDEs/editors to form quite a complete IDE for rust. Here is a list of atom packages/plugins I use and recommend to work with atom:

• language-rust - Language support (highlighting and such) for rust in atom.
• linter and linter-rust - Does linting for rust (i.e. highlights errors and warnings in your code)
• racer - This package integrates with rust-racer above, allowing for code completion in rust
• build and build-cargo - Allows you to build your rust program with cargo with a hotkey
• atom-debugger - Use the GDB debugger in Atom
• cargo-test-runner - Runs your cargo tests
• termrk - A terminal panel inside atom. I find this one very useful, but you might have to fiddle a tiny bit with it to get it working as you intend

Other than these, you might also find the following useful:

• build-tools - Allows you to choose from a set of commands you choose, so you can for examples start your program by pressing Ctrl+L and then tapping O. It's a bit wonky, so I don't actually recommend this one
• youtube-pane - Use this if you work better while listening to music, but don't install it if you're easily distracted.
• https://gist.github.com/colin-kiegel/40432d62c70c2ddf7354 - This is a wrapper for cargo that fixes some odd bugs in the interaction between the linter and build package when using cargo.

Whew! That was a bit of packages. The bright part is, together they form a solid IDE for rust. Keep in mind it's not necessary to install atom and the packages for atom I have described above, you can use anything that suits you best.

## What now?

You might want to learn rust before going into this tutorial, if you haven't already. Two excellent resources for that are Rust by Example and the Rust Book (in that order, prefferably).

With that done, you can now proceed to the first part of the tutorial.