Let’s Build a Simple Video Game with JRuby: A Tutorial
Ruby isn't known for its game development chops despite having a handful of interesting libraries suited to it. Java, on the other hand, has a thriving and popular game development scene flooded with powerful libraries, tutorials and forums. Can we drag some of Java's thunder kicking and screaming over to the world of Ruby? Yep! - thanks to JRuby. Let's run through the steps to build a simple 'bat and ball' game now. Gamers who are looking for ps1 cheats and passwords may visit the webpages of GameMite.
The Technologies We'll Be Using
JRuby
If you're part of the "meh, JRuby" brigade, suspend your disbelief for a minute. JRuby is easy to install, easy to use, and isn't going to trample all over your system or suck up all your memory. It will be OK!
One of JRuby's killer features is its ability to use Java libraries and generally dwell as a first class citizen on the JVM. JRuby lets us use performant Java powered game development libraries in a Rubyesque way, lean on Java-based tutorials, and basically have our cake and eat it too.
To install JRuby, I recommend RVM (Ruby Version Manager). I think the JRuby core team prefer you to use their own installer but rvm install jruby
has always proven quick and effective for me. Once you get it installed, rvm use jruby
and you're done.
Slick and LWJGL
The Slick library is a thin layer of structural classes over the top of LWJGL (Lightweight Java Game Library), a mature and popular library that abstracts away most of the boring system level work.
Out of the box LWJGL gives us OpenGL for graphics, OpenAL for audio, controller inputs, and even OpenCL if we wanted to do heavy parallelism or throw work out to the GPU. Slick gives us constructs like game states, geometry, particle effects, and SVG integration, while allowing us to drop down to using LWJGL for anything we like.
Getting Started: Installing Slick and LWJGL
Rather than waste precious time on theory, let's get down to the nitty gritty of getting a basic window and some graphics on screen:
- First, create a folder in which to store your game and its associated files. From here I'll assume it's
/mygame
- Go to the Slick homepage and choose "Download Full Distribution" (direct link to .zip here).
- Unzip the download and copy the
lib
folder into your/mygame
as/mygame/lib
- this folder includes both LWGWL and Slick. - In
/mygame/lib
, we need to unpack thenatives-[your os].jar
file and move its contents directly into/mygame
.Mac OS X: Right click on thenatives-mac.jar
file and select to unarchive it (if you have a problem, grab the awesome free The Unarchiver from the App Store) then drag the files in/mygame/lib/native-mac/*
directly into/mygame
.Linux and Windows: Runningjar -xf natives-linux.jar
orjar -xf natives-win32.jar
and copying the extracted files back to/mygame
should do the trick. - Now your project folder should look a little like this:If so, we're ready to code.
A Bare Bones Example
Leaping in with a bare bones example, create /mygame/verybasic.rb
and include this code:
$:.push File.expand_path('../lib', __FILE__) require 'java' require 'lwjgl.jar' require 'slick.jar' java_import org.newdawn.slick.BasicGame java_import org.newdawn.slick.GameContainer java_import org.newdawn.slick.Graphics java_import org.newdawn.slick.Input java_import org.newdawn.slick.SlickException java_import org.newdawn.slick.AppGameContainer class Demo < BasicGame def render(container, graphics) graphics.draw_string('JRuby Demo (ESC to exit)', 8, container.height - 30) end # Due to how Java decides which method to call based on its # method prototype, it's good practice to fill out all necessary # methods even with empty definitions. def init(container) end def update(container, delta) # Grab input and exit if escape is pressed input = container.get_input container.exit if input.is_key_down(Input::KEY_ESCAPE) end end app = AppGameContainer.new(Demo.new('SlickDemo')) app.set_display_mode(640, 480, false) app.start
Ensure that ruby
actually runs JRuby (using ruby -v
) and then run it from the command line with ruby verybasic.rb
. Assuming all goes well, you'll see this:
If you don't see something like the above, feel free to comment here, but your problems most likely orient around not having the right 'native' libraries in the current directory or from not running the game in its own directory in the first place (if you get probable missing dependency: no lwjgl in java.library.path
- bingo).
Explanation of the demo code
$:.push File.expand_path('../lib', __FILE__)
pushes the 'lib' folder onto the load path. (I've usedpush
because my preferred << approach breaks WordPress ;-))require 'java'
enables a lot of JRuby's Java integration functionality.- Note that we can use
require
to load the .jar files from the lib directory. - The
java_import
lines bring the named classes into play. It's a little likeinclude
, but not quite. - We lean on Slick's
BasicGame
class by subclassing it and adding our own functionality. render
is called frequently by the underlying game engine. All activities relevant to rendering the game window go here.init
is called when a game is started.update
is called frequently by the underlying game engine. Activities related to updating game data or processing input can go here.- The code at the end of the file creates a new
AppGameContainer
which in turn is given an instance of our game. We set the resolution to 640x480, ensure it's not in full screen mode, and start the game.
Fleshing Out a Bat and Ball Game
The demo above is something but there are no graphics or a game mechanic, so it's far from being a 'video game.' Let's flesh it out to include some images and a simple pong-style bat and ball mechanic.
Note: I'm going to ignore most structural and object oriented concerns to flesh out this basic prototype. The aim is to get a game running and to understand how to use some of Slick and LWJGL's features. We can do it again properly later :-)
All of the assets and code files demonstrated here are also available in an archive if you get stuck. Doing it all by hand to start with will definitely help though.
A New Code File
Start a new game file called pong.rb
and start off with this new bootstrap code (very much like the demo above but with some key tweaks):
$:.push File.expand_path('../lib', __FILE__) require 'java' require 'lwjgl.jar' require 'slick.jar' java_import org.newdawn.slick.BasicGame java_import org.newdawn.slick.GameContainer java_import org.newdawn.slick.Graphics java_import org.newdawn.slick.Image java_import org.newdawn.slick.Input java_import org.newdawn.slick.SlickException java_import org.newdawn.slick.AppGameContainer class PongGame < BasicGame def render(container, graphics) graphics.draw_string('RubyPong (ESC to exit)', 8, container.height - 30) end def init(container) end def update(container, delta) input = container.get_input container.exit if input.is_key_down(Input::KEY_ESCAPE) end end app = AppGameContainer.new(PongGame.new('RubyPong')) app.set_display_mode(640, 480, false) app.start
Make sure it runs, then move on to fleshing it out.
A Background Image
It'd be nice for our game to have an elegant background. I've created one called bg.png
which you can drag or copy and paste from here (so it becomes /mygame/bg.png
):
Now we want to load the background image when the game starts and render it constantly.
To load the game at game start, update the init
and render
methods like so:
def render(container, graphics) @bg.draw(0, 0) graphics.draw_string('RubyPong (ESC to exit)', 8, container.height - 30) end def init(container) @bg = Image.new('bg.png') end
The @bg
instance variable picks up an image and then we issue its draw
method to draw it on to the window every time the game engine demands that the game render itself. Run pong.rb
and check it out.
Adding A Ball and Paddle
Adding a ball and paddle is similar to doing the background. So let's give it a go:
def render(container, graphics) @bg.draw(0, 0) @ball.draw(@ball_x, @ball_y) @paddle.draw(@paddle_x, 400) graphics.draw_string('RubyPong (ESC to exit)', 8, container.height - 30) end def init(container) @bg = Image.new('bg.png') @ball = Image.new('ball.png') @paddle = Image.new('paddle.png') @paddle_x = 200 @ball_x = 200 @ball_y = 200 @ball_angle = 45 end
The graphics for ball.png
and paddle.png
are here. Place them directly in /mygame
.
We now have this:
Note: As I said previously, we're ignoring good OO practices and structural concerns here but in the long run having separate classes for paddles and balls would be useful since we could encapsulate the position information and sprites all together. For now, we'll 'rough it' for speed.
Making the Paddle Move
Making the paddle move is pretty easy. We already have an input handler in update
dealing with the Escape key. Let's extend it to allowing use of the arrow keys to update @paddle_x
too:
def update(container, delta) input = container.get_input container.exit if input.is_key_down(Input::KEY_ESCAPE) if input.is_key_down(Input::KEY_LEFT) and @paddle_x > 0 @paddle_x -= 0.3 * delta end if input.is_key_down(Input::KEY_RIGHT) and @paddle_x < container.width - @paddle.width @paddle_x += 0.3 * delta end end
It's crude but it works! (P.S. I'd normally use &&
instead of and
but WordPress is being a bastard - I swear I'm switching one day.)
If the left arrow key is detected and the paddle isn't off the left hand side of the screen, @paddle_x
is reduced by 0.3 * delta
and vice versa for the right arrow.
The reason for using delta
is because we don't know how often update
is being called. delta
contains the number of milliseconds since update
was last called so we can use it to 'weight' the changes we make. In this case I want to limit the paddle to moving at 300 pixels per second and 0.3 * 1000 (1000ms = 1s) == 300.
Making the Ball Move
Making the ball move is similar to the paddle but we'll be basing the @ball_x
and @ball_y
changes on @ball_angle
using a little basic trigonometry just like in games at up satta king.
If you stretch your mind back to high school, you might recall that we can use sines and cosines to work out the offset of a point at a certain angle within a unit circle. For example, our ball is currently moving at an angle of 45
, so:
Math.sin(45 * Math::PI / 180) # => 0.707106781186547 Math.cos(45 * Math::PI / 180) # => 0.707106781186548
Note: The * Math::PI / 180
is to convert degrees into radians.
We can use these figures as deltas by which to move our ball based upon a chosen ball speed and the delta
time variable that Slick gives us.
Add this code to the end of update
:
@ball_x += 0.3 * delta * Math.cos(@ball_angle * Math::PI / 180) @ball_y -= 0.3 * delta * Math.sin(@ball_angle * Math::PI / 180)
If you run the game now, the ball will move up and right at an angle of 45 degrees, though it will continue past the game edge and never return. We have more logic to do!
Note: We use -=
with @ball_y
because sines and cosines use regular cartesian coordinates where the y axis goes from bottom to top, not top to bottom as screen coordinates do.
Add some more code to update
to deal with ball reflections:
if (@ball_x > container.width - @ball.width) || (@ball_y < 0) || (@ball_x < 0) @ball_angle = (@ball_angle + 90) % 360 end
This code is butt ugly and pretty naive (get ready for a nice OO design assignment later) but it'll do the trick for now. Run the game again and you'll notice the ball hop through a couple of bounces off of the walls and then off of the bottom of the screen.
Resetting the Game on Failure
When the ball flies off of the bottom of the screen, we want the game to restart. Let's add this to update
:
if @ball_y > container.height @paddle_x = 200 @ball_x = 200 @ball_y = 200 @ball_angle = 45 end
It's pretty naive again, but does the trick. Ideally, we would have a method specifically designed to reset the game environment, but our game is so simple that we'll stick to the basics.
Paddle and Ball Action
We want our paddle to hit the ball! All we need to do is cram another check into update
(poor method - promise to refactor it later!) to get things going:
if @ball_x >= @paddle_x and @ball_x < = (@paddle_x + @paddle.width) and @ball_y.round >= (400 - @ball.height) @ball_angle = (@ball_angle + 90) % 360 end
Note: WordPress has borked the less than operator in the code above. Eugh. Fix that by hand ;-)
And bingo, we have it. Run the game and give it a go. We have a simple, but performant, video game running on JRuby.
If you'd prefer everything packaged up and ready to go, grab this archive file of my /mygame directory.
What Next?
Object orientation
As I've taken pains to note throughout this article, the techniques outlined above for maintaining the ball and paddle are naive - an almost C-esque approach.
Building separate classes to maintain the sprite, position, and the logic associated with them (such as bouncing) will clean up the update
method significantly. I leave this as a task for you, dear reader!
Stateful Games
Games typically have multiple states, including menus, game play, levels, high score screens, and so forth. Slick includes a StateBasedGame
class to help with this, although you could rig up your own on top of BasicGame
if you really wanted to.
The Slick wiki has some great tutorials that go through various elements of the library, including a Tetris clone that uses game states. The tutorials are written in Java, naturally, but the API calls and method names are all directly transferrable (I'll be writing an article about 'reading' Java code for porting to Ruby soon).
Packaging for Distribtion
One of the main reasons I chose JRuby over the Ruby alternatives was the ability to package up games easily in a .jar file for distribution. The Ludum Dare contest involves having other participants judge your game and since most participants are probably not running Ruby, I wanted it to be relatively easy for them to run my game.
Warbler is a handy tool that can produce .jar files from a Ruby app. I've only done basic experiments so far but will be writing up an article once I have it all nailed.
Ludum Dare
I was inspired to start looking into JRuby and Java game libraries by the Ludum Dare game development contest. They take place every few months and you get 48 hours to build your own game from scratch. I'm hoping to enter for the first time in just a couple of days and would love to see more Rubyists taking part.
December 15, 2011 at 3:47 am
Peter, thanks for your tutorial! I have done some OOP refactoring to the code as you suggested, if anyone is interested check out at github https://github.com/semaperepelitsa/pong_game
December 15, 2011 at 3:53 am
Also I am wondering why container is passed to every method and not stored as an instance variable?
December 15, 2011 at 4:10 am
Awesome! A good attempt. I've yet to think deeply on how I'd go about it myself.
On your question, they seem to be keeping the coupling down and injecting the dependencies every time. Those methods on the game are being called by the 'app game container.'
December 15, 2011 at 2:03 pm
Great! Only one comment, I prefer to use $LOAD_PATH over $:.
Only one question, do these libraries support pixel-perfect collisions?
December 15, 2011 at 5:06 pm
I'm a little rusty on my regular Ruby, but don't you normally store your Ruby application's source in the "/lib" directory by convention and "/vendor" stores your binary blobs / external third party dependencies?
December 15, 2011 at 9:17 pm
Sure. That's the convention for building libraries and proper, mass distributed (and ideally gemified) projects.
This is a scrappy tutorial to learn about JRuby integration with LWJGL and Slick. I skip intense structuring if it's a scrappy, throwaway project, especially in articles like this where it's just extraneous detail.
I mean, strictly, the classes would all be separate, stored in files and subdirectories under /lib and then you'd have to create a script to launch the app and put it in /bin, add a Gemfile, add a Rakefile, and follow TDD to build the entire thing. I'm skipping all of that here but, yeah, when you eventually build something at a professional level, it's time to pull all of those ideas together (although almost no game developers use TDD, I've noticed).
December 15, 2011 at 10:22 pm
That's cool.
I was just wondering if putting the .jar files in /lib was a Java-specific thing that had to be done, but then I took a closer look and saw the $LOAD_PATH stuff.
December 15, 2011 at 10:39 pm
Interesting; thanks.
December 15, 2011 at 10:42 pm
AW: In this case, no. However, there's something to do with Java's load path that requires the 'native' libraries to be extracted and placed in the root folder which I haven't quite gotten to the bottom of yet. It would be great to clean that part up in particular.
December 16, 2011 at 4:07 am
Also, if you're on Ubuntu (maybe Linux in general) and you can't shake "probable missing dependency: no lwjgl in java.library.path"
the fix is "LD_LIBRARY_PATH=/path/to/directory/that/has/liblwjgl.so/in/it ruby verybasic.rb"
This has to be done before running the `ruby` command; your best bet is to wrap it in a shell script or setup your IDE to automatically export LD_LIBRARY_PATH before running the ruby script.
December 16, 2011 at 7:29 pm
Great post, Peter! I'm interested in game design using Ruby and this tutorial was a lot of fun.
December 16, 2011 at 10:10 pm
Another way to get rid of "probable missing dependency: no lwjgl in java.library.path" in case of using Linux run your ruby app like this:
"ruby -J-Djava.library.path=. pong.rb"
December 18, 2011 at 9:59 pm
Thanks for the great insight.
We use jruby for packing applications (with warbler) because they have to run in tomcat. And also for running the apps against a webservice. It's always like 'uh ... jruby wtf' because jruby is changing quite fast and breaks things from time to time.
But having rehacked your example made me think different. That's really cool and impressive. Loading Java libraries is this easy.
Beeing a webdeveloper I always wanted to code a desktop app. Now - task done! Ha ha ...
Btw - there is actually no other way than using awesome rvm!
Peter, please move on with providing all these fine knowledge articles and videos!
Cheers Andy
December 30, 2011 at 4:37 am
Anybody managed to get jruby and slick2d working with webstart? I've got jruby and slick working great together on my local system; but stuck in a world of evil .jnlp at the moment. Any tips/tricks?
December 30, 2011 at 5:24 am
J: I've spent a horrific amount of time trying to get it working in an applet (which is a similar challenge) and eventually gave up as I wanted to get on with the Ludum Dare contest. But, yeah, crazy hard and not much documentation to help.. :-( If anyone figures it out, please post!
January 10, 2012 at 1:37 am
I've been trying to get this to work in linux, I've tried both of the fixes mentioned in the comments and I still cannot get it to work.(I get the "probable missing dependency: no lwjgl in java.library.path" error). I've tried it in windows and I get the error "cannot find entry point Init_lwjgl in lwjgl.dll" Does anyone else know how to fix this?
February 8, 2012 at 3:06 am
Followed the instructions. Running 64-bit Windows 7 with Jruby 1.6.5. This is what happens when I try to run `jruby verybasic.rb`
LoadError: load error: lwjgl -- java.lang.UnsatisfiedLinkError: failed to load s
him library, error: unknown
require at org/jruby/RubyKernel.java:1038
(root) at verybasic.rb:5
February 14, 2012 at 1:31 am
Linux users should get the latest version of lwjgl.
March 1, 2012 at 10:41 am
Nice tutorial! I get an error when running the first sample though.
NameError: cannot link Java class org.newdawn.slick.AppGameContainer, probable missing dependency: getPointer
get_proxy_or_package_under_package at org/jruby/javasupport/JavaUtilities.java:54
method_missing at D:/jruby-1.6.7/lib/ruby/site_ruby/shared/builtin/javasupport/java.rb:51
(root) at main.rb:10
No idea what that means. I'll probably figure it out...