Talks

Building a game for the Nintendo Switch using Ruby - First Half

The second half will be "coming soon"

Amir Rajan has done something insane. He released a Nintendo Switch game written entirely in mRuby and C. Amir will share his journey with you from the inception of A Dark Room Switch, it's integration with libSDL, all the way to the submission to his publisher. Hopefully, Amir will inspire others to pursue game development projects of their own using Ruby.

RubyKaigi 2019 https://rubykaigi.org/2019/presentations/amirrajan.html#apr19

RubyKaigi 2019

00:00:00.060 All right, game development combined with Ruby equals happiness. This is part two. Was anyone here for the talk I gave back in 2016? No one? Okay, that's exciting.
00:00:07.529 I gave a talk back in 2016, and this is technically part two of that talk. The most important word on this slide is 'hope.' There are three bullet points. One of them is for Lawrence and Zanetti; they started on a game engine in Ruby, specifically RubyMotion, and this game engine wrapped cocos2d-x. This was one of the inspirations for my journey.
00:00:20.760 Another point of hope for me was a presentation by Zack Oakes. If you want to look at game development in Clojure, he presents a different way to build video games. The final moment of hope was a talk by Brett Victor, where he speaks about feedback loops and what development should feel like.
00:00:35.880 These three inspirations put me in a position where I couldn't ignore this light of hope. However, with that hope comes some potential downsides. I initially thought one downside would be that I had to learn C. Who here is at an intermediate to expert level in C? I see a couple of hands, but not the entire room.
00:00:49.350 Back in 2016, looking at that first bullet line regarding Lawrence's work was daunting. Fast forward two years, and I feel more at an intermediate level now. This brings me to the essential C code methodologies.
00:01:02.250 There are three very important methods inside of this codebase. We have our 'rb_define_class' under our 'rb_define_method.' In the case of RubyMotion, this is a macro for 'RBObjectiveC_define_method.' Meanwhile, 'rb_define_module' effectively allows every Ruby runtime to expose any arbitrary C construct as a Ruby class.
00:01:19.560 This was a fascinating moment for me. When I looked at cocos2d-x, I understood how to use these methods. Within game development, the sprite is one of the most vital concepts in games. Here, you can see a C construct created for cocos2d-x and how we expose all these constructs to Ruby.
00:01:31.900 The first term is the class it belongs to; the second is the function; the third is the callback (which C function will execute based on this 'rb_define_method'), and the last parameter specifies the number of arguments. By combining 'rb_define' methods with these C structures, you achieve the productivity of Ruby and the speed of C.
00:01:56.220 It's crucial. When I finally grasped this concept, a whole new world opened up to me. If you take away anything from this talk, those two slides showcasing the foreign function interface to C-based structures are the most important.
00:02:08.200 What I initially feared turned out to be a beacon of hope. The entire language specification of C is surprisingly tiny. There's a certain ubiquity. If you need a library for something, it may not exist in Ruby, but I guarantee that a C library likely does. C is so old that most problems have been solved, and every time I think there's no library for a problem, I invariably find one.
00:02:26.640 The simplicity and portability of C due to its foundational nature are essential. When new hardware like VR emerges, it allows for quick adaptation. The minimum work needed to dive into higher-level languages offers further productivity.
00:02:44.150 What’s fascinating is that Ruby provides a necessary path to complexity. At the bottom, I have a very simple set of Ruby code, utilizing a variable with my name and calling a function. This fundamental aspect of Ruby is often underappreciated.
00:03:00.800 You can introduce complexity as needed. For instance, I can change the variable to a function without altering the call's structure. Then I can transform that function into a module with a simple addition and continue without disrupting the call side.
00:03:20.909 This effortless introduction of complexity makes Ruby such a beautiful language. I can eventually expand into the full complexity of a class, but at any point along that spectrum, I have valid constructs. I don’t have to commit entirely to a class structure from the outset.
00:03:39.220 With the simplicity, ubiquity, and portability of C—combined with the path to complexity and productivity provided by Ruby—these two elements together present an enticing opportunity for game developers.
00:04:02.000 As I honed in on this API, I began reflecting on what a Ruby developer would desire when crafting video games. This journey began in earnest and simply wasn't feasible a decade ago.
00:04:16.400 The first line showcases the crucial 'build' statement. Clang has essentially taken over the world; iOS runs it, and Windows recently integrated it into their Visual Studio Build Tools. Notably, Google Chrome is built on Clang, as well as platforms from iOS to the Nintendo Switch.
00:04:30.380 Clang originated as a project by Chris Lattner at MIT and was developed further under Apple’s sponsorship. Another important component is SDL, which stands for Simple DirectMedia Layer. This is a highly portable and cross-platform library that facilitates Input/Output operations for various devices, including consoles.
00:04:48.480 Lastly, mRuby itself is a portable embedded Ruby engine built on straight C. This essential piece enables the integration of Ruby into C, forming the foundation for my work.
00:05:00.510 With Clang, SDL, and mRuby, I can run the compilation steps, and if successful, it outputs to the screen. At this point, I knew I was onto something. Now, here’s the next progression.
00:05:19.190 In game development, your game loop is the most critical component, handling input, controls, and rendering—often at 60 frames per second, especially for 2D games, which I'm particularly interested in. Here’s what the game loop looks like.
00:05:38.060 Inside the loop, I have an accumulator and a frame rate at 60. When it hits the threshold, I execute the actions; otherwise, I insert a small delay to prevent the CPU from maxing out. If not implemented, it risks hitting 100%, which is not ideal.
00:06:00.440 Next, I needed to initialize the render loop but wasn't displaying anything yet. The crucial line to modify in that code was allocated for presenting the screen: creating a window of dimensions 1280 by 720.
00:06:18.520 Consoles typically run at a resolution of 1080p but must handle 720p content. Therefore, I set my maximum resolution at 1280, which allows for scaling down without loss, ensuring quality remains intact.
00:06:40.360 Now that I've implemented a window for display, it was time to incorporate Ruby. The next significant transformation involved the entry point for initializing the game integrating the mRuby runtime.
00:06:50.579 In the main initialization, I use 'mrb_open' for the mRuby runtime and then 'mrb_load_irep' to load the Ruby program from a hex file. This process effectively brought Ruby into the mRuby context, which made everything function harmoniously.
00:07:17.000 When I refer to definitions, the magic happens with methods on mRuby similar to how they interacted with cocos2d-x. By initializing the game class and calling it, I've invoked Ruby functionality from C—definitely a rewarding moment for me.
00:07:35.160 Now, looking into what 'game_tick' does: within that render loop, it invokes the 'tick' method defined in Ruby. This maintains a smooth and responsive game design environment.
00:07:51.700 In summary, using SDL, you set up your tick rate to manage frame rates effectively within your program. Upon loading the Ruby program itself, employing mRuby runtime class methods allows you to call Ruby code directly.
00:08:06.620 By following these steps, integrating Ruby programming into my project became efficiently manageable and successful. At this stage, I began testing the boundaries of what could be achieved.
00:08:25.160 What started out as a concept has taken shape—I constructed my first game prototype, utilizing an amalgamation of various resources but significantly made with Ruby.
00:08:44.200 You’ll notice elements like camera shakes, frame animations, and fluid movements, all operating at 60 frames per second. The prototype also includes sound effects, animations, and robust gameplay, demonstrating that it works.
00:09:04.780 Although composed of duct tape, bubble gum, and some improvisation, the game functioned well. Soon, I began generalizing the concepts and building out further capabilities in my development framework.
00:09:25.380 Ultimately, I wanted to refine these further and develop live reloading, so changes could be seen instantly during development. It was crucial for efficiency and productivity as I crafted the gameplay experience.
00:09:49.700 While simulating a game setting, I received great feedback from the prototype tests running successfully with live reloading, which proved to be a game-changer for my workflow.
00:10:10.770 Adjustments led to refinements, including optimizing the visual aspects, character movements, and enhancing the overall gameplay experience.
00:10:29.000 Eventually, I integrated comprehensive physics elements for particles, managed the collision detection reliably, and optimized the gameplay mechanics, ensuring everything was live reloaded seamlessly.
00:10:49.000 The resulting game engine was compact, fitting within a mere 300 lines of code, making it feasible for deployment across multiple platforms like Windows, Mac, Linux, iOS, Android, the Switch, and even WebAssembly.
00:11:15.000 The total engine size, post-dead code elimination, remained efficient at just 5 megabytes. This optimization allows easy updates and enhancements.
00:11:32.000 The culmination of this work resulted in a release—A Dark Room for the Nintendo Switch is now available in the EU and US stores, generating excitement across gaming communities.
00:11:50.000 Successfully receiving ratings confirmation, I face the upcoming Japanese release, which is on the horizon. Currently, everyone interested can log into the EU/US accounts and enjoy the game.
00:12:10.000 This framework—crafted with passion and perseverance—has laid the foundation for broader implementations and might transform the way games are developed with Ruby.
00:12:29.000 I invite you to explore DragonRuby.org, where the toolkit is available for download and further training opportunities are accessible!
00:12:49.000 If you participate in game jams, you may receive toolkit access for free through partnerships. This endeavor encapsulates the passion behind my project.