Cameron Gose

From Start to Published, Create a game with Ruby!

From Start to Published, Create a game with Ruby!

by Cameron Gose

In the RubyConf 2022 workshop titled "From Start to Published," Cameron Gose guides attendees through the process of creating a simple game using Ruby and the DragonRuby Game Toolkit. The workshop focuses on building a basic version of the classic Snake game and publishing it on itch.io. The session is designed for developers interested in game development and highlights key steps in programming and publishing a game using Ruby. Key points discussed during the workshop include:

  • Introduction to DragonRuby Game Toolkit:

    • A powerful development toolkit that supports Ruby across multiple platforms, including PC, Mac, Linux, and consoles like PlayStation and Xbox.
    • Importance of setting up a development environment by acquiring the DragonRuby toolkit via itch.io.
  • Step-by-Step Game Development:

    • Initial setup instructions for the code editor and toolkit.
    • Publishing the game early in the development process to streamline iterative changes.
    • Structure of the game code, including the primary loop functions: handle_input, update, and render.
  • Designing the Game Elements:

    • Building game mechanics such as grid drawing, snake movement, and collision detection with walls and collectibles.
    • Creating functions for rendering game elements, managing state persistence, and updating graphics during game ticks.
    • Assigning colors and visual properties to game components to enhance the user experience.
  • Scoring and Difficulty Mechanisms:

    • Implementing a scoring system where collecting items increases player points and contributes to game complexity over time.
    • Adapting the game state to handle collisions and resetting mechanisms for a seamless user experience.
  • Tips for Game Publishing:

    • Updating itch.io game pages with builds and metadata, ensuring correctness for game visibility.
    • Encouragement to utilize the DragonRuby community and resources for further exploration and learning.
    • Promotion of participating in game jams as a way to practice and innovate.

In conclusion, Cameron emphasizes the value of early publishing, community interaction through platforms like Discord, and the importance of leveraging available documentation to enhance game development skills. The workshop encapsulates the fundamentals of programming a simple game using Ruby, instilling confidence in attendees to create and publish their own game projects successfully.

