• Docs
  • Samples
  • Table Of Contents

    Getting Started link

    This is a tutorial written by Ryan C Gordon (a Juggernaut in the industry who has contracted to Valve, Epic, Activision, and EA... check out his Wikipedia page: https://en.wikipedia.org/wiki/Ryan_C._Gordon).

    Introduction link

    Welcome!

    Here's just a little push to get you started if you're new to programming or game development.

    If you want to write a game, it's no different than writing any other program for any other framework: there are a few simple rules that might be new to you, but more or less programming is programming no matter what you are building.

    Did you not know that? Did you think you couldn't write a game because you're a "web guy" or you're writing Java at a desk job? Stop letting people tell you that you can't, because you already have everything you need.

    Here, we're going to be programming in a language called "Ruby." In the interest of full disclosure, I (Ryan Gordon) wrote the C parts of this toolkit and Ruby looks a little strange to me (Amir Rajan wrote the Ruby parts, discounting the parts I mangled), but I'm going to walk you through the basics because we're all learning together, and if you mostly think of yourself as someone that writes C (or C++, C#, Objective-C), PHP, or Java, then you're only a step behind me right now.

    Prerequisites link

    Here's the most important thing you should know: Ruby lets you do some complicated things really easily, and you can learn that stuff later. I'm going to show you one or two cool tricks, but that's all.

    Do you know what an if statement is? A for-loop? An array? That's all you'll need to start.

    The Game Loop link

    Ok, here are few rules with regards to game development with GTK:

    That's an entire video game in one run-on sentence.

    Here's that function. You're going to want to put this in mygame/app/main.rb, because that's where we'll look for it by default. Load it up in your favorite text editor.

    def tick args
      args.outputs.labels << [580, 400, 'Hello World!']
    end
    

    Now run dragonruby ...did you get a window with "Hello World!" written in it? Good, you're officially a game developer!

    Breakdown Of The tick Method link

    mygame/app/main.rb, is where the Ruby source code is located. This looks a little strange, so I'll break it down line by line. In Ruby, a '#' character starts a single-line comment, so I'll talk about this inline.

    # This "def"ines a function, named "tick," which takes a single argument
    # named "args". DragonRuby looks for this function and calls it every
    # frame, 60 times a second. "args" is a magic structure with lots of
    # information in it. You can set variables in there for your own game state,
    # and every frame it will updated if keys are pressed, joysticks moved,
    # mice clicked, etc.
    def tick args
    
      # One of the things in "args" is the "outputs" object that your game uses
      # to draw things. Afraid of rendering APIs? No problem. In DragonRuby,
      # you use arrays to draw things and we figure out the details.
      # If you want to draw text on the screen, you give it an array (the thing
      # in the [ brackets ]), with an X and Y coordinate and the text to draw.
      # The "<<" thing says "append this array onto the list of them at
      # args.outputs.labels)
      args.outputs.labels << [580, 400, 'Hello World!']
    end
    

    Once your tick function finishes, we look at all the arrays you made and figure out how to draw it. You don't need to know about graphics APIs. You're just setting up some arrays! DragonRuby clears out these arrays every frame, so you just need to add what you need _right now_ each time.

    Rendering A Sprite link

    Now let's spice this up a little.

    We're going to add some graphics. Each 2D image in DragonRuby is called a "sprite," and to use them, you just make sure they exist in a reasonable file format (png, jpg, gif, bmp, etc) and specify them by filename. The first time you use one, DragonRuby will load it and keep it in video memory for fast access in the future. If you use a filename that doesn't exist, you get a fun checkerboard pattern!

    There's a "dragonruby.png" file included, just to get you started. Let's have it draw every frame with our text:

    def tick args
      args.outputs.labels  << [580, 400, 'Hello World!']
      args.outputs.sprites << [576, 100, 128, 101, 'dragonruby.png']
    end
    

    NOTE:

    Pro Tip: you don't have to restart DragonRuby to test your changes; when you save main.rb, DragonRuby will notice and reload your program.

    That .sprites line says "add a sprite to the list of sprites we're drawing, and draw it at position (576, 100) at a size of 128x101 pixels". You can find the image to draw at dragonruby.png.

    Coordinate System and Virtual Canvas link

    Quick note about coordinates: (0, 0) is the bottom left corner of the screen, and positive numbers go up and to the right. This is more "geometrically correct," even if it's not how you remember doing 2D graphics, but we chose this for a simpler reason: when you're making Super Mario Brothers and you want Mario to jump, you should be able to add to Mario's y position as he goes up and subtract as he falls. It makes things easier to understand.

    Also: your game screen is _always_ 1280x720 pixels. If you resize the window, we will scale and letterbox everything appropriately, so you never have to worry about different resolutions.

    Ok, now we have an image on the screen, let's animate it:

    def tick args
      args.state.rotation  ||= 0
      args.outputs.labels  << [580, 400, 'Hello World!' ]
      args.outputs.sprites << [576, 100, 128, 101, 'dragonruby.png', args.state.rotation]
      args.state.rotation  -= 1
    end
    

    Now you can see that this function is getting called a lot!

    Game State link

    Here's a fun Ruby thing: args.state.rotation ||= 0 is shorthand for "if args.state.rotation isn't initialized, set it to zero." It's a nice way to embed your initialization code right next to where you need the variable.

    args.state is a place you can hang your own data. It's an open data structure that allows you to define properties that are arbitrarily nested. You don't need to define any kind of class.

    In this case, the current rotation of our sprite, which is happily spinning at 60 frames per second. If you don't specify rotation (or alpha, or color modulation, or a source rectangle, etc), DragonRuby picks a reasonable default, and the array is ordered by the most likely things you need to tell us: position, size, name.

    There Is No Delta Time link

    One thing we decided to do in DragonRuby is not make you worry about delta time: your function runs at 60 frames per second (about 16 milliseconds) and that's that. Having to worry about framerate is something massive triple-AAA games do, but for fun little 2D games? You'd have to work really hard to not hit 60fps. All your drawing is happening on a GPU designed to run Fortnite quickly; it can definitely handle this.

    Since we didn't make you worry about delta time, you can just move the rotation by 1 every time and it works without you having to keep track of time and math. Want it to move faster? Subtract 2.

    Handling User Input link

    Now, let's move that image around.

    def tick args
      args.state.rotation ||= 0
      args.state.x ||= 576
      args.state.y ||= 100
    
      if args.inputs.mouse.click
        args.state.x = args.inputs.mouse.click.point.x - 64
        args.state.y = args.inputs.mouse.click.point.y - 50
      end
    
      args.outputs.labels  << [580, 400, 'Hello World!']
      args.outputs.sprites << [args.state.x,
                               args.state.y,
                               128,
                               101,
                               'dragonruby.png',
                               args.state.rotation]
    
      args.state.rotation -= 1
    end
    

    Everywhere you click your mouse, the image moves there. We set a default location for it with args.state.x ||= 576, and then we change those variables when we see the mouse button in action. You can get at the keyboard and game controllers in similar ways.

    Coding On A Raspberry Pi link

    We have only tested DragonRuby on a Raspberry Pi 3, Models B and B+, but we believe it _should_ work on any model with comparable specs.

    If you're running DragonRuby Game Toolkit on a Raspberry Pi, or trying to run a game made with the Toolkit on a Raspberry Pi, and it's really really slow-- like one frame every few seconds--then there's likely a simple fix.

    You're probably running a desktop environment: menus, apps, web browsers, etc. This is okay! Launch the terminal app and type:

    do raspi-config
    

    It'll ask you for your password (if you don't know, try "raspberry"), and then give you a menu of options. Find your way to "Advanced Options", then "GL Driver", and change this to "GL (Full KMS)" ... not "fake KMS," which is also listed there. Save and reboot. In theory, this should fix the problem.

    If you're _still_ having problems and have a Raspberry Pi 2 or better, go back to raspi-config and head over to "Advanced Options", "Memory split," and give the GPU 256 megabytes. You might be able to avoid this for simple games, as this takes RAM away from the system and reserves it for graphics. You can also try 128 megabytes as a gentler option.

    Note that you can also run DragonRuby without X11 at all: if you run it from a virtual terminal it will render fullscreen and won't need the "Full KMS" option. This might be attractive if you want to use it as a game console sort of thing, or develop over ssh, or launch it from RetroPie, etc.

    Conclusion link

    There is a lot more you can do with DragonRuby, but now you've already got just about everything you need to make a simple game. After all, even the most fancy games are just creating objects and moving them around. Experiment a little. Add a few more things and have them interact in small ways. Want something to go away? Just don't add it to args.output anymore.

    Starting a New DragonRuby Project link

    The DragonRuby zip that contains the engine is a complete, self contained project structure. To create a new project, unzip the zip file again in its entirety and use that as a starting point for another game. This is the recommended approach to starting a new project.

    NOTE:

    It's strongly recommended that you do NOT keep DragonRuby Game Toolkit in a shared location and instead unzip a clean copy for every game (and commit everything to source control).

    File access functions are sandoxed and assume that the dragonruby binary lives alongside the game you are building. Do not expect file access functions to return correct values if you are attempting to run the dragonruby binary from a shared location. It's recommended that the directory structure contained in the zip is not altered and games are built using that starting directory structure.

    Public Repos link

    Your public repository needs only to contain the contents of ./mygame. This approach is the cleanest and doesn't require your .gitignore to be polluted with DragonRuby specific files.

    Option 2 (Restrictions Apply) link

    NOTE:

    Do NOT commit dragonruby-publish(.exe), or dragonruby-bind(.exe).

    dragonruby
    dragonruby.exe
    dragonruby-publish
    dragonruby-publish.exe
    dragonruby-bind
    dragonruby-bind.exe
    /tmp/
    /builds/
    /logs/
    /samples/
    /docs/
    /.dragonruby/
    

    If you'd like people who do not own a DragonRuby license to run your game, you may include the dragonruby(.exe) binary within the repo. This permission is granted in good-faith and can be revoked if abused.

    Private Repos / Commercial Games link

    The following .gitignore should be used for private repositories (commercial games).

    /tmp/
    /logs/
    

    You'll notice that everything else is committed to source control (even the ./samples, ./docs, and ./builds directory).

    NOTE:

    The DragonRuby binary/package is designed to be committed in its entirety with your source code (it’s why we keep it small). This protects the “shelf life” for commercial games. 3 years from now, we might be on a vastly different version of the engine. But you know that the code you’ve written will definitely work with the version that was committed to source control.

    Deploying To Itch.io link

    Once you've built your game, you're all set to deploy! Good luck in your game dev journey and if you get stuck, come to the Discord channel!

    Creating Your Game Landing Page link

    Log into Itch.io and go to <https://itch.io/game/new>.

    You can fill out all the other options later.

    Update Your Game's Metadata link

    Point your text editor at mygame/metadata/game_metadata.txt and make it look like this:

    NOTE: Remove the # at the beginning of each line.

    devid=bob
    devtitle=Bob The Game Developer
    gameid=mygame
    gametitle=My Game
    version=0.1
    

    The devid property is the username you use to log into Itch.io. The devtitle is your name or company name (it can contain spaces). The gameid is the Project URL value. The gametitle is the name of your game (it can contain spaces). The version can be any major.minor number format.

    Building Your Game For Distribution link

    Open up the terminal and run this from the command line:

    ./dragonruby-publish --package mygame
    

    NOTE:

    If you're on Windows, don't put the "./" on the front. That's a Mac and Linux thing.

    A directory called ./build will be created that contains your binaries. You can upload this to Itch.io manually.

    Browser Game Settings link

    For the HTML version of your game, the following configuration is required for your game to run correctly:

    For subsequent updates you can use an automated deployment to Itch.io:

    ./dragonruby-publish mygame
    

    DragonRuby will package <span class="underline">and publish</span> your game to itch.io! Tell your friends to go to your game's very own webpage and buy it!

    If you make changes to your game, just re-run dragonruby-publish and it'll update the downloads for you.

    Consider Adding Pause When Game is In Background link

    It's a good idea to pause the game if it doesn't have focus. Here's an example of how to do that

    def tick args
      # if the keyboard doesn't have focus, and the game is in production mode, and it isn't the first tick
      if (!args.inputs.keyboard.has_focus &&
          args.gtk.production &&
          Kernel.tick_count != 0)
        args.outputs.background_color = [0, 0, 0]
        args.outputs.labels << { x: 640,
                                 y: 360,
                                 text: "Game Paused (click to resume).",
                                 alignment_enum: 1,
                                 r: 255, g: 255, b: 255 }
        # consider setting all audio volume to 0.0
      else
        # perform your regular tick function
      end
    end
    

    If you want your game to run at full speed even when it's in the background, add the following line to mygame/metadata/cvars.txt:

    renderer.background_sleep=0

    Consider Adding a Request to Review Your Game In-Game link

    Getting reviews of your game are extremely important and it's recommended that you put an option to review within the game itself. You can use args.gtk.open_url plus a review URL. Here's an example:

    def tick args
      # render the review button
      args.state.review_button ||= { x:    640 - 50,
                                     y:    360 - 25,
                                     w:    100,
                                     h:    50,
                                     path: :pixel,
                                     r:    0,
                                     g:    0,
                                     b:    0 }
      args.outputs.sprites << args.state.review_button
      args.outputs.labels << { x: 640,
                               y: 360,
                               anchor_x: 0.5,
                               anchor_y: 0.5,
                               text: "Review" }
    
      # check to see if the review button was clicked
      if args.inputs.mouse.intersect_rect? args.state.review_button
        # open platform specific review urls
        if args.gtk.platform? :ios
          # your app id is provided at Apple's Developer Portal (numeric value)
          args.gtk.openurl "itms-apps://itunes.apple.com/app/idYOURGAMEID?action=write-review"
        elsif args.gtk.platform? :android
          # your app id is the name of your android package
          args.gtk.openurl "https://play.google.com/store/apps/details?id=YOURGAMEID"
        elsif args.gtk.platform? :web
          # if they are playing the web version of the game, take them to the purchase page on itch
          args.gtk.openurl "https://amirrajan.itch.io/YOURGAMEID/purchase"
        else
          # if they are playing the desktop version of the game, take them to itch's rating page
          args.gtk.openurl "https://amirrajan.itch.io/YOURGAMEID/rate?source=game"
        end
      end
    end
    

    Deploying To Mobile Devices link

    If you have a Pro subscription, you also have the capability to deploy to mobile devices.

    Deploying to iOS link

    To deploy to iOS, you need to have a Mac running MacOS Catalina, an iOS device, and an active/paid Developer Account with Apple. From the Console type: $wizards.ios.start and you will be guided through the deployment process.

    Deploying to Android link

    To deploy to Android, you need to have an Android emulator/device, and an environment that is able to run Android SDK. dragonruby-publish will create an APK for you. From there, you can sign the APK and install it to your device. The signing and installation procedure varies from OS to OS. Here's an example of what the command might look like:

    NOTE:

    Be sure you specify packageid=TLD.YOURCOMPANY.YOURGAME (reverse domain name convention) within metadata/game_metadata.txt before running dragonruby-publish (for example: packageid=com.tempuri.mygame).

    # generating a keystore (one time creation, save key to ./profiles for safe keeping)
    keytool -genkey -v -keystore APP.keystore -alias mygame -keyalg RSA -keysize 2048 -validity 10000
    
    # signing binaries
    apksigner sign --min-sdk-version 26 --ks ./profiles/APP.keystore ./builds/APP-android.apk
    apksigner sign --min-sdk-version 26 --ks ./profiles/APP.keystore ./builds/APP-googleplay.aab
    
    # Deploying APK to a local device/emulator
    adb install ./builds/APP-android.apk
    
    # Deploying Google Play APK to a local device/emulator
    bundletool build-apks --bundle=./builds/APP-googleplay.aab --output=./builds/app.apks --mode=universal
    mv ./builds/app.apks ./builds/app.zip
    cd ./builds
    rm -rf tmp
    mkdir tmp
    cd tmp
    unzip ../app.zip
    cp ./universal.apk ./universal.zip
    unzip ./universal.zip
    
    # uninstall, install, launch
    adb shell am force-stop PACKAGEID
    adb uninstall PACKAGEID
    adb install ./universal.apk
    adb shell am start -n PACKAGEID/PACKAGEID.DragonRubyActivity
    
    # read logs of device
    adb logcat -e mygame
    

    DragonRuby's Philosophy link

    The following tenants of DragonRuby are what set us apart from other game engines. Given that Game Toolkit is a relatively new engine, there are definitely features that are missing. So having a big check list of "all the cool things" is not this engine's forte. This is compensated with a strong commitment to the following principles.

    Challenge The Status Quo link

    Game engines of today are in a local maximum and don't take into consideration the challenges of this day and age. Unity and GameMaker specifically rot your brain. It's not sufficient to say:

    But that's how we've always done it.

    It's a hard pill to swallow, but forget blindly accepted best practices and try to figure out the underlying motivation for a specific approach to game development. Collaborate with us.

    Continuity of Design link

    There is a programming idiom in software called "The Pit of Success". The term normalizes upfront pain as a necessity/requirement in the hopes that the investment will yield dividends "when you become successful" or "when the code becomes more complicated". This approach to development is strongly discouraged by us. It leads to over-architected and unnecessary code; creates barriers to rapid prototyping and shipping a game; and overwhelms beginners who are new to the engine or programming in general.

    DragonRuby's philosophy is to provide multiple options across the "make it fast" vs "make it right" spectrum, with incremental/intuitive transitions between the options provided. A concrete example of this philosophy would be render primitives: the spectrum of options allows renderable constructs that take the form of tuples/arrays (easy to pickup, simple, and fast to code/prototype with), hashes (a little more work, but gives you the ability to add additional properties), open and strict entities (more work than hashes, but yields cleaner apis), and finally - if you really need full power/flexibility in rendering - classes (which take the most amount of code and programming knowledge to create).

    Release Early and Often link

    The biggest mistake game devs make is spending too much time in isolation building their game. Release something, however small, and release it soon.

    Stop worrying about everything being pixel perfect. Don't wait until your game is 100% complete. Build your game publicly and iterate. Post in the #show-and-tell channel in the community Discord. You'll find a lot of support and encouragement there.

    Real artists ship. Remember that.

    Sustainable And Ethical Monetization link

    We all aspire to put food on the table doing what we love. Whether it is building games, writing tools to support game development, or anything in between.

    Charge a fair amount of money for the things you create. It's expected and encouraged within the community. Give what you create away for free to those that can't afford it.

    If you are gainfully employed, pay full price for the things you use. If you do end up getting something at a discount, pay the difference "forward" to someone else.

    Sustainable And Ethical Open Source link

    This goes hand in hand with sustainable and ethical monetization. The current state of open source is not sustainable. There is an immense amount of contributor burnout. Users of open source expect everything to be free, and few give back. This is a problem we want to fix (we're still trying to figure out the best solution).

    So, don't be "that guy" in the Discord that says "DragonRuby should be free and open source!" You will be personally flogged by Amir.

    People Over Entities link

    We prioritize the endorsement of real people over faceless entities. This game engine, and other products we create, are not insignificant line items of a large company. And you aren't a generic "commodity" or "corporate resource". So be active in the community Discord and you'll reap the benefits as more devs use DragonRuby.

    Building A Game Should Be Fun And Bring Happiness link

    We will prioritize the removal of pain. The aesthetics of Ruby make it such a joy to work with, and we want to capture that within the engine.

    Real World Application Drives Features link

    We are bombarded by marketing speak day in and day out. We don't do that here. There are things that are really great in the engine, and things that need a lot of work. Collaborate with us so we can help you reach your goals. Ask for features you actually need as opposed to anything speculative.

    We want DragonRuby to -actually- help you build the game you want to build (as opposed to sell you something piece of demoware that doesn't work).

    Frequently Asked Questions, Comments, and Concerns link

    Here are questions, comments, and concerns that frequently come up.

    Frequently Asked Questions link

    What is DragonRuby LLP? link

    DragonRuby LLP is a partnership of four devs who came together with the goal of bringing the aesthetics and joy of Ruby, everywhere possible.

    Under DragonRuby LLP, we offer a number of products (with more on the way):

    All of the products above leverage a shared core called DragonRuby.

    NOTE:

    NOTE: From an official branding standpoint each one of the products is suffixed with "A DragonRuby LLP Product" tagline. Also, DragonRuby is _one word, title cased_.

    NOTE:

    NOTE: We leave the "A DragonRuby LLP Product" off of this one because that just sounds really weird.

    NOTE:

    NOTE: Devs who use DragonRuby are "Dragon Riders/Riders of Dragons". That's a bad ass identifier huh?

    What is DragonRuby? link

    The response to this question requires a few subparts. First we need to clarify some terms. Specifically _language specification_ vs _runtime_.

    --Okay... so what is the difference between a language specification and a runtime?--

    A runtime is an _implementation_ of a language specification. When people say "Ruby," they are usually referring to "the Ruby 3.0+ language specification implemented via the CRuby/MRI Runtime."

    But, there are many Ruby Runtimes: CRuby/MRI, JRuby, Truffle, Rubinius, Artichoke, and (last but certainly not least) DragonRuby.

    --Okay... what language specification does DragonRuby use then?--

    DragonRuby's goal is to be compliant with the ISO/IEC 30170:2012 standard. It's syntax is Ruby 2.x compatible, but also contains semantic changes that help it natively interface with platform specific libraries.

    --So... why another runtime?--

    The elevator pitch is:

    DragonRuby is a Multilevel Cross-platform Runtime. The "multiple levels" within the runtime allows us to target platforms no other Ruby can target: PC, Mac, Linux, Raspberry Pi, WASM, iOS, Android, Nintendo Switch, PS4, Xbox, and Stadia.

    What does Multilevel Cross-platform mean? link

    There are complexities associated with targeting all the platforms we support. Because of this, the runtime had to be architected in such a way that new platforms could be easily added (which lead to us partitioning the runtime internally):

    Levels 1 through 3 are fairly commonplace in many runtime implementations (with level 1 being the most portable, and level 3 being the fastest). But the DragonRuby Runtime has taken things a bit further:

    These levels allow us to stay up to date with open source implementations of Ruby; provide fast, native code execution on proprietary platforms; ensure good separation between these two worlds; and provides a means to add new platforms without going insane.

    --Cool cool. So given that I understand everything to this point, can we answer the original question? What is DragonRuby?--

    DragonRuby is a Ruby runtime implementation that takes all the lessons we've learned from MRI/CRuby, and merges it with the latest and greatest compiler and OSS technologies.

    How is DragonRuby different than MRI? link

    DragonRuby supports a subset of MRI apis. Our target is to support all of mRuby's standard lib. There are challenges to this given the number of platforms we are trying to support (specifically console).

    Does DragonRuby support Gems? link

    DragonRuby does not support gems because that requires the installation of MRI Ruby on the developer's machine (which is a non-starter given that we want DragonRuby to be a zero dependency runtime). While this seems easy for Mac and Linux, it is much harder on Windows and Raspberry Pi. mRuby has taken the approach of having a git repository for compatible gems and we will most likely follow suite: https://github.com/mruby/mgem-list.

    Does DragonRuby have a REPL/IRB? link

    You can use DragonRuby's Console within the game to inspect object and execute small pieces of code. For more complex pieces of code create a file called repl.rb and put it in mygame/app/repl.rb:

    Any code you write in there will be executed when you change the file. You can organize different pieces of code using the repl method:

    repl do
      puts "hello world"
      puts 1 + 1
    end
    

    If you use the repl method, the code will be executed and the DragonRuby Console will automatically open so you can see the results (on Mac and Linux, the results will also be printed to the terminal). All puts statements will also be saved to logs/puts.txt. So if you want to stay in your editor and not look at the terminal, or the DragonRuby Console, you can tail this file. 4. To ignore code in repl.rb, instead of commenting it out, prefix repl with the letter x and it'll be ignored.

    xrepl do # <------- line is prefixed with an "x"
      puts "hello world"
      puts 1 + 1
    end
    
    # This code will be executed when you save the file.
    repl do
      puts "Hello"
    end
    
    repl do
      puts "This code will also be executed."
    end
    
    # use xrepl to "comment out" code
    xrepl do
      puts "This code will not be executed because of the x in front of repl".
    end
    

    Does DragonRuby support pry or have any other debugging facilities? link

    pry is a gem that assumes you are using the MRI Runtime (which is incompatible with DragonRuby). Eventually DragonRuby will have a pry based experience that is compatible with a debugging infrastructure called LLDB. Take the time to read about LLDB as it shows the challenges in creating something that is compatible.

    You can use DragonRuby's replay capabilities to troubleshoot:

    DragonRuby is hot loaded which gives you a very fast feedback loop (if the game throws an exception, it's because of the code you just added). Use ./dragonruby mygame --record to create a game play recording that you can use to find the exception (you can replay a recording by executing ./dragonruby mygame --replay last_replay.txt or through the DragonRuby Console using $gtk.recording.start_replay "last_replay.txt".

    DragonRuby also ships with a unit testing facility. You can invoke the following command to run a test: ./dragonruby mygame --test tests/some_ruby_file.rb. Get into the habit of adding debugging facilities within the game itself. You can add drawing primitives to args.outputs.debug that will render on top of your game but will be ignored in a production release.

    Debugging something that runs at 60fps is (imo) not that helpful. The exception you are seeing could have been because of a change that occurred many frames ago.

    Frequent Comments About Ruby as a Language Choice link

    But Ruby is dead. link

    Let's check the official source for the answer to this question: [isrubydead.com](https://isrubydead.com/).

    On a more serious note, Ruby's _quantity_ levels aren't what they used to be. And that's totally fine. Everyone chases the new and shiny.

    What really matters is _quality/maturity_. Here's a StackOverflow Survey sorted by highest paid developers: https://insights.stackoverflow.com/survey/2021#section-top-paying-technologies-top-paying-technologies.

    Let's stop making this comment shall we?

    But Ruby is slow. link

    That doesn't make any sense. A language specification can't be slow... it's a language spec. Sure, an _implementation/runtime_ can be slow though, but then we'd have to talk about which runtime.

    Here are some quick demonstrations of how well DragonRuby Game Toolkit Performs:

    Dynamic languages are slow. link

    They are certainly slower than statically compiled languages. With the processing power and compiler optimizations we have today, dynamic languages like Ruby are _fast enough_.

    Unless you are writing in some form of intermediate representation by hand, your language of choice also suffers this same fallacy of slow. Like, nothing is faster than a low level assembly-like language. So unless you're writing in that, let's stop making this comment.

    NOTE:

    NOTE: If you _are_ hand writing LLVM IR, we are always open to bringing on new partners with such a skill set. Email us ^_^.

    Frequent Concerns link

    DragonRuby is not open source. That's not right. link

    The current state of open source is unsustainable. Contributors work for free, most all open source repositories are severely under-staffed, and burnout from core members is rampant.

    We believe in open source very strongly. Parts of DragonRuby are in fact, open source. Just not all of it (for legal reasons, and because the IP we've created has value). And we promise that we are looking for (or creating) ways to _sustainably_ open source everything we do.

    If you have ideas on how we can do this, email us!

    If the reason above isn't sufficient, then definitely use something else.

    All this being said, we do have parts of the engine open sourced on GitHub: https://github.com/dragonruby/dragonruby-game-toolkit-contrib/

    DragonRuby is for pay. You should offer a free version. link

    If you can afford to pay for DragonRuby, you should (and will). We don't tell authors that they should give us their books for free, and only require payment if we read the entire thing. It's time we stop asking that of software products.

    That being said, we will _never_ put someone out financially. We have income assistance for anyone that can't afford a license to any one of our products.

    You qualify for a free, unrestricted license to DragonRuby products if any of the following items pertain to you:

    Just contact Amir at [email protected] with a short explanation of your current situation and he'll set you up. No questions asked.

    --But still, you should offer a free version. So I can try it out and see if I like it.--

    You can try our web-based sandbox environment at http://fiddle.dragonruby.org. But it won't do the runtime justice. Or just come to our Discord Channel at http://discord.dragonruby.org and ask questions. We'd be happy to have a one on one video chat with you and show off all the cool stuff we're doing.

    Seriously just buy it. Get a refund if you don't like it. We make it stupid easy to do so.

    --I still think you should do a free version. Think of all people who would give it a shot.--

    Free isn't a sustainable financial model. We don't want to spam your email. We don't want to collect usage data off of you either. We just want to provide quality toolchains to quality developers (as opposed to a large quantity of developers).

    The people that pay for DragonRuby and make an effort to understand it are the ones we want to build a community around, partner with, and collaborate with. So having that small monetary wall deters entitled individuals that don't value the same things we do.

    What if I build something with DragonRuby, but DragonRuby LLP becomes insolvent. link

    We want to be able to work on the stuff we love, every day of our lives. And we'll go to great lengths to make that continues.

    But, in the event that sad day comes, our partnership bylaws state that _all_ DragonRuby IP that can be legally open sourced, will be released under a permissive license.

    Troubleshoot Performance link

    args.state.bullets.each do |bullet|
      args.outputs.sprites << bullet.sprite
    end
    

    do

    args.outputs.sprites << args.state.bullets.map do |b|
      b.sprite
    end
    
    args.state.fx_queue.each |fx|
      fx.count_down ||= 255
      fx.countdown -= 5
      if fx.countdown < 0
        args.state.fx_queue.delete fx
      end
    end
    

    Do:

    args.state.fx_queue.each |fx|
      fx.countdown ||= 255
      fx.countdown -= 5
    end
    
    args.state.fx_queue.reject! { |fx| fx.countdown < 0 }
    

    How To Render A Label link

    args.outputs.labels is used to render labels.

    Labels are how you display text. This code will go directly inside of the def tick args method.

    NOTE: Rendering using an Array is "quick and dirty". It's generally recommended that you render using Hashes long term.

    Here is the minimum code:

    def tick args
      #                       X    Y    TEXT
      args.outputs.labels << [640, 360, "I am a black label."]
    end
    

    A Colored Label link

    def tick args
      # A colored label
      #                       X    Y    TEXT,                   RED    GREEN  BLUE  ALPHA
      args.outputs.labels << [640, 360, "I am a redish label.", 255,     128,  128,   255]
    end
    

    Extended Label Properties link

    def tick args
      # A colored label
      #                       X    Y     TEXT           SIZE  ALIGNMENT  RED  GREEN  BLUE  ALPHA  FONT FILE
      args.outputs.labels << [
        640,                   # X
        360,                   # Y
        "Hello world",         # TEXT
        0,                     # SIZE_ENUM
        1,                     # ALIGNMENT_ENUM
        0,                     # RED
        0,                     # GREEN
        0,                     # BLUE
        255,                   # ALPHA
        "fonts/coolfont.ttf"   # FONT
      ]
    end
    

    A SIZE_ENUM of 0 represents "default size". A negative value will decrease the label size. A positive value will increase the label's size.

    An ALIGNMENT_ENUM of 0 represents "left aligned". 1 represents "center aligned". 2 represents "right aligned".

    Rendering A Label As A Hash link

    You can add additional metadata about your game within a label, which requires you to use a `Hash` instead.

    If you use a Hash to render a label, you can set the label's size using either SIZE_ENUM or SIZE_PX. If both options are provided, SIZE_PX will be used.

    def tick args
      args.outputs.labels << {
        x:                       200,
        y:                       550,
        text:                    "dragonruby",
        # size specification can be either size_enum or size_px
        size_enum:               2,
        size_px:                 22,
        alignment_enum:          1,
        r:                       155,
        g:                       50,
        b:                       50,
        a:                       255,
        font:                    "fonts/manaspc.ttf",
        vertical_alignment_enum: 0, # 0 is bottom, 1 is middle, 2 is top
        anchor_x: 0.5,
        anchor_y: 0.5
        # You can add any properties you like (this will be ignored/won't cause errors)
        game_data_one:  "Something",
        game_data_two: {
           value_1: "value",
           value_2: "value two",
           a_number: 15
        }
      }
    end
    

    Getting The Size Of A Piece Of Text link

    You can get the render size of any string using args.gtk.calcstringbox.

    def tick args
      #                             TEXT           SIZE_ENUM  FONT
      w, h = args.gtk.calcstringbox("some string",         0, "font.ttf")
    
      # NOTE: The SIZE_ENUM and FONT are optional arguments.
    
      # Render a label showing the w and h of the text:
      args.outputs.labels << [
        10,
        710,
        # This string uses Ruby's string interpolation literal: #{}
        "'some string' has width: #{w}, and height: #{h}."
      ]
    end
    

    Rendering Labels With New Line Characters And Wrapping link

    You can use a strategy like the following to create multiple labels from a String.

    def tick args
      long_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elitteger dolor velit, ultricies vitae libero vel, aliquam imperdiet enim."
      max_character_length = 30
      long_strings_split = args.string.wrapped_lines long_string, max_character_length
      args.outputs.labels << long_strings_split.map_with_index do |s, i|
        { x: 10, y: 600 - (i * 20), text: s }
      end
    end
    

    How To Play A Sound link

    Sounds that end .wav will play once:

    def tick args
      # Play a sound every second
      if (Kernel.tick_count % 60) == 0
        args.outputs.sounds << 'something.wav'
      end
    end
    

    Sounds that end .ogg is considered background music and will loop:

    def tick args
      # Start a sound loop at the beginning of the game
      if Kernel.tick_count == 0
        args.outputs.sounds << 'background_music.ogg'
      end
    end
    

    If you want to play a .ogg once as if it were a sound effect, you can do:

    def tick args
      # Play a sound every second
      if (Kernel.tick_count % 60) == 0
        args.gtk.queue_sound 'some-ogg.ogg'
      end
    end
    

    Using args.state To Store Your Game State link

    args.state is a open data structure that allows you to define properties that are arbitrarily nested. You don't need to define any kind of class.

    To initialize your game state, use the ||= operator. Any value on the right side of ||= will only be assigned _once_.

    To assign a value every frame, just use the = operator, but _make sure_ you've initialized a default value.

    def tick args
      # initialize your game state ONCE
      args.state.player.x  ||= 0
      args.state.player.y  ||= 0
      args.state.player.hp ||= 100
    
      # increment the x position of the character by one every frame
      args.state.player.x += 1
    
      # Render a sprite with a label above the sprite
      args.outputs.sprites << [
        args.state.player.x,
        args.state.player.y,
        32, 32,
        "player.png"
      ]
    
      args.outputs.labels << [
        args.state.player.x,
        args.state.player.y - 50,
        args.state.player.hp
      ]
    end
    

    Accessing files link

    DragonRuby uses a sandboxed filesystem which will automatically read from and write to a location appropriate for your platform so you don't have to worry about theses details in your code. You can just use gtk.read_file, gtk.write_file, and gtk.append_file with a relative path and the engine will take care of the rest.

    The data directories that will be written to in a production build are:

    The values in square brackets are the values you set in your app/metadata/game_metadata.txt file.

    When reading files, the engine will first look in the game's data directory and then in the game directory itself. This means that if you write a file to the data directory that already exists in your game directory, the file in the data directory will be used instead of the one that is in your game.

    When running a development build you will directly write to your game directory (and thus overwrite existing files). This can be useful for built-in development tools like level editors.

    For more details on the implementation of the sandboxed filesystem, see Ryan C. Gordon's PhysicsFS documentation: https://icculus.org/physfs/

    IMPORTANT: File access functions are sandoxed and assume that the dragonruby binary lives alongside the game you are building. Do not expect file access functions to return correct values if you are attempting to run the dragonruby binary from a shared location. It's recommended that the directory structure contained in the zip is not altered and games are built using that starter template.

    RECIPIES: link

    How To Determine What Frame You Are On link

    There is a property on state called tick_count that is incremented by DragonRuby every time the tick method is called. The following code renders a label that displays the current tick_count.

    def tick args
      args.outputs.labels << [10, 670, "\#{Kernel.tick_count}"]
    end
    

    How To Get Current Framerate link

    Current framerate is a top level property on the Game Toolkit Runtime and is accessible via args.gtk.current_framerate.

    def tick args
      args.outputs.labels << [10, 710, "framerate: \#{args.gtk.current_framerate.round}"]
    end
    

    How To Render A Sprite Using An Array link

    All file paths should use the forward slash / *not* backslash \. Game Toolkit includes a number of sprites in the sprites folder (everything about your game is located in the mygame directory).

    The following code renders a sprite with a width and height of 100 in the center of the screen.

    args.outputs.sprites is used to render a sprite.

    NOTE: Rendering using an Array is "quick and dirty". It's generally recommended that you render using Hashes long term.

    def tick args
      args.outputs.sprites << [
        640 - 50,                 # X
        360 - 50,                 # Y
        100,                      # W
        100,                      # H
        'sprites/square-blue.png' # PATH
     ]
    end
    

    Rendering a Sprite Using a Hash link

    Using ordinal positioning can get a little unruly given so many properties you have control over.

    You can represent a sprite as a Hash:

    def tick args
      args.outputs.sprites << {
        x: 640 - 50,
        y: 360 - 50,
        w: 100,
        h: 100,
    
        path: 'sprites/square-blue.png',
        angle: 0,
    
        a: 255,
        r: 255,
        g: 255,
        b: 255,
    
        # source_ properties have origin of bottom left
        source_x:  0,
        source_y:  0,
        source_w: -1,
        source_h: -1,
    
        # tile_ properties have origin of top left
        tile_x:  0,
        tile_y:  0,
        tile_w: -1,
        tile_h: -1,
    
        flip_vertically: false,
        flip_horizontally: false,
    
        angle_anchor_x: 0.5,
        angle_anchor_y: 1.0,
    
        blendmode_enum: 1
    
        # sprites anchor/alignment (default is nil)
        anchor_x: 0.5,
        anchor_y: 0.5
      }
    end
    

    The blendmode_enum value can be set to 0 (no blending), 1 (alpha blending), 2 (additive blending), 3 (modulo blending), 4 (multiply blending).

    Outputs (args.outputs) link

    Outputs is how you render primitives to the screen. The minimal setup for rendering something to the screen is via a tick method defined in mygame/app/main.rb

    def tick args
      args.outputs.solids     << { x: 0, y: 0, w: 100, h: 100 }
      args.outputs.sprites    << { x: 100, y: 100, w: 100, h: 100, path: "sprites/square/blue.png" }
      args.outputs.labels     << { x: 200, y: 200, text: "Hello World" }
      args.outputs.borders    << { x: 0, y: 0, w: 100, h: 100 }
      args.outputs.lines      << { x: 300, y: 300, x2: 400, y2: 400 }
    end
    

    Collection Render Orders link

    Primitives are rendered first-in, first-out. The rendering order (sorted by bottom-most to top-most):

    primitives link

    args.outputs.primitives can take in any primitive and will render first in, first out.

    For example, you can render a solid above a sprite:

    def tick args
      # sprite
      args.outputs.primitives << { x: 100, y: 100,
                                   w: 100, h: 100,
                                   path: "sprites/square/blue.png" }
    
      # solid
      args.outputs.primitives << { x: 0,
                                   y: 0,
                                   w: 100,
                                   h: 100,
                                   primitive_marker: :solid }
    
      # border
      args.outputs.primitives << { x: 0,
                                   y: 0,
                                   w: 100,
                                   h: 100,
                                   primitive_marker: :border }
    
      # label
      args.outputs.primitives << { x: 100, y: 100,
                                   text: "hello world" }
    
      # line
      args.outputs.primitives << { x: 100, y: 100, x2: 150, y2: 150 }
    end
    

    debug link

    args.outputs.debug will not render in production mode and behaves like args.outputs.primitives. Objects in this collection are rendered above everything.

    String Primitives link

    Additionally, args.outputs.debug allows you to pass in a String as a primitive type. This is helpful for quickly showing the value of a variable on the screen. A label with black text and a white background will be created for each String sent in. The labels will be automatically stacked vertically for you. New lines in the string will be respected.

    Example:

    def tick args
      args.state.player ||= { x: 100, y: 100 }
      args.state.player.x += 1
      args.state.player.x = 0 if args.state.player.x > 1280
    
      # the following string values will generate labels with backgrounds
      # and will auto stack vertically
      args.outputs.debug << "current tick: #{Kernel.tick_count}"
      args.outputs.debug << "player x: #{args.state.player.x}"
      args.outputs.debug << "hello\nworld"
    end
    

    watch link

    If you need additional control over a string value, you can use the args.outputs.debug.watch function.

    The functions takes in the following parameters:

    Example:

    def tick args
      args.state.player ||= { x: 100, y: 100 }
      args.state.player.x += 1
      args.state.player.x = 0 if args.state.player.x > 1280
    
      args.outputs.debug.watch args.state.player
      args.outputs.debug.watch pretty_format(args.state.player),
                               label_style: { r: 0,
                                              g: 0,
                                              b: 255,
                                              size_px: 10 },
                               background_style: { r: 0,
                                                   g: 255,
                                                   b: 0,
                                                   a: 128,
                                                   path: :solid }
    end
    

    solids link

    Add primitives to this collection to render a solid to the screen.

    NOTE:

    This render primitive is fine to use sparingly. If you find yourself rendering a large number of solids, render sprites instead (the textures that solid primitives generate are not cached and do not perform as well as rendering sprites).

    For example, the following solid and sprite are equivalent:

    def tick args
      args.outputs.solids << {
        x: 0,
        y: 0,
        w: 100,
        h: 100,
        r: 255,
        g: 255,
        b: 255,
        a: 128
      }
    
      # is equivalent to
    
      args.outputs.sprites << {
        x: 0,
        y: 0,
        w: 100,
        h: 100,
        path: :solid,
        r: 255,
        g: 255,
        b: 255,
        a: 128
      }
    end
    

    Array Render Primitive link

    Creates a solid black rectangle located at 100, 100. 160 pixels wide and 90 pixels tall.

    def tick args
      #                         X    Y  WIDTH  HEIGHT
      args.outputs.solids << [100, 100,   160,     90]
    end
    

    NOTE:

    Array-based primitives are find for debugging purposes/quick prototypes. But should not be used as the default rendering approach. Use Hash-based or Class-based primitives.

    While not recommended for long term maintainability, you can also set the following properties.

    Example Creates a green solid rectangle with an opacity of 50% (the value for the color and alpha is a number between 0 and 255, the alpha property is optional and will be set to 255 if not specified):

    def tick args
      #                         X    Y  WIDTH  HEIGHT  RED  GREEN  BLUE  ALPHA
      args.outputs.solids << [100, 100,   160,     90,   0,   255,    0,   128]
    end
    

    Hash Render Primitive link

    If you want a more readable invocation. You can use the following hash to create a solid. Any parameters that are not specified will be given a default value. The keys of the hash can be provided in any order.

    def tick args
      args.outputs.solids << {
        x:    0,
        y:    0,
        w:  100,
        h:  100,
        r:    0,
        g:  255,
        b:    0,
        a:  255,
        anchor_x: 0,
        anchor_y: 0,
        blendmode_enum: 1
      }
    end
    

    Class Render Primitive link

    You can also create a class with solid properties and render it as a primitive. ALL properties must be on the class. --Additionally--, a method called primitive_marker must be defined on the class.

    Here is an example:

    # Create type with ALL solid properties AND primitive_marker
    class Solid
      attr_accessor :x, :y, :w, :h, :r, :g, :b, :a, :anchor_x, :anchor_y, :blendmode_enum
    
      def primitive_marker
        :solid # or :border
      end
    end
    
    # Inherit from type
    class Square < Solid
      # constructor
      def initialize x, y, size
        self.x = x
        self.y = y
        self.w = size
        self.h = size
      end
    end
    
    def tick args
      # render solid/border
      args.outputs.solids  << Square.new(10, 10, 32)
    end
    

    borders link

    Add primitives to this collection to render an unfilled solid to the screen. Take a look at the documentation for Outputs#solids.

    The only difference between the two primitives is where they are added.

    Instead of using args.outputs.solids:

    def tick args
      #                         X    Y  WIDTH  HEIGHT
      args.outputs.solids << [100, 100,   160,     90]
    end
    

    You have to use args.outputs.borders:

    def tick args
      #                           X    Y  WIDTH  HEIGHT
      args.outputs.borders << [100, 100,   160,     90]
    end
    

    sprites link

    Add primitives to this collection to render a sprite to the screen.

    Properties link

    Here are all the properties that you can set on a sprite. The only required ones are x, y, w, h, and path.

    Required

    Anchors and Rotations

    Here's an example of rendering a 80x80 pixel sprite in the center of the screen:

    def tick args
      args.outputs.sprites << {
        x: 640 - 40, # the logical center of the screen horizontally is 640, minus half the width of the sprite
        y: 360 - 40, # the logical center of the screen vertically is 360, minus half the height of the sprite
        w: 80,
        h: 80,
        path: "sprites/square/blue.png"
     }
    end
    

    Instead of computing the offset, you can use anchor_x, and anchor_y to center the sprite. The following is equivalent to the code above:

    def tick args
      args.outputs.sprites << {
        x: 640,
        y: 360,
        w: 80,
        h: 80,
        path: "sprites/square/blue.png",
        anchor_x: 0.5, # position horizontally at 0.5 of the sprite's width
        anchor_y: 0.5  # position vertically at 0.5 of the sprite's height
     }
    end
    

    Cropping

    See the sample apps under ./samples/03_rendering_sprites for examples of how to use this properties non-trivially.

    Blending

    The following sample apps show how blendmode_enum can be leveraged to create coloring and lighting effects:

    Triangles (Indie, Pro Feature)

    Sprites can be rendered as triangles at the Indie and Pro License Tiers. To rendering using triangles, instead of providing a w, h property, provide x2, y2, x3, y3. This applies for positioning and cropping.

    Here is an example:

    def tick args
      args.outputs.sprites << {
        x: 0,
        y: 0,
        x2: 80,
        y2: 0,
        x3: 0,
        y3: 80,
        source_x: 0,
        source_y: 0,
        source_x2: 80,
        source_y2: 0,
        source_x3: 0,
        source_y3: 80,
        path: "sprites/square/blue.png"
      }
    end
    

    For more example of rendering using triangles see:

    Array Render Primitive link

    Creates a sprite of a white circle located at 100, 100. 160 pixels wide and 90 pixels tall.

    def tick args
      #                         X    Y   WIDTH   HEIGHT                      PATH
      args.outputs.sprites << [100, 100,   160,     90, "sprites/circle/white.png"]
    end
    

    NOTE:

    Array-based sprites have limited access to sprite properties, but nice for quick prototyping. Use a Hash or Class to gain access to all properties, gain long term maintainability of code, and a boost in rendering performance.

    Hash Render Primitive link

    If you want a more readable (and faster) invocation, you can use the following hash to create a sprite. Any parameters that are not specified will be given a default value. The keys of the hash can be provided in any order.

    def tick args
      args.outputs.sprites << {
        x: 0,
        y: 0,
        w: 100,
        h: 100,
        path: "sprites/circle/white.png",
        angle: 0,
        a: 255
      }
    end
    

    Class Render Primitive link

    You can also create a class with sprite properties and render it as a primitive. ALL properties must be on the class. --Additionally--, a method called primitive_marker must be defined on the class.

    Here is an example:

    # Create type with ALL sprite properties AND primitive_marker
    class Sprite
      attr_accessor :x, :y, :w, :h, :path, :angle, :a, :r, :g, :b, :tile_x,
                    :tile_y, :tile_w, :tile_h, :flip_horizontally,
                    :flip_vertically, :angle_anchor_x, :angle_anchor_y, :id,
                    :angle_x, :angle_y, :z,
                    :source_x, :source_y, :source_w, :source_h, :blendmode_enum,
                    :source_x2, :source_y2, :source_x3, :source_y3, :x2, :y2, :x3, :y3,
                    :anchor_x, :anchor_y, :scale_quality_enum
    
      def primitive_marker
        :sprite
      end
    end
    
    # Inherit from type
    class Circle < Sprite
      # constructor
      def initialize x, y, size, path
        self.x = x
        self.y = y
        self.w = size
        self.h = size
        self.path = path
      end
    
      def serialize
        {x:self.x, y:self.y, w:self.w, h:self.h, path:self.path}
      end
    
      def inspect
        serialize.to_s
      end
    
      def to_s
        serialize.to_s
      end
    end
    
    def tick args
      # render circle sprite
      args.outputs.sprites  << Circle.new(10, 10, 32,"sprites/circle/white.png")
    end
    

    attr_sprite link

    The attr_sprite class macro adds all properties needed to render a sprite to a class. This removes the need to manually define all sprites properties that DragonRuby offers for rendering.

    Instead of manually defining the properties, you can represent a sprite using the attr_sprite class macro:

    class BlueSquare
      # invoke the helper function at the class level for
      # anything you want to represent as a sprite
      attr_sprite
    
      def initialize(x: 0, y: 0, w: 0, h: 0)
        @x = x
        @y = y
        @w = w
        @h = h
        @path = 'sprites/square-blue.png'
      end
    end
    
    def tick args
      args.outputs.sprites << BlueSquare.new(x: 640 - 50,
                                             y: 360 - 50,
                                             w: 50,
                                             h: 50)
    end
    

    lines link

    Add primitives to this collection to render a line.

    Array Render Primitive link

    def tick args
                             #  X    Y   X2   Y2
      args.outputs.lines << [100, 100, 150, 150]
    end
    

    NOTE:

    Array-based primitives are find for debugging purposes/quick prototypes. But should not be used as the default rendering approach. Use Hash-based or Class-based primitives.

    Hash Render Primitive link

    def tick args
      args.outputs.lines << {
        x:  100,
        y:  100,
        x2: 150,
        y2: 150,
        r:  0,
        g:  0,
        b:  0,
        a:  255,
        blendmode_enum: 1
      }
    end
    

    Class Render Primitive link

    # Create type with ALL line properties AND primitive_marker
    class Line
      attr_accessor :x, :y, :x2, :y2, :r, :g, :b, :a, :blendmode_enum
    
      def primitive_marker
        :line
      end
    end
    
    # Inherit from type
    class RedLine < Line
      # constructor
      def initialize x, y, x2, y2
        self.x = x
        self.y = y
        self.x2 = x2
        self.y2 = y2
        self.r  = 255
        self.g  = 0
        self.b  = 0
        self.a  = 255
      end
    end
    
    def tick args
      # render line
      args.outputs.lines << RedLine.new(100, 100, 150, 150)
    end
    
    

    labels link

    Add primitives to this collection to render a label.

    Array Render Primitive link

    Labels represented as Arrays/Tuples:

    def tick args
                             #        X         Y              TEXT   SIZE_ENUM
      args.outputs.labels << [175 + 150, 610 - 50, "Smaller label.",         0]
    end
    

    Here are all the properties that you can set with a label represented as an Array. It's recommended to move over to using Hashes once you've specified a lot of properties.

    def tick args
      args.outputs.labels << [
        640,                   # X
        360,                   # Y
        "Hello world",         # TEXT
        0,                     # SIZE_ENUM
        1,                     # ALIGNMENT_ENUM
        0,                     # RED
        0,                     # GREEN
        0,                     # BLUE
        255,                   # ALPHA
        "fonts/coolfont.ttf"   # FONT
      ]
    end
    

    NOTE:

    Array-based primitives are find for debugging purposes/quick prototypes. But should not be used as the default rendering approach. Use Hash-based or Class-based primitives.

    Hash Render Primitive link

    ?> size_enum is an opaque unit and signifies the recommended size for labels. The default size_enum of 0 means "this size is the smallest font size that is comfortable to read on a hand-held device". size_enum of 0 corresponds to 22px at 720p. Each increment of size_enum increases/decreases the pixels by 2 (size_enum of 1 means 24px, size_enum of -1 means 20px, etc). If you want to control the size of a label explicitly, use size_px instead.

    def tick args
      args.outputs.labels << {
          x:                       200,
          y:                       550,
          text:                    "dragonruby",
          size_enum:               2,
          alignment_enum:          1, # 0 = left, 1 = center, 2 = right
          r:                       155,
          g:                       50,
          b:                       50,
          a:                       255,
          font:                    "fonts/manaspc.ttf",
          vertical_alignment_enum: 0  # 0 = bottom, 1 = center, 2 = top,
          anchor_x:                0, # if provided, alignment_enum is ignored
          anchor_y:                1, # if provided, vertical_alignment_enum is ignored,
          size_px:                 30, # if provided, size_enum is ignored.
          blendmode_enum:          1
      }
    end
    

    Class Render Primitive link

    # Create type with ALL label properties AND primitive_marker
    class Label
      attr_accessor :x, :y, :w, :h, :r, :g, :b, :a, :text, :font, :anchor_x,
                    :anchor_y, :blendmode_enum, :size_px, :size_enum, :alignment_enum,
                    :vertical_alignment_enum
    
      def primitive_marker
        :label
      end
    end
    

    Render Targets ([] operator) link

    The args.outputs structure renders to the screen. You can render to a texture/virtual canvas using args.outputs[SYMBOL]. What ever primitives are sent to the virtual canvas are cached and reused (the cache is invalidated whenever you render to virtual canvas).

    ?> You can also use render targets to accomplish many complex layouts such as a game camera, perform scene management, or add lighting.

    Take a look at the following sample apps:

    &nbsp;&nbsp;&nbsp;&nbsp;All sample apps under ./samples/07_advanced_rendering.

    &nbsp;&nbsp;&nbsp;&nbsp;The Map Editor reference implementation (samples/99_genre_platformer/map_editor).

    &nbsp;&nbsp;&nbsp;&nbsp;The arcade game Square Fall (samples/99_genre_arcade/squares).

    Many of the sample apps use render targets, so be sure to explore as many as you can!

    Here's an example that programmatically creates a :combined sprite composed of two pngs and a label:

    def tick args
      # on tick 0, create a render target composed of two sprites and a label
      if Kernel.tick_count == 0
        # to reiterate, this sprite will be cached until it's written to again
        # the :combined sprite has a w/h of 200
        args.outputs[:combined].w = 200
        args.outputs[:combined].h = 200
    
        # and a black transparent background
        args.outputs[:combined].background_color = [0, 0, 0, 0]
    
        # add two sprites to the render target
        args.outputs[:combined].primitives << {
          x: 0,
          y: 0,
          w: 100,
          h: 200,
          path: "sprites/square/blue.png"
        }
    
        args.outputs[:combined].primitives << {
          x: 100,
          y: 0,
          w: 100,
          h: 200,
          path: "sprites/square/red.png"
        }
    
        # add a label in the center of the render target
        args.outputs[:combined].primitives << {
          x: 100,
          y: 100,
          text: "COMBINED!",
          anchor_x: 0.5,
          anchor_y: 0.5,
        }
      end
    
      # rendered the :combined sprite in multiple
      # places on the screen
      args.outputs.sprites << {
        x: 0,
        y: 0,
        w: 400,
        h: 400,
        path: :combined,
        angle: 33
      }
    
      args.outputs.sprites << {
        x: 640,
        y: 360,
        w: 100,
        h: 100,
        path: :combined,
        angle: 180,
        a: 128
      }
    end
    

    Render targets are extremely powerful and you'll end up using them a lot (so be sure to get familiar with them by studying the sample apps).

    screenshots link

    Add a hash to this collection to take a screenshot and save as png file. The keys of the hash can be provided in any order.

    def tick args
      args.outputs.screenshots << {
        x: 0, y: 0, w: 100, h: 100,    # Which portion of the screen should be captured
        path: 'screenshot.png',        # Output path of PNG file (inside game directory)
        r: 255, g: 255, b: 255, a: 0   # Optional chroma key
      }
    end
    

    Chroma key (Making a color transparent) link

    By specifying the r, g, b and a keys of the hash you change the transparency of a color in the resulting PNG file. This can be useful if you want to create files with transparent background like spritesheets. The transparency of the color specified by r, g, b will be set to the transparency specified by a.

    The example above sets the color white (255, 255, 255) as transparent.

    Shaders link

    Shaders are available to Indie and Pro license holders via dragonruby-shadersim. Download DragonRuby ShaderSim at [dragonruby.org](https://dragonruby.org).

    NOTE:

    Shaders are currently in Beta.

    Shaders must be GLSL ES2 compatible.

    The long term goal is for DR Shaders to baseline to GLSL version 300 and cross-compile to Vulkan, WebGL 2, Metal, and HLSL.

    Here is a minimal example of using shaders:

    # mygame/app/main.rb
    def tick args
      args.outputs.shader_path ||= "shaders/example.glsl"
    end
    
    // mygame/shaders/example.glsl
    uniform sampler2D tex0;
    
    varying vec2 v_texCoord;
    
    void main() {
      gl_FragColor = texture2D(tex0, v_texCoord);
    }
    

    shader_path link

    Setting shader_path on outputs signifies to DragonRuby that a shader should be compiled and loaded.

    shader_uniforms link

    You can bind uniforms to a shader by providing an Array of Hashes to shader_uniforms with keys name:, value:, and type: which currently supports :int and :float.

    def tick args
      args.outputs.shader_path ||= "shaders/example.glsl"
    
      args.outputs.shader_uniforms = [
        {
          name: :mouse_coord_x,
          value: args.inputs.mouse.x.fdiv(1280),
          type: :float
        },
        {
          name: :mouse_coord_y,
          value: 1.0 - args.inputs.mouse.y.fdiv(720),
          type: :float
        },
        {
          name: :tick_count,
          value: Kernel.tick_count,
          type: :int
        }
      ]
    end
    
    // mygame/shaders/example.glsl
    uniform sampler2D tex0;
    
    uniform float mouse_coord_x;
    uniform float mouse_coord_y;
    uniform int tick_count;
    
    varying vec2 v_texCoord;
    
    void main() {
      gl_FragColor = texture2D(tex0, v_texCoord);
    }
    

    shader_tex(1-15) link

    You can bind up to 15 additional render targets via shaders_tex1, shaders_tex2, shaders_tex3, etc. tex0 is reserved for what has been rendered to the screen and cannot be set.

    def tick args
      args.outputs.shader_path = "shaders/example.glsl"
      args.outputs.shader_tex1 = :single_blue_square
    
      args.outputs[:single_blue_square].background_color = { r: 255, g: 255, b: 255, a: 255 }
      args.outputs[:single_blue_square].w = 1280;
      args.outputs[:single_blue_square].h = 720;
      args.outputs[:single_blue_square].sprites << {
        x: 0,
        y: 0,
        w: 100,
        h: 100,
        path:
        "sprites/square/blue.png"
      }
    
      args.outputs.background_color = { r: 0, g: 0, b: 0 }
      args.outputs.sprites << {
        x: 0,
        y: 0,
        w: 200,
        h: 200,
        path:
        "sprites/square/red.png"
      }
    end
    
    uniform sampler2D tex0;
    uniform sampler2D tex1; // :single_blue_square render target
    
    varying vec2 v_texCoord;
    
    void noop() {
      vec4 pixel_from_rt = texture2D(tex1, v_texCoord);
    
      // if the pixel from the render target isn't white
      // then render the pixel from the RT
      // otherwise render the pixel from the screen
      if (pixel_from_rt.r < 1.0 ||
          pixel_from_rt.g < 1.0 ||
          pixel_from_rt.b < 1.0) {
        gl_FragColor.r = pixel_from_rt.r;
        gl_FragColor.g = pixel_from_rt.g;
        gl_FragColor.b = pixel_from_rt.b;
      } else {
        gl_FragColor = texture2D(tex0, v_texCoord);
      }
    }
    

    Inputs (args.inputs) link

    Access using input using args.inputs.

    last_active link

    This function returns the last active input which will be set to either :keyboard, :mouse, or :controller. The function is helpful when you need to present on screen instructions based on the input the player chose to play with.

    def tick args
      if args.inputs.last_active == :controller
        args.outputs.labels << { x: 60, y: 60, text: "Use the D-Pad to move around." }
      else
        args.outputs.labels << { x: 60, y: 60, text: "Use the arrow keys to move around." }
      end
    end
    

    last_active_at link

    Returns Kernel.tick_count of which the specific input was last active.

    last_active_global_at link

    Returns the Kernel.global_tick_count of which the specific input was last active.

    locale link

    Returns the ISO 639-1 two-letter language code based on OS preferences. Refer to the following link for locale strings: <https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>).

    Defaults to "en" if locale can't be retrieved (args.inputs.locale_raw will be nil in this case).

    up link

    Returns true if: the up arrow or w key is pressed or held on the keyboard; or if up is pressed or held on controller_one; or if the left_analog on controller_one is tilted upwards.

    down link

    Returns true if: the down arrow or s key is pressed or held on the keyboard; or if down is pressed or held on controller_one; or if the left_analog on controller_one is tilted downwards.

    left link

    Returns true if: the left arrow or a key is pressed or held on the keyboard; or if left is pressed or held on controller_one; or if the left_analog on controller_one is tilted to the left.

    right link

    Returns true if: the right arrow or d key is pressed or held on the keyboard; or if right is pressed or held on controller_one; or if the left_analog on controller_one is tilted to the right.

    left_right link

    Returns -1 (left), 0 (neutral), or +1 (right). This method is aliased to args.inputs.left_right_with_wasd.

    The following inputs are inspected to determine the result:

    left_right_perc link

    Returns a floating point value between -1 and 1. This method is aliased to args.inputs.left_right_perc_with_wasd

    The following inputs are inspected to determine the result:

    left_right_directional link

    Returns -1 (left), 0 (neutral), or +1 (right). This method is aliased to args.inputs.left_right_arrow.

    The following inputs are inspected to determine the result:

    left_right_directional_perc link

    Returns a floating point value between -1 and 1. The following inputs are inspected to determine the result:

    Here is some sample code to help visualize the left_right functions.

    def tick args
      args.outputs.debug << "* Variations of args.inputs.left_right"
      args.outputs.debug << "  args.inputs.left_right(_with_wasd) #{args.inputs.left_right}"
      args.outputs.debug << "  args.inputs.left_right_perc(_with_wasd) #{args.inputs.left_right_perc}"
      args.outputs.debug << "  args.inputs.left_right_directional #{args.inputs.left_right_directional}"
      args.outputs.debug << "  args.inputs.left_right_directional_perc #{args.inputs.left_right_directional_perc}"
      args.outputs.debug << "** Keyboard"
      args.outputs.debug << "   args.inputs.keyboard.a #{args.inputs.keyboard.a}"
      args.outputs.debug << "   args.inputs.keyboard.d #{args.inputs.keyboard.d}"
      args.outputs.debug << "   args.inputs.keyboard.left_arrow #{args.inputs.keyboard.left_arrow}"
      args.outputs.debug << "   args.inputs.keyboard.right_arrow #{args.inputs.keyboard.right_arrow}"
      args.outputs.debug << "** Controller"
      args.outputs.debug << "   args.inputs.controller_one.dpad_left #{args.inputs.controller_one.dpad_left}"
      args.outputs.debug << "   args.inputs.controller_one.dpad_right #{args.inputs.controller_one.dpad_right}"
      args.outputs.debug << "   args.inputs.controller_one.left_analog_x_perc #{args.inputs.controller_one.left_analog_x_perc}"
    end
    

    up_down link

    Returns -1 (down), 0 (neutral), or +1 (up). This method is aliased to args.inputs.up_down_with_wasd.

    The following inputs are inspected to determine the result:

    up_down_directional link

    Returns -1 (down), 0 (neutral), or +1 (up). This method is aliased to args.inputs.up_down_arrow.

    The following inputs are inspected to determine the result:

    up_down_perc link

    Returns a floating point value between -1 and 1. The following inputs are inspected to determine the result:

    Here is some sample code to help visualize the up_down functions.

    def tick args
      args.outputs.debug << "* Variations of args.inputs.up_down"
      args.outputs.debug << "  args.inputs.up_down(_with_wasd) #{args.inputs.up_down}"
      args.outputs.debug << "  args.inputs.up_down_perc(_with_wasd) #{args.inputs.up_down_perc}"
      args.outputs.debug << "  args.inputs.up_down_directional #{args.inputs.up_down_directional}"
      args.outputs.debug << "  args.inputs.up_down_directional_perc #{args.inputs.up_down_directional_perc}"
      args.outputs.debug << "** Keyboard"
      args.outputs.debug << "   args.inputs.keyboard.w #{args.inputs.keyboard.w}"
      args.outputs.debug << "   args.inputs.keyboard.s #{args.inputs.keyboard.s}"
      args.outputs.debug << "   args.inputs.keyboard.up_arrow #{args.inputs.keyboard.up_arrow}"
      args.outputs.debug << "   args.inputs.keyboard.down_arrow #{args.inputs.keyboard.down_arrow}"
      args.outputs.debug << "** Controller"
      args.outputs.debug << "   args.inputs.controller_one.dpad_up #{args.inputs.controller_one.dpad_up}"
      args.outputs.debug << "   args.inputs.controller_one.dpad_down #{args.inputs.controller_one.dpad_down}"
      args.outputs.debug << "   args.inputs.controller_one.left_analog_y_perc #{args.inputs.controller_one.left_analog_y_perc}"
    end
    

    text link

    Returns a string that represents the last key that was pressed on the keyboard.

    Mouse (args.inputs.mouse) link

    Represents the user's mouse.

    has_focus link

    Returns true if the game has mouse focus.

    x link

    Returns the current x location of the mouse.

    y link

    Returns the current y location of the mouse.

    previous_x link

    Returns the x location of the mouse on the previous frame.

    previous_y link

    Returns the y location of the mouse on the previous frame.

    relative_x link

    Returns the difference between the current x location of the mouse and its previous x location.

    relative_y link

    Returns the difference between the current y location of the mouse and its previous y location.

    inside_rect? rect link

    Return. args.inputs.mouse.inside_rect? takes in any primitive that responds to x, y, w, h:

    inside_circle? center_point, radius link

    Returns true if the mouse is inside of a specified circle. args.inputs.mouse.inside_circle? takes in any primitive that responds to x, y (which represents the circle's center), and takes in a radius:

    moved link

    Returns true if the mouse has moved on the current frame.

    button_left link

    Returns true if the left mouse button is down.

    button_middle link

    Returns true if the middle mouse button is down.

    button_right link

    Returns true if the right mouse button is down.

    button_bits link

    Returns a bitmask for all buttons on the mouse: 1 for a button in the down state, 0 for a button in the up state.

    Here is a snippet to help visualize all mouse button states:

    def tick args
      args.outputs.debug.watch "button_left:   #{inputs.mouse.button_left}"
      args.outputs.debug.watch "button_middle: #{inputs.mouse.button_middle}"
      args.outputs.debug.watch "button_right:  #{inputs.mouse.button_right}"
      args.outputs.debug.watch "button_bits:   #{inputs.mouse.button_bits.to_s(2)}"
    end
    

    wheel link

    Represents the mouse wheel. Returns nil if no mouse wheel actions occurred. Otherwise args.inputs.mouse.wheel will return a Hash with x, and y (representing movement on each axis).

    click OR down, previous_click, up link

    The properties args.inputs.mouse.(click|down|previous_click|up) each return nil if the mouse button event didn't occur. And return an Entity that has an x, y properties along with helper functions to determine collision: inside_rect?, inside_circle. This value will be true if any of the mouse's buttons caused these events. To scope to a specific button use .button_left, .button_middle, .button_right, or .button_bits.

    key_down link

    Returns true if the specific button was pressed on this frame. args.inputs.mouse.key_down.BUTTON will only be true on the frame it was pressed.

    The following BUTTON values are applicable for key_down, key_held, and key_up:

    key_held link

    Returns true if the specific button is being held. args.inputs.mouse.key_held.BUTTON will be true for all frames after key_down (until released).

    key_up link

    Returns true if the specific button was released. args.inputs.mouse.key_up.BUTTON will be true only on the frame the button was released.

    ?> For a full demonstration of all mouse button states, refer to the sample app located at ./samples/02_input_basics/02_mouse_properties

    Touch link

    The following touch apis are available on touch devices (iOS, Android, Mobile Web, Surface).

    args.inputs.touch link

    Returns a Hash representing all touch points on a touch device.

    args.inputs.finger_left link

    Returns a Hash with x and y denoting a touch point that is on the left side of the screen.

    args.inputs.finger_right link

    Returns a Hash with x and y denoting a touch point that is on the right side of the screen.

    Controller (args.inputs.controller_(one-four)) link

    Represents controllers connected to the usb ports. There is also args.inputs.controllers which returns controllers one through four as an array (args.inputs.controllers[0] points to args.inputs.controller_one).

    connected link

    Returns true if a controller is connected. If this value is false, controller properties will not be nil, but return 0 for directional based properties and false button state properties.

    name link

    String value representing the controller's name.

    active link

    Returns true if any of the controller's buttons were used.

    up link

    Returns true if up is pressed or held on the directional or left analog.

    down link

    Returns true if down is pressed or held on the directional or left analog.

    left link

    Returns true if left is pressed or held on the directional or left analog.

    right link

    Returns true if right is pressed or held on the directional or left analog.

    left_right link

    Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.controller_(one-four).left and args.inputs.controller_(one-four).right.

    up_down link

    Returns -1 (down), 0 (neutral), or +1 (up) depending on results of args.inputs.controller_(one-four).up and args.inputs.controller_(one-four).down.

    (left|right)_analog_x_raw link

    Returns the raw integer value for the analog's horizontal movement (-32,767 to +32,767).

    (left|right)_analog_y_raw link

    Returns the raw integer value for the analog's vertical movement (-32,767 to +32,767).

    (left|right)_analog_x_perc link

    Returns a number between -1 and 1 which represents the percentage the analog is moved horizontally as a ratio of the maximum horizontal movement.

    (left|right)_analog_y_perc link

    Returns a number between -1 and 1 which represents the percentage the analog is moved vertically as a ratio of the maximum vertical movement.

    dpad_up, directional_up link

    Returns true if up is pressed or held on the dpad.

    dpad_down, directional_down link

    Returns true if down is pressed or held on the dpad.

    dpad_left, directional_left link

    Returns true if left is pressed or held on the dpad.

    dpad_right, directional_right link

    Returns true if right is pressed or held on the dpad.

    (a|b|x|y|l1|r1|l2|r2|l3|r3|start|select) link

    Returns true if the specific button is pressed or held. Note: For PS4 and PS5 controllers a maps to Cross, b maps to Circle, x maps to Square, and y maps to Triangle.

    truthy_keys link

    Returns a collection of Symbols that represent all keys that are in the pressed or held state.

    key_down link

    Returns true if the specific button was pressed on this frame. args.inputs.controller_(one-four).key_down.BUTTON will only be true on the frame it was pressed.

    key_held link

    Returns true if the specific button is being held. args.inputs.controller_(one-four).key_held.BUTTON will be true for all frames after key_down (until released).

    key_up link

    Returns true if the specific button was released. args.inputs.controller_(one-four).key_up.BUTTON will be true only on the frame the button was released.

    left_analog_active?(threshold_raw:, threshold_perc:) link

    Returns true if the Left Analog Stick is tilted. The threshold_raw and threshold_perc are optional parameters that can be used to set the minimum threshold for the analog stick to be considered active. The threshold_raw is a number between 0 and 32,767, and the threshold_perc is a number between 0 and 1.

    right_analog_active?(threshold_raw:, threshold_perc:) link

    Returns true if the Right Analog Stick is tilted. The threshold_raw and threshold_perc are optional parameters that can be used to set the minimum threshold for the analog stick to be considered active. The threshold_raw is a number between 0 and 32,767, and the threshold_perc is a number between 0 and 1.

    key_STATE?(key) link

    There are situations where you may want to get the status of a key dynamically as opposed to accessing a property. The following methods are provided to assist in this:

    Given a symbol, these functions return true or false if the key is in the current state.

    Here's how each of these methods are equivalent to key-based methods:

    # key_down equivalent
    args.inputs.controller_one.key_down.enter
    args.inputs.controller_one.key_down?(:enter)
    
    # key_up
    args.inputs.controller_one.key_up.enter
    args.inputs.controller_one.key_up?(:enter)
    
    # key held
    args.inputs.controller_one.key_held.enter
    args.inputs.controller_one.key_held?(:enter)
    
    # key down or held
    args.inputs.controller_one.enter
    args.inputs.controller_one.key_down_or_held?(:enter)
    

    Keyboard (args.inputs.keyboard) link

    Represents the user's keyboard.

    active link

    Returns Kernel.tick_count if any keys on the keyboard were pressed.

    has_focus link

    Returns true if the game has keyboard focus.

    up link

    Returns true if up or w is pressed or held on the keyboard.

    down link

    Returns true if down or s is pressed or held on the keyboard.

    left link

    Returns true if left or a is pressed or held on the keyboard.

    right link

    Returns true if right or d is pressed or held on the keyboard.

    left_right link

    Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.keyboard.left and args.inputs.keyboard.right.

    up_down link

    Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.keyboard.up and args.inputs.keyboard.up.

    Keyboard properties link

    The following properties represent keys on the keyboard and are available on args.inputs.keyboard.KEY, args.inputs.keyboard.key_down.KEY, args.inputs.keyboard.key_held.KEY, args.inputs.keyboard.key_up.KEY, args.inputs.keyboard.key_repeat.KEY:

    Here is an example showing all the ways to access a key's state:

    def tick args
      # create a value in state to
      # track tick_count of the G key
      args.state.g_key ||= {
        ctrl_at: nil,
        key_down_at: nil,
        key_repeat_at: nil,
        key_last_repeat_at: nil,
        key_held_at: nil,
        key_down_or_held_at: nil,
        key_up_at: nil,
      }
    
      # for each keyboard event, capture the tick_count
      # that the event occurred
      # Ctrl + G
      if args.inputs.keyboard.ctrl_g
        args.state.g_key.ctrl_at = args.inputs.keyboard.ctrl_g
      end
    
      # G pressed/down
      if args.inputs.keyboard.key_down.g
        args.state.g_key.key_down_at = args.inputs.keyboard.key_down.g
      end
    
      # G pressed or repeated (based on OS key repeat speed)
      if args.inputs.keyboard.key_repeat.g
        args.state.g_key.key_last_repeat_at = args.state.g_key.key_repeat_at
        args.state.g_key.key_repeat_at = args.inputs.keyboard.key_repeat.g
      end
    
      # G held
      if args.inputs.keyboard.key_held.g
        args.state.g_key.key_held_at = args.inputs.keyboard.key_held.g
      end
    
      # G down or held
      if args.inputs.keyboard.g
        args.state.g_key.key_down_or_held_at = args.inputs.keyboard.g
      end
    
      # G up
      if args.inputs.keyboard.key_up.g
        args.state.g_key.key_up_at = args.inputs.keyboard.key_up.g
      end
    
      # display the tick_count of each event
      args.outputs.debug.watch "ctrl+g?         #{args.state.g_key.ctrl_at}"
      args.outputs.debug.watch "g down?         #{args.state.g_key.key_down_at}"
      args.outputs.debug.watch "g repeat?       #{args.state.g_key.key_repeat_at}"
      args.outputs.debug.watch "g last_repeat?  #{args.state.g_key.key_last_repeat_at}"
      args.outputs.debug.watch "g held?         #{args.state.g_key.key_held_at}"
      args.outputs.debug.watch "g down or held? #{args.state.g_key.key_down_or_held_at}"
      args.outputs.debug.watch "g up?           #{args.state.g_key.key_up_at}"
    end
    

    keycodes link

    If the explicit named key isn't in the list above, you can still get the raw keycode via args.inputs.keyboard.key_(down|held|up).keycodes[KEYCODE_NUMBER]. The KEYCODE_NUMBER represents the keycode provided by SDL.

    Here is a list SDL Keycodes: <https://wiki.libsdl.org/SDL2/SDLKeycodeLookup>

    char link

    Method is available under inputs.key_down, inputs.key_held, and inputs.key_up. Take note that args.inputs.keyboard.key_held.char will only return the ascii value of the last key that was held. Use args.inputs.keyboard.key_held.truthy_keys to get an Array of Symbols representing all keys being held.

    To get a picture of all key states args.inputs.keyboard.keys returns a Hash with the following keys: :down, :held, :down_or_held, :up.

    NOTE: args.inputs.keyboard.key_down.char will be set in line with key repeat behavior of your OS.

    This is a demonstration of the behavior (see ./samples/02_input_basics/01_keyboard for a more detailed example):

    def tick args
      # uncomment the line below to see the value changes at a slower rate
      # $gtk.slowmo! 30
    
      keyboard = args.inputs.keyboard
    
      args.outputs.labels << { x: 30,
                               y: 720,
                               text: "use the J key to test" }
    
      args.outputs.labels << { x: 30,
                               y: 720 - 30,
                               text: "key_down.char: #{keyboard.key_down.char.inspect}" }
    
      args.outputs.labels << { x: 30,
                               y: 720 - 60,
                               text: "key_down.j:    #{keyboard.key_down.j}" }
    
      args.outputs.labels << { x: 30,
                               y: 720 - 30,
                               text: "key_held.char: #{keyboard.key_held.char.inspect}" }
    
      args.outputs.labels << { x: 30,
                               y: 720 - 60,
                               text: "key_held.j:    #{keyboard.key_held.j}" }
    
      args.outputs.labels << { x: 30,
                               y: 720 - 30,
                               text: "key_up.char:   #{keyboard.key_up.char.inspect}" }
    
      args.outputs.labels << { x: 30,
                               y: 720 - 60,
                               text: "key_up.j:      #{keyboard.key_up.j}" }
    end
    

    key_STATE?(key) link

    There are situations where you may want to get the status of a key dynamically as opposed to accessing a property. The following methods are provided to assist in this:

    Given a symbol, these methods return true or false if the key is in the current state.

    Here's how each of these methods are equivalent to key-based methods:

    # key_down equivalent
    args.inputs.keyboard.key_down.enter
    args.inputs.keyboard.key_down?(:enter)
    
    # key_up
    args.inputs.keyboard.key_up.enter
    args.inputs.keyboard.key_up?(:enter)
    
    # key held
    args.inputs.keyboard.key_held.enter
    args.inputs.keyboard.key_held?(:enter)
    
    # key down or held
    args.inputs.keyboard.enter
    args.inputs.keyboard.key_down_or_held?(:enter)
    

    keys link

    Returns a Hash with all keys on the keyboard in their respective state. The Hash contains the following keys

    Runtime (args.gtk) link

    The GTK::Runtime class is the core of DragonRuby.

    ?> All functions $gtk, GTK, or inside of the tick method through args.

    def tick args
      args.gtk.function(...)
    
      # OR available globally
      $gtk.function(...)
      # OR
      GTK.function(...)
    end
    

    Class Macros link

    The following class macros are available within DragonRuby.

    attr link

    The attr class macro is an alias to attr_accessor.

    Instead of:

    class Player
      attr_accessor :hp, :armor
    end
    

    You can do:

    class Player
      attr :hp, :armor
    end
    

    attr_gtk link

    As the size/complexity of your game increases. You may want to create classes to organize everything. The attr_gtk class macro adds DragonRuby's environment methods (such as args.state, args.inputs, args.outputs, args.audio, etc) to your class so you don't have to pass args around everywhere.

    Instead of:

    class Game
      def tick args
        defaults args
        calc args
        render args
      end
    
      def defaults args
        args.state.space_pressed_at ||= 0
      end
    
      def calc args
        if args.inputs.keyboard.key_down.space
          args.state.space_pressed_at = Kernel.tick_count
        end
      end
    
      def render args
        if args.state.space_pressed_at == 0
          args.outputs.labels << { x: 100, y: 100,
                                   text: "press space" }
        else
          args.outputs.labels << { x: 100, y: 100,
                                   text: "space was pressed at: #{args.state.space_pressed_at}" }
        end
      end
    end
    
    def tick args
      $game ||= Game.new
      $game.tick args
    end
    

    You can do:

    class Game
      attr_gtk # attr_gtk class macro
    
      def tick
        defaults
        calc
        render
      end
    
      def defaults
        state.space_pressed_at ||= 0
      end
    
      def calc
        if inputs.keyboard.key_down.space
          state.space_pressed_at = Kernel.tick_count
        end
      end
    
      def render
        if state.space_pressed_at == 0
          outputs.labels << { x: 100, y: 100,
                              text: "press space" }
        else
          outputs.labels << { x: 100, y: 100,
                              text: "space was pressed at: #{state.space_pressed_at}" }
        end
      end
    end
    
    def tick args
      $game ||= Game.new
      $game.args = args # set args property on game
      $game.tick        # call tick without passing in args
    end
    
    $game = nil
    

    Indie and Pro Functions link

    The following functions are only available at the Indie and Pro License tiers.

    get_pixels link

    Given a file_path to a sprite, this function returns a Hash with w, h, and pixels. The pixels key contains an array of hexadecimal values representing the ABGR of each pixel in a sprite with item 0 representing the top left corner of the png.

    Here's an example of how to get the color data for a pixel:

    def tick args
      # load the pixels from the image
      args.state.image ||= args.gtk.get_pixels "sprites/square/blue.png"
    
      # initialize state variables for the pixel coordinates
      args.state.x_px ||= 0
      args.state.y_px ||= 0
    
      sprite_pixels = args.state.image.pixels
      sprite_h = args.state.image.h
      sprite_w = args.state.image.w
    
      # move the pixel coordinates using keyboard
      args.state.x_px += args.inputs.left_right
      args.state.y_px += args.inputs.up_down
    
      # get pixel at the current coordinates
      args.state.x_px = args.state.x_px.clamp(0, sprite_w - 1)
      args.state.y_px = args.state.y_px.clamp(0, sprite_h - 1)
      row = sprite_h - args.state.y_px - 1
      col = args.state.x_px
      abgr = sprite_pixels[sprite_h * row + col]
      a = (abgr >> 24) & 0xff
      b = (abgr >> 16) & 0xff
      g = (abgr >> 8) & 0xff
      r = (abgr >> 0) & 0xff
    
      # render debug information
      args.outputs.debug << "row: #{row} col: #{col}"
      args.outputs.debug << "pixel entry 0: rgba #{r} #{g} #{b} #{a}"
    
      # render the sprite plus crosshairs
      args.outputs.sprites << { x: 0, y: 0, w: 80, h: 80, path: "sprites/square/blue.png" }
      args.outputs.lines << { x: args.state.x_px, y: 0, h: 720 }
      args.outputs.lines << { x: 0, y: args.state.y_px, w: 1280 }
    end
    

    See the following sample apps for how to use pixel arrays:

    dlopen link

    Loads a precompiled C Extension into your game.

    See the sample apps at ./samples/12_c_extensions for detailed walkthroughs of creating C extensions.

    Environment and Utility Functions link

    The following functions will help in interacting with the OS and rendering pipeline.

    calcstringbox link

    Returns the render width and render height as a tuple for a piece of text. The parameters this method takes are:

    def tick args
      text = "a piece of text"
      size_enum = 5 # "large font size"
    
      # path is relative to your game directory (eg mygame/fonts/courier-new.ttf)
      font = "fonts/courier-new.ttf"
    
      # get the render width and height
      string_w, string_h = args.gtk.calcstringbox text, size_enum, font
    
      # render the label
      args.outputs.labels << {
        x: 100,
        y: 100,
        text: text,
        size_enum: size_enum,
        font: font
      }
    
      # render a border around the label based on the results from calcstringbox
      args.outputs.borders << {
        x: 100,
        y: 100,
        w: string_w,
        h: string_h,
        r: 0,
        g: 0,
        b: 0
      }
    end
    

    calcstringbox also supports named parameters for size_enum and size_px.

      # size_enum, and font named parameters
      string_w, string_h = args.gtk.calcstringbox text, size_enum: 0, font: "fonts/example.ttf"
    
      # size_px, and font named parameters
      string_w, string_h = args.gtk.calcstringbox text, size_px: 20, font: "fonts/example.ttf"
    

    request_quit link

    Call this function to exit your game. You will be given one additional tick if you need to perform any housekeeping before that game closes.

    def tick args
      # exit the game after 600 frames (10 seconds)
      if Kernel.tick_count == 600
        args.gtk.request_quit
      end
    end
    

    quit_requested? link

    This function will return true if the game is about to exit (either from the user closing the game or if request_quit was invoked).

    set_window_fullscreen link

    This function takes in a single boolean parameter. true to make the game fullscreen, false to return the game back to windowed mode.

    def tick args
      # make the game full screen after 600 frames (10 seconds)
      if Kernel.tick_count == 600
        args.gtk.set_window_fullscreen true
      end
    
      # return the game to windowed mode after 20 seconds
      if Kernel.tick_count == 1200
        args.gtk.set_window_fullscreen false
      end
    end
    

    window_fullscreen? link

    Returns true if the window is currently in fullscreen mode.

    set_window_scale link

    The first parameter is a float value used to resize the game window to a percentage of 1280x720 (or 720x1280 in portrait mode). The valid scale options are 0.1, 0.25, 0.5, 0.75, 1.25, 1.5, 2.0, 2.5, 3.0, and 4.0. The float value you pass in will be floored to the nearest valid scale option.

    The second and third parameters are optional and default to 16 and 9 (representing with width and height aspect ratios for the window). Providing these parameters will resize the window with the non-standard aspect ratio. This is useful for testing letter-boxing and All Screen modes (Pro feature).

    Setting the window scale to the following will give a good representation of your game on various form factors.

    # how your game will look on an iPad
    $gtk.set_window_scale 1.0, 4, 3
    
    # how your game will look on a wide aspect ratio
    $gtk.set_window_scale 1.0, 21, 9
    

    set_window_title link

    This function takes in a string updates the title of the game in the Menu Bar.

    Note: The default title for your game is specified in via the gametitle property in mygame/metadata/game_metadata.txt.

    platform? link

    You can ask DragonRuby which platform your game is currently being run on. This can be useful if you want to perform different pieces of logic based on where the game is running.

    The raw platform string value is available via args.gtk.platform which takes in a symbol representing the platform's categorization/mapping.

    You can see all available platform categorizations via the args.gtk.platform_mappings function.

    Here's an example of how to use args.gtk.platform? category_symbol:

    def tick args
      label_style = { x: 640, y: 360, anchor_x: 0.5, anchor_y: 0.5 }
      if    args.gtk.platform? :macos
        args.outputs.labels << { text: "I am running on MacOS.", **label_style }
      elsif args.gtk.platform? :win
        args.outputs.labels << { text: "I am running on Windows.", **label_style }
      elsif args.gtk.platform? :linux
        args.outputs.labels << { text: "I am running on Linux.", **label_style }
      elsif args.gtk.platform? :web
        args.outputs.labels << { text: "I am running on a web page.", **label_style }
      elsif args.gtk.platform? :android
        args.outputs.labels << { text: "I am running on Android.", **label_style }
      elsif args.gtk.platform? :ios
        args.outputs.labels << { text: "I am running on iOS.", **label_style }
      elsif args.gtk.platform? :touch
        args.outputs.labels << { text: "I am running on a device that supports touch (either iOS/Android native or mobile web).", **label_style }
      elsif args.gtk.platform? :steam
        args.outputs.labels << { text: "I am running via steam (covers both desktop and steamdeck).", **label_style }
      elsif args.gtk.platform? :steam_deck
        args.outputs.labels << { text: "I am running via steam on the Steam Deck (not steam desktop).", **label_style }
      elsif args.gtk.platform? :steam_desktop
        args.outputs.labels << { text: "I am running via steam on desktop (not steam deck).", **label_style }
      end
    end
    

    production? link

    Returns true if the game is being run in a released/shipped state.

    If you want to simulate a production build. Add an empty file called dragonruby_production_build inside of the metadata folder. This will turn of all logging and all creation of temp files used for development purposes.

    platform_mappings link

    These are the current platform categorizations (args.gtk.platform_mappings):

    {
      "Mac OS X"   => [:desktop, :macos, :osx, :mac, :macosx], # may also include :steam and :steam_desktop run via steam
      "Windows"    => [:desktop, :windows, :win],              # may also include :steam and :steam_desktop run via steam
      "Linux"      => [:desktop, :linux, :nix],                # may also include :steam and :steam_desktop run via steam
      "Emscripten" => [:web, :wasm, :html, :emscripten],       # may also include :touch if running in the web browser on mobile
      "iOS"        => [:mobile, :ios, :touch],
      "Android"    => [:mobile, :android, :touch],
      "Steam Deck" => [:steamdeck, :steam_deck, :steam],
    }
    

    Given the mappings above, args.gtk.platform? :desktop would return true if the game is running on a player's computer irrespective of OS (MacOS, Linux, and Windows are all categorized as :desktop platforms).

    open_url link

    Given a uri represented as a string. This function will open the uri in the user's default browser.

    def tick args
      # open a url after 600 frames (10 seconds)
      if Kernel.tick_count == 600
        args.gtk.open_url "http://dragonruby.org"
      end
    end
    

    system link

    Given an OS dependent cli command represented as a string, this function executes the command and puts the results to the DragonRuby Console (returns nil).

    def tick args
      # execute ls on the current directory in 10 seconds
      if Kernel.tick_count == 600
        args.gtk.system "ls ."
      end
    end
    

    exec link

    Given an OS dependent cli command represented as a string, this function executes the command and returns a string representing the results.

    def tick args
      # execute ls on the current directory in 10 seconds
      if Kernel.tick_count == 600
        results = args.gtk.exec "ls ."
        puts "The results of the command are:"
        puts results
      end
    end
    

    show_cursor link

    Shows the mouse cursor.

    hide_cursor link

    Hides the mouse cursor.

    cursor_shown? link

    Returns true if the mouse cursor is visible.

    set_mouse_grab link

    Takes in a numeric parameter representing the mouse grab mode.

    set_system_cursor link

    Takes in a string value of "arrow", "ibeam", "wait", or "hand" and sets the mouse cursor to the corresponding system cursor (if available on the OS).

    set_cursor link

    Replaces the mouse cursor with a sprite. Takes in a path to the sprite, and optionally an x and y value representing the relative positioning the sprite will have to the mouse cursor.

    def tick args
      if Kernel.tick_count == 0
        # assumes a sprite of size 80x80 and centers the sprite
        # relative to the cursor position.
        args.gtk.set_cursor "sprites/square/blue.png", 40, 40
      end
    end
    

    File IO Functions link

    The following functions give you the ability to interact with the file system.

    DragonRuby uses a sandboxed filesystem which will automatically read from and write to a location appropriate for your platform so you don't have to worry about theses details in your code. You can just use $gtk.read_file, $gtk.write_file, and $gtk.append_file with a relative path and the engine will take care of the rest.

    The data directories that will be written to in a production build are:

    The values in square brackets are the values you set in your app/metadata/game_metadata.txt file.

    When reading files, the engine will first look in the game's data directory and then in the game directory itself. This means that if you write a file to the data directory that already exists in your game directory, the file in the data directory will be used instead of the one that is in your game.

    When running a development build you will directly write to your game directory (and thus overwrite existing files). This can be useful for built-in development tools like level editors.

    For more details on the implementation of the sandboxed filesystem, see Ryan C. Gordon's PhysicsFS documentation: [https://icculus.org/physfs/](https://icculus.org/physfs/)

    NOTE:

    File access functions are sandboxed and assume that the dragonruby binary lives alongside the game you are building. --Do not expect these functions to return correct values if you are attempting to run the dragonruby binary from a shared location--. It's --strongly-- recommended that the directory structure contained in the zip is not altered and games are built using that starter template.

    list_files link

    This function takes in one parameter. The parameter is the directory path and assumes the the game directory is the root. The method returns an Array of String representing all files within the directory. Use stat_file to determine whether a specific path is a file or a directory.

    stat_file link

    This function takes in one parameter. The parameter is the file path and assumes the the game directory is the root. The method returns nil if the file doesn't exist otherwise it returns a Hash with the following information:

    # {
    #   path: String,
    #   file_size: Int,
    #   mod_time: Int,
    #   create_time: Int,
    #   access_time: Int,
    #   readonly: Boolean,
    #   file_type: Symbol (:regular, :directory, :symlink, :other),
    # }
    
    def tick args
      if args.inputs.mouse.click
        args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{Kernel.tick_count}."
      end
    
      file_info = args.gtk.stat_file "last-mouse-click.txt"
    
      if file_info
        args.outputs.labels << {
          x: 30,
          y: 30.from_top,
          text: file_info.to_s,
          size_enum: -3
        }
      else
        args.outputs.labels << {
          x: 30,
          y: 30.from_top,
          text: "file does not exist, click to create file",
          size_enum: -3
        }
      end
    end
    

    read_file link

    Given a file path, a string will be returned representing the contents of the file. nil will be returned if the file does not exist. You can use stat_file to get additional information of a file.

    write_file link

    This function takes in two parameters. The first parameter is the file path and assumes the the game directory is the root. The second parameter is the string that will be written. The method ----overwrites---- whatever is currently in the file. Use append_file to append to the file as opposed to overwriting.

    def tick args
      if args.inputs.mouse.click
        args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{Kernel.tick_count}."
      end
    end
    

    append_file link

    This function takes in two parameters. The first parameter is the file path and assumes the the game directory is the root. The second parameter is the string that will be written. The method appends to whatever is currently in the file (a new file is created if one does not already exist). Use write_file to overwrite the file's contents as opposed to appending.

    def tick args
      if args.inputs.mouse.click
        args.gtk.append_file "click-history.txt", "Mouse was clicked at #{Kernel.tick_count}.\n"
        puts args.gtk.read_file("click-history.txt")
      end
    end
    

    delete_file link

    This function takes in a single parameters. The parameter is the file or directory path that should be deleted. This function will raise an exception if the path requesting to be deleted does not exist.

    Here is a list of reasons an exception could be raised:

    Notes:

    def tick args
      if args.inputs.keyboard.key_down.w
        # press w to write file
        args.gtk.append_file "example-file.txt", "File written at #{Kernel.tick_count}\n"
        args.gtk.notify "File written/appended."
      elsif args.inputs.keyboard.key_down.d
        # press d to delete file "unsafely"
        args.gtk.delete_file "example-file.txt"
        args.gtk.notify "File deleted."
      elsif args.inputs.keyboard.key_down.r
        # press r to read file
        contents = args.gtk.read_file "example-file.txt"
        args.gtk.notify "File contents written to console."
        puts contents
      end
    end
    

    parse_json link

    Given a json string, this function returns a hash representing the json data.

    hash = args.gtk.parse_json '{ "name": "John Doe", "aliases": ["JD"] }'
    # structure of hash: { "name"=>"John Doe", "aliases"=>["JD"] }
    

    parse_json_file link

    Same behavior as parse_json_file except a file path is read for the json string.

    parse_xml link

    Given xml data as a string, this function will return a hash that represents the xml data in the following recursive structure:

    {
      type: :element,
      name: "Person",
      children: [...]
    }
    

    parse_xml_file link

    Function has the same behavior as parse_xml except that the parameter must be a file path that contains xml contents.

    Network IO Functions link

    The following functions help with interacting with the network.

    http_get link

    Returns an object that represents an http response which will eventually have a value. This http_get method is invoked asynchronously. Check for completion before attempting to read results.

    def tick args
      # perform an http get and print the response when available
      args.state.result ||= args.gtk.http_get "https://httpbin.org/html"
    
      if args.state.result && args.state.result[:complete] && !args.state.printed
        if args.state.result[:http_response_code] == 200
          puts "The response was successful. The body is:"
          puts args.state.result[:response_data]
        else
          puts "The response failed. Status code:"
          puts args.state.result[:http_response_code]
        end
        # set a flag denoting that the response has been printed
        args.state.printed = true
    
        # show the console
        args.gtk.show_console
      end
    end
    

    http_post link

    Returns an object that represents an http response which will eventually have a value. This http_post method is invoked asynchronously. Check for completion before attempting to read results.

    def tick args
      # perform an http get and print the response when available
    
      args.state.form_fields ||= { "userId" => "#{Time.now.to_i}" }
      args.state.result ||= args.gtk.http_post "http://httpbin.org/post",
                                               args.state.form_fields,
                                               ["Content-Type: application/x-www-form-urlencoded"]
    
    
      if args.state.result && args.state.result[:complete] && !args.state.printed
        if args.state.result[:http_response_code] == 200
          puts "The response was successful. The body is:"
          puts args.state.result[:response_data]
        else
          puts "The response failed. Status code:"
          puts args.state.result[:http_response_code]
        end
        # set a flag denoting that the response has been printed
        args.state.printed = true
    
        # show the console
        args.gtk.show_console
      end
    end
    

    http_post_body link

    Returns an object that represents an http response which will eventually have a value. This http_post_body method is invoked asynchronously. Check for completion before attempting to read results.

    def tick args
      # perform an http get and print the response when available
    
      args.state.json ||= "{ "userId": "#{Time.now.to_i}"}"
      args.state.result ||= args.gtk.http_post_body "http://httpbin.org/post",
                                                    args.state.json,
                                                    ["Content-Type: application/json", "Content-Length: #{args.state.json.length}"]
    
    
      if args.state.result && args.state.result[:complete] && !args.state.printed
        if args.state.result[:http_response_code] == 200
          puts "The response was successful. The body is:"
          puts args.state.result[:response_data]
        else
          puts "The response failed. Status code:"
          puts args.state.result[:http_response_code]
        end
        # set a flag denoting that the response has been printed
        args.state.printed = true
    
        # show the console
        args.gtk.show_console
      end
    end
    

    start_server! link

    Starts a in-game http server that can be process http requests. When your game is running in development mode. A dev server is started at http://localhost:9001

    ?> You must set webserver.enabled=true in metadata/cvars.txt to view docs locally. These docs are also available under ./docs within the zip file in markdown format.

    You can start an in-game http server in production via:

    def tick args
      # server explicitly enabled in production
      args.gtk.start_server! port: 9001, enable_in_prod: true
    end
    

    Here's how you would respond to http requests:

    def tick args
      # server explicitly enabled in production
      args.gtk.start_server! port: 9001, enable_in_prod: true
    
      # loop through pending requests and respond to them
      args.inputs.http_requests.each do |request|
        puts "#{request}"
        request.respond 200, "ok"
      end
    end
    

    http_get link

    Returns an object that represents an http response which will eventually have a value. This http_get method is invoked asynchronously. Check for completion before attempting to read results.

    def tick args
      # perform an http get and print the response when available
      args.state.result ||= args.gtk.http_get "https://httpbin.org/html"
    
      if args.state.result && args.state.result[:complete] && !args.state.printed
        if args.state.result[:http_response_code] == 200
          puts "The response was successful. The body is:"
          puts args.state.result[:response_data]
        else
          puts "The response failed. Status code:"
          puts args.state.result[:http_response_code]
        end
        # set a flag denoting that the response has been printed
        args.state.printed = true
    
        # show the console
        args.gtk.show_console
      end
    end
    

    http_post link

    Returns an object that represents an http response which will eventually have a value. This http_post method is invoked asynchronously. Check for completion before attempting to read results.

    def tick args
      # perform an http get and print the response when available
    
      args.state.form_fields ||= { "userId" => "#{Time.now.to_i}" }
      args.state.result ||= args.gtk.http_post "http://httpbin.org/post",
                                               args.state.form_fields,
                                               ["Content-Type: application/x-www-form-urlencoded"]
    
    
      if args.state.result && args.state.result[:complete] && !args.state.printed
        if args.state.result[:http_response_code] == 200
          puts "The response was successful. The body is:"
          puts args.state.result[:response_data]
        else
          puts "The response failed. Status code:"
          puts args.state.result[:http_response_code]
        end
        # set a flag denoting that the response has been printed
        args.state.printed = true
    
        # show the console
        args.gtk.show_console
      end
    end
    

    http_post_body link

    Returns an object that represents an http response which will eventually have a value. This http_post_body method is invoked asynchronously. Check for completion before attempting to read results.

    def tick args
      # perform an http get and print the response when available
    
      args.state.json ||= "{ "userId": "#{Time.now.to_i}"}"
      args.state.result ||= args.gtk.http_post_body "http://httpbin.org/post",
                                                    args.state.json,
                                                    ["Content-Type: application/json", "Content-Length: #{args.state.json.length}"]
    
    
      if args.state.result && args.state.result[:complete] && !args.state.printed
        if args.state.result[:http_response_code] == 200
          puts "The response was successful. The body is:"
          puts args.state.result[:response_data]
        else
          puts "The response failed. Status code:"
          puts args.state.result[:http_response_code]
        end
        # set a flag denoting that the response has been printed
        args.state.printed = true
    
        # show the console
        args.gtk.show_console
      end
    end
    

    start_server! link

    Starts a in-game http server that can be process http requests. When your game is running in development mode. A dev server is started at http://localhost:9001

    ?> You must set webserver.enabled=true in metadata/cvars.txt to view docs locally. These docs are also available under ./docs within the zip file in markdown format.

    You can start an in-game http server in production via:

    def tick args
      # server explicitly enabled in production
      args.gtk.start_server! port: 9001, enable_in_prod: true
    end
    

    Here's how you would respond to http requests:

    def tick args
      # server explicitly enabled in production
      args.gtk.start_server! port: 9001, enable_in_prod: true
    
      # loop through pending requests and respond to them
      args.inputs.http_requests.each do |request|
        puts "#{request}"
        request.respond 200, "ok"
      end
    end
    

    Developer Support Functions link

    The following functions help support the development process. It is not recommended to use this functions in "production" game logic.

    version link

    Returns a string representing the version of DragonRuby you are running.

    version_pro? link

    Returns true if the version of DragonRuby is NOT Standard Edition.

    game_version link

    Returns a version string within mygame/game_metadata.txt.

    To get other values from mygame/game_metadata.txt, you can do:

    def tick args
      if Kernel.tick_count == 0
        puts args.gtk.game_version
        args.cvars["game_metadata.version"].value
      end
    end
    

    reset link

    This function will be called if $gtk.reset is invoked. It will be called before DragonRuby resets game state and is useful for capturing state information before a reset occurs (you can use this override to reset state external to DragonRuby's args.state construct).

    # class that handles your game loop
    class MyGame
      attr :foo
    
      # initialization method that sets member variables
      # external to args.state
      def initialize args
        puts "initializing game"
        @foo = 0
        args.state.bar ||= 0
      end
    
      # game logic
      def tick args
        args.state.bar += 1
        @foo += 1
        args.outputs.labels << {
          x: 640,
          y: 360,
          text: "#{$game.foo}, #{args.state.bar}"
        }
      end
    end
    
    def tick args
      # initialize global game variable if it's nil
      $game ||= MyGame.new(args)
    
      # run tick
      $game.tick args
    
      # at T=600, invoke reset
      if Kernel.tick_count == 600
        $gtk.reset
      end
    end
    
    # this function will be invoked before
    # $gtk.reset occurs
    def reset args
      puts "resetting"
      puts "foo is: #{$game.foo}"
      puts "bar is: #{args.state.bar}"
      puts "tick count: #{Kernel.tick_count}"
    
      # reset global game to nil so that it will be re-initialized next tick
      $game = nil
    end
    

    reset_next_tick link

    Has the same behavior as reset except the reset occurs before tick is executed again. reset resets the environment immediately (while the tick method is in-flight). It's recommended that reset should be called outside of the tick method (invoked when a file is saved/hotloaded), and reset_next_tick be used inside of the tick method so you don't accidentally blow away state the your game depends on to complete the current tick without exceptions.

    def tick args
      # reset the game if "r" is pressed on the keyboard
      if args.inputs.keyboard.key_down.r
        args.gtk.reset_next_tick # use reset_next_tick instead of reset
      end
    end
    
    # reset the game if this file is hotloaded/required
    # (removes the need to press "r" when I file is updated)
    $gtk.reset
    

    reset_sprite link

    Sprites when loaded are cached. Given a string parameter, this method invalidates the cache record of a sprite so that updates on from the disk can be loaded.

    This function can also be used to delete/garbage collect render targets you are no longer using.

    reset_sprites link

    Sprites when loaded are cached. This method invalidates the cache record of all sprites so that updates on from the disk can be loaded. This function is automatically called when args.gtk.reset ($gtk.reset) is invoked.

    calcspritebox link

    Given a path to a sprite, this method returns the width and height of a sprite as a tuple.

    NOTE: This method should be used for development purposes only and is expensive to call every frame. Do not use this method to set the size of sprite when rendering (hard code those values since you know what they are beforehand).

    current_framerate link

    Returns a float value representing the framerate of your game. This is an approximation/moving average of your framerate and should eventually settle to 60fps.

    def tick args
      # render a label to the screen that shows the current framerate
      # formatted as a floating point number with two decimal places
      args.outputs.labels << { x: 30, y: 30.from_top, text: "#{args.gtk.current_framerate.to_sf}" }
    end
    

    framerate_diagnostics_primitives link

    Returns a set of primitives that can be rendered to the screen which provide more detailed information about the speed of your simulation (framerate, draw call count, mouse position, etc).

    def tick args
      args.outputs.primitives << args.gtk.framerate_diagnostics_primitives
    end
    

    warn_array_primitives! link

    This function helps you audit your game of usages of array-based primitives. While array-based primitives are simple to create and use, they are slower to process than Hash or Class based primitives.

    def tick args
      # enable array based primitives warnings
      args.gtk.warn_array_primitives!
    
      # array-based primitive elsewhere in code
      # an log message will be posted giving the location of the array
      # based primitive usage
      args.outputs.sprites << [100, 100, 200, 200, "sprites/square/blue.png"]
    
      # instead of using array based primitives, migrate to hashes as needed
      args.outputs.sprites << {
        x: 100,
        y: 100,
        w: 200,
        h: 200, path:
        "sprites/square/blue.png"
      }
    end
    

    benchmark link

    You can use this function to compare the relative performance of blocks of code.

    Function takes in either iterations or seconds along with a collection of lambdas.

    If iterations is provided, the winner will be determined by the fastest completion time.

    If seconds is provided, the winner will be determined by the most completed iterations.

    def tick args
      # press i to run benchmark using iterations
      if args.inputs.keyboard.key_down.i
        args.gtk.console.show
        args.gtk.benchmark iterations: 1000, # number of iterations
                           # label for experiment
                           using_numeric_map: -> () {
                             # experiment body
                             v = 100.map_with_index do |i|
                               i * 100
                             end
                           },
                           # label for experiment
                           using_numeric_times: -> () {
                             # experiment body
                             v = []
                             100.times do |i|
                               v << i * 100
                             end
                           }
      end
    
      # press s to run benchmark using seconds
      if args.inputs.keyboard.key_down.s
        args.gtk.console.show
        args.gtk.benchmark seconds: 1, # number of seconds to run each experiment
                           # label for experiment
                           using_numeric_map: -> () {
                             # experiment body
                             v = 100.map_with_index do |i|
                               i * 100
                             end
                           },
                           # label for experiment
                           using_numeric_times: -> () {
                             # experiment body
                             v = []
                             100.times do |i|
                               v << i * 100
                             end
                           }
      end
    end
    

    notify! link

    Given a string, this function will present a message at the bottom of your game. This method is only invoked in dev mode and is useful for debugging.

    An optional parameter of duration (number value representing ticks) can also be passed in. The default value if 300 ticks (5 seconds).

    def tick args
      if args.inputs.mouse.click
        args.gtk.notify! "Mouse was clicked!"
      end
    
      if args.inputs.keyboard.key_down.r
        # optional duration parameter
        args.gtk.notify! "R key was pressed!", 600 # present message for 10 seconds/600 frames
      end
    end
    

    notify_extended! link

    Has similar behavior as notify! except you have additional options to show messages in a production environment.

    def tick args
      if args.inputs.mouse.click
        args.gtk.notify_extended! message: "message",
                                  duration: 300,
                                  env: :prod
      end
    end
    

    slowmo! link

    Given a numeric value representing the factor of 60fps. This function will bring your simulation loop down to slower rate. This method is intended to be used for debugging purposes.

    def tick args
      # set your simulation speed to (15 fps): args.gtk.slowmo! 4
      # set your simulation speed to (1 fps): args.gtk.slowmo! 60
      # set your simulation speed to (30 fps):
      args.gtk.slowmo! 2
    end
    

    Remove this line from your tick method will automatically set your simulation speed back to 60 fps.

    show_console link

    Shows the DragonRuby console. Useful when debugging/customizing an in-game dev workflow.

    hide_console link

    Shows the DragonRuby console. Useful when debugging/customizing an in-game dev workflow.

    enable_console link

    Enables the DragonRuby Console so that it can be presented by pressing the tilde key (the key next to the number 1 key).

    disable_console link

    Disables the DragonRuby Console so that it won't show up even if you press the tilde key or call args.gtk.show_console.

    disable_reset_via_ctrl_r link

    By default, pressing CTRL+R invokes $gtk.reset_next_tick (safely resetting your game with a convenient key combo).

    If you want to disable this behavior, add the following to the main.rb:

    def tick args
      ...
    end
    
    $gtk.disable_reset_via_ctrl_r
    

    NOTE: $gtk.disable_console will also disable the CTRL+R reset behavior.

    disable_controller_config link

    DragonRuby has a built-in controller configuration/mapping wizard. You can disable this wizard by adding $gtk.disable_controller_config at the top of main.rb.

    enable_controller_config link

    DragonRuby has a built-in controller configuration/mapping wizard. You can re-enable this wizard by adding $gtk.enable_controller_config at the top of main.rb (this is enabled by default).

    start_recording link

    Resets the game to tick 0 and starts recording gameplay. Useful for visual regression tests/verification.

    stop_recording link

    Function takes in a destination file for the currently recording gameplay. This file can be used to replay a recording.

    cancel_recording link

    Function cancels a gameplay recording session and discards the replay.

    start_replay link

    Given a file that represents a recording, this method will run the recording against the current codebase.

    You can start a replay from the command line also:

    # first argument: the game directory
    # --replay switch is the file path relative to the game directory
    # --speed switch is optional. a value of 4 will run the replay and game at 4x speed
    # cli command example is in the context of Linux and Mac, for Windows the binary would be ./dragonruby.exe
    ./dragonruby ./mygame --replay replay.txt --speed 4
    

    stop_replay link

    Function stops a replay that is currently executing.

    get_base_dir link

    Returns the path to the location of the dragonruby binary. In production mode, this value will be the same as the value returned by get_game_dir. Function should only be used for debugging/development workflows.

    get_game_dir link

    Returns the location within sandbox storage that the game is running. When developing your game, this value will be your mygame directory. In production, it'll return a value that is OS specific (eg the Roaming directory on Windows or the Application Support directory on Mac).

    Invocations of (write|append)_file will write to this sandboxed directory.

    get_game_dir_url link

    Returns a url encoded string representing the sandbox location for game data.

    open_game_dir link

    Opens the game directory in the OS's file explorer. This should be used for debugging purposes only.

    write_file_root link

    Given a file path and contents, the contents will be written to a directory outside of the game directory. This method should be used for development purposes only. In production this method will write to the same sandboxed location as write_file.

    append_file_root link

    Has the same behavior as write_file_root except that it appends the contents as opposed to overwriting them.

    argv link

    Returns a string representing the command line arguments passed to the DragonRuby binary. This should be used for development/debugging purposes only.

    cli_arguments link

    Returns a Hash for command line arguments in the format of --switch value (two hyphens preceding the switch flag with the value separated by a space). This should be used for development/debugging purposes only.

    download_stb_rb(_raw) link

    These two functions can help facilitate the integration of external code files. OSS contributors are encouraged to create libraries that all fit in one file (lowering the barrier to entry for adoption).

    Examples:

    def tick args
    end
    
    # option 1:
    # source code will be downloaded from the specified GitHub url, and saved locally with a
    # predefined folder convention.
    $gtk.download_stb_rb "https://github.com/xenobrain/ruby_vectormath/blob/main/vectormath_2d.rb"
    
    # option 2:
    # source code will be downloaded from the specified GitHub username, repository, and file.
    # code will be saved locally with a predefined folder convention.
    $gtk.download_stb_rb "xenobrain", "ruby_vectormath", "vectormath_2d.rb"
    
    # option 3:
    # source code will be downloaded from a direct/raw url and saved to a direct/raw local path.
    $gtk.download_stb_rb_raw "https://raw.githubusercontent.com/xenobrain/ruby_vectormath/main/vectormath_2d.rb",
                             "lib/xenobrain/ruby_vectionmath/vectormath_2d.rb"
    

    reload_history link

    Returns a Hash representing the code files that have be loaded for your game along with timings for the events. This should be used for development/debugging purposes only.

    reload_history_pending link

    Returns a Hash for files that have been queued for reload, but haven't been processed yet. This should be used for development/debugging purposes only.

    reload_if_needed link

    Given a file name, this function will queue the file for reload if it's been modified. An optional second parameter can be passed in to signify if the file should be forced loaded regardless of modified time (true means to force load, false means to load only if the file has been modified). This function should be used for development/debugging purposes only.

    State (args.state) link

    Store your game state inside of this state. Properties with arbitrary nesting is allowed and a backing Entity will be created on your behalf.

    def tick args
      args.state.player.x ||= 0
      args.state.player.y ||= 0
    end
    

    entity_id link

    Entities automatically receive an entity_id of type Fixnum.

    entity_type link

    Entities can have an entity_type which is represented as a Symbol.

    created_at link

    Entities have created_at set to Kernel.tick_count when they are created.

    created_at_elapsed link

    Returns the elapsed number of ticks since creation.

    global_created_at link

    Entities have global_created_at set to Kernel.global_tick_count when they are created.

    global_created_at_elapsed link

    Returns the elapsed number of global ticks since creation.

    as_hash link

    Entity cast to a Hash so you can update values as if you were updating a Hash.

    new_entity link

    Creates a new Entity with a type, and initial properties. An option block can be passed to change the newly created entity:

    def tick args
      args.state.player ||= args.state.new_entity :player, x: 0, y: 0 do |e|
        e.max_hp = 100
        e.hp     = e.max_hp * rand
      end
    end
    

    new_entity_strict link

    Creates a new Strict Entity. While Entities created via args.state.new_entity can have new properties added later on, Entities created using args.state.new_entity_strict must define all properties that are allowed during its initialization. Attempting to add new properties after initialization will result in an exception.

    tick_count link

    Returns the current tick of the game. Kernel.tick_count is 0 when the game is first started or if the game is reset via $gtk.reset.

    Geometry (args.geometry) link

    The Geometry module contains methods for calculations that are frequently used in game development.

    The following functions of Geometry are mixed into Hash, Array, and DragonRuby's Entity class:

    You can invoke the functions above using either the mixin variant or the module variant. Example:

    def tick args
      # define to rectangles
      rect_1 = { x: 0, y: 0, w: 100, h: 100 }
      rect_2 = { x: 50, y: 50, w: 100, h: 100 }
    
      # mixin variant
      # call geometry method function from instance of a Hash class
      puts rect_1.intersect_rect?(rect_2)
    
      # OR
    
      # module variants
      puts args.geometry.intersect_rect?(rect_1, rect_2)
      puts Geometry.intersect_rect?(rect_1, rect_2)
    end
    

    CVars / Configuration (args.cvars) link

    Hash contains metadata pulled from the files under the ./metadata directory. To get the keys that are available type $args.cvars.keys in the Console. Here is an example of how to retrieve the game version number:

    def tick args
      args.outputs.labels << {
        x: 640,
        y: 360,
        text: args.cvars["game_metadata.version"].value.to_s
      }
    end
    

    Each CVar has the following properties value, name, description, type, locked.

    Available Configuration link

    ?> See metadata/game_metadata.txt and metadata/cvars.txt for detailed information of all the configuration values that are supported. The following table is a high level summary of each value.

    | File | Name | Values | Description | |--------------------------------|----------------------------|------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| | --metadata/cvars.txt-- | webserver.enabled | true or false (default is false) | Controls whether or not the in-game web server at localhost:9001 is enabled in dev mode. The in-game web server is primarily needed for remote-hotloading. | | | webserver.port | Number representing a port (default is 9001) | Port that the in-game web server runs on. For remote-hotloading, this value must be 9001. | | | webserver.remote_clients | true or false (default is false) | Controls whether or not remote connections to the in-game web server are allowed. Must be set to true for remote-hotloading. | | --metadata/game_metadata.txt-- | devid | String value | Your Developer Id on Itch.io. | | | devname | String value | Developer name/studio name. | | | gameid | String value | Your Game Id on Itch.io | | | gametitle | String value | The title of your game. | | | version | String value | MAJOR.MINOR Version number for your game. | | | icon | String value | Path to your game icon. | | | orientation | landscape or portrait (default is landscape) | Orientation for your game. | | | orientation_ios | landscape or portrait | Overrides the default orientation on iOS. This is a Pro feature. | | | orientation_android | landscape or portrait | Overrides the default orientation on Android. This is a Pro feature. | | | scale_quality | 0, 1, or 2 (default is 0) | Specifies the render scale quality for your game (full details of what each number means in metadata/game_metadata.txt). | | | ignore_directories | Comma delimited list of directories | Directories to exclude when packaging your game. | | | packageid | String in reverse domain convention | Android Package Id for your game. This is a Pro feature. | | | compile_ruby | true or false (default is false) | Signifies if your game code will be compiled to bytecode during packaging. This is a Pro feature. | | | hd | true or false (default is false) | Whether your game will be rendered in HD. This is a Pro feature. | | | highdpi | true or false (default is false) | Whether your game will be rendered with High DPI. This is a Pro feature. | | | sprites_directory | String value | The path that DR should search for HD texture atlases. This is a Pro feature. | | | hd_letterbox | true or false (default is true) | Disables the letter box around your game. This is a Pro feature. | | | hd_max_scale | 0, 100, 125, 150, 175, 200, 250, 300, 400 (default is 0) | Signifies the max scale of your game. 0 means size to fit (full details of what each number means in metadata/game_metadata.txt). | | --metadata/ios_metadata.txt-- | teamid | String value | Apple Team Id. This is a Pro feature. | | | appid | String value | Apple App Id. This is a Pro feature. | | | appname | String value | The name to show under the App icon. | | | devcert | String value | Apple Development Certificate name used to sign your game for local device deployment. This is a Pro feature. | | | prodcert | String value | Apple Distribution Certificate name used to sign your game release to the App Store. This is a Pro feature. |

    Layout (args.layout) link

    Layout provides apis for placing primitives on a virtual grid that's within the "safe area" accross all platforms. This virtual grid is useful for rendering static controls (buttons, menu items, configuration screens, etc).

    ?> All functions are available globally via Layout.-.

    def tick args
       puts args.layout.function(...)
    
       # OR available globally
       puts Layout.function(...)
    end
    

    For reference implementations, take a look at the following sample apps:

    The following example creates two menu items and updates a label with the button that was clicked:

    def tick args
      # render debug_primitives of args.layout for help with placement
      # args.outputs.primitives << args.layout.debug_primitives
    
      # capture the location for a label centered at the top
      args.state.label_rect ||= args.layout.rect(row: 0, col: 10, w: 4, h: 1)
      # state variable to hold the current click status
      args.state.label_message ||= "click a menu item"
    
      # capture the location of two menu items positioned in the center
      # with a cell width of 4 and cell height of 2
      args.state.menu_item_1_rect ||= args.layout.rect(row: 1, col: 10, w: 4, h: 2)
      args.state.menu_item_2_rect ||= args.layout.rect(row: 3, col: 10, w: 4, h: 2)
    
      # render the label at the center of the label_rect
      args.outputs.labels << args.state.label_rect.center.merge(text: args.state.label_message,
                                                                anchor_x: 0.5,
                                                                anchor_y: 0.5)
    
      # render menu items
      args.outputs.sprites << args.state.menu_item_1_rect.merge(path: :solid,
                                                                r: 100,
                                                                g: 100,
                                                                b: 200)
      args.outputs.labels << args.state.menu_item_1_rect.center.merge(text: "item 1",
                                                                      r: 255,
                                                                      g: 255,
                                                                      b: 255,
                                                                      anchor_x: 0.5,
                                                                      anchor_y: 0.5)
    
      args.outputs.sprites << args.state.menu_item_2_rect.merge(path: :solid,
                                                                r: 100,
                                                                g: 100,
                                                                b: 200)
      args.outputs.labels << args.state.menu_item_2_rect.center.merge(text: "item 2",
                                                                      r: 255,
                                                                      g: 255,
                                                                      b: 255,
                                                                      anchor_x: 0.5,
                                                                      anchor_y: 0.5)
    
      # if click occurs, then determine which menu item was clicked
      if args.inputs.mouse.click
        if args.inputs.mouse.intersect_rect?(args.state.menu_item_1_rect)
          args.state.label_message = "menu item 1 clicked"
        elsif args.inputs.mouse.intersect_rect?(args.state.menu_item_2_rect)
          args.state.label_message = "menu item 2 clicked"
        else
          args.state.label_message = "click a menu item"
        end
      end
    end
    

    rect link

    Given a row:, col:, w:, h:, returns a Hash with properties x, y, w, h, and center (which contains a Hash with x, y). The virtual grid is 12 rows by 24 columns (or 24 columns by 12 rows in portrait mode).

    debug_primitives link

    Function returns an array of primitives that can be rendered to the screen to help you place items within the grid.

    Example:

    def tick args
      ...
    
      # at the end of tick method, render the
      # grid overlay to static_primitives on
      # tick_count=0 to help with positioning
      if Kernel.tick_count == 0
        # Layout.debug_primitives returns a flat hash of values
        # so you can customize the colors/alphas if needed
        # args.outputs.static_primitives << Layout.debug_primitives.map do |primitive|
        #   primitive.merge(r: ..., g: ..., b: ..., etc)
        # end
        args.outputs.static_primitives << Layout.debug_primitives
      end
    end
    

    Grid (args.grid) link

    Provides information about the screen and game canvas.

    ?> All functions are available globally via Grid.-.

    def tick args
       puts args.grid.function(...)
    
       # OR available globally
       puts Grid.function(...)
    end
    

    orientation link

    Returns either :landscape (default) or :portrait. The orientation of your game is set within ./mygame/metadata/game_metadata.txt.

    origin_name link

    Returns either :bottom_left (default) or :center.

    origin_bottom_left! link

    Change the grids coordinate system where 0, 0 is at the bottom left corner. origin_name will be set to :bottom_left.

    origin_center! link

    Change the grids coordinate system where 0, 0 is at the center of the screen. origin_name will be set to :center.

    portrait? link

    Returns true if orientation is :portrait.

    landscape? link

    Returns true if orientation is :landscape.

    Grid Property Categorizations link

    There are two categories of Grid properties that you should be aware of:

    NOTE:

    You will almost always use the Logical Category's properties.

    The Pixel is helpful for sanity checking of Texture Atlases, creating C Extensions, and Shaders (Indie and Pro License features).

    For the Standard License, the Pixel Category properties will all return values from the Logical Category.

    Here's an example of what the property conventions look like:

    def tick args
      # Logical width
      args.grid.w
    
      # Width in pixels
      args.grid.w_px
    end
    

    Note: Grid properties are globally accessible via $grid.

    NOTE:

    All Grid properties that follow take origin_name, and orientation into consideration.

    bottom link

    Returns value that represents the bottom of the grid.

    Given that the logical canvas is 720p, these are the values that bottom may return:

    top link

    Returns value that represents the top of the grid.

    left link

    Returns value that represents the left of the grid.

    right link

    Returns the x value that represents the right of the grid.

    rect link

    Returns a rectangle Primitive that represents the grid.

    w link

    Returns the grid's width.

    h link

    Returns the grid's width.

    aspect_ratio_w link

    Returns either 16 or 9 based on orientation.

    aspect_ratio_h link

    Returns either 16 or 9 based on orientation.

    HD, HighDPI, and All Screen Modes link

    The following properties are available to Pro License holders. These features are enabled via ./mygame/metadata/game_metadata.txt:

    NOTE:

    For a demonstration of these configurations/property usages, see: ./samples/07_advanced_rendering/03_allscreen_properties.

    When All Screen mode is enabled (hd_letterbox=false), you can render outside of the 1280x720 safe area. The 1280x720 logical canvas will be centered within the screen and scaled to one of the following closest/best-fit resolutions.

    All Screen Properties link

    These properties provide dimensions of the screen outside of the 16:9 safe area as logical 720p values.

    NOTE:

    With the main canvas being centered in the screen, allscreen_left and allscreen_bottom may return negative numbers for origin :bottom_left since x: 0, y: 0 might not align with the bottom left border of the game window.

    NOTE:

    It is strongly recommended that you don't use All Screen properties for any elements the player would interact with (eg buttons in an options menu) as they could get rendered underneath a "notch" on a mobile device or at the far edge of an ultrawide display.

    Logical, Point, Pixel Category Value Comparisons

    NOTE:

    Reminder: it's unlikely that you'll use any of the _px variants. The explanation that follows if for those that want the nitty gritty details.

    Here are the values that a 16-inch MacBook Pro would return for allscreen_ properties.

    Device Specs:

    Spec               | Value       |
    ------------------ | ----------- |
    Aspect Ratio       | 16:10       |
    Points Resolution  | 1728x1080   |
    Screen Resolution  | 3456x2160   |
    

    Game Settings:

    The property breakdown is:

    Property              | Value      |
    --------------------- | ---------- |
    Left/Right            |            |
    left                  | 0          |
    left_px               | 0          |
    right                 | 1280       |
    right_px              | 3456       |
    All Screen Left/Right |            |
    allscreen_left        | 0          |
    allscreen_left_px     | 0          |
    allscreen_right       | 1280      |
    allscreen_right_px    | 1728      |
    allscreen_offset_x    | 0         |
    allscreen_offset_x_px | 0         |
    op/Bottom             |           |
    bottom                | 0         |
    bottom_px             | 0         |
    top                   | 720       |
    top_px                | 1944      |
    All Screen Top/Bottom |           |
    allscreen_bottom      | -40       |
    allscreen_bottom_px   | -108      |
    allscreen_top         | 780       |
    allscreen_top_px      | 2052      |
    allscreen_offset_y    | 40        |
    allscreen_offset_y_px | 108       |
    

    Texture Atlases link

    texture_scale link

    Returns a decimal value representing the rendering scale of the game.

    texture_scale_enum link

    Returns an integer value representing the rendering scale of the game at a best-fit value. For example, given a render scale of 2.7, the textures atlas that will be selected will be 1880p (enum_value 250).

    Given the following code:

    def tick args
      args.outputs.sprites << { x: 0, y: 0, w: 100, h: 100, path: "sprites/player.png" }
    end
    

    The sprite path of sprites/player.png will be replaced according to the following naming conventions (fallback to a lower resolution is automatically handled if a sprite with naming convention isn't found):

    Array link

    The Array class has been extend to provide methods that will help in common game development tasks. Array is one of the most powerful classes in Ruby and a very fundamental component of Game Toolkit.

    map_2d link

    Assuming the array is an array of arrays, Given a block, each 2D array index invoked against the block. A 2D array is a common way to store data/layout for a stage.

    repl do
      stage = [
        [:enemy, :empty, :player],
        [:empty, :empty,  :empty],
        [:enemy, :empty,  :enemy],
      ]
    
      occupied_tiles = stage.map_2d do |row, col, tile|
        if tile == :empty
          nil
        else
          [row, col, tile]
        end
      end.reject_nil
    
      puts "Stage:"
      puts stage
    
      puts "Occupied Tiles"
      puts occupied_tiles
    end
    

    include_any? link

    Given a collection of items, the function will return true if any of self's items exists in the collection of items passed in:

    l1 = [:a, :b, :c]
    result = l1.include_any?(:b, :c, :d)
    puts result # true
    
    l1 = [:a, :b, :c]
    l2 = [:b, :c, :d]
    # returns true, but requires the parameter to be "splatted"
    # consider using (l1 & l2) instead
    result = l1.include_any?(*l2)
    puts result # true
    
    # & (bit-wise and) operator usage
    l1 = [:a, :b, :c]
    l2 = [:d, :c]
    result = (l1 & l2)
    puts result # [:c]
    
    # | (bit-wise or) operator usage
    l1 = [:a, :b, :c, :a]
    l2 = [:d, :f, :a]
    result = l1 | l2
    puts result # [:d, :f, :a, :b, :c]
    

    any_intersect_rect? link

    Assuming the array contains objects that respond to left, right, top, bottom, this method returns true if any of the elements within the array intersect the object being passed in. You are given an optional parameter called tolerance which informs how close to the other rectangles the elements need to be for it to be considered intersecting.

    The default tolerance is set to 0.1, which means that the primitives are not considered intersecting unless they are overlapping by more than 0.1.

    repl do
      # Here is a player class that has position and implement
      # the ~attr_rect~ contract.
      class Player
        attr_rect
        attr_accessor :x, :y, :w, :h
    
        def initialize x, y, w, h
          @x = x
          @y = y
          @w = w
          @h = h
        end
    
        def serialize
          { x: @x, y: @y, w: @w, h: @h }
        end
    
        def inspect
          "#{serialize}"
        end
    
        def to_s
          "#{serialize}"
        end
      end
    
      # Here is a definition of two walls.
      walls = [
         [10, 10, 10, 10],
         { x: 20, y: 20, w: 10, h: 10 },
       ]
    
      # Display the walls.
      puts "Walls."
      puts walls
      puts ""
    
      # Check any_intersect_rect? on player
      player = Player.new 30, 20, 10, 10
      puts "Is Player #{player} touching wall?"
      puts (walls.any_intersect_rect? player)
      # => false
      # The value is false because of the default tolerance is 0.1.
      # The overlap of the player rect and any of the wall rects is
      # less than 0.1 (for those that intersect).
      puts ""
    
      player = Player.new 9, 10, 10, 10
      puts "Is Player #{player} touching wall?"
      puts (walls.any_intersect_rect? player)
      # => true
      puts ""
    end
    

    map_2d link

    Assuming the array is an array of arrays, Given a block, each 2D array index invoked against the block. A 2D array is a common way to store data/layout for a stage.

    repl do
      stage = [
        [:enemy, :empty, :player],
        [:empty, :empty,  :empty],
        [:enemy, :empty,  :enemy],
      ]
    
      occupied_tiles = stage.map_2d do |row, col, tile|
        if tile == :empty
          nil
        else
          [row, col, tile]
        end
      end.reject_nil
    
      puts "Stage:"
      puts stage
    
      puts "Occupied Tiles"
      puts occupied_tiles
    end
    

    each link

    The function, given a block, invokes the block for each item in the Array. Array#each is synonymous to for each constructs in other languages.

    Example of using Array#each in conjunction with args.state and args.outputs.sprites to render sprites to the screen:

    def tick args
      # define the colors of the rainbow in ~args.state~
      # as an ~Array~ of ~Hash~es with :order and :name.
      # :order will be used to determine render location
      #  and :name will be used to determine sprite path.
      args.state.rainbow_colors ||= [
        { order: 0, name: :red    },
        { order: 1, name: :orange },
        { order: 2, name: :yellow },
        { order: 3, name: :green  },
        { order: 4, name: :blue   },
        { order: 5, name: :indigo },
        { order: 6, name: :violet },
      ]
    
      # render sprites diagonally to the screen
      # with a width and height of 50.
      args.state
          .rainbow_colors
          .each do |color| # <-- ~Array#each~ usage
            args.outputs.sprites << [
              color[:order] * 50,
              color[:order] * 50,
              50,
              50,
              "sprites/square-#{color[:name]}.png"
            ]
          end
    end
    

    reject_nil link

    Returns an Enumerable rejecting items that are nil, this is an alias for Array#compact:

    repl do
      a = [1, nil, 4, false, :a]
      puts a.reject_nil
      # => [1, 4, false, :a]
      puts a.compact
      # => [1, 4, false, :a]
    end
    

    reject_false link

    Returns an \Enumerable\ rejecting items that are \nil\ or \false\.

    repl do
      a = [1, nil, 4, false, :a]
      puts a.reject_false
      # => [1, 4, :a]
    end
    

    product link

    Returns all combinations of values between two arrays.

    Here are some examples of using product. Paste the following code at the bottom of main.rb and save the file to see the results:

    repl do
      a = [0, 1]
      puts a.product
      # => [[0, 0], [0, 1], [1, 0], [1, 1]]
    end
    
    repl do
      a = [ 0,  1]
      b = [:a, :b]
      puts a.product b
      # => [[0, :a], [0, :b], [1, :a], [1, :b]]
    end
    

    Numeric link

    The Numeric class has been extend to provide methods that will help in common game development tasks.

    frame_index link

    This function is helpful for determining the index of frame-by-frame sprite animation. The numeric value self represents the moment the animation started.

    frame_index takes three additional parameters:

    frame_index will return nil if the time for the animation is out of bounds of the parameter specification.

    Example using variables:

    def tick args
      start_looping_at = 0
      number_of_sprites = 6
      number_of_frames_to_show_each_sprite = 4
      does_sprite_loop = true
    
      sprite_index =
        start_looping_at.frame_index number_of_sprites,
                                     number_of_frames_to_show_each_sprite,
                                     does_sprite_loop
    
      sprite_index ||= 0
    
      args.outputs.sprites << [
        640 - 50,
        360 - 50,
        100,
        100,
        "sprites/dragon-#{sprite_index}.png"
      ]
    end
    

    Example using named parameters. The named parameters version allows you to also specify a repeat_index which is useful if your animation has starting frames that shouldn't be considered when looped:

    def tick args
      start_looping_at = 0
    
      sprite_index =
        start_looping_at.frame_index count: 6,
                                     hold_for: 4,
                                     repeat: true,
                                     repeat_index: 0,
                                     tick_count_override: Kernel.tick_count
    
      sprite_index ||= 0
    
      args.outputs.sprites << [
        640 - 50,
        360 - 50,
        100,
        100,
        "sprites/dragon-#{sprite_index}.png"
      ]
    end
    

    The named parameter variant of frame_index is also available on Numeric:

    def tick args
      sprite_index =
        Numeric.frame_index start_at: 0,
                            count: 6, # or frame_count: 6 (if both are provided frame_count will be used)
                            hold_for: 4,
                            repeat: true,
                            repeat_index: 0,
                            tick_count_override: Kernel.tick_count
    
      sprite_index ||= 0
    
      args.outputs.sprites << [
        640 - 50,
        360 - 50,
        100,
        100,
        "sprites/dragon-#{sprite_index}.png"
      ]
    end
    

    Another example where frame_index is applied to a sprite sheet.

    def tick args
      index = Numeric.frame_index start_at: 0,
                                  frame_count: 7,
                                  repeat: true
      args.outputs.sprites << {
        x: 0,
        y: 0,
        w: 32,
        h: 32,
        source_x: 32 * index,
        source_y: 0,
        source_w: 32,
        source_h: 32,
        path: "sprites/misc/explosion-sheet.png"
      }
    end
    

    elapsed_time link

    For a given number, the elapsed frames since that number is returned. `Kernel.tick_count` is used to determine how many frames have elapsed. An optional numeric argument can be passed in which will be used instead of `Kernel.tick_count`.

    Here is an example of how elapsed_time can be used.

    def tick args
      args.state.last_click_at ||= 0
    
      # record when a mouse click occurs
      if args.inputs.mouse.click
        args.state.last_click_at = Kernel.tick_count
      end
    
      # Use Numeric#elapsed_time to determine how long it's been
      if args.state.last_click_at.elapsed_time > 120
        args.outputs.labels << [10, 710, "It has been over 2 seconds since the mouse was clicked."]
      end
    end
    

    And here is an example where the override parameter is passed in:

    def tick args
      args.state.last_click_at ||= 0
    
      # create a state variable that tracks time at half the speed of Kernel.tick_count
      args.state.simulation_tick = Kernel.tick_count.idiv 2
    
      # record when a mouse click occurs
      if args.inputs.mouse.click
        args.state.last_click_at = args.state.simulation_tick
      end
    
      # Use Numeric#elapsed_time to determine how long it's been
      if (args.state.last_click_at.elapsed_time args.state.simulation_tick) > 120
        args.outputs.labels << [10, 710, "It has been over 4 seconds since the mouse was clicked."]
      end
    end
    

    elapsed? link

    Returns true if Numeric#elapsed_time is greater than the number. An optional parameter can be passed into elapsed? which is added to the number before evaluating whether elapsed? is true.

    Example usage (no optional parameter):

    def tick args
      args.state.box_queue ||= []
    
      if args.state.box_queue.empty?
        args.state.box_queue << { name: :red,
                                  destroy_at: Kernel.tick_count + 60 }
        args.state.box_queue << { name: :green,
                                  destroy_at: Kernel.tick_count + 60 }
        args.state.box_queue << { name: :blue,
                                  destroy_at: Kernel.tick_count + 120 }
      end
    
      boxes_to_destroy = args.state
                             .box_queue
                             .find_all { |b| b[:destroy_at].elapsed? }
    
      if !boxes_to_destroy.empty?
        puts "boxes to destroy count: #{boxes_to_destroy.length}"
      end
    
      boxes_to_destroy.each { |b| puts "box #{b} was elapsed? on #{Kernel.tick_count}." }
    
      args.state.box_queue -= boxes_to_destroy
    end
    

    Example usage (with optional parameter):

    def tick args
      args.state.box_queue ||= []
    
      if args.state.box_queue.empty?
        args.state.box_queue << { name: :red,
                                  create_at: Kernel.tick_count + 120,
                                  lifespan: 60 }
        args.state.box_queue << { name: :green,
                                  create_at: Kernel.tick_count + 120,
                                  lifespan: 60 }
        args.state.box_queue << { name: :blue,
                                  create_at: Kernel.tick_count + 120,
                                  lifespan: 120 }
      end
    
      # lifespan is passed in as a parameter to ~elapsed?~
      boxes_to_destroy = args.state
                             .box_queue
                             .find_all { |b| b[:create_at].elapsed? b[:lifespan] }
    
      if !boxes_to_destroy.empty?
        puts "boxes to destroy count: #{boxes_to_destroy.length}"
      end
    
      boxes_to_destroy.each { |b| puts "box #{b} was elapsed? on #{Kernel.tick_count}." }
    
      args.state.box_queue -= boxes_to_destroy
    end
    

    to_sf link

    Returns a "string float" representation of a number with two decimal places. eg: 5.8778 will be shown as 5.88.

    to_si link

    Returns a "string int" representation of a number with underscores for thousands seperator. eg: 50000.8778 will be shown as 50_000.

    new? link

    Returns true if Numeric#elapsed_time == 0. Essentially communicating that number is equal to the current frame.

    Example usage:

    def tick args
      args.state.box_queue ||= []
    
      if args.state.box_queue.empty?
        args.state.box_queue << { name: :red,
                                  create_at: Kernel.tick_count + 60 }
      end
    
      boxes_to_spawn_this_frame = args.state
                                      .box_queue
                                      .find_all { |b| b[:create_at].new? }
    
      boxes_to_spawn_this_frame.each { |b| puts "box #{b} was new? on #{Kernel.tick_count}." }
    
      args.state.box_queue -= boxes_to_spawn_this_frame
    end
    

    Kernel link

    Kernel in the DragonRuby Runtime has patches for how standard out is handled and also contains a unit of time in games called a tick.

    tick_count link

    Returns the current tick of the game. This value is reset if you call $gtk.reset.

    global_tick_count link

    Returns the current tick of the application from the point it was started. This value is never reset.