diff options
| author | Amir Rajan <[email protected]> | 2020-07-30 16:45:24 -0500 |
|---|---|---|
| committer | Amir Rajan <[email protected]> | 2020-07-30 16:58:40 -0500 |
| commit | eb8b770e1952af371832f5f31e00dae09d498cf9 (patch) | |
| tree | 0884e11fedbfa01c7ce9f1e028cb6c43a13d2052 /docs/docs.txt | |
| parent | 8d000345a3489988e9e86ee9fda9dcc6c70b7012 (diff) | |
| download | dragonruby-game-toolkit-contrib-eb8b770e1952af371832f5f31e00dae09d498cf9.tar.gz dragonruby-game-toolkit-contrib-eb8b770e1952af371832f5f31e00dae09d498cf9.zip | |
OSS synced with 1.12.
Diffstat (limited to 'docs/docs.txt')
| -rw-r--r-- | docs/docs.txt | 1649 |
1 files changed, 1649 insertions, 0 deletions
diff --git a/docs/docs.txt b/docs/docs.txt new file mode 100644 index 0000000..8627f1b --- /dev/null +++ b/docs/docs.txt @@ -0,0 +1,1649 @@ +* DragonRuby Game Toolkit Live Docs + +The information contained here is all available via the DragonRuby +Console. You can Open the DragonRuby Console by pressing [`] [~] [²] +[^] [º] or [§] within your game. + +To search docs you can type ~docs_search "SEARCH TERM"~ or if you want +to get fancy you can provide a ~lambda~ to filter documentation: + +#+begin_src + docs_search { |entry| (entry.include? "Array") && (!entry.include? "Enumerable") } +#+end_src + +[[docs_search.gif]] + +* Hello World + +Welcome to DragonRuby Game Toolkit. Take the steps below to get started. + +* Join the Discord and Subscribe to the News Letter + +Our Discord channel is [[http://discord.dragonruby.org]]. + +The News Letter will keep you in the loop with regards to current +DragonRuby Events: [[http://dragonrubydispatch.com]]. + +Those who use DragonRuby are called Dragon Riders. This identity is +incredibly important to us. When someone asks you: + +#+begin_quote +What game engine do you use? +#+end_quote + +Reply with: + +#+begin_quote +I am a Dragon Rider. +#+end_quote + +* Watch Some Intro Videos + +Each video is only 20 minutes and all of them will fit into a lunch +break. So please watch them: + +1. Beginner Introduction to DragonRuby Game Toolkit: [[https://youtu.be/ixw7TJhU08E]] +2. Intermediate Introduction to Ruby Syntax: [[https://youtu.be/HG-XRZ5Ppgc]] +3. Intermediate Introduction to Arrays in Ruby: [[https://youtu.be/N72sEYFRqfo]] + +The second and third videos are not required if you are proficient +with Ruby, but *definitely* watch the first one. + +You may also want to try this free course provided at +[[http://dragonruby.school]]. + +* Getting Started Tutorial + +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 + +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 + +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 + +Ok, here are few rules with regards to game development with GTK: + +- Your game is all going to happen under one function ... +- that runs 60 times a second ... +- and has to tell the computer what to draw each time. + +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. + +#+begin_src ruby + def tick args + args.outputs.labels << [580, 400, 'Hello World!'] + end +#+end_src + +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 + +~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. + +#+begin_src ruby + # 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 +#+end_src + +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 + +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: + +#+begin_src ruby + def tick args + args.outputs.labels << [580, 400, 'Hello World!'] + args.outputs.sprites << [576, 100, 128, 101, 'dragonruby.png'] + end +#+end_src + +(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 + +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: + +#+begin_src ruby + 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 +#+end_src + +Now you can see that this function is getting called a lot! + +** Game State + +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 and have it survive +past the life of the function call. 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 + +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 + +Now, let's move that image around. + +#+begin_src ruby + 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 +#+end_src + +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 + +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: + +#+begin_src +sudo raspi-config +#+end_src + +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 + +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. + +** IMPORTANT: Go Through All Of The Sample Apps! Study Them Thoroughly!! + +Now that you've completed the Hello World tutorial. Head over to the +`samples` directory. It is very very important that you study the +sample apps thoroughly! Go through them in order. Here is a short +description of each sample app. + +1. 00_beginner_ruby_primer: This is an interactive tutorial that shows how to render ~solid~s, animated ~sprite~s, ~label~s. +2. 00_intermediate_ruby_primer: This is a set of sample Ruby snippets that give you a high level introduction to the programming language. +3. 01_api_01_labels: Various ways to render ~label~s. +4. 01_api_02_lines: Various ways to render ~line~s. +5. 01_api_03_rects: Sample app shows various ways to render ~solid~s and ~border~s. +6. 01_api_04_sprites: Sample app shows various ways to render ~sprite~s. +7. 01_api_05_keyboard: Hows how to get keyboard input from the user. +8. 01_api_06_mouse: Hows how to get mouse mouse position. +9. 01_api_07_point_to_rect: How to get mouse input from the user and shows collision/hit detection. +10. 01_api_08_rect_to_rect: Hit detection/collision between two rectangles. +11. 01_api_10_controller: Interaction with a USB/Bluetooth controller. +12. 01_api_99_tech_demo: All the different render primitives along with using ~render_targets~. +13. 02_collision_01_simple: Collision detection with dynamically moving bodies. +14. 02_collision_02_moving_objects: Collision detection between many primitives, simple platformer physics, and keyboard input. +15. 02_collision_03_entities: Collision with entities and serves as a small introduction to ECS (entity component system). +16. 02_collision_04_ramp_with_debugging: How ramp trajectory can be calculated. +17. 02_collision_05_ramp_with_debugging_two: How ramp trajectory can be calculated. +18. 02_sprite_animation_and_keyboard_input: How to animate a sprite based off of keyboard input. +19. 03_mouse_click: How to determine what direction/vector a mouse was clicked relative to a player. +20. 04_sounds: How to play sounds and work with buttons. +21. 05_mouse_move: How to determine what direction/vector a mouse was clicked relative to a player. +22. 05_mouse_move_paint_app: Represents a simple paint app. +23. 05_mouse_move_tile_editor: A starting point for a tile editor. +24. 06_coordinate_systems: Shows the two origin systems within Game Toolkit where the origin is in the center and where the origin is at the bottom left. +25. 07_render_targets: Shows a powerful concept called ~render_target~s. You can use this to programatically create sprites (it's also useful for representing parts of a scene as if it was a view port/camera). +26. 07_render_targets_advanced: Advanced usage of ~render_target~s. +27. 08_platformer_collisions: Axis aligned collision along with platformer physics. +28. 08_platformer_collisions_metroidvania: How to save map data and place sprites live within a game. +29. 08_platformer_jumping_inertia: Jump physics and how inertia affects collision. +30. 09_controller_analog_usage_advanced_sprites: Extended properties of a ~sprite~ and how to change the rotation anchor point and render a subset/tile of a sprite. +31. 09_sprite_animation_using_tile_sheet: How to perform sprite animates using a tile sheet. +32. 10_save_load_game: Save and load game data. +33. 11_coersion_of_primitives: How primitives of one specific type can be rendered as another primitive type. +34. 11_hash_primitives: How primitives can be represented using a ~Hash~. +35. 12_controller_input_sprite_sheet_animations: How to leverage vectors to move a player around the screen. +36. 12_top_down_area: How to render a top down map and how to manage collision of a player. +37. 13_01_easing_functions: How to use lerping functions to define animations/movement. +38. 13_02_cubic_bezier: How to create a bezier curve using lines. +39. 13_03_easing_using_spline: How a collection of bezier curves can be used to define an animation. +40. 13_04_parametric_enemy_movement: How to define the movement of enemies and projectiles using lerping/parametric functions. +41. 14_sprite_limits: Upper limit for how many sprites can be rendered to the screen. +42. 14_sprite_limits_static_references: Upper limit for how many sprites can be rendered to the screen using ~static~ output collections (which are updated by reference as opposed to by value). +43. 15_collision_limits: How many collisions can be processed across many primitives. +44. 18_moddable_game: How you can make a game where content is authored by the player (modding support). +45. 19_lowrez_jam_01_hello_world: How to use ~render_targets~ to create a low resolution game. +46. 19_lowrez_jam_02_buttons: How to use ~render_targets~ to create a low resolution game. +47. 19_lowrez_jam_03_space_shooter: How to use ~render_targets~ to create a low resolution game. +48. 20_roguelike_starting_point: A starting point for a roguelike and explores concepts such as line of sight. +49. 20_roguelike_starting_point_two: A starting point for a roguelike where sprites are provided from a tile map/tile sheet. +50. 21_mailbox_usage: How to do interprocess communication. +51. 22_trace_debugging: Debugging techniques and tracing execution through your game. +52. 22_trace_debugging_classes: Debugging techniques and tracing execution through your game. +53. 23_hexagonal_grid: How to make a tactical grid/map made of hexagons. +54. 23_isometric_grid: How to make a tactical grid/map made of isometric sprites. +55. 24_http_example: How to make http requests. +56. 25_3d_experiment_01_square: How to create 3D objects. +57. 26_jam_craft: Starting point for crafting game. It also shows how to customize the mouse cursor. +58. 99_sample_game_basic_gorillas: Reference implementation of a full game. Topics covered: physics, keyboard input, collision, sprite animation. +59. 99_sample_game_clepto_frog: Reference implementation of a full game. Topics covered: camera control, spring/rope physics, scene orchestration. +60. 99_sample_game_dueling_starships: Reference implementation that shows local multiplayer. Topics covered: vectors, particles, friction, inertia. +61. 99_sample_game_flappy_dragon: Reference implementation that is a clone of Flappy Bird. Topics covered: scene orchestration, collision, sound, sprite animations, lerping. +62. 99_sample_game_pong: Reference implementation of pong. +63. 99_sample_game_return_of_serenity: Reference implementation of low resolution story based game. +64. 99_sample_game_the_little_probe: Reference implementation of a full game. Topics covered: Arbitrary collision detection, loading map data, bounce/ball physics. +65. 99_sample_nddnug_workshop: Reference implementation of a full game. Topics covered: vectors, controller input, sound, trig functions. +66. 99_sample_snakemoji: Shows that Ruby supports coding with emojis. +67. 99_zz_gtk_unit_tests: A collection of unit tests that exercise parts of DragonRuby's API. + + + +* Deploying To Itch.io + +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 + +Log into Itch.io and go to [[https://itch.io/game/new]]. + +- Title: Give your game a Title. This value represents your `gametitle`. +- Project URL: Set your project url. This value represents your `gameid`. +- Classification: Keep this as Game. +- Kind of Project: Select HTML from the drop down list. Don't worry, + the HTML project type _also supports binary downloads_. +- Uploads: Skip this section for now. + +You can fill out all the other options later. + +** Update Your Game's Metadata + +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. + +#+begin_src +devid=bob +devtitle=Bob The Game Developer +gameid=mygame +gametitle=My Game +version=0.1 +#+end_src + +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 + +Open up the terminal and run this from the command line: + +#+begin_src +./dragonruby-publish --only-package mygame +#+end_src + +(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. + +For the HTML version of your game after you upload it. Check the checkbox labeled +"This file will be played in the browser". + +For subsequent updates you can use an automated deployment to Itch.io: + +#+begin_src +./dragonruby-publish mygame +#+end_src + +DragonRuby will package _and publish_ 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. + +** DragonRuby's Philosophy + +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 principals. + +*** Challenge The Status Quo + +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: + +#+begin_quote +But that's how we've always done it. +#+end_quote + +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. + +*** Release Often And Quickly + +The biggest mistake game devs make is spending too much time in +isolation building their game. Release something, however small, and +release it quickly. + +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. + +Remember: + +#+begin_quote +Real artists ship. +#+end_quote + +*** Sustainable And Ethical Monetization + +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. + +*** Sustainable And Ethical Open Source + +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 + +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 + +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 + +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). + +* How To Determine What Frame You Are On + +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~. + +#+begin_src ruby + def tick args + args.outputs.labels << [10, 670, "#{args.state.tick_count}"] + end +#+end_src + +* How To Get Current Framerate + +Current framerate is a top level property on the Game Toolkit Runtime +and is accessible via ~args.gtk.current_framerate~. + +#+begin_src ruby + def tick args + args.outputs.labels << [10, 710, "framerate: #{args.gtk.current_framerate.round}"] + end +#+end_src + +* How To Render A Sprite Using An Array + +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. + +#+begin_src ruby + def tick args + args.outputs.sprites << [ + 640 - 50, # X + 360 - 50, # Y + 100, # W + 100, # H + 'sprites/square-blue.png' # PATH + ] + end +#+end_src + +* More Sprite Properties As An Array + +Here are all the properties you can set on a sprite. + +#+begin_src ruby + def tick args + args.outputs.sprites << [ + 100, # X + 100, # Y + 32, # W + 64, # H + 'sprites/square-blue.png', # PATH + 0, # ANGLE + 255, # ALPHA + 0, # RED_SATURATION + 255, # GREEN_SATURATION + 0 # BLUE_SATURATION + ] + end +#+end_src + +* Different Sprite Representations + +Using ordinal positioning can get a little unruly given so many +properties you have control over. + +You can represent a sprite as a ~Hash~: + +#+begin_src ruby + 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_x: 0, + source_y: 0, + source_w: -1, + source_h: -1, + flip_vertically: false, + flip_horizontally: false, + angle_anchor_x: 0.5, + angle_anchor_y: 1.0 + } + end +#+end_src + +You can represent a sprite as an ~object~: + +#+begin_src ruby + # Create type with ALL sprite properties AND primitive_marker + class Sprite + attr_accessor :x, :y, :w, :h, :path, :angle, :a, :r, :g, :b, + :source_x, :source_y, :source_w, :source_h, + :tile_x, :tile_y, :tile_w, :tile_h, + :flip_horizontally, :flip_vertically, + :angle_anchor_x, :angle_anchor_y + + def primitive_marker + :sprite + end + end + + class BlueSquare < Sprite + def initialize opts + @x = opts[:x] + @y = opts[:y] + @w = opts[:w] + @h = opts[: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 +#+end_src + +* How To Render A Label + +~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. + +Here is the minimum code: + +#+begin_src + def tick args + # X Y TEXT + args.outputs.labels << [640, 360, "I am a black label."] + end +#+end_src + +* A Colored Label + +#+begin_src + 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 +#+end_src + +* Extended Label Properties + +#+begin_src + 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 +#+end_src + +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~ + +You can add additional metadata about your game within a label, which requires you to use a `Hash` instead. + +#+begin_src + def tick args + args.outputs.labels << { + x: 200, + y: 550, + text: "dragonruby", + size_enum: 2, + alignment_enum: 1, + r: 155, + g: 50, + b: 50, + a: 255, + font: "fonts/manaspc.ttf", + # 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 +#+end_src + +* Getting The Size Of A Piece Of Text + +You can get the render size of any string using ~args.gtk.calcstringbox~. + +#+begin_src ruby + 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 +#+end_src + +* How To Play A Sound + +Sounds that end ~.wav~ will play once: + +#+begin_src ruby + def tick args + # Play a sound every second + if (args.state.tick_count % 60) == 0 + args.outputs.sounds << 'something.wav' + end + end +#+end_src + +Sounds that end ~.ogg~ is considered background music and will loop: + +#+begin_src ruby + def tick args + # Start a sound loop at the beginning of the game + if args.state.tick_count == 0 + args.outputs.sounds << 'background_music.ogg' + end + end +#+end_src + +If you want to play a ~.ogg~ once as if it were a sound effect, you can do: + +#+begin_src ruby + def tick args + # Play a sound every second + if (args.state.tick_count % 60) == 0 + args.gtk.queue_sound 'some-ogg.ogg' + end + end +#+end_src + +* Using ~args.state~ To Store Your Game State + +~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. + +#+begin_src + def tick args + # initialize your game state ONCE + args.player.x ||= 0 + args.player.y ||= 0 + args.player.hp ||= 100 + + # increment the x position of the character by one every frame + args.player.x += 1 + + # Render a sprite with a label above the sprite + args.outputs.sprites << [ + args.player.x, + args.player.y, + 32, 32, + "player.png" + ] + + args.outputs.labels << [ + args.player.x, + args.player.y - 50, + args.player.hp + ] + end +#+end_src + +* Frequently Asked Questions, Comments, and Concerns + +Here are questions, comments, and concerns that frequently come +up. + +** Frequently Asked Questions + +*** What is DragonRuby LLP? + +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): + +- Game Toolkit (GTK): A 2D game engine that is compatible with modern + gaming platforms. [Home Page]() [FAQ Page]() +- RubyMotion (RM): A compiler toolchain that allows you to build native, cross-platform mobile + apps. [Home Page]() [FAQ Page]() +- Commandline Toolkit (CTK): A zero dependency, zero installation Ruby + environment that works on Windows, Mac, and Linux. [Home Page]() [FAQ Page]() + +All of the products above leverage a shared core called DragonRuby. + +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: We leave the "A DragonRuby LLP Product" off of this one because +that just sounds really weird. + +NOTE: Devs who use DragonRuby are "Dragon Riders/Riders of Dragons". That's a bad ass +identifier huh? + +*** What is DragonRuby? + +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 langauge 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 Scadia. + +*** What does Multilevel Cross-platform mean? + +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): + +- Level 1 we leverage a good portion of mRuby. +- Level 2 consists of optimizations to mRuby we've made given that our + target platforms are well known. +- Level 3 consists of portable C libraries and their Ruby + C-Extensions. + +Levels 1 through 3 are fairly commonplace in many runtime +implemenations (with level 1 being the most portable, and level 3 +being the fastest). But the DragonRuby Runtime has taken things a +bit further: + +- Level 4 consists of shared abstractions around hardware I/O and operating + system resources. This level leverages open source and proprietary + components within Simple DirectMedia Layer (a lowlevel multimedia + component library that has been in active development for 22 years + and counting). + +- Level 5 is a codegeneration layer which creates metadata that allows + for native interopability with host runtime libraries. It also + includes OS specific message pump orchestrations. + +- Level 6 is a Ahead of Time/Just in Time Ruby compiler built with LLVM. This + compiler outputs _very_ fast platform specific bitcode, but only + supports a subset of the Ruby language specification. + +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 seperation 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 implentation that takes all the lessons +we've learned from MRI/CRuby, and merges it with the latest and greatest +compiler and OSS technologies. + +** Frequent Comments + +*** But Ruby is dead. + +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. Every one chases the new and shiny. + +What really matters is _quality/maturity_. Here is the latest (StackOverflow +Survey sorted by highest paid developers)[https://insights.stackoverflow.com/survey/2019#top-paying-technologies]. + +Let's stop making this comment shall we? + +*** But Ruby is slow. + +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. + +*** Dynamic langauges are slow. + +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 langauge 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: If you _are_ hand writing LLVM IR, we are always open to +bringing on new partners with such a skillset. Email us ^_^. + +** Frequent Concerns + +*** DragonRuby is not open source. That's not right. + +The current state of open source is unsustainable. Contributors work +for free, most all open source repositories are serverly understaffed, +and burnout from core members is rampant. + +We believe in open source very strongly. Parts of DragonRuby are +infact, 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. + +*** DragonRuby is for pay. You should offer a free version. + +If you can afford to pay for DragonRuby, you should (and will). We don't go +around telling writers 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: + +- Your income is below $2,000.00 (USD) per month. +- You are under 18 years of age. +- You are a student of any type: traditional public school, home + schooling, college, bootcamp, or online. +- You are a teacher, mentor, or parent who wants to teach a kid how to code. +- You work/worked in public service or at a charitable organization: + for example public office, army, or any 501(c)(3) organization. + +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](). But it won't do the +runtime justice. Or just come to our [Slack]() or [Discord]() channel +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 peiple 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. + +That won't happen if the development world stop asking for free stuff +and non-trivially compensate open source developers. Look, 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 happen. + +But, in the event that sad day comes, our partnershiop bylaws state that +_all_ DragonRuby IP that can be legally open sourced, will be released +under a permissive license. + +* DOCS: ~GTK::Runtime~ +The GTK::Runtime class is the core of DragonRuby. It is globally accessible via ~$gtk~. + +* DOCS: ~GTK::Runtime#calcstringbox~ +This function returns the width and height of a string. + +#+begin_src ruby + def tick args + args.state.string_size ||= args.gtk.calcstringbox "Hello World" + args.state.string_size_font_size ||= args.gtk.calcstringbox "Hello World" + end +#+end_src + +* DOCS: ~GTK::Runtime#reset~ +This function will reset Kernel.tick_count to 0 and will remove all data from args.state. + +* DOCS: ~Array~ + +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. + + +* DOCS: ~Array#map~ + +The function given a block returns a new ~Enumerable~ of values. + +Example of using ~Array#map~ in conjunction with ~args.state~ and +~args.outputs.sprites~ to render sprites to the screen. + +#+begin_src + 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.outputs + .sprites << args.state + .rainbow_colors + .map do |color| # <-- ~Array#map~ usage + [ + color[:order] * 50, + color[:order] * 50, + 50, + 50, + "sprites/square-#{color[:name]}.png" + ] + end + end +#+end_src + + +* DOCS: ~Array#each~ + +The function, given a block, invokes the block for each item in the +~Array~. ~Array#each~ is synonymous to foreach constructs in other languages. + +Example of using ~Array#each~ in conjunction with ~args.state~ and +~args.outputs.sprites~ to render sprites to the screen: + +#+begin_src + 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 + .map do |color| # <-- ~Array#each~ usage + args.outputs.sprites << [ + color[:order] * 50, + color[:order] * 50, + 50, + 50, + "sprites/square-#{color[:name]}.png" + ] + end + end +#+end_src + + +* DOCS: ~Array#reject_nil~ + +Returns an ~Enumerable~ rejecting items that are ~nil~, this is an alias +for ~Array#compact~: + +#+begin_src + repl do + a = [1, nil, 4, false, :a] + puts a.reject_nil + # => [1, 4, false, :a] + puts a.compact + # => [1, 4, false, :a] + end +#+end_src + + +* DOCS: ~Array#reject_false~ + +Returns an `Enumerable` rejecting items that are `nil` or `false`. + +#+begin_src + repl do + a = [1, nil, 4, false, :a] + puts a.reject_false + # => [1, 4, :a] + end +#+end_src + + +* DOCS: ~Array#product~ + +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: + +#+begin_src + repl do + a = [0, 1] + puts a.product + # => [[0, 0], [0, 1], [1, 0], [1, 1]] + end +#+end_src + +#+begin_src + repl do + a = [ 0, 1] + b = [:a, :b] + puts a.product b + # => [[0, :a], [0, :b], [1, :a], [1, :b]] + end +#+end_src + + +* DOCS: ~Array#map_2d~ + +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. + +#+begin_src + 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 +#+end_src + + +* DOCS: ~Array#include_any?~ + +Given a collection of items, the function will return +~true~ if any of ~self~'s items exists in the collection of items passed in: + + +* DOCS: ~Array#any_intersect_rect?~ + +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~. + +#+begin_src + 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 +#+end_src + + +* DOCS: ~GTK::Outputs~ + +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 + +#+begin_src + def tick args + # code goes here + end +#+end_src + + +* DOCS: ~GTK::Outputs#borders~ + +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~: + +#+begin_src + def tick args + # X Y WIDTH HEIGHT + args.outputs.solids << [100, 100, 160, 90] + end +#+end_src + +You have to use ~args.outputs.borders~: + +#+begin_src + def tick args + # X Y WIDTH HEIGHT + args.outputs.borders << [100, 100, 160, 90] + end +#+end_src + + +* DOCS: ~GTK::Outputs#solids~ + +Add primitives to this collection to render a solid to the screen. + +** Rendering a solid using an Array + +Creates a solid black rectangle located at 100, 100. 160 pixels +wide and 90 pixels tall. + +#+begin_src + def tick args + # X Y WIDTH HEIGHT + args.outputs.solids << [100, 100, 160, 90] + end +#+end_src + +** Rendering a solid using an Array with colors and alpha + +The value for the color and alpha is an number between ~0~ and ~255~. The +alpha property is optional and will be set to ~255~ if not specified. + +Creates a green solid rectangle with an opacity of 50%. + +#+begin_src + def tick args + # X Y WIDTH HEIGHT RED GREEN BLUE ALPHA + args.outputs.solids << [100, 100, 160, 90, 0, 255, 0, 128] + end +#+end_src + +** Rendering a solid using a Hash + +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. + +#+begin_src + def tick args + args.outputs.solids << { + x: 0, + y: 0, + w: 100, + h: 100, + r: 0, + g: 255, + b: 0, + a: 255 + } + end +#+end_src + +** Rendering a solid using a Class + +You can also create a class with solid/border properties and render it as a primitive. +ALL properties must on the class. *Additionally*, a method called ~primitive_marker~ +must be defined on the class. + +Here is an example: + +#+begin_src + # Create type with ALL solid properties AND primitive_marker + class Solid + attr_accessor :x, :y, :w, :h, :r, :g, :b, :a + + def primitive_marker + :solid + 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 +#+end_src + + +* DOCS: ~GTK::Mouse~ + +The mouse is accessible via ~args.inputs.mouse~: + +#+begin_src ruby + def tick args + # Rendering a label that shows the mouse's x and y position (via args.inputs.mouse). + args.outputs.labels << [ + 10, + 710, + "The mouse's position is: #{args.inputs.mouse.x} #{args.inputs.mouse.y}." + ] + end +#+end_src + +The mouse has the following properties. + +- ~args.inputs.mouse.x~: Returns the x position of the mouse. +- ~args.inputs.mouse.y~: Returns the y position of the mouse. +- ~args.inputs.mouse.moved~: Returns true if the mouse moved during the tick. +- ~args.inputs.mouse.moved_at~: Returns the tick_count (~args.state.tick_count~) that the mouse was moved at. This property will be ~nil~ if the mouse didn't move. +- ~args.inputs.mouse.global_moved_at~: Returns the global tick_count (~Kernel.global_tick_count~) that the mouse was moved at. This property will be ~nil~ if the mouse didn't move. +- ~args.inputs.mouse.click~: Returns a ~GTK::MousePoint~ for that specific frame (~args.state.tick_count~) if the mouse button was pressed. +- ~args.inputs.mouse.previous_click~: Returns a ~GTK::MousePoint~ for the previous frame (~args.state.tick_count - 1~) if the mouse button was pressed. +- ~args.inputs.mouse.up~: Returns true if for that specific frame (~args.state.tick_count~) if the mouse button was released. +- ~args.inputs.mouse.point~ | ~args.inputs.mouse.position~: Returns an ~Array~ which contains the ~x~ and ~y~ position of the mouse. +- ~args.inputs.mouse.has_focus~: Returns true if the game window has the mouse's focus. +- ~args.inputs.mouse.wheel~: Returns an ~GTK::OpenEntity~ that contains an ~x~ and ~y~ property which represents how much the wheel has moved. If the wheel has not moved within the tick, this property will be ~nil~. +- ~args.inputs.mouse.button_left~: Returns true if the left mouse button is down. +- ~args.inputs.mouse.button_right~: Returns true if the right mouse button is down. +- ~args.inputs.mouse.button_middle~: Returns true if the middle mouse button is down. +- ~args.inputs.mouse.button_bits~: Gives the bits for each mouse button and its current state. + +* DOCS: ~GTK::MousePoint~ + +The ~GTK::MousePoint~ has the following properties. + +- ~x~: Integer representing the mouse's x. +- ~y~: Integer representing the mouse's y. +- ~point~: Array with the ~x~ and ~y~ values. +- ~w~: Width of the point that always returns ~0~ (included so that it can seemlessly work with ~GTK::Geometry~ functions). +- ~h~: Height of the point that always returns ~0~ (included so that it can seemlessly work with ~GTK::Geometry~ functions). +- ~left~: This value is the same as ~x~ (included so that it can seemlessly work with ~GTK::Geometry~ functions). +- ~right~: This value is the same as ~x~ (included so that it can seemlessly work with ~GTK::Geometry~ functions). +- ~top~: This value is the same as ~y~ (included so that it can seemlessly work with ~GTK::Geometry~ functions). +- ~bottom~: This value is the same as ~y~ (included so that it can seemlessly work with ~GTK::Geometry~ functions). +- ~created_at~: The tick (~args.state.tick_count~) that this structure was created. +- ~global_created_at~: The global tick (~Kernel.global_tick_count~) that this structure was created. + + +* DOCS: ~GTK::OpenEntity~ + +~GTK::OpenEntity~ is accessible within the DragonRuby's top level +~tick~ function via the ~args.state~ property. + +#+begin_src + def tick args + args.state.x ||= 100 + args.outputs.labels << [10, 710, "value of x is: #{args.state.x}."] + end +#+end_src + +The primary benefit of using ~args.state~ as opposed to instance +variables is that ~GTK::OpenEntity~ allows for arbitrary nesting +of properties without the need to create intermediate objects. + +For example: + +#+begin_src + def tick args + # intermediate player object does not need to be created + args.state.player.x ||= 100 + args.state.player.y ||= 100 + args.outputs.labels << [ + 10, + 710, + "player x, y is:#{args.state.player.x}, #{args.state.player.y}." + ] + end +#+end_src + + +* DOCS: ~GTK::OpenEntity#as_hash~ + +Returns a reference to the ~GTK::OpenEntity~ as a ~Hash~. This +property is useful when you want to treat ~args.state~ as a ~Hash~ and +invoke methods such as ~Hash#each~. + +Example: + +#+begin_src + def tick args + args.state.x ||= 100 + args.state.y ||= 100 + values = args.state + .as_hash + .map { |k, v| "#{k} #{v}" } + + args.outputs.labels << values.map.with_index do |v, i| + [ + 10, + 710 - (30 * i), + v + ] + end + end +#+end_src + + +* DOCS: ~Numeric#frame_index~ + +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: + +- How many frames exist in the sprite animation. +- How long to hold each animation for. +- Whether the animation should repeat. + +~frame_index~ will return ~nil~ if the time for the animation is out +of bounds of the parameter specification. + +Example using variables: + +#+begin_src ruby + 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 +#+end_src + +Example using named parameters: + +#+begin_src ruby + def tick args + start_looping_at = 0 + + sprite_index = + start_looping_at.frame_index count: 6, + hold_for: 4, + repeat: true, + tick_count_override: args.state.tick_count + + sprite_index ||= 0 + + args.outputs.sprites << [ + 640 - 50, + 360 - 50, + 100, + 100, + "sprites/dragon-#{sprite_index}.png" + ] + end +#+end_src + + +* DOCS: ~Kernel~ + +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. + + +* DOCS: ~Kernel::tick_count~ + +Returns the current tick of the game. This value is reset if you call $gtk.reset. + + +* DOCS: ~Kernel::global_tick_count~ + +Returns the current tick of the application from the point it was started. This value is never reset. + + |