00:00:11.660 Welcome to "From Start to Published" using Ruby! Today, we'll be building a game from scratch.
00:00:18.119 We'll be recreating a basic implementation of the arcade classic, Snake, from start to finish. Then, we will
00:00:24.420 publish it onto a platform called itch.io.
00:00:29.519 If you have the DragonRuby engine from itch.io, that's great! If you don't,
00:00:35.219 I recommend visiting itch.io later to create an account and claim the DragonRuby Game Toolkit so that you can publish later if you wish.
00:00:42.600 Alrighty! My name is Cameron Gose. This is a year of a lot of firsts for me.
00:00:47.820 This is my first official Ruby Central conference. This is also the first time I've given a workshop at a conference. My first conference was actually RailsConf, which was last month, where I met my good friend Tim Chapeirini.
00:01:06.000 He's been a great friend. This speaks to the Ruby community's support; after I told him about what I was doing, he offered to help. I really appreciated that.
00:01:18.000 The DragonRuby Game Toolkit—it's not a typo, it's "DragonRuby" as one word for SEO purposes—is a powerful game development toolkit.
00:01:25.560 The runtime targets Ruby language specs, allowing us to use Ruby, one of the best programming languages, to develop games across multiple platforms. It's a multi-level cross-platform runtime that can target PC, Mac, Linux, Raspberry Pi, WebAssembly, iOS, Android, Nintendo Switch, PlayStation, Xbox, and Oculus VR, with more support being added. Recently, you can also use a Steam Deck as a developing device.
00:01:44.840 Part of the runtime implements features similar to M Ruby, rather than CRuby, so there may be some differences in the available Ruby methods. The game toolkit is built on top of the runtime, providing a self-contained environment. No dependencies are needed, and you can download it and start right away.
00:02:00.000 Currently, it functions primarily as a 2D game engine, but there are 3D capabilities being added. For example, a lot of work is being done on drawing triangles, which is a crucial piece for building polygons in 3D models.
00:02:12.180 As for requirements, you'll need to bring your own code editor. You can use any editor you prefer—we have no restrictions! Additionally, you need the game toolkit, either through download or the files we provide. You will also need a Linux machine, a Mac, Raspberry Pi, or Windows.
00:02:25.320 Now, how do you get it? Earlier I mentioned one way—the itch.io link. I will walk through it for those watching at home.
00:02:38.520 There is the itch.io page, and then dragonruby.org. You might be familiar with Ruby Motion; DragonRuby came out of Ruby Motion by Amir and his team.
00:02:44.940 If you scroll down to the bottom, you'll see the game toolkit, where you can read more about it with a more detailed list of available features at different levels.
00:02:58.680 The standard level is essentially what you have now. If you have itch.io access, that’s mostly the standard tier; however, if you want more features, such as using C extensions or deploying to iOS or Android, you'll need a higher level.
00:03:12.240 Alright, let's move into setup! First, ensure you have the unzipped folder ready.
00:03:29.700 For those who are following along, please copy the unzipped folder directly to your desktop. You will want to have a terminal pointing to it, or you can change directories into it in your terminal.
00:03:42.060 You will also want to point your editor to open this directory.
00:03:48.660 Once you have this set up, we can start running the game! You will do this by executing the command: .\dragonruby for macOS or Linux, and .\dragonruby.exe for Windows machines.
00:04:05.220 I should also mention that for macOS, it works well on Apple silicon too. The publishing step runs flawlessly on my machine.
00:04:18.240 When you run that command, you should see a window like this that says "Hello, World!" Thank you for reminding me of the intro! This window will display some text and also output an image.
00:04:43.680 Let’s quickly review the code so you can get an overview of how it operates. In the main.rb file, there is a 'tick' function. This function receives arguments, where the arguments being passed in represent the game toolkit environment. This environment gives you access to all the APIs available within the game toolkit.
00:05:04.380 The outputs, such as args.outputs.labels, are collections that you can append to, which will display text. We'll go over this in more detail later, but for now, the sprites will be used to display images in the game window.
00:05:17.700 Now, we're going to approach this a bit differently: we'll actually start by publishing! If you access the internet right now, feel free to follow along.
00:05:29.880 First, if you haven’t created an itch.io account yet, please do so. Once you create an account, you need to visit the following URL.
00:05:37.920 Navigate to "/game/new" and this will open up a new window.
00:05:43.600 Here, we will need to create a game page. Enter a title—let's call it "RubyConf Mini Snake." When you type in a title, an auto-generated slug for the project webpage will be created.
00:06:02.640 Keep note of this slug, as we'll need it for the publishing step. For the game description, we can mention: "This is a basic recreation of Snake." The classification will be a game.
00:06:16.560 For now, we want to specify that the project will be downloadable. Here, enable the option for accepting no payments, but just to note, the itch.io platform is a great resource for content creators to monetize their work.
00:06:27.240 Once done, scroll to the bottom and hit ‘Save and view page.’ Right now, it's not visible to the public as it's in draft mode, as shown in the top right of the screen.
00:06:40.140 Next, we need to go back into the code editor and update the metadata.
00:06:49.860 In your code editor, expand the file section and navigate to my game—there you should find a folder called metadata.
00:06:57.480 Inside, there will be a file called game_metadata.txt. To publish, you'll need an itch.io account. Your Dev ID will be your itch.io username.
00:07:09.800 The Dev Title will be the title you provided earlier.
00:07:15.720 As for the Game ID, that will be the slug from the URL, which, in this case, is "ruby-conf-mini-snake".
00:07:27.120 The Dev Title is just your name, not the game title. The Game Title is what you filled out in the form.
00:07:39.120 You can uncomment the version and icon lines in the metadata file. If you want to change the icon for your game, you can always do that in the future.
00:08:01.080 Currently, open another tab in your terminal and go into the same folder.
00:08:07.640 We will then execute the command "dragonruby.publish" and include the flag "--only package my_game".
00:08:15.600 This command will initiate the initial builds for the game and produce it. Starting off with this publish step is important because DragonRuby makes it easy to iterate on your publish once you have it set up.
00:08:36.600 The process automatically publishes to itch.io afterward. Can everyone see that okay? Let me make things a bit bigger.
00:08:56.520 Does anyone need to back up? Tim!
00:09:00.720 If you have entered this command, press enter and it will take some time.
00:09:07.920 The command will run some compiling and building, generating a number of files that will be visible in the builds folder.
00:09:14.280 To see your builds folder, check within the game toolkit folder. You should now observe multiple build options.
00:09:23.280 This includes an HTML5.zip for WebAssembly, a Linux binary, a Raspberry Pi binary, a macOS zip folder, or folder that you can run the game from, and also a Windows build.
00:09:31.200 It's fairly straightforward to produce executables for each of these platforms.
00:09:45.720 Once you have produced these builds, go back into your itch.io game page and select "edit game."
00:09:54.200 From there, you will scroll down to the uploads section and click "upload files." Find the builds directory.
00:10:01.680 Select the respective builds you want to target for your game.
00:10:06.920 You can either select all of them or just specific ones. In this case, select the HTML5.zip or the Linux 64 builds and so on.
00:10:15.840 Hit upload; this may take a while, but since the game is small, it should not take too long.
00:10:25.720 The game size is relatively small, ranging between three to six megabytes, representing the basic transition from "Hello World" to publishing.
00:10:34.200 Now, returning to your game page, go to "view page" to see your game available for download.
00:10:42.000 Let’s move on. Now, we will cover the basics of making a game.
00:10:47.560 The core idea behind building any kind of game consists of three steps: the game loop.
00:10:58.520 The game loop handles input, updates and calculates the current game state, and finally, it renders to the screen.
00:11:05.280 In short, you need to take user input, determine what's going to happen on the screen based on that input, then draw it accordingly.
00:11:14.860 In the main.rb file, we will create three functions. You can go ahead and delete the "Hello World" text currently in your main.rb file.
00:11:27.180 We will fill it with functions to handle the game loop. The first function we'll create is 'handle_input'. This will be used for handling player inputs.
00:11:40.860 Next, we'll create an update function, which will manage changes to the game state.
00:11:51.060 Finally, we will implement the render function to draw things on the screen. The 'tick' function needs to call the handle_input, update, and render functions in that specific order.
00:12:05.760 The tick function is essential in DragonRuby, as it runs everything in a cyclical manner. Each time the tick is called, the game updates and redraws at a smooth 60 frames per second.
00:12:20.760 Now, we'll begin to set up the scene. First, we’ll draw a grid. The snake travels within a grid, similar to the original Snake game.
00:12:36.360 At the top of the main.rb file, let's create a constant called grid_size, and set it to 20. I chose 20 because it evenly divides the aspect ratio that DragonRuby uses for rendering.
00:12:56.720 Above the render function, create another function called 'render_grid.' We will call this function above when we create the render function.
00:13:14.640 Now, let’s go back to the render_grid function. Here, we will first draw the vertical lines along the x-axis.
00:13:29.880 We will create a numeric grid to split the grid on the x-axis. The total width of the window, which you can access via arguments grid.width.
00:13:44.400 Divide that total width by the grid size, and you will have the number of vertical grid lines.
00:13:52.680 We will also iterate through each vertical grid line using a "each_with_index" method.
00:14:01.620 Finally, we will use args.outputs.lines to draw the lines. Using a hash syntax is very straightforward for our needs, allowing us to specify the X position, Y position, and X2 and Y2 values for rendering lines.
00:14:15.720 Now that we've implemented the vertical lines for the x-axis, we will repeat a similar process for the y-axis.
00:14:27.720 The code will be very similar; only this time we will draw lines horizontally across the game window.
00:14:40.300 We'll determine the number of lines needed by dividing the grid height by the grid size. As before, we'll also use 'each_with_index' to iterate through those y-axis coordinates.
00:14:54.900 Once we have the coordinates, we will generate each horizontal line, ensuring to set proper x and y values based on the grid size.
00:15:09.480 Next up, we will start drawing the head of the snake. We will create a green square at the center of the grid.
00:15:22.680 Let's create another function that will be called within the tick. This is going to hold the state of the game.
00:15:32.240 In the 'defaults' function, we will use the state property available to us from the game toolkit. This will help us store the game state across ticks, enabling data persistence.
00:15:52.440 We will initialize the snake’s head, centering it in the grid by setting the x and y positions to half of grid.width and grid.height.
00:16:02.760 Next, we need to set the width and height of the snake’s head equal to the grid size.
00:16:15.480 For the head's RGB representation, we'll set it to bright lime green with the values 23, 245, and 23.
00:16:24.280 Once we finish defining how the head is set up, we will update the tick function to call defaults, ensuring the parameters are passed in.
00:16:36.320 This means that every time tick is called, the defaults will instantiate the initial state.
00:16:50.000 Next, we proceed to rendering the snake. Here, we’ll create a separate 'render_snake' function.
00:17:01.800 In this function, we will pass in the array containing the head state.
00:17:12.680 To draw the head, we will utilize args.outputs.solids, which is a collection used for outputting squares and rectangles.
00:17:24.880 Now that we have the head rendering function, ensure that this function is called inside the render function so that it displays the green head on the grid.
00:17:37.480 Next, we’ll add movement to the snake to make it interactive. This is where we start handling input in earnest.
00:17:53.480 A few changes shall be made. We will create some local variables within the handle_input function. For instance, we'll assign inputs equals args.inputs for ease.
00:18:05.460 Similarly, we will set a variable called head that equals args.state.head.
00:18:16.040 Now that we have those variables, we can write conditions to handle movement based on user input. For example, if inputs.left is pressed, we will set head.direction to left.
00:18:31.720 We’ll do the same for right, up, and down inputs, ensuring the snake can move fluidly.
00:18:48.240 Next, we will introduce another constant at the top of the file under the grid size. This constant will be called speed, and we will set it to 10.
00:19:01.920 Though this may seem like the speed of the snake, it actually represents the wait time every 10 ticks before we increment the snake.
00:19:12.480 Now, we will create a new function called 'move_snake' and place it above the update function.
00:19:23.440 In this function, we will again create a local variable to store the snake head and define a directional vector.
00:19:39.320 The vector will hold x and y position values for movement. Next, we will use a case statement to determine the direction based on the head’s current direction.
00:19:58.240 When moving right, the x vector will equal one, while for left, it will equate to negative one. We'll do a similar computation for the y direction.
00:20:10.820 Next, we update the head’s x and y values based on those directional vectors.
00:20:30.940 In the update function, we will incorporate a condition that checks if the current tick count, mod speed, equals zero.
00:20:43.840 If so, we’ll execute the move_snake function, allowing the snake to move based on user input and the set speed.
00:20:58.300 We should now see the snake randomly jumping across the screen based on input, simulating a retro gaming aesthetic.
00:21:09.260 In line with the philosophy of the DragonRuby community, we emphasize the importance of publishing early and often.
00:21:24.520 Once you have something presentable, you can call the Dragon Ruby publish command again without any flags.
00:21:36.360 Doing so will rebuild the game and allow for updates through itch.io.
00:21:48.480 This will execute some more tasks using Butler, automating the deployment process.
00:22:00.480 When you run the command, you will see an authentication dialogue. Once authorized, you won't have to authenticate again.
00:22:11.640 Once published successfully, it will show up with a timestamp for when it was last updated.
00:22:22.440 Next, we will introduce boundaries, ensuring that the snake has walls it cannot cross—ultimately leading to a game over state.
00:22:38.920 In the defaults function, we will create properties for the walls, defining their positions relative to the game screen.
00:22:49.640 These properties will also include a color specification via RGB values.
00:23:02.680 We will also set up another function named render_walls to draw the walls around the game window.
00:23:12.320 The walls will be added to the solids collection rendered as per the current state.
00:23:23.900 After rendering walls, you’ll notice the snake may not appear in that wall area upon initialization.
00:23:38.160 To reset the game to its initial state, DragonRuby has an in-built feature, the Quake Terminal, making it easy to do that.
00:23:52.480 Once that’s initialized, you should see the snake in the middle with walls surrounding it.
00:24:06.880 Now, we will add collision detection to halt the snake when it hits the walls.
00:24:21.240 Create a new function called handle_boundary_collision to check for intersections between the head of the snake and the walls.
00:24:35.000 This will include logic to check whether the head intersects with any of the walls using additional parameters for min and max boundaries.
00:24:47.040 In the update function, we shall call handle_boundary_collision to ensure the snake remains within the game area.
00:24:59.840 Now, if everything is set up properly, you should see that moving the snake left or right should not allow it to cross beyond the walls.
00:25:16.640 So, onto scoring: as the snake moves around, collectible items will appear in random locations.
00:25:31.440 These will serve two functions: incrementing the player’s score and making the game progressively harder.
00:25:44.080 We will create a new function called spawn_collectible, which will generate collectibles in specific conditions.
00:25:56.640 The function will check if a collectible is nil; if it is, we will generate random x and y coordinates for it.
00:26:09.760 The x coordinate will be derived from the grid width, divided by the grid size, with adjustments to avoid collision.
00:26:24.320 Following this, we will initialize the collectible with equivalent size attributes and assign it a color.
00:26:36.920 Once the spawn_collectible function is created, we will also set up a render_collectible function above the render function.
00:26:51.640 The render function will add the collectibles to the output solids collection for display in the game.
00:27:05.760 Next, we’ll implement collision detection between the snake’s head and the collectible. This function will be called handle_collectible_collision.
00:27:19.480 In this function, if the collectible is nil, we have no need to check further.
00:27:31.680 When the head intersects with the collectible, we set the collectible to nil to respawn in a new location at the next update.
00:27:47.100 Additionally, we will update the score every time a collectible is picked up, storing this in the state score.
00:28:01.560 Upon each successful collection, the player’s score will increase by one.
00:28:13.260 Finally, we need to display the score to the player. In the render function, we will include code that outputs the score in a specified location on the screen.
00:28:28.920 The x position will be derived from the left edge of the grid, shifted slightly to the right, while the y position will be near the top edge.
00:28:41.840 Finally, we will render the current score in the outputs section, displaying it in real time as the game progresses.
00:29:00.560 Next, let's add sound effects! For this workshop, use the provided collect.wav sound file.
00:29:14.880 You will need to add that file to the sounds folder within your game directory.
00:29:27.080 In handle_collectible_collision, you simply use args.outputs.sounds to append the sound whenever a collectible is grabbed.
00:29:39.240 If you choose .wav files for sound effects, they will only play once, while .ogg files will continuously play.
00:29:53.440 Now, let's conclude by increasing the difficulty of the game by expanding the snake body.
00:30:05.560 In the defaults function, add an empty array called args.state.body and set this up so it initializes once.
00:30:18.800 In the update function, we'll call 'grow_body.' If the body has any segments, we will clone the last segment and track the head’s position.
00:30:34.480 We will then set the direction of each segment of the body to follow the direction of the head.
00:30:48.840 Now, we want to ensure the segments are drawn along with the head while also checking for any collisions with the body.
00:31:02.880 To do this, we will create a function handle_body_collision to check for intersections between the head and the body.
00:31:14.720 If any part of the body collides with the head, we will set the state to game_over.
00:31:27.960 Render an additional label to display the game over message, including the final score.
00:31:38.920 In the main update function, include checks for the game state. If the game state is set to game over, it will trigger rendering the game over screen.
00:31:51.480 To reset the game, include checks for keyboard input. If the escape key is pressed, reset the game state.
00:32:03.880 Finally, let's publish the finished game and deploy it on itch.io!
00:32:18.100 Although Snake might not be the most thrilling game, this process demonstrates the basics of game development.
00:32:34.360 If you've stuck around and followed along, thank you all for participating! In the DragonRuby community, we have resources and documentation available to you.
00:32:48.660 I highly recommend joining the DragonRuby Discord. It's an engaging community eager to expand their familiarity with game development.
00:33:03.440 The documentation on dragonruby.org is crucial, and additionally, exciting samples for various game genres are available.
00:33:18.640 Finally, I encourage everyone to explore game jams, including the current DragonRuby community's 20 second game jam.
00:33:36.960 Participating in game jams provides a fantastic opportunity to practice and showcase your development skills.