diff options
Diffstat (limited to 'docs/docs.html')
| -rw-r--r-- | docs/docs.html | 7865 |
1 files changed, 5816 insertions, 2049 deletions
diff --git a/docs/docs.html b/docs/docs.html index 39d0086..67cd054 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -1,27 +1,29 @@ -<html> +<html lang="en"> <head> + <meta charset="utf-8"> <title>DragonRuby Game Toolkit Documentation</title> - <link href="docs.css?ver=1617643886" rel="stylesheet" type="text/css" media="all"> + <link href="docs.css?ver=1637820563" rel="stylesheet" type="text/css" media="all"> </head> <body> - <div id='toc'> + <div id='table-of-contents'> <h1>Table Of Contents</h1> <ul> <li><a class='header-1' href='#--dragonruby-game-toolkit-live-docs'>DragonRuby Game Toolkit Live Docs</a></li> <li><a class='header-1' href='#--hello-world'>Hello World</a></li> <li><a class='header-1' href='#--join-the-discord-and-subscribe-to-the-news-letter'>Join the Discord and Subscribe to the News Letter</a></li> -<li><a class='header-1' href='#--watch-some-intro-videos'>Watch Some Intro Videos</a></li> -<li><a class='header-1' href='#--getting-started-tutorial'>Getting Started Tutorial</a></li> -<ul><li><a class='header-2' href='#---introduction'>Introduction</a></li></ul><ul><li><a class='header-2' href='#---prerequisites'>Prerequisites</a></li></ul><ul><li><a class='header-2' href='#---the-game-loop'>The Game Loop</a></li></ul><ul><li><a class='header-2' href='#---breakdown-of-the--tick--method'>Breakdown Of The <code>tick</code> Method</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-sprite'>Rendering A Sprite</a></li></ul><ul><li><a class='header-2' href='#---coordinate-system-and-virtual-canvas'>Coordinate System and Virtual Canvas</a></li></ul><ul><li><a class='header-2' href='#---game-state'>Game State</a></li></ul><ul><li><a class='header-2' href='#---there-is-no-delta-time'>There Is No Delta Time</a></li></ul><ul><li><a class='header-2' href='#---handling-user-input'>Handling User Input</a></li></ul><ul><li><a class='header-2' href='#---coding-on-a-raspberry-pi'>Coding On A Raspberry Pi</a></li></ul><ul><li><a class='header-2' href='#---conclusion'>Conclusion</a></li></ul><li><a class='header-1' href='#--deploying-to-itch.io'>Deploying To Itch.io</a></li> +<li><a class='header-1' href='#--intro-videos'>Intro Videos</a></li> +<ul><li><a class='header-2' href='#---quick-api-tour'>Quick Api Tour</a></li></ul><ul><li><a class='header-2' href='#---if-you-are-completely-new-to-ruby-and-programming'>If You Are Completely New to Ruby and Programming</a></li></ul><ul><li><a class='header-2' href='#---if-you-have-game-dev-experience'>If You Have Game Dev Experience</a></li></ul><li><a class='header-1' href='#--getting-started-tutorial'>Getting Started Tutorial</a></li> +<ul><li><a class='header-2' href='#---introduction'>Introduction</a></li></ul><ul><li><a class='header-2' href='#---prerequisites'>Prerequisites</a></li></ul><ul><li><a class='header-2' href='#---the-game-loop'>The Game Loop</a></li></ul><ul><li><a class='header-2' href='#---breakdown-of-the--tick--method'>Breakdown Of The <code>tick</code> Method</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-sprite'>Rendering A Sprite</a></li></ul><ul><li><a class='header-2' href='#---coordinate-system-and-virtual-canvas'>Coordinate System and Virtual Canvas</a></li></ul><ul><li><a class='header-2' href='#---game-state'>Game State</a></li></ul><ul><li><a class='header-2' href='#---there-is-no-delta-time'>There Is No Delta Time</a></li></ul><ul><li><a class='header-2' href='#---handling-user-input'>Handling User Input</a></li></ul><ul><li><a class='header-2' href='#---coding-on-a-raspberry-pi'>Coding On A Raspberry Pi</a></li></ul><ul><li><a class='header-2' href='#---conclusion'>Conclusion</a></li></ul><li><a class='header-1' href='#--deploying-to-itch-io'>Deploying To Itch.io</a></li> <ul><li><a class='header-2' href='#---creating-your-game-landing-page'>Creating Your Game Landing Page</a></li></ul><ul><li><a class='header-2' href='#---update-your-game's-metadata'>Update Your Game's Metadata</a></li></ul><ul><li><a class='header-2' href='#---building-your-game-for-distribution'>Building Your Game For Distribution</a></li></ul><li><a class='header-1' href='#--deploying-to-mobile-devices'>Deploying To Mobile Devices</a></li> <li><a class='header-1' href='#--dragonruby's-philosophy'>DragonRuby's Philosophy</a></li> -<ul><li><a class='header-2' href='#---challenge-the-status-quo'>Challenge The Status Quo</a></li></ul><ul><li><a class='header-2' href='#---continuity-of-design'>Continuity of Design</a></li></ul><ul><li><a class='header-2' href='#---release-early-and-often'>Release Early and Often</a></li></ul><ul><li><a class='header-2' href='#---sustainable-and-ethical-monetization'>Sustainable And Ethical Monetization</a></li></ul><ul><li><a class='header-2' href='#---sustainable-and-ethical-open-source'>Sustainable And Ethical Open Source</a></li></ul><ul><li><a class='header-2' href='#---people-over-entities'>People Over Entities</a></li></ul><ul><li><a class='header-2' href='#---building-a-game-should-be-fun-and-bring-happiness'>Building A Game Should Be Fun And Bring Happiness</a></li></ul><ul><li><a class='header-2' href='#---real-world-application-drives-features'>Real World Application Drives Features</a></li></ul><li><a class='header-1' href='#--frequently-asked-questions,-comments,-and-concerns'>Frequently Asked Questions, Comments, and Concerns</a></li> -<ul><li><a class='header-2' href='#---frequently-asked-questions'>Frequently Asked Questions</a></li></ul><ul><ul><li><a class='header-3' href='#----what-is-dragonruby-llp?'>What is DragonRuby LLP?</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----what-is-dragonruby?'>What is DragonRuby?</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----how-is-dragonruby-different-than-mri?'>How is DragonRuby different than MRI?</a></li></ul></ul><ul><li><a class='header-2' href='#---frequent-comments-about-ruby-as-a-language-choice'>Frequent Comments About Ruby as a Language Choice</a></li></ul><ul><ul><li><a class='header-3' href='#----but-ruby-is-dead.'>But Ruby is dead.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----but-ruby-is-slow.'>But Ruby is slow.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dynamic-languages-are-slow.'>Dynamic languages are slow.</a></li></ul></ul><ul><li><a class='header-2' href='#---frequent-concerns'>Frequent Concerns</a></li></ul><ul><ul><li><a class='header-3' href='#----dragonruby-is-not-open-source.-that's-not-right.'>DragonRuby is not open source. That's not right.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dragonruby-is-for-pay.-you-should-offer-a-free-version.'>DragonRuby is for pay. You should offer a free version.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----but-still,-you-should-offer-a-free-version.-so-i-can-try-it-out-and-see-if-i-like-it.'>But still, you should offer a free version. So I can try it out and see if I like it.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----i-still-think-you-should-do-a-free-version.-think-of-all-people-who-would-give-it-a-shot.'>I still think you should do a free version. Think of all people who would give it a shot.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----what-if-i-build-something-with-dragonruby,-but-dragonruby-llp-becomes-insolvent.'>What if I build something with DragonRuby, but DragonRuby LLP becomes insolvent.</a></li></ul></ul><li><a class='header-1' href='#--recipies-'>RECIPIES:</a></li> -<ul><li><a class='header-2' href='#---how-to-determine-what-frame-you-are-on'>How To Determine What Frame You Are On</a></li></ul><ul><li><a class='header-2' href='#---how-to-get-current-framerate'>How To Get Current Framerate</a></li></ul><ul><li><a class='header-2' href='#---how-to-render-a-sprite-using-an-array'>How To Render A Sprite Using An Array</a></li></ul><ul><li><a class='header-2' href='#---more-sprite-properties-as-an-array'>More Sprite Properties As An Array</a></li></ul><ul><li><a class='header-2' href='#---different-sprite-representations'>Different Sprite Representations</a></li></ul><ul><li><a class='header-2' href='#---how-to-render-a-label'>How To Render A Label</a></li></ul><ul><li><a class='header-2' href='#---a-colored-label'>A Colored Label</a></li></ul><ul><li><a class='header-2' href='#---extended-label-properties'>Extended Label Properties</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-label-as-a--hash-'>Rendering A Label As A <code>Hash</code></a></li></ul><ul><li><a class='header-2' href='#---getting-the-size-of-a-piece-of-text'>Getting The Size Of A Piece Of Text</a></li></ul><ul><li><a class='header-2' href='#---how-to-play-a-sound'>How To Play A Sound</a></li></ul><ul><li><a class='header-2' href='#---using--args.state--to-store-your-game-state'>Using <code>args.state</code> To Store Your Game State</a></li></ul><ul><li><a class='header-2' href='#---troubleshoot-performance'>Troubleshoot Performance</a></li></ul><li><a class='header-1' href='#--docs---gtk--runtime-'>DOCS: <code>GTK::Runtime</code></a></li> +<ul><li><a class='header-2' href='#---challenge-the-status-quo'>Challenge The Status Quo</a></li></ul><ul><li><a class='header-2' href='#---continuity-of-design'>Continuity of Design</a></li></ul><ul><li><a class='header-2' href='#---release-early-and-often'>Release Early and Often</a></li></ul><ul><li><a class='header-2' href='#---sustainable-and-ethical-monetization'>Sustainable And Ethical Monetization</a></li></ul><ul><li><a class='header-2' href='#---sustainable-and-ethical-open-source'>Sustainable And Ethical Open Source</a></li></ul><ul><li><a class='header-2' href='#---people-over-entities'>People Over Entities</a></li></ul><ul><li><a class='header-2' href='#---building-a-game-should-be-fun-and-bring-happiness'>Building A Game Should Be Fun And Bring Happiness</a></li></ul><ul><li><a class='header-2' href='#---real-world-application-drives-features'>Real World Application Drives Features</a></li></ul><li><a class='header-1' href='#--frequently-asked-questions--comments--and-concerns'>Frequently Asked Questions, Comments, and Concerns</a></li> +<ul><li><a class='header-2' href='#---frequently-asked-questions'>Frequently Asked Questions</a></li></ul><ul><ul><li><a class='header-3' href='#----what-is-dragonruby-llp-'>What is DragonRuby LLP?</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----what-is-dragonruby-'>What is DragonRuby?</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----how-is-dragonruby-different-than-mri-'>How is DragonRuby different than MRI?</a></li></ul></ul><ul><li><a class='header-2' href='#---frequent-comments-about-ruby-as-a-language-choice'>Frequent Comments About Ruby as a Language Choice</a></li></ul><ul><ul><li><a class='header-3' href='#----but-ruby-is-dead-'>But Ruby is dead.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----but-ruby-is-slow-'>But Ruby is slow.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dynamic-languages-are-slow-'>Dynamic languages are slow.</a></li></ul></ul><ul><li><a class='header-2' href='#---frequent-concerns'>Frequent Concerns</a></li></ul><ul><ul><li><a class='header-3' href='#----dragonruby-is-not-open-source--that's-not-right-'>DragonRuby is not open source. That's not right.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dragonruby-is-for-pay--you-should-offer-a-free-version-'>DragonRuby is for pay. You should offer a free version.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----but-still--you-should-offer-a-free-version--so-i-can-try-it-out-and-see-if-i-like-it-'>But still, you should offer a free version. So I can try it out and see if I like it.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----i-still-think-you-should-do-a-free-version--think-of-all-people-who-would-give-it-a-shot-'>I still think you should do a free version. Think of all people who would give it a shot.</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----what-if-i-build-something-with-dragonruby--but-dragonruby-llp-becomes-insolvent-'>What if I build something with DragonRuby, but DragonRuby LLP becomes insolvent.</a></li></ul></ul><li><a class='header-1' href='#--recipies-'>RECIPIES:</a></li> +<ul><li><a class='header-2' href='#---how-to-determine-what-frame-you-are-on'>How To Determine What Frame You Are On</a></li></ul><ul><li><a class='header-2' href='#---how-to-get-current-framerate'>How To Get Current Framerate</a></li></ul><ul><li><a class='header-2' href='#---how-to-render-a-sprite-using-an-array'>How To Render A Sprite Using An Array</a></li></ul><ul><li><a class='header-2' href='#---more-sprite-properties-as-an-array'>More Sprite Properties As An Array</a></li></ul><ul><li><a class='header-2' href='#---different-sprite-representations'>Different Sprite Representations</a></li></ul><ul><li><a class='header-2' href='#---how-to-render-a-label'>How To Render A Label</a></li></ul><ul><li><a class='header-2' href='#---a-colored-label'>A Colored Label</a></li></ul><ul><li><a class='header-2' href='#---extended-label-properties'>Extended Label Properties</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-label-as-a--hash-'>Rendering A Label As A <code>Hash</code></a></li></ul><ul><li><a class='header-2' href='#---getting-the-size-of-a-piece-of-text'>Getting The Size Of A Piece Of Text</a></li></ul><ul><li><a class='header-2' href='#---rendering-labels-with-new-line-characters-and-wrapping'>Rendering Labels With New Line Characters And Wrapping</a></li></ul><ul><li><a class='header-2' href='#---how-to-play-a-sound'>How To Play A Sound</a></li></ul><ul><li><a class='header-2' href='#---using--args-state--to-store-your-game-state'>Using <code>args.state</code> To Store Your Game State</a></li></ul><ul><li><a class='header-2' href='#---troubleshoot-performance'>Troubleshoot Performance</a></li></ul><li><a class='header-1' href='#--docs---gtk--runtime-'>DOCS: <code>GTK::Runtime</code></a></li> <li><a class='header-1' href='#--summary---def-tick-args;-end;-'>SUMMARY: <code>def tick args; end;</code></a></li> -<ul><li><a class='header-2' href='#----args.state-'><code>args.state</code></a></li></ul><ul><ul><li><a class='header-3' href='#-----.-.entity_id-'><code>.*.entity_id</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.-.entity_type-'><code>.*.entity_type</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.-.created_at-'><code>.*.created_at</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.-.created_at_elapsed-'><code>.*.created_at_elapsed</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.-.global_created_at-'><code>.*.global_created_at</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.-.global_created_at_elapsed-'><code>.*.global_created_at_elapsed</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.-.as_hash-'><code>.*.as_hash</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.new_entity-'><code>.new_entity</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.new_entity_strict-'><code>.new_entity_strict</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.tick_count-'><code>.tick_count</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args.inputs-'><code>args.inputs</code></a></li></ul><ul><ul><li><a class='header-3' href='#-----.up-'><code>.up</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.down-'><code>.down</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.left-'><code>.left</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.right-'><code>.right</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.left_right-'><code>.left_right</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.up_down-'><code>.up_down</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.text--or--.history-'><code>.text</code> OR <code>.history</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.mouse-'><code>.mouse</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.controller_one-,--.controller_two-'><code>.controller_one</code>, <code>.controller_two</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.keyboard-'><code>.keyboard</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args.outputs-'><code>args.outputs</code></a></li></ul><ul><ul><li><a class='header-3' href='#-----.background_color-'><code>.background_color</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.sounds-'><code>.sounds</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.solids-'><code>.solids</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.static_solids-'><code>.static_solids</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.sprites-,--.static_sprites-'><code>.sprites</code>, <code>.static_sprites</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.primitives-,--.static_primitives-'><code>.primitives</code>, <code>.static_primitives</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.labels-,--.static_labels-'><code>.labels</code>, <code>.static_labels</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.lines-,--.static_lines-'><code>.lines</code>, <code>.static_lines</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.borders-,--.static_borders-'><code>.borders</code>, <code>.static_borders</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.debug-,--.static_debug-'><code>.debug</code>, <code>.static_debug</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args.geometry-'><code>args.geometry</code></a></li></ul><ul><ul><li><a class='header-3' href='#-----.inside_rect?-rect_1,-rect_2-'><code>.inside_rect? rect_1, rect_2</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.intersect_rect?-rect_2,-rect_2-'><code>.intersect_rect? rect_2, rect_2</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.scale_rect-rect,-x_percentage,-y_percentage-'><code>.scale_rect rect, x_percentage, y_percentage</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.angle_to-start_point,-end_point-'><code>.angle_to start_point, end_point</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.angle_from-start_point,-end_point-'><code>.angle_from start_point, end_point</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.point_inside_circle?-point,-circle_center_point,-radius-'><code>.point_inside_circle? point, circle_center_point, radius</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.center_inside_rect-rect,-other_rect-'><code>.center_inside_rect rect, other_rect</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.center_inside_rect_x-rect,-other_rect-'><code>.center_inside_rect_x rect, other_rect</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.center_inside_rect_y-rect,-other_rect-'><code>.center_inside_rect_y rect, other_rect</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.anchor_rect-rect,-anchor_x,-anchor_y-'><code>.anchor_rect rect, anchor_x, anchor_y</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.shift_line-line,-x,-y-'><code>.shift_line line, x, y</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.line_y_intercept-line-'><code>.line_y_intercept line</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.angle_between_lines-line_one,-line_two,-replace_infinity--'><code>.angle_between_lines line_one, line_two, replace_infinity:</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.line_slope-line,-replace_infinity--'><code>.line_slope line, replace_infinity:</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.line_rise_run-'><code>.line_rise_run</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.ray_test-point,-line-'><code>.ray_test point, line</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.line_rect-line-'><code>.line_rect line</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.line_intersect-line_one,-line_two-'><code>.line_intersect line_one, line_two</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.distance-point_one,-point_two-'><code>.distance point_one, point_two</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.cubic_bezier-t,-a,-b,-c,-d-'><code>.cubic_bezier t, a, b, c, d</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args.easing-'><code>args.easing</code></a></li></ul><ul><ul><li><a class='header-3' href='#-----.ease-start_tick,-current_tick,-duration,-easing_functions-'><code>.ease start_tick, current_tick, duration, easing_functions</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.ease_spline-start_tick,-current_tick,-duration,-spline-'><code>.ease_spline start_tick, current_tick, duration, spline</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args.string-'><code>args.string</code></a></li></ul><ul><ul><li><a class='header-3' href='#-----.wrapped_lines-string,-max_character_length-'><code>.wrapped_lines string, max_character_length</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args.grid-'><code>args.grid</code></a></li></ul><ul><ul><li><a class='header-3' href='#-----.name-'><code>.name</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.bottom-'><code>.bottom</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.top-'><code>.top</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.left-'><code>.left</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.right-'><code>.right</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.rect-'><code>.rect</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.origin_bottom_left!-'><code>.origin_bottom_left!</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.origin_center!-'><code>.origin_center!</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.w-'><code>.w</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.h-'><code>.h</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args.gtk-'><code>args.gtk</code></a></li></ul><ul><ul><li><a class='header-3' href='#-----.argv-'><code>.argv</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.platform-'><code>.platform</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.request_quit-'><code>.request_quit</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.write_file-path,-contents-'><code>.write_file path, contents</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.write_file_root-'><code>.write_file_root</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.append_file-path,-contents-'><code>.append_file path, contents</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.append_file_root-path,-contents-'><code>.append_file_root path, contents</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.read_file-path-'><code>.read_file path</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.parse_xml-string,-parse_xml_file-path-'><code>.parse_xml string, parse_xml_file path</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.parse_json-string,-parse_json_file-path-'><code>.parse_json string, parse_json_file path</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.http_get-url,-extra_headers-=-{}-'><code>.http_get url, extra_headers = {}</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.http_post-url,-form_fields-=-{},-extra_headers-=-{}-'><code>.http_post url, form_fields = {}, extra_headers = {}</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.reset-'><code>.reset</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.stop_music-'><code>.stop_music</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.calcstringbox-str,-size_enum,-font-'><code>.calcstringbox str, size_enum, font</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.slowmo!-factor-'><code>.slowmo! factor</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.notify!-string-'><code>.notify! string</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.system-'><code>.system</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.exec-'><code>.exec</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.save_state-'><code>.save_state</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.load_state-'><code>.load_state</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.serialize_state-file,-state-'><code>.serialize_state file, state</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.deserialize_state-file-'><code>.deserialize_state file</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.reset_sprite-path-'><code>.reset_sprite path</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.show_cursor-'><code>.show_cursor</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.hide_cursor-'><code>.hide_cursor</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.cursor_shown?-'><code>.cursor_shown?</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.set_window_fullscreen-enabled-'><code>.set_window_fullscreen enabled</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.openurl-url-'><code>.openurl url</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.get_base_dir-'><code>.get_base_dir</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#-----.get_game_dir-'><code>.get_game_dir</code></a></li></ul></ul><li><a class='header-1' href='#--docs---gtk--runtime#reset-'>DOCS: <code>GTK::Runtime#reset</code></a></li> +<ul><li><a class='header-2' href='#----args-state-'><code>args.state</code></a></li></ul><ul><ul><li><a class='header-3' href='#--------entity_id-'><code>.*.entity_id</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#--------entity_type-'><code>.*.entity_type</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#--------created_at-'><code>.*.created_at</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#--------created_at_elapsed-'><code>.*.created_at_elapsed</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#--------global_created_at-'><code>.*.global_created_at</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#--------global_created_at_elapsed-'><code>.*.global_created_at_elapsed</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#--------as_hash-'><code>.*.as_hash</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------new_entity-'><code>.new_entity</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------new_entity_strict-'><code>.new_entity_strict</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------tick_count-'><code>.tick_count</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args-inputs-'><code>args.inputs</code></a></li></ul><ul><ul><li><a class='header-3' href='#------up-'><code>.up</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------down-'><code>.down</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------left-'><code>.left</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------right-'><code>.right</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------left_right-'><code>.left_right</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------up_down-'><code>.up_down</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------text--or---history-'><code>.text</code> OR <code>.history</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------mouse-'><code>.mouse</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------controller_one-----controller_two-'><code>.controller_one</code>, <code>.controller_two</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------keyboard-'><code>.keyboard</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args-outputs-'><code>args.outputs</code></a></li></ul><ul><ul><li><a class='header-3' href='#------background_color-'><code>.background_color</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------sounds-'><code>.sounds</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------solids-'><code>.solids</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------static_solids-'><code>.static_solids</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------sprites-----static_sprites-'><code>.sprites</code>, <code>.static_sprites</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------primitives-----static_primitives-'><code>.primitives</code>, <code>.static_primitives</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------labels-----static_labels-'><code>.labels</code>, <code>.static_labels</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------lines-----static_lines-'><code>.lines</code>, <code>.static_lines</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------borders-----static_borders-'><code>.borders</code>, <code>.static_borders</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------debug-----static_debug-'><code>.debug</code>, <code>.static_debug</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args-geometry-'><code>args.geometry</code></a></li></ul><ul><ul><li><a class='header-3' href='#------inside_rect--rect_1--rect_2-'><code>.inside_rect? rect_1, rect_2</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------intersect_rect--rect_2--rect_2-'><code>.intersect_rect? rect_2, rect_2</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------scale_rect-rect--x_percentage--y_percentage-'><code>.scale_rect rect, x_percentage, y_percentage</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------angle_to-start_point--end_point-'><code>.angle_to start_point, end_point</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------angle_from-start_point--end_point-'><code>.angle_from start_point, end_point</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------point_inside_circle--point--circle_center_point--radius-'><code>.point_inside_circle? point, circle_center_point, radius</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------center_inside_rect-rect--other_rect-'><code>.center_inside_rect rect, other_rect</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------center_inside_rect_x-rect--other_rect-'><code>.center_inside_rect_x rect, other_rect</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------center_inside_rect_y-rect--other_rect-'><code>.center_inside_rect_y rect, other_rect</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------anchor_rect-rect--anchor_x--anchor_y-'><code>.anchor_rect rect, anchor_x, anchor_y</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------shift_line-line--x--y-'><code>.shift_line line, x, y</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------line_y_intercept-line-'><code>.line_y_intercept line</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------angle_between_lines-line_one--line_two--replace_infinity--'><code>.angle_between_lines line_one, line_two, replace_infinity:</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------line_slope-line--replace_infinity--'><code>.line_slope line, replace_infinity:</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------line_rise_run-'><code>.line_rise_run</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------ray_test-point--line-'><code>.ray_test point, line</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------line_rect-line-'><code>.line_rect line</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------line_intersect-line_one--line_two-'><code>.line_intersect line_one, line_two</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------distance-point_one--point_two-'><code>.distance point_one, point_two</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------cubic_bezier-t--a--b--c--d-'><code>.cubic_bezier t, a, b, c, d</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args-easing-'><code>args.easing</code></a></li></ul><ul><ul><li><a class='header-3' href='#------ease-start_tick--current_tick--duration--easing_functions-'><code>.ease start_tick, current_tick, duration, easing_functions</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------ease_spline-start_tick--current_tick--duration--spline-'><code>.ease_spline start_tick, current_tick, duration, spline</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args-string-'><code>args.string</code></a></li></ul><ul><ul><li><a class='header-3' href='#------wrapped_lines-string--max_character_length-'><code>.wrapped_lines string, max_character_length</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args-grid-'><code>args.grid</code></a></li></ul><ul><ul><li><a class='header-3' href='#------name-'><code>.name</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------bottom-'><code>.bottom</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------top-'><code>.top</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------left-'><code>.left</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------right-'><code>.right</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------rect-'><code>.rect</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------origin_bottom_left!-'><code>.origin_bottom_left!</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------origin_center!-'><code>.origin_center!</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------w-'><code>.w</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------h-'><code>.h</code></a></li></ul></ul><ul><li><a class='header-2' href='#----args-gtk-'><code>args.gtk</code></a></li></ul><ul><ul><li><a class='header-3' href='#------argv-'><code>.argv</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------platform-'><code>.platform</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------request_quit-'><code>.request_quit</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------write_file-path--contents-'><code>.write_file path, contents</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------write_file_root-'><code>.write_file_root</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------append_file-path--contents-'><code>.append_file path, contents</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------append_file_root-path--contents-'><code>.append_file_root path, contents</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------read_file-path-'><code>.read_file path</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------parse_xml-string--parse_xml_file-path-'><code>.parse_xml string, parse_xml_file path</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------parse_json-string--parse_json_file-path-'><code>.parse_json string, parse_json_file path</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------http_get-url--extra_headers-=-{}-'><code>.http_get url, extra_headers = {}</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------http_post-url--form_fields-=-{}--extra_headers-=-{}-'><code>.http_post url, form_fields = {}, extra_headers = {}</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------reset-'><code>.reset</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------stop_music-'><code>.stop_music</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------calcstringbox-str--size_enum--font-'><code>.calcstringbox str, size_enum, font</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------slowmo!-factor-'><code>.slowmo! factor</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------notify!-string-'><code>.notify! string</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------system-'><code>.system</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------exec-'><code>.exec</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------save_state-'><code>.save_state</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------load_state-'><code>.load_state</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------serialize_state-file--state-'><code>.serialize_state file, state</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------deserialize_state-file-'><code>.deserialize_state file</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------reset_sprite-path-'><code>.reset_sprite path</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------show_cursor-'><code>.show_cursor</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------hide_cursor-'><code>.hide_cursor</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------cursor_shown--'><code>.cursor_shown?</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------set_window_fullscreen-enabled-'><code>.set_window_fullscreen enabled</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------openurl-url-'><code>.openurl url</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------get_base_dir-'><code>.get_base_dir</code></a></li></ul></ul><ul><ul><li><a class='header-3' href='#------get_game_dir-'><code>.get_game_dir</code></a></li></ul></ul><li><a class='header-1' href='#--docs---gtk--runtime#reset-'>DOCS: <code>GTK::Runtime#reset</code></a></li> <li><a class='header-1' href='#--docs---gtk--runtime#calcstringbox-'>DOCS: <code>GTK::Runtime#calcstringbox</code></a></li> <li><a class='header-1' href='#--docs---gtk--runtime#write_file-'>DOCS: <code>GTK::Runtime#write_file</code></a></li> +<li><a class='header-1' href='#--docs---gtk--runtime#benchmark-'>DOCS: <code>GTK::Runtime#benchmark</code></a></li> <li><a class='header-1' href='#--docs---array-'>DOCS: <code>Array</code></a></li> <li><a class='header-1' href='#--docs---array#map-'>DOCS: <code>Array#map</code></a></li> <li><a class='header-1' href='#--docs---array#each-'>DOCS: <code>Array#each</code></a></li> @@ -29,28 +31,29 @@ <li><a class='header-1' href='#--docs---array#reject_false-'>DOCS: <code>Array#reject_false</code></a></li> <li><a class='header-1' href='#--docs---array#product-'>DOCS: <code>Array#product</code></a></li> <li><a class='header-1' href='#--docs---array#map_2d-'>DOCS: <code>Array#map_2d</code></a></li> -<li><a class='header-1' href='#--docs---array#include_any?-'>DOCS: <code>Array#include_any?</code></a></li> -<li><a class='header-1' href='#--docs---array#any_intersect_rect?-'>DOCS: <code>Array#any_intersect_rect?</code></a></li> +<li><a class='header-1' href='#--docs---array#include_any--'>DOCS: <code>Array#include_any?</code></a></li> +<li><a class='header-1' href='#--docs---array#any_intersect_rect--'>DOCS: <code>Array#any_intersect_rect?</code></a></li> <li><a class='header-1' href='#--docs---gtk--args#audio-'>DOCS: <code>GTK::Args#audio</code></a></li> <ul><li><a class='header-2' href='#---audio-synthesis-(pro-only)'>Audio synthesis (Pro only)</a></li></ul><ul><ul><li><a class='header-3' href='#----sound-source'>Sound source</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----example-'>Example:</a></li></ul></ul><li><a class='header-1' href='#--docs---gtk--outputs-'>DOCS: <code>GTK::Outputs</code></a></li> <li><a class='header-1' href='#--docs---gtk--outputs#solids-'>DOCS: <code>GTK::Outputs#solids</code></a></li> <ul><li><a class='header-2' href='#---rendering-a-solid-using-an-array'>Rendering a solid using an Array</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-solid-using-an-array-with-colors-and-alpha'>Rendering a solid using an Array with colors and alpha</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-solid-using-a-hash'>Rendering a solid using a Hash</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-solid-using-a-class'>Rendering a solid using a Class</a></li></ul><li><a class='header-1' href='#--docs---gtk--outputs#borders-'>DOCS: <code>GTK::Outputs#borders</code></a></li> -<li><a class='header-1' href='#--docs---gtk--outputs#screenshots-'>DOCS: <code>GTK::Outputs#screenshots</code></a></li> +<li><a class='header-1' href='#--docs---gtk--outputs#sprites-'>DOCS: <code>GTK::Outputs#sprites</code></a></li> +<ul><li><a class='header-2' href='#---rendering-a-sprite-using-an-array'>Rendering a sprite using an Array</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-sprite-using-an-array-with-colors-and-alpha'>Rendering a sprite using an Array with colors and alpha</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-sprite-using-a-hash'>Rendering a sprite using a Hash</a></li></ul><ul><li><a class='header-2' href='#---rendering-a-solid-using-a-class'>Rendering a solid using a Class</a></li></ul><li><a class='header-1' href='#--docs---gtk--outputs#screenshots-'>DOCS: <code>GTK::Outputs#screenshots</code></a></li> <ul><li><a class='header-2' href='#---chroma-key-(making-a-color-transparent)'>Chroma key (Making a color transparent)</a></li></ul><li><a class='header-1' href='#--docs---gtk--mouse-'>DOCS: <code>GTK::Mouse</code></a></li> <li><a class='header-1' href='#--docs---gtk--mousepoint-'>DOCS: <code>GTK::MousePoint</code></a></li> <li><a class='header-1' href='#--docs---gtk--openentity-'>DOCS: <code>GTK::OpenEntity</code></a></li> <li><a class='header-1' href='#--docs---gtk--openentity#as_hash-'>DOCS: <code>GTK::OpenEntity#as_hash</code></a></li> <li><a class='header-1' href='#--docs---numeric#frame_index-'>DOCS: <code>Numeric#frame_index</code></a></li> <li><a class='header-1' href='#--docs---numeric#elapsed_time-'>DOCS: <code>Numeric#elapsed_time</code></a></li> -<li><a class='header-1' href='#--docs---numeric#elapsed?-'>DOCS: <code>Numeric#elapsed?</code></a></li> -<li><a class='header-1' href='#--docs---numeric#created?-'>DOCS: <code>Numeric#created?</code></a></li> +<li><a class='header-1' href='#--docs---numeric#elapsed--'>DOCS: <code>Numeric#elapsed?</code></a></li> +<li><a class='header-1' href='#--docs---numeric#created--'>DOCS: <code>Numeric#created?</code></a></li> <li><a class='header-1' href='#--docs---kernel-'>DOCS: <code>Kernel</code></a></li> <li><a class='header-1' href='#--docs---kernel--tick_count-'>DOCS: <code>Kernel::tick_count</code></a></li> <li><a class='header-1' href='#--docs---kernel--global_tick_count-'>DOCS: <code>Kernel::global_tick_count</code></a></li> <li><a class='header-1' href='#--docs---geometry-'>DOCS: <code>Geometry</code></a></li> <li><a class='header-1' href='#--docs---gtk--geometry#scale_rect-'>DOCS: <code>GTK::Geometry#scale_rect</code></a></li> <li><a class='header-1' href='#--source-code'>Source Code</a></li> -<ul><li><a class='header-2' href='#---samples'>Samples</a></li></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---beginner-ruby-primer---automation.rb'>Learn Ruby Optional - Beginner Ruby Primer - automation.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---beginner-ruby-primer---main.rb'>Learn Ruby Optional - Beginner Ruby Primer - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---printing.txt'>Learn Ruby Optional - Intermediate Ruby Primer - printing.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---strings.txt'>Learn Ruby Optional - Intermediate Ruby Primer - strings.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---numbers.txt'>Learn Ruby Optional - Intermediate Ruby Primer - numbers.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---booleans.txt'>Learn Ruby Optional - Intermediate Ruby Primer - booleans.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---conditionals.txt'>Learn Ruby Optional - Intermediate Ruby Primer - conditionals.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---looping.txt'>Learn Ruby Optional - Intermediate Ruby Primer - looping.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---functions.txt'>Learn Ruby Optional - Intermediate Ruby Primer - functions.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---arrays.txt'>Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---main.rb'>Learn Ruby Optional - Intermediate Ruby Primer - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---labels---main.rb'>Rendering Basics - Labels - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---lines---main.rb'>Rendering Basics - Lines - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---solids-borders---main.rb'>Rendering Basics - Solids Borders - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---sprites---main.rb'>Rendering Basics - Sprites - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---sounds---main.rb'>Rendering Basics - Sounds - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---audio-mixer---main.rb'>Rendering Basics - Audio Mixer - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---sound-synthesis---main.rb'>Rendering Basics - Sound Synthesis - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---keyboard---main.rb'>Input Basics - Keyboard - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---mouse---main.rb'>Input Basics - Mouse - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---mouse-point-to-rect---main.rb'>Input Basics - Mouse Point To Rect - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---mouse-rect-to-rect---main.rb'>Input Basics - Mouse Rect To Rect - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---controller---main.rb'>Input Basics - Controller - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---touch---main.rb'>Input Basics - Touch - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-sprites---animation-using-separate-pngs---main.rb'>Rendering Sprites - Animation Using Separate Pngs - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-sprites---animation-using-sprite-sheet---main.rb'>Rendering Sprites - Animation Using Sprite Sheet - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-sprites---animation-states---main.rb'>Rendering Sprites - Animation States - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-sprites---color-and-rotation---main.rb'>Rendering Sprites - Color And Rotation - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---simple---main.rb'>Physics And Collisions - Simple - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---moving-objects---main.rb'>Physics And Collisions - Moving Objects - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---entities---main.rb'>Physics And Collisions - Entities - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---box-collision---main.rb'>Physics And Collisions - Box Collision - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---box-collision-2---main.rb'>Physics And Collisions - Box Collision 2 - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---box-collision-3---main.rb'>Physics And Collisions - Box Collision 3 - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---jump-physics---main.rb'>Physics And Collisions - Jump Physics - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---ball.rb'>Physics And Collisions - Bouncing On Collision - ball.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---block.rb'>Physics And Collisions - Bouncing On Collision - block.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---cannon.rb'>Physics And Collisions - Bouncing On Collision - cannon.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---main.rb'>Physics And Collisions - Bouncing On Collision - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---peg.rb'>Physics And Collisions - Bouncing On Collision - peg.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---vector2d.rb'>Physics And Collisions - Bouncing On Collision - vector2d.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---ball.rb'>Physics And Collisions - Arbitrary Collision - ball.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---blocks.rb'>Physics And Collisions - Arbitrary Collision - blocks.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---linear_collider.rb'>Physics And Collisions - Arbitrary Collision - linear_collider.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---main.rb'>Physics And Collisions - Arbitrary Collision - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---paddle.rb'>Physics And Collisions - Arbitrary Collision - paddle.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---rectangle.rb'>Physics And Collisions - Arbitrary Collision - rectangle.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---square_collider.rb'>Physics And Collisions - Arbitrary Collision - square_collider.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---vector2d.rb'>Physics And Collisions - Arbitrary Collision - vector2d.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---ball.rb'>Physics And Collisions - Collision With Object Removal - ball.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---linear_collider.rb'>Physics And Collisions - Collision With Object Removal - linear_collider.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---main.rb'>Physics And Collisions - Collision With Object Removal - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---paddle.rb'>Physics And Collisions - Collision With Object Removal - paddle.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---tests.rb'>Physics And Collisions - Collision With Object Removal - tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---vector2d.rb'>Physics And Collisions - Collision With Object Removal - vector2d.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mouse---mouse-click---main.rb'>Mouse - Mouse Click - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mouse---mouse-move---main.rb'>Mouse - Mouse Move - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mouse---mouse-move-paint-app---main.rb'>Mouse - Mouse Move Paint App - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mouse---coordinate-systems---main.rb'>Mouse - Coordinate Systems - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----save-load---save-load-game---main.rb'>Save Load - Save Load Game - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---simple-render-targets---main.rb'>Advanced Rendering - Simple Render Targets - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-targets-with-tile-manipulation---main.rb'>Advanced Rendering - Render Targets With Tile Manipulation - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-target-viewports---main.rb'>Advanced Rendering - Render Target Viewports - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-primitive-hierarchies---main.rb'>Advanced Rendering - Render Primitive Hierarchies - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-primitives-as-hash---main.rb'>Advanced Rendering - Render Primitives As Hash - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---pixel-arrays---main.rb'>Advanced Rendering - Pixel Arrays - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---splitscreen-camera---main.rb'>Advanced Rendering - Splitscreen Camera - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---z-targeting-camera---main.rb'>Advanced Rendering - Z Targeting Camera - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweening-lerping-easing-functions---easing-functions---main.rb'>Tweening Lerping Easing Functions - Easing Functions - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweening-lerping-easing-functions---cubic-bezier---main.rb'>Tweening Lerping Easing Functions - Cubic Bezier - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweening-lerping-easing-functions---easing-using-spline---main.rb'>Tweening Lerping Easing Functions - Easing Using Spline - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweening-lerping-easing-functions---parametric-enemy-movement---main.rb'>Tweening Lerping Easing Functions - Parametric Enemy Movement - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-hash---main.rb'>Performance - Sprites As Hash - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-entities---main.rb'>Performance - Sprites As Entities - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-strict-entities---main.rb'>Performance - Sprites As Strict Entities - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-classes---main.rb'>Performance - Sprites As Classes - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---static-sprites-as-classes---main.rb'>Performance - Static Sprites As Classes - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---static-sprites-as-classes-with-custom-drawing---main.rb'>Performance - Static Sprites As Classes With Custom Drawing - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---collision-limits---main.rb'>Performance - Collision Limits - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---trace-debugging---main.rb'>Advanced Debugging - Trace Debugging - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---trace-debugging-classes---main.rb'>Advanced Debugging - Trace Debugging Classes - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---exception_raising_tests.rb'>Advanced Debugging - Unit Tests - exception_raising_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---fn_tests.rb'>Advanced Debugging - Unit Tests - fn_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---gen_docs.rb'>Advanced Debugging - Unit Tests - gen_docs.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---geometry_tests.rb'>Advanced Debugging - Unit Tests - geometry_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---http_tests.rb'>Advanced Debugging - Unit Tests - http_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---nil_coercion_tests.rb'>Advanced Debugging - Unit Tests - nil_coercion_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---object_to_primitive_tests.rb'>Advanced Debugging - Unit Tests - object_to_primitive_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---parsing_tests.rb'>Advanced Debugging - Unit Tests - parsing_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---require_tests.rb'>Advanced Debugging - Unit Tests - require_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---serialize_deserialize_tests.rb'>Advanced Debugging - Unit Tests - serialize_deserialize_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---state_serialization_experimental_tests.rb'>Advanced Debugging - Unit Tests - state_serialization_experimental_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---suggest_autocompletion_tests.rb'>Advanced Debugging - Unit Tests - suggest_autocompletion_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----http---retrieve-images---main.rb'>Http - Retrieve Images - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----http---web-server---main.rb'>Http - Web Server - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----c-extensions---basics---main.rb'>C Extensions - Basics - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----c-extensions---intermediate---main.rb'>C Extensions - Intermediate - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----c-extensions---native-pixel-arrays---main.rb'>C Extensions - Native Pixel Arrays - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---breadth-first-search---main.rb'>Path Finding Algorithms - Breadth First Search - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---detailed-breadth-first-search---main.rb'>Path Finding Algorithms - Detailed Breadth First Search - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---breadcrumbs---main.rb'>Path Finding Algorithms - Breadcrumbs - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---early-exit---main.rb'>Path Finding Algorithms - Early Exit - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---dijkstra---main.rb'>Path Finding Algorithms - Dijkstra - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---heuristic---main.rb'>Path Finding Algorithms - Heuristic - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---heuristic-with-walls---main.rb'>Path Finding Algorithms - Heuristic With Walls - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---a-star---main.rb'>Path Finding Algorithms - A Star - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---tower-defense---main.rb'>Path Finding Algorithms - Tower Defense - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----3d---3d-cube---main.rb'>3d - 3d Cube - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----3d---wireframe---main.rb'>3d - Wireframe - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----3d---wireframe---data---what-is-this.txt'>3d - Wireframe - Data - what-is-this.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---bullet-hell---main.rb'>Arcade - Bullet Hell - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---dueling-starships---main.rb'>Arcade - Dueling Starships - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade/flappy-dragon/credits.txt'>arcade/flappy dragon/credits.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade/flappy-dragon/main.rb'>arcade/flappy dragon/main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---pong---main.rb'>Arcade - Pong - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---snakemoji---main.rb'>Arcade - Snakemoji - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---solar-system---main.rb'>Arcade - Solar System - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---sound-golf---main.rb'>Arcade - Sound Golf - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---twinstick---main.rb'>Arcade - Twinstick - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----crafting---craft-game-starting-point---main.rb'>Crafting - Craft Game Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dev-tools---add-buttons-to-console---main.rb'>Dev Tools - Add Buttons To Console - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dev-tools---animation-creator-starting-point---main.rb'>Dev Tools - Animation Creator Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dev-tools---tile-editor-starting-point---main.rb'>Dev Tools - Tile Editor Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----lowrez---nokia-3310---main.rb'>Lowrez - Nokia 3310 - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----lowrez---nokia-3310---nokia.rb'>Lowrez - Nokia 3310 - nokia.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----lowrez---resolution-64x64---lowrez.rb'>Lowrez - Resolution 64x64 - lowrez.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----lowrez---resolution-64x64---main.rb'>Lowrez - Resolution 64x64 - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---clepto-frog---main.rb'>Platformer - Clepto Frog - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---clepto-frog---map.rb'>Platformer - Clepto Frog - map.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---gorillas-basic---credits.txt'>Platformer - Gorillas Basic - credits.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---gorillas-basic---main.rb'>Platformer - Gorillas Basic - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---gorillas-basic---tests.rb'>Platformer - Gorillas Basic - tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---gorillas-basic---tests---building_generation_tests.rb'>Platformer - Gorillas Basic - Tests - building_generation_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---the-little-probe---main.rb'>Platformer - The Little Probe - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---the-little-probe---data---level.txt'>Platformer - The Little Probe - Data - level.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---the-little-probe---data---level_lava.txt'>Platformer - The Little Probe - Data - level_lava.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---choose-your-own-adventure---decision.rb'>Rpg Narrative - Choose Your Own Adventure - decision.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---choose-your-own-adventure---main.rb'>Rpg Narrative - Choose Your Own Adventure - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---lowrez_simulator.rb'>Rpg Narrative - Return Of Serenity - lowrez_simulator.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---main.rb'>Rpg Narrative - Return Of Serenity - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---require.rb'>Rpg Narrative - Return Of Serenity - require.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline.rb'>Rpg Narrative - Return Of Serenity - storyline.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_anka.rb'>Rpg Narrative - Return Of Serenity - storyline_anka.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_blinking_light.rb'>Rpg Narrative - Return Of Serenity - storyline_blinking_light.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_day_one.rb'>Rpg Narrative - Return Of Serenity - storyline_day_one.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_final_decision.rb'>Rpg Narrative - Return Of Serenity - storyline_final_decision.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_final_message.rb'>Rpg Narrative - Return Of Serenity - storyline_final_message.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_serenity_alive.rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_alive.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_serenity_bio.rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_bio.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_serenity_introduction.rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_introduction.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_speed_of_light.rb'>Rpg Narrative - Return Of Serenity - storyline_speed_of_light.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-starting-point---constants.rb'>Rpg Roguelike - Roguelike Starting Point - constants.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-starting-point---legend.rb'>Rpg Roguelike - Roguelike Starting Point - legend.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-starting-point---main.rb'>Rpg Roguelike - Roguelike Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-starting-point---sprite_lookup.rb'>Rpg Roguelike - Roguelike Starting Point - sprite_lookup.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-line-of-sight---main.rb'>Rpg Roguelike - Roguelike Line Of Sight - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-tactical---hexagonal-grid---main.rb'>Rpg Tactical - Hexagonal Grid - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-tactical---isometric-grid---main.rb'>Rpg Tactical - Isometric Grid - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-topdown---topdown-starting-point---main.rb'>Rpg Topdown - Topdown Starting Point - main.rb</a></li></ul></ul><ul><li><a class='header-2' href='#---oss'>OSS</a></li></ul><ul><ul><li><a class='header-3' href='#----args.rb'>args.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----assert.rb'>assert.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----attr_gtk.rb'>attr_gtk.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----attr_sprite.rb'>attr_sprite.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console.rb'>console.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console_color.rb'>console_color.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console_font_style.rb'>console_font_style.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console_menu.rb'>console_menu.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console_prompt.rb'>console_prompt.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----controller.rb'>controller.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----controller/config.rb'>controller/config.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----controller/keys.rb'>controller/keys.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----directional_input_helper_methods.rb'>directional_input_helper_methods.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----easing.rb'>easing.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----entity.rb'>entity.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----geometry.rb'>geometry.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----grid.rb'>grid.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----inputs.rb'>inputs.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----ios_wizard.rb'>ios_wizard.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----itch_wizard.rb'>itch_wizard.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----layout.rb'>layout.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----log.rb'>log.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----numeric.rb'>numeric.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----remote_hotload_client.rb'>remote_hotload_client.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/autocomplete.rb'>runtime/autocomplete.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/draw.rb'>runtime/draw.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/framerate.rb'>runtime/framerate.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/framerate_diagnostics.rb'>runtime/framerate_diagnostics.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/hotload.rb'>runtime/hotload.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----string.rb'>string.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tests.rb'>tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----trace.rb'>trace.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----wizards.rb'>wizards.rb</a></li></ul></ul></ul> </div> +<ul><li><a class='header-2' href='#---samples'>Samples</a></li></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---beginner-ruby-primer---automation-rb'>Learn Ruby Optional - Beginner Ruby Primer - automation.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---beginner-ruby-primer---main-rb'>Learn Ruby Optional - Beginner Ruby Primer - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---printing-txt'>Learn Ruby Optional - Intermediate Ruby Primer - printing.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---strings-txt'>Learn Ruby Optional - Intermediate Ruby Primer - strings.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---numbers-txt'>Learn Ruby Optional - Intermediate Ruby Primer - numbers.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---booleans-txt'>Learn Ruby Optional - Intermediate Ruby Primer - booleans.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---conditionals-txt'>Learn Ruby Optional - Intermediate Ruby Primer - conditionals.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---looping-txt'>Learn Ruby Optional - Intermediate Ruby Primer - looping.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---functions-txt'>Learn Ruby Optional - Intermediate Ruby Primer - functions.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---arrays-txt'>Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----learn-ruby-optional---intermediate-ruby-primer---main-rb'>Learn Ruby Optional - Intermediate Ruby Primer - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---labels---main-rb'>Rendering Basics - Labels - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---lines---main-rb'>Rendering Basics - Lines - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---solids-borders---main-rb'>Rendering Basics - Solids Borders - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---sprites---main-rb'>Rendering Basics - Sprites - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-basics---sounds---main-rb'>Rendering Basics - Sounds - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---keyboard---main-rb'>Input Basics - Keyboard - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---moving-a-sprite---main-rb'>Input Basics - Moving A Sprite - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---mouse---main-rb'>Input Basics - Mouse - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---mouse-point-to-rect---main-rb'>Input Basics - Mouse Point To Rect - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---mouse-rect-to-rect---main-rb'>Input Basics - Mouse Rect To Rect - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---controller---main-rb'>Input Basics - Controller - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----input-basics---touch---main-rb'>Input Basics - Touch - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-sprites---animation-using-separate-pngs---main-rb'>Rendering Sprites - Animation Using Separate Pngs - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-sprites---animation-using-sprite-sheet---main-rb'>Rendering Sprites - Animation Using Sprite Sheet - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-sprites---animation-states---main-rb'>Rendering Sprites - Animation States - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rendering-sprites---color-and-rotation---main-rb'>Rendering Sprites - Color And Rotation - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---simple---main-rb'>Physics And Collisions - Simple - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---moving-objects---main-rb'>Physics And Collisions - Moving Objects - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---entities---main-rb'>Physics And Collisions - Entities - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---box-collision---main-rb'>Physics And Collisions - Box Collision - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---box-collision-2---main-rb'>Physics And Collisions - Box Collision 2 - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---box-collision-3---main-rb'>Physics And Collisions - Box Collision 3 - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---jump-physics---main-rb'>Physics And Collisions - Jump Physics - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---ball-rb'>Physics And Collisions - Bouncing On Collision - ball.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---block-rb'>Physics And Collisions - Bouncing On Collision - block.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---cannon-rb'>Physics And Collisions - Bouncing On Collision - cannon.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---main-rb'>Physics And Collisions - Bouncing On Collision - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---peg-rb'>Physics And Collisions - Bouncing On Collision - peg.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---bouncing-on-collision---vector2d-rb'>Physics And Collisions - Bouncing On Collision - vector2d.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---ball-rb'>Physics And Collisions - Arbitrary Collision - ball.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---blocks-rb'>Physics And Collisions - Arbitrary Collision - blocks.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---linear_collider-rb'>Physics And Collisions - Arbitrary Collision - linear_collider.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---main-rb'>Physics And Collisions - Arbitrary Collision - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---paddle-rb'>Physics And Collisions - Arbitrary Collision - paddle.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---rectangle-rb'>Physics And Collisions - Arbitrary Collision - rectangle.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---square_collider-rb'>Physics And Collisions - Arbitrary Collision - square_collider.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---arbitrary-collision---vector2d-rb'>Physics And Collisions - Arbitrary Collision - vector2d.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---ball-rb'>Physics And Collisions - Collision With Object Removal - ball.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---linear_collider-rb'>Physics And Collisions - Collision With Object Removal - linear_collider.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---main-rb'>Physics And Collisions - Collision With Object Removal - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---paddle-rb'>Physics And Collisions - Collision With Object Removal - paddle.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---tests-rb'>Physics And Collisions - Collision With Object Removal - tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----physics-and-collisions---collision-with-object-removal---vector2d-rb'>Physics And Collisions - Collision With Object Removal - vector2d.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mouse---mouse-click---main-rb'>Mouse - Mouse Click - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mouse---mouse-move---main-rb'>Mouse - Mouse Move - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mouse---mouse-move-paint-app---main-rb'>Mouse - Mouse Move Paint App - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mouse---coordinate-systems---main-rb'>Mouse - Coordinate Systems - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----save-load---save-load-game---main-rb'>Save Load - Save Load Game - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-audio---audio-mixer---main-rb'>Advanced Audio - Audio Mixer - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-audio---audio-mixer---server_ip_address-txt'>Advanced Audio - Audio Mixer - server_ip_address.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-audio---sound-synthesis---main-rb'>Advanced Audio - Sound Synthesis - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---labels-with-wrapped-text---main-rb'>Advanced Rendering - Labels With Wrapped Text - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---rotating-label---main-rb'>Advanced Rendering - Rotating Label - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---simple-render-targets---main-rb'>Advanced Rendering - Simple Render Targets - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-targets-with-tile-manipulation---main-rb'>Advanced Rendering - Render Targets With Tile Manipulation - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-target-viewports---main-rb'>Advanced Rendering - Render Target Viewports - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-primitive-hierarchies---main-rb'>Advanced Rendering - Render Primitive Hierarchies - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-primitives-as-hash---main-rb'>Advanced Rendering - Render Primitives As Hash - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---pixel-arrays---main-rb'>Advanced Rendering - Pixel Arrays - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---simple-camera---main-rb'>Advanced Rendering - Simple Camera - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---splitscreen-camera---main-rb'>Advanced Rendering - Splitscreen Camera - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---z-targeting-camera---main-rb'>Advanced Rendering - Z Targeting Camera - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---blend-modes---main-rb'>Advanced Rendering - Blend Modes - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-rendering---render-target-noclear---main-rb'>Advanced Rendering - Render Target Noclear - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweening-lerping-easing-functions---easing-functions---main-rb'>Tweening Lerping Easing Functions - Easing Functions - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweening-lerping-easing-functions---cubic-bezier---main-rb'>Tweening Lerping Easing Functions - Cubic Bezier - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweening-lerping-easing-functions---easing-using-spline---main-rb'>Tweening Lerping Easing Functions - Easing Using Spline - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweening-lerping-easing-functions---parametric-enemy-movement---main-rb'>Tweening Lerping Easing Functions - Parametric Enemy Movement - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-hash---main-rb'>Performance - Sprites As Hash - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-entities---main-rb'>Performance - Sprites As Entities - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-struct---main-rb'>Performance - Sprites As Struct - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-strict-entities---main-rb'>Performance - Sprites As Strict Entities - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---sprites-as-classes---main-rb'>Performance - Sprites As Classes - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---static-sprites-as-classes---main-rb'>Performance - Static Sprites As Classes - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---static-sprites-as-classes-with-custom-drawing---main-rb'>Performance - Static Sprites As Classes With Custom Drawing - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----performance---collision-limits---main-rb'>Performance - Collision Limits - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---logging---main-rb'>Advanced Debugging - Logging - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---trace-debugging---main-rb'>Advanced Debugging - Trace Debugging - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---trace-debugging-classes---main-rb'>Advanced Debugging - Trace Debugging Classes - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---benchmark_api_tests-rb'>Advanced Debugging - Unit Tests - benchmark_api_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---exception_raising_tests-rb'>Advanced Debugging - Unit Tests - exception_raising_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---fn_tests-rb'>Advanced Debugging - Unit Tests - fn_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---gen_docs-rb'>Advanced Debugging - Unit Tests - gen_docs.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---geometry_tests-rb'>Advanced Debugging - Unit Tests - geometry_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---http_tests-rb'>Advanced Debugging - Unit Tests - http_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---nil_coercion_tests-rb'>Advanced Debugging - Unit Tests - nil_coercion_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---object_to_primitive_tests-rb'>Advanced Debugging - Unit Tests - object_to_primitive_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---parsing_tests-rb'>Advanced Debugging - Unit Tests - parsing_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---pretty_format_tests-rb'>Advanced Debugging - Unit Tests - pretty_format_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---require_tests-rb'>Advanced Debugging - Unit Tests - require_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---serialize_deserialize_tests-rb'>Advanced Debugging - Unit Tests - serialize_deserialize_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---state_serialization_experimental_tests-rb'>Advanced Debugging - Unit Tests - state_serialization_experimental_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----advanced-debugging---unit-tests---suggest_autocompletion_tests-rb'>Advanced Debugging - Unit Tests - suggest_autocompletion_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----http---retrieve-images---main-rb'>Http - Retrieve Images - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----http---in-game-web-server-http-get---main-rb'>Http - In Game Web Server Http Get - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----http---in-game-web-server-http-post---main-rb'>Http - In Game Web Server Http Post - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----c-extensions---basics---main-rb'>C Extensions - Basics - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----c-extensions---intermediate---main-rb'>C Extensions - Intermediate - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----c-extensions---native-pixel-arrays---main-rb'>C Extensions - Native Pixel Arrays - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---breadth-first-search---main-rb'>Path Finding Algorithms - Breadth First Search - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---detailed-breadth-first-search---main-rb'>Path Finding Algorithms - Detailed Breadth First Search - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---breadcrumbs---main-rb'>Path Finding Algorithms - Breadcrumbs - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---early-exit---main-rb'>Path Finding Algorithms - Early Exit - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---dijkstra---main-rb'>Path Finding Algorithms - Dijkstra - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---heuristic---main-rb'>Path Finding Algorithms - Heuristic - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---heuristic-with-walls---main-rb'>Path Finding Algorithms - Heuristic With Walls - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---a-star---main-rb'>Path Finding Algorithms - A Star - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----path-finding-algorithms---tower-defense---main-rb'>Path Finding Algorithms - Tower Defense - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----3d---3d-cube---main-rb'>3d - 3d Cube - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----3d---wireframe---main-rb'>3d - Wireframe - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----3d---wireframe---data---what-is-this-txt'>3d - Wireframe - Data - what-is-this.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----3d---yaw-pitch-roll---main-rb'>3d - Yaw Pitch Roll - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---bullet-hell---main-rb'>Arcade - Bullet Hell - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---dueling-starships---main-rb'>Arcade - Dueling Starships - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade/flappy-dragon/credits-txt'>arcade/flappy dragon/credits.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade/flappy-dragon/main-rb'>arcade/flappy dragon/main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---pong---main-rb'>Arcade - Pong - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---snakemoji---main-rb'>Arcade - Snakemoji - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---solar-system---main-rb'>Arcade - Solar System - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---sound-golf---main-rb'>Arcade - Sound Golf - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----arcade---twinstick---main-rb'>Arcade - Twinstick - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----crafting---craft-game-starting-point---main-rb'>Crafting - Craft Game Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----crafting---farming-game-starting-point---main-rb'>Crafting - Farming Game Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----crafting---farming-game-starting-point---tests-rb'>Crafting - Farming Game Starting Point - tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dev-tools---add-buttons-to-console---main-rb'>Dev Tools - Add Buttons To Console - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dev-tools---animation-creator-starting-point---main-rb'>Dev Tools - Animation Creator Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dev-tools---tile-editor-starting-point---main-rb'>Dev Tools - Tile Editor Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----dungeon-crawl---classics-jam---main-rb'>Dungeon Crawl - Classics Jam - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----fighting---special-move-inputs---main-rb'>Fighting - Special Move Inputs - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----lowrez---nokia-3310---main-rb'>Lowrez - Nokia 3310 - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----lowrez---nokia-3310---nokia-rb'>Lowrez - Nokia 3310 - nokia.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----lowrez---resolution-64x64---lowrez-rb'>Lowrez - Resolution 64x64 - lowrez.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----lowrez---resolution-64x64---main-rb'>Lowrez - Resolution 64x64 - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mario---jumping---main-rb'>Mario - Jumping - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----mario---jumping-and-collisions---main-rb'>Mario - Jumping And Collisions - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---clepto-frog---main-rb'>Platformer - Clepto Frog - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---clepto-frog---map-rb'>Platformer - Clepto Frog - map.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---gorillas-basic---credits-txt'>Platformer - Gorillas Basic - credits.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---gorillas-basic---main-rb'>Platformer - Gorillas Basic - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---gorillas-basic---tests-rb'>Platformer - Gorillas Basic - tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---gorillas-basic---tests---building_generation_tests-rb'>Platformer - Gorillas Basic - Tests - building_generation_tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---the-little-probe---main-rb'>Platformer - The Little Probe - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---the-little-probe---data---level-txt'>Platformer - The Little Probe - Data - level.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----platformer---the-little-probe---data---level_lava-txt'>Platformer - The Little Probe - Data - level_lava.txt</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---choose-your-own-adventure---decision-rb'>Rpg Narrative - Choose Your Own Adventure - decision.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---choose-your-own-adventure---main-rb'>Rpg Narrative - Choose Your Own Adventure - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---lowrez_simulator-rb'>Rpg Narrative - Return Of Serenity - lowrez_simulator.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---main-rb'>Rpg Narrative - Return Of Serenity - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---require-rb'>Rpg Narrative - Return Of Serenity - require.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline-rb'>Rpg Narrative - Return Of Serenity - storyline.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_anka-rb'>Rpg Narrative - Return Of Serenity - storyline_anka.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_blinking_light-rb'>Rpg Narrative - Return Of Serenity - storyline_blinking_light.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_day_one-rb'>Rpg Narrative - Return Of Serenity - storyline_day_one.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_final_decision-rb'>Rpg Narrative - Return Of Serenity - storyline_final_decision.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_final_message-rb'>Rpg Narrative - Return Of Serenity - storyline_final_message.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_serenity_alive-rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_alive.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_serenity_bio-rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_bio.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_serenity_introduction-rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_introduction.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-narrative---return-of-serenity---storyline_speed_of_light-rb'>Rpg Narrative - Return Of Serenity - storyline_speed_of_light.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-starting-point---constants-rb'>Rpg Roguelike - Roguelike Starting Point - constants.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-starting-point---legend-rb'>Rpg Roguelike - Roguelike Starting Point - legend.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-starting-point---main-rb'>Rpg Roguelike - Roguelike Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-starting-point---sprite_lookup-rb'>Rpg Roguelike - Roguelike Starting Point - sprite_lookup.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-roguelike---roguelike-line-of-sight---main-rb'>Rpg Roguelike - Roguelike Line Of Sight - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-tactical---hexagonal-grid---main-rb'>Rpg Tactical - Hexagonal Grid - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-tactical---isometric-grid---main-rb'>Rpg Tactical - Isometric Grid - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-topdown---topdown-casino---main-rb'>Rpg Topdown - Topdown Casino - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----rpg-topdown---topdown-starting-point---main-rb'>Rpg Topdown - Topdown Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----teenytiny---teenytiny-starting-point---main-rb'>Teenytiny - Teenytiny Starting Point - main.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----teenytiny---teenytiny-starting-point---license-txt'>Teenytiny - Teenytiny Starting Point - license.txt</a></li></ul></ul><ul><li><a class='header-2' href='#---oss'>OSS</a></li></ul><ul><ul><li><a class='header-3' href='#----args-rb'>args.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----assert-rb'>assert.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----attr_gtk-rb'>attr_gtk.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----attr_sprite-rb'>attr_sprite.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console-rb'>console.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console_color-rb'>console_color.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console_font_style-rb'>console_font_style.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console_menu-rb'>console_menu.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----console_prompt-rb'>console_prompt.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----controller-rb'>controller.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----controller/config-rb'>controller/config.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----controller/keys-rb'>controller/keys.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----directional_input_helper_methods-rb'>directional_input_helper_methods.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----easing-rb'>easing.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----entity-rb'>entity.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----geometry-rb'>geometry.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----grid-rb'>grid.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----inputs-rb'>inputs.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----ios_wizard-rb'>ios_wizard.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----itch_wizard-rb'>itch_wizard.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----layout-rb'>layout.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----log-rb'>log.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----metadata-rb'>metadata.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----numeric-rb'>numeric.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----recording-rb'>recording.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----remote_hotload_client-rb'>remote_hotload_client.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/autocomplete-rb'>runtime/autocomplete.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/benchmark-rb'>runtime/benchmark.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/draw-rb'>runtime/draw.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/framerate-rb'>runtime/framerate.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/framerate_diagnostics-rb'>runtime/framerate_diagnostics.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----runtime/hotload-rb'>runtime/hotload.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----string-rb'>string.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tests-rb'>tests.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----trace-rb'>trace.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----tweetcart-rb'>tweetcart.rb</a></li></ul></ul><ul><ul><li><a class='header-3' href='#----wizards-rb'>wizards.rb</a></li></ul></ul></ul> </div> <div id='content'> <h1 id='--dragonruby-game-toolkit-live-docs'>DragonRuby Game Toolkit Live Docs</h1> <p> @@ -61,9 +64,6 @@ To search docs you can type <code>docs_search "SEARCH TERM"</code> or if you wan </p> <pre><code class="language-ruby">docs_search { |entry| (entry.include? "Array") && (!entry.include? "Enumerable") } </code></pre> -<p> -<img src='docs_search.gif'></img> -</p> <h1 id='--hello-world'>Hello World</h1> <p> Welcome to DragonRuby Game Toolkit. Take the steps below to get started. @@ -91,21 +91,26 @@ Reply with: I am a Dragon Rider. </p> </blockquote> -<h1 id='--watch-some-intro-videos'>Watch Some Intro Videos</h1> +<h1 id='--intro-videos'>Intro Videos</h1> <p> -Each video is only 20 minutes and all of them will fit into a lunch break. So please watch them: +Here are some videos to help you get the lay of the land. </p> +<h2 id='---quick-api-tour'>Quick Api Tour</h2> <ol> <li> Beginner Introduction to DragonRuby Game Toolkit: <a href='https://youtu.be/ixw7TJhU08E'>https://youtu.be/ixw7TJhU08E</a></li> +</ol> +<h2 id='---if-you-are-completely-new-to-ruby-and-programming'>If You Are Completely New to Ruby and Programming</h2> +<ol> <li> Intermediate Introduction to Ruby Syntax: <a href='https://youtu.be/HG-XRZ5Ppgc'>https://youtu.be/HG-XRZ5Ppgc</a></li> <li> Intermediate Introduction to Arrays in Ruby: <a href='https://youtu.be/N72sEYFRqfo'>https://youtu.be/N72sEYFRqfo</a></li> +<li> You may also want to try this free course provided at <a href='http://dragonruby.school'>http://dragonruby.school</a>.</li> +</ol> +<h2 id='---if-you-have-game-dev-experience'>If You Have Game Dev Experience</h2> +<ol> +<li> Building Tetris - Part 1: <a href='https://youtu.be/xZMwRSbC4rY'>https://youtu.be/xZMwRSbC4rY</a></li> +<li> Building Tetris - Part 2: <a href='https://youtu.be/C3LLzDUDgz4'>https://youtu.be/C3LLzDUDgz4</a></li> +<li> Low Res Game Jam Tutorial: <a href='https://youtu.be/pCI90ukKCME'>https://youtu.be/pCI90ukKCME</a></li> </ol> -<p> -The second and third videos are not required if you are proficient with Ruby, but *definitely* watch the first one. -</p> -<p> -You may also want to try this free course provided at <a href='http://dragonruby.school'>http://dragonruby.school</a>. -</p> <h1 id='--getting-started-tutorial'>Getting Started Tutorial</h1> <p> 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: <a href='https://en.wikipedia.org/wiki/Ryan_C._Gordon'>https://en.wikipedia.org/wiki/Ryan_C._Gordon</a>). @@ -291,7 +296,7 @@ Note that you can also run DragonRuby without X11 at all: if you run it from a v <p> 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 <code>args.output</code> anymore. </p> -<h1 id='--deploying-to-itch.io'>Deploying To Itch.io</h1> +<h1 id='--deploying-to-itch-io'>Deploying To Itch.io</h1> <p> 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! </p> @@ -360,7 +365,7 @@ If you have a Pro subscription, you also have the capability to deploy to mobile 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: <code>$wizards.ios.start</code> and you will be guided through the deployment process. </p> <p> -To deploy to Android, you need to have an Android emulator/device, and a environment that is able to run Android SDK. <code>dragonruby-publish</code> 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: +To deploy to Android, you need to have an Android emulator/device, and an environment that is able to run Android SDK. <code>dragonruby-publish</code> 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: </p> <pre><code class="language-ruby">> adb logcat -e mygame # you'll want to run this in a separate terminal > keytool -genkey -v -keystore mygame.keystore -alias mygame -keyalg RSA -keysize 2048 -validity 10000 @@ -369,7 +374,7 @@ To deploy to Android, you need to have an Android emulator/device, and a environ </code></pre> <h1 id='--dragonruby's-philosophy'>DragonRuby's Philosophy</h1> <p> -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. +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. </p> <h2 id='---challenge-the-status-quo'>Challenge The Status Quo</h2> <p> @@ -388,7 +393,7 @@ It's a hard pill to swallow, but forget blindly accepted best practices and try 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. </p> <p> -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 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 string 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). +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). </p> <h2 id='---release-early-and-often'>Release Early and Often</h2> <p> @@ -432,12 +437,12 @@ We are bombarded by marketing speak day in and day out. We don't do that here. T <p> 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). </p> -<h1 id='--frequently-asked-questions,-comments,-and-concerns'>Frequently Asked Questions, Comments, and Concerns</h1> +<h1 id='--frequently-asked-questions--comments--and-concerns'>Frequently Asked Questions, Comments, and Concerns</h1> <p> Here are questions, comments, and concerns that frequently come up. </p> <h2 id='---frequently-asked-questions'>Frequently Asked Questions</h2> -<h3 id='----what-is-dragonruby-llp?'>What is DragonRuby LLP?</h3> +<h3 id='----what-is-dragonruby-llp-'>What is DragonRuby LLP?</h3> <p> DragonRuby LLP is a partnership of four devs who came together with the goal of bringing the aesthetics and joy of Ruby, everywhere possible. </p> @@ -462,7 +467,7 @@ NOTE: We leave the "A DragonRuby LLP Product" off of this one because that just <p> NOTE: Devs who use DragonRuby are "Dragon Riders/Riders of Dragons". That's a bad ass identifier huh? </p> -<h3 id='----what-is-dragonruby?'>What is DragonRuby?</h3> +<h3 id='----what-is-dragonruby-'>What is DragonRuby?</h3> <p> The response to this question requires a few subparts. First we need to clarify some terms. Specifically _language specification_ vs _runtime_. </p> @@ -482,7 +487,7 @@ DragonRuby's goal is to be compliant with the ISO/IEC 30170:2012 standard. It's The elevator pitch is: </p> <p> -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. +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. </p> <h4>What does Multilevel Cross-platform mean?</h4> <p> @@ -513,7 +518,7 @@ These levels allow us to stay up to date with open source implementations of Rub <p> 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. </p> -<h3 id='----how-is-dragonruby-different-than-mri?'>How is DragonRuby different than MRI?</h3> +<h3 id='----how-is-dragonruby-different-than-mri-'>How is DragonRuby different than MRI?</h3> <p> 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). </p> @@ -535,7 +540,7 @@ end </code></pre> <ul> <li>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).</li> -<li>All <code>puts</code> statements will also be saved to <code>logs/log.txt</code>. So if you want to stay in your editor and not look at the terminal, or the DragonRuby Console, you can <code>tail</code> this file.</li> +<li>All <code>puts</code> statements will also be saved to <code>logs/puts.txt</code>. So if you want to stay in your editor and not look at the terminal, or the DragonRuby Console, you can <code>tail</code> this file.</li> </ul> <p> 4. To ignore code in <code>repl.rb</code>, instead of commenting it out, prefix <code>repl</code> with the letter <code>x</code> and it'll be ignored. @@ -568,18 +573,18 @@ You can use DragonRuby's replay capabilities to troubleshoot: </p> <ol> <li> 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).</li> -<li> Use <code>./dragonruby mygame --record</code> to create a game play recording that you can use to find the exception (you can replay a recoding by executing <code>./dragonruby mygame --replay last_replay.txt</code> or through the DragonRuby Console using <code>$gtk.recording.start_replay "last_replay.txt"</code>.</li> +<li> Use <code>./dragonruby mygame --record</code> to create a game play recording that you can use to find the exception (you can replay a recording by executing <code>./dragonruby mygame --replay last_replay.txt</code> or through the DragonRuby Console using <code>$gtk.recording.start_replay "last_replay.txt"</code>.</li> <li> DragonRuby also ships with a unit testing facility. You can invoke the following command to run a test: <code>./dragonruby . --eval some_ruby_file.rb --no-tick</code>.</li> <li> Get into the habit of adding debugging facilities within the game itself. You can add drawing primitives to <code>args.outputs.debug</code> that will render on top of your game but will be ignored in a production release.</li> <li> 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.</li> </ol> <h2 id='---frequent-comments-about-ruby-as-a-language-choice'>Frequent Comments About Ruby as a Language Choice</h2> -<h3 id='----but-ruby-is-dead.'>But Ruby is dead.</h3> +<h3 id='----but-ruby-is-dead-'>But Ruby is dead.</h3> <p> Let's check the official source for the answer to this question: isrubydead.com: <a href='https://isrubydead.com/'>https://isrubydead.com/</a>. </p> <p> -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. +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. </p> <p> 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]. @@ -587,11 +592,11 @@ What really matters is _quality/maturity_. Here is the latest (StackOverflow Sur <p> Let's stop making this comment shall we? </p> -<h3 id='----but-ruby-is-slow.'>But Ruby is slow.</h3> +<h3 id='----but-ruby-is-slow-'>But Ruby is slow.</h3> <p> 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. </p> -<h3 id='----dynamic-languages-are-slow.'>Dynamic languages are slow.</h3> +<h3 id='----dynamic-languages-are-slow-'>Dynamic languages are slow.</h3> <p> 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_. </p> @@ -602,7 +607,7 @@ Unless you are writing in some form of intermediate representation by hand, your NOTE: If you _are_ hand writing LLVM IR, we are always open to bringing on new partners with such a skill set. Email us ^_^. </p> <h2 id='---frequent-concerns'>Frequent Concerns</h2> -<h3 id='----dragonruby-is-not-open-source.-that's-not-right.'>DragonRuby is not open source. That's not right.</h3> +<h3 id='----dragonruby-is-not-open-source--that's-not-right-'>DragonRuby is not open source. That's not right.</h3> <p> 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. </p> @@ -618,7 +623,7 @@ If the reason above isn't sufficient, then definitely use something else. <p> All this being said, we do have parts of the engine open sourced on GitHub: <a href='https://github.com/dragonruby/dragonruby-game-toolkit-contrib/'>https://github.com/dragonruby/dragonruby-game-toolkit-contrib/</a> </p> -<h3 id='----dragonruby-is-for-pay.-you-should-offer-a-free-version.'>DragonRuby is for pay. You should offer a free version.</h3> +<h3 id='----dragonruby-is-for-pay--you-should-offer-a-free-version-'>DragonRuby is for pay. You should offer a free version.</h3> <p> 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. </p> @@ -640,21 +645,21 @@ You qualify for a free, unrestricted license to DragonRuby products if any of th <p> Just contact Amir at [email protected] with a short explanation of your current situation and he'll set you up. No questions asked. </p> -<h3 id='----but-still,-you-should-offer-a-free-version.-so-i-can-try-it-out-and-see-if-i-like-it.'>But still, you should offer a free version. So I can try it out and see if I like it.</h3> +<h3 id='----but-still--you-should-offer-a-free-version--so-i-can-try-it-out-and-see-if-i-like-it-'>But still, you should offer a free version. So I can try it out and see if I like it.</h3> <p> -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. +You can try our web-based sandbox environment at <a href='http://fiddle.dragonruby.org'>http://fiddle.dragonruby.org</a>. But it won't do the runtime justice. Or just come to our Discord Channel at <a href='http://discord.dragonruby.org'>http://discord.dragonruby.org</a> 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. </p> <p> Seriously just buy it. Get a refund if you don't like it. We make it stupid easy to do so. </p> -<h3 id='----i-still-think-you-should-do-a-free-version.-think-of-all-people-who-would-give-it-a-shot.'>I still think you should do a free version. Think of all people who would give it a shot.</h3> +<h3 id='----i-still-think-you-should-do-a-free-version--think-of-all-people-who-would-give-it-a-shot-'>I still think you should do a free version. Think of all people who would give it a shot.</h3> <p> 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). </p> <p> 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. </p> -<h3 id='----what-if-i-build-something-with-dragonruby,-but-dragonruby-llp-becomes-insolvent.'>What if I build something with DragonRuby, but DragonRuby LLP becomes insolvent.</h3> +<h3 id='----what-if-i-build-something-with-dragonruby--but-dragonruby-llp-becomes-insolvent-'>What if I build something with DragonRuby, but DragonRuby LLP becomes insolvent.</h3> <p> 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. </p> @@ -743,11 +748,15 @@ You can represent a sprite as a <code>Hash</code>: flip_vertically: false, flip_horizontally: false, angle_anchor_x: 0.5, - angle_anchor_y: 1.0 + angle_anchor_y: 1.0, + blendmode_enum: 1 } end </code></pre> <p> +The <code>blendmode_enum</code> value can be set to <code>0</code> (no blending), <code>1</code> (alpha blending), <code>2</code> (additive blending), <code>3</code> (modulo blending), <code>4</code> (multiply blending). +</p> +<p> You can represent a sprite as an <code>object</code>: </p> <pre><code class="language-ruby"># Create type with ALL sprite properties AND primitive_marker @@ -756,7 +765,7 @@ class Sprite :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 + :angle_anchor_x, :angle_anchor_y, :blendmode_enum def primitive_marker :sprite @@ -832,16 +841,17 @@ You can add additional metadata about your game within a label, which requires y </p> <pre><code class="language-ruby">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", + x: 200, + y: 550, + text: "dragonruby", + size_enum: 2, + 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 # You can add any properties you like (this will be ignored/won't cause errors) game_data_one: "Something", game_data_two: { @@ -871,6 +881,19 @@ You can get the render size of any string using <code>args.gtk.calcstringbox</co ] end </code></pre> +<h2 id='---rendering-labels-with-new-line-characters-and-wrapping'>Rendering Labels With New Line Characters And Wrapping</h2> +<p> +You can use a strategy like the following to create multiple labels from a String. +</p> +<pre><code class="language-ruby">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 +</code></pre> <h2 id='---how-to-play-a-sound'>How To Play A Sound</h2> <p> Sounds that end <code>.wav</code> will play once: @@ -902,7 +925,7 @@ If you want to play a <code>.ogg</code> once as if it were a sound effect, you c end end </code></pre> -<h2 id='---using--args.state--to-store-your-game-state'>Using <code>args.state</code> To Store Your Game State</h2> +<h2 id='---using--args-state--to-store-your-game-state'>Using <code>args.state</code> To Store Your Game State</h2> <p> <code>args.state</code> is a open data structure that allows you to define properties that are arbitrarily nested. You don't need to define any kind of <code>class</code>. </p> @@ -975,7 +998,7 @@ All the properties below hang off of <code>args</code> and can be accessed in th args.PROPERTY end </code></pre> -<h2 id='----args.state-'><code>args.state</code></h2> +<h2 id='----args-state-'><code>args.state</code></h2> <p> Store your game state inside of this <code>state</code>. Properties with arbitrary nesting is allowed and a backing Entity will be created on your behalf. </p> @@ -984,35 +1007,35 @@ Store your game state inside of this <code>state</code>. Properties with arbitra args.state.player.y ||= 0 end </code></pre> -<h3 id='-----.-.entity_id-'><code>.*.entity_id</code></h3> +<h3 id='--------entity_id-'><code>.*.entity_id</code></h3> <p> Entities automatically receive an <code>entity_id</code> of type <code>Fixnum</code>. </p> -<h3 id='-----.-.entity_type-'><code>.*.entity_type</code></h3> +<h3 id='--------entity_type-'><code>.*.entity_type</code></h3> <p> Entities can have an <code>entity_type</code> which is represented as a <code>Symbol</code>. </p> -<h3 id='-----.-.created_at-'><code>.*.created_at</code></h3> +<h3 id='--------created_at-'><code>.*.created_at</code></h3> <p> Entities have <code>created_at</code> set to <code>args.state.tick_count</code> when they are created. </p> -<h3 id='-----.-.created_at_elapsed-'><code>.*.created_at_elapsed</code></h3> +<h3 id='--------created_at_elapsed-'><code>.*.created_at_elapsed</code></h3> <p> Returns the elapsed number of ticks since creation. </p> -<h3 id='-----.-.global_created_at-'><code>.*.global_created_at</code></h3> +<h3 id='--------global_created_at-'><code>.*.global_created_at</code></h3> <p> Entities have <code>global_created_at</code> set to <code>Kernel.global_tick_count</code> when they are created. </p> -<h3 id='-----.-.global_created_at_elapsed-'><code>.*.global_created_at_elapsed</code></h3> +<h3 id='--------global_created_at_elapsed-'><code>.*.global_created_at_elapsed</code></h3> <p> Returns the elapsed number of global ticks since creation. </p> -<h3 id='-----.-.as_hash-'><code>.*.as_hash</code></h3> +<h3 id='--------as_hash-'><code>.*.as_hash</code></h3> <p> Entity cast to a <code>Hash</code> so you can update values as if you were updating a <code>Hash</code>. </p> -<h3 id='-----.new_entity-'><code>.new_entity</code></h3> +<h3 id='------new_entity-'><code>.new_entity</code></h3> <p> Creates a new Entity with a <code>type</code>, and initial properties. An option block can be passed to change the newly created entity: </p> @@ -1023,47 +1046,47 @@ Creates a new Entity with a <code>type</code>, and initial properties. An option end end </code></pre> -<h3 id='-----.new_entity_strict-'><code>.new_entity_strict</code></h3> +<h3 id='------new_entity_strict-'><code>.new_entity_strict</code></h3> <p> Creates a new Strict Entity. While Entities created via <code>args.state.new_entity</code> can have new properties added later on, Entities created using <code>args.state.new_entity</code> must define all properties that are allowed during its initialization. Attempting to add new properties after initialization will result in an exception. </p> -<h3 id='-----.tick_count-'><code>.tick_count</code></h3> +<h3 id='------tick_count-'><code>.tick_count</code></h3> <p> Returns the current tick of the game. <code>args.state.tick_count</code> is <code>0</code> when the game is first started or if the game is reset via <code>$gtk.reset</code>. </p> -<h2 id='----args.inputs-'><code>args.inputs</code></h2> +<h2 id='----args-inputs-'><code>args.inputs</code></h2> <p> Access using input using <code>args.inputs</code>. </p> -<h3 id='-----.up-'><code>.up</code></h3> +<h3 id='------up-'><code>.up</code></h3> <p> Returns <code>true</code> if: the <code>up</code> arrow or <code>w</code> key is pressed or held on the <code>keyboard</code>; or if <code>up</code> is pressed or held on <code>controller_one</code>; or if the <code>left_analog</code> on <code>controller_one</code> is tilted upwards. </p> -<h3 id='-----.down-'><code>.down</code></h3> +<h3 id='------down-'><code>.down</code></h3> <p> Returns <code>true</code> if: the <code>down</code> arrow or <code>s</code> key is pressed or held on the <code>keyboard</code>; or if <code>down</code> is pressed or held on <code>controller_one</code>; or if the <code>left_analog</code> on <code>controller_one</code> is tilted downwards. </p> -<h3 id='-----.left-'><code>.left</code></h3> +<h3 id='------left-'><code>.left</code></h3> <p> Returns <code>true</code> if: the <code>left</code> arrow or <code>a</code> key is pressed or held on the <code>keyboard</code>; or if <code>left</code> is pressed or held on <code>controller_one</code>; or if the <code>left_analog</code> on <code>controller_one</code> is tilted to the left. </p> -<h3 id='-----.right-'><code>.right</code></h3> +<h3 id='------right-'><code>.right</code></h3> <p> Returns <code>true</code> if: the <code>right</code> arrow or <code>d</code> key is pressed or held on the <code>keyboard</code>; or if <code>right</code> is pressed or held on <code>controller_one</code>; or if the <code>left_analog</code> on <code>controller_one</code> is tilted to the right. </p> -<h3 id='-----.left_right-'><code>.left_right</code></h3> +<h3 id='------left_right-'><code>.left_right</code></h3> <p> Returns <code>-1</code> (left), <code>0</code> (neutral), or <code>+1</code> (right) depending on results of <code>args.inputs.left</code> and <code>args.inputs.right</code>. </p> -<h3 id='-----.up_down-'><code>.up_down</code></h3> +<h3 id='------up_down-'><code>.up_down</code></h3> <p> -Returns <code>-1</code> (down), <code>0</code> (neutral), or <code>+1</code> (right) depending on results of <code>args.inputs.up</code> and <code>args.inputs.down</code>. +Returns <code>-1</code> (down), <code>0</code> (neutral), or <code>+1</code> (up) depending on results of <code>args.inputs.down</code> and <code>args.inputs.up</code>. </p> -<h3 id='-----.text--or--.history-'><code>.text</code> OR <code>.history</code></h3> +<h3 id='------text--or---history-'><code>.text</code> OR <code>.history</code></h3> <p> Returns a string that represents the last key that was pressed on the keyboard. </p> -<h3 id='-----.mouse-'><code>.mouse</code></h3> +<h3 id='------mouse-'><code>.mouse</code></h3> <p> Represents the user's </p> @@ -1105,37 +1128,49 @@ Returns a bitmask for all buttons on the mouse: <code>1</code> for a button in t </p> <h4><code>mouse.wheel</code></h4> <p> -Represents the mouse wheel. Returns <code>nil</code> if no mouse wheel actions occurred. ***** <code>.x</code> Returns the negative or positive number if the mouse wheel has changed in the <code>x</code> axis. ***** <code>.y</code> Returns the negative or positive number if the mouse wheel has changed in the <code>y</code> axis. +Represents the mouse wheel. Returns <code>nil</code> if no mouse wheel actions occurred. +</p> +<p> +***** <code>.x</code> +</p> +<p> +Returns the negative or positive number if the mouse wheel has changed in the <code>x</code> axis. +</p> +<p> +***** <code>.y</code> +</p> +<p> +Returns the negative or positive number if the mouse wheel has changed in the <code>y</code> axis. </p> <h4><code>.click</code> OR <code>.down</code>, <code>.previous_click</code>, <code>.up</code></h4> <p> The properties <code>args.inputs.mouse.(click|down|previous_click|up)</code> each return <code>nil</code> if the mouse button event didn't occur. And return an Entity that has an <code>x</code>, <code>y</code> properties along with helper functions to determine collision: <code>inside_rect?</code>, <code>inside_circle</code>. </p> -<h3 id='-----.controller_one-,--.controller_two-'><code>.controller_one</code>, <code>.controller_two</code></h3> +<h3 id='------controller_one-----controller_two-'><code>.controller_one</code>, <code>.controller_two</code></h3> <p> Represents controllers connected to the usb ports. </p> -<h4>~.up</h4> +<h4><code>.up</code></h4> <p> Returns <code>true</code> if <code>up</code> is pressed or held on the directional or left analog. </p> -<h4>~.down</h4> +<h4><code>.down</code></h4> <p> Returns <code>true</code> if <code>down</code> is pressed or held on the directional or left analog. </p> -<h4>~.left</h4> +<h4><code>.left</code></h4> <p> Returns <code>true</code> if <code>left</code> is pressed or held on the directional or left analog. </p> -<h4>~.right</h4> +<h4><code>.right</code></h4> <p> Returns <code>true</code> if <code>right</code> is pressed or held on the directional or left analog. </p> -<h4>~.left_right</h4> +<h4><code>.left_right</code></h4> <p> Returns <code>-1</code> (left), <code>0</code> (neutral), or <code>+1</code> (right) depending on results of <code>args.inputs.controller_(one|two).left</code> and <code>args.inputs.controller_(one|two).right</code>. </p> -<h4>~.up_down</h4> +<h4><code>.up_down</code></h4> <p> Returns <code>-1</code> (down), <code>0</code> (neutral), or <code>+1</code> (up) depending on results of <code>args.inputs.controller_(one|two).up</code> and <code>args.inputs.controller_(one|two).down</code>. </p> @@ -1155,19 +1190,19 @@ Returns a number between <code>-1</code> and <code>1</code> which represents the <p> Returns a number between <code>-1</code> and <code>1</code> which represents the percentage the analog is moved vertically as a ratio of the maximum vertical movement. </p> -<h4><code>.directional_up)</code></h4> +<h4><code>.directional_up</code></h4> <p> Returns <code>true</code> if <code>up</code> is pressed or held on the directional. </p> -<h4><code>.directional_down)</code></h4> +<h4><code>.directional_down</code></h4> <p> Returns <code>true</code> if <code>down</code> is pressed or held on the directional. </p> -<h4><code>.directional_left)</code></h4> +<h4><code>.directional_left</code></h4> <p> Returns <code>true</code> if <code>left</code> is pressed or held on the directional. </p> -<h4><code>.directional_right)</code></h4> +<h4><code>.directional_right</code></h4> <p> Returns <code>true</code> if <code>right</code> is pressed or held on the directional. </p> @@ -1191,7 +1226,7 @@ Returns <code>true</code> if the specific button is being held. <code>args.input <p> Returns <code>true</code> if the specific button was released. <code>args.inputs.controller_(one|two).key_up.BUTTON</code> will be true only on the frame the button was released. </p> -<h3 id='-----.keyboard-'><code>.keyboard</code></h3> +<h3 id='------keyboard-'><code>.keyboard</code></h3> <p> Represents the user's keyboard </p> @@ -1333,139 +1368,148 @@ Returns a <code>Hash</code> with all keys on the keyboard in their respective st <li><code>:down_or_held</code></li> <li><code>:up</code></li> </ul> -<h2 id='----args.outputs-'><code>args.outputs</code></h2> +<h2 id='----args-outputs-'><code>args.outputs</code></h2> <p> <code>args.outputs.PROPERTY</code> is how you render to the screen. </p> -<h3 id='-----.background_color-'><code>.background_color</code></h3> +<h3 id='------background_color-'><code>.background_color</code></h3> <p> Set <code>args.outputs.background_color</code> to an <code>Array</code> with <code>RGB</code> values (eg. <code>[255, 255, 255]</code> for the color white). </p> -<h3 id='-----.sounds-'><code>.sounds</code></h3> +<h3 id='------sounds-'><code>.sounds</code></h3> <p> Send a file path to this collection to play a sound. The sound file must be under the <code>mygame</code> directory. Example: <code>args.outputs.sounds << "sounds/jump.wav"</code>. </p> -<h3 id='-----.solids-'><code>.solids</code></h3> +<h3 id='------solids-'><code>.solids</code></h3> <p> Send a Primitive to this collection to render a filled in rectangle to the screen. This collection is cleared at the end of every frame. </p> -<h3 id='-----.static_solids-'><code>.static_solids</code></h3> +<h3 id='------static_solids-'><code>.static_solids</code></h3> <p> Send a Primitive to this collection to render a filled in rectangle to the screen. This collection is not cleared at the end of every frame. And objects can be mutated by reference. </p> -<h3 id='-----.sprites-,--.static_sprites-'><code>.sprites</code>, <code>.static_sprites</code></h3> +<h3 id='------sprites-----static_sprites-'><code>.sprites</code>, <code>.static_sprites</code></h3> <p> Send a Primitive to this collection to render a sprite to the screen. </p> -<h3 id='-----.primitives-,--.static_primitives-'><code>.primitives</code>, <code>.static_primitives</code></h3> +<h3 id='------primitives-----static_primitives-'><code>.primitives</code>, <code>.static_primitives</code></h3> <p> Send a Primitive of any type and it'll be rendered. The Primitive must have a <code>primitive_marker</code> that returns <code>:solid</code>, <code>:sprite</code>, <code>:label</code>, <code>:line</code>, <code>:border</code>. </p> -<h3 id='-----.labels-,--.static_labels-'><code>.labels</code>, <code>.static_labels</code></h3> +<h3 id='------labels-----static_labels-'><code>.labels</code>, <code>.static_labels</code></h3> <p> Send a Primitive to this collection to render text to the screen. </p> -<h3 id='-----.lines-,--.static_lines-'><code>.lines</code>, <code>.static_lines</code></h3> +<h3 id='------lines-----static_lines-'><code>.lines</code>, <code>.static_lines</code></h3> <p> Send a Primitive to this collection to render a line to the screen. </p> -<h3 id='-----.borders-,--.static_borders-'><code>.borders</code>, <code>.static_borders</code></h3> +<h3 id='------borders-----static_borders-'><code>.borders</code>, <code>.static_borders</code></h3> <p> Send a Primitive to this collection to render an unfilled rectangle to the screen. </p> -<h3 id='-----.debug-,--.static_debug-'><code>.debug</code>, <code>.static_debug</code></h3> +<h3 id='------debug-----static_debug-'><code>.debug</code>, <code>.static_debug</code></h3> <p> Send any Primitive to this collection which represents things you render to the screen for debugging purposes. Primitives in this collection will not be rendered in a production release of your game. </p> -<h2 id='----args.geometry-'><code>args.geometry</code></h2> +<h2 id='----args-geometry-'><code>args.geometry</code></h2> <p> This property contains geometric functions. Functions can be invoked via <code>args.geometry.FUNCTION</code>. </p> -<h3 id='-----.inside_rect?-rect_1,-rect_2-'><code>.inside_rect? rect_1, rect_2</code></h3> +<p> +Here are some general notes with regards to the arguments these geometric functions accept. +</p> +<ol> +<li> <code>Rectangles</code> can be represented as an <code>Array</code> with four (or more) values <code>[x, y, w, h]</code>, as a <code>Hash</code> <code>{ x:, y:, w:, h: }</code> or an object that responds to <code>x</code>, <code>y</code>, <code>w</code>, and <code>h</code>.</li> +<li> <code>Points</code> can be represent as an <code>Array</code> with two (or more) values <code>[x, y]</code>, as a <code>Hash</code> <code>{ x:, y:}</code> or an object that responds to <code>x</code>, and <code>y</code>.</li> +<li> <code>Lines</code> can be represented as an <code>Array</code> with four (or more) values <code>[x, y, x2, y2]</code>, as a <code>Hash</code> <code>{ x:, y:, x2:, y2: }</code> or an object that responds to <code>x</code>, <code>y</code>, <code>x2</code>, and <code>y2</code>.</li> +<li> <code>Angles</code> are represented as degrees (not radians).</li> +</ol> +<h3 id='------inside_rect--rect_1--rect_2-'><code>.inside_rect? rect_1, rect_2</code></h3> <p> Returns <code>true</code> if <code>rect_1</code> is inside <code>rect_2</code>. </p> -<h3 id='-----.intersect_rect?-rect_2,-rect_2-'><code>.intersect_rect? rect_2, rect_2</code></h3> +<h3 id='------intersect_rect--rect_2--rect_2-'><code>.intersect_rect? rect_2, rect_2</code></h3> <p> Returns <code>true</code> if <code>rect_1</code> intersects <code>rect_2</code>. </p> -<h3 id='-----.scale_rect-rect,-x_percentage,-y_percentage-'><code>.scale_rect rect, x_percentage, y_percentage</code></h3> +<h3 id='------scale_rect-rect--x_percentage--y_percentage-'><code>.scale_rect rect, x_percentage, y_percentage</code></h3> <p> Returns a new rectangle that is scaled by the percentages provided. </p> -<h3 id='-----.angle_to-start_point,-end_point-'><code>.angle_to start_point, end_point</code></h3> +<h3 id='------angle_to-start_point--end_point-'><code>.angle_to start_point, end_point</code></h3> <p> Returns the angle in degrees between two points <code>start_point</code> to <code>end_point</code>. </p> -<h3 id='-----.angle_from-start_point,-end_point-'><code>.angle_from start_point, end_point</code></h3> +<h3 id='------angle_from-start_point--end_point-'><code>.angle_from start_point, end_point</code></h3> <p> Returns the angle in degrees between two points <code>start_point</code> from <code>end_point</code>. </p> -<h3 id='-----.point_inside_circle?-point,-circle_center_point,-radius-'><code>.point_inside_circle? point, circle_center_point, radius</code></h3> +<h3 id='------point_inside_circle--point--circle_center_point--radius-'><code>.point_inside_circle? point, circle_center_point, radius</code></h3> <p> Returns <code>true</code> if a point is inside a circle defined by its center and radius. </p> -<h3 id='-----.center_inside_rect-rect,-other_rect-'><code>.center_inside_rect rect, other_rect</code></h3> +<h3 id='------center_inside_rect-rect--other_rect-'><code>.center_inside_rect rect, other_rect</code></h3> <p> Returns a new rectangle based of off <code>rect</code> that is centered inside of <code>other_rect</code>. </p> -<h3 id='-----.center_inside_rect_x-rect,-other_rect-'><code>.center_inside_rect_x rect, other_rect</code></h3> +<h3 id='------center_inside_rect_x-rect--other_rect-'><code>.center_inside_rect_x rect, other_rect</code></h3> <p> Returns a new rectangle based of off <code>rect</code> that is centered horizontally inside of <code>other_rect</code>. </p> -<h3 id='-----.center_inside_rect_y-rect,-other_rect-'><code>.center_inside_rect_y rect, other_rect</code></h3> +<h3 id='------center_inside_rect_y-rect--other_rect-'><code>.center_inside_rect_y rect, other_rect</code></h3> <p> Returns a new rectangle based of off <code>rect</code> that is centered vertically inside of <code>other_rect</code>. </p> -<h3 id='-----.anchor_rect-rect,-anchor_x,-anchor_y-'><code>.anchor_rect rect, anchor_x, anchor_y</code></h3> +<h3 id='------anchor_rect-rect--anchor_x--anchor_y-'><code>.anchor_rect rect, anchor_x, anchor_y</code></h3> <p> Returns a new rectangle based of off <code>rect</code> that has been repositioned based on the percentages passed into anchor_x, and anchor_y. </p> -<h3 id='-----.shift_line-line,-x,-y-'><code>.shift_line line, x, y</code></h3> +<h3 id='------shift_line-line--x--y-'><code>.shift_line line, x, y</code></h3> <p> Returns a line that is offset by <code>x</code>, and <code>y</code>. </p> -<h3 id='-----.line_y_intercept-line-'><code>.line_y_intercept line</code></h3> +<h3 id='------line_y_intercept-line-'><code>.line_y_intercept line</code></h3> <p> Given a line, the <code>b</code> value is determined for the point slope form equation: <code>y = mx + b</code>. </p> -<h3 id='-----.angle_between_lines-line_one,-line_two,-replace_infinity--'><code>.angle_between_lines line_one, line_two, replace_infinity:</code></h3> +<h3 id='------angle_between_lines-line_one--line_two--replace_infinity--'><code>.angle_between_lines line_one, line_two, replace_infinity:</code></h3> <p> Returns the angle between two lines as if they were infinitely long. A numeric value can be passed in for the last parameter which would represent lines that do not intersect. </p> -<h3 id='-----.line_slope-line,-replace_infinity--'><code>.line_slope line, replace_infinity:</code></h3> +<h3 id='------line_slope-line--replace_infinity--'><code>.line_slope line, replace_infinity:</code></h3> <p> Given a line, the <code>m</code> value is determined for the point slope form equation: <code>y = mx + b</code>. </p> -<h3 id='-----.line_rise_run-'><code>.line_rise_run</code></h3> +<h3 id='------line_rise_run-'><code>.line_rise_run</code></h3> <p> Given a line, a <code>Hash</code> is returned that returns the slope as <code>x</code> and <code>y</code> properties with normalized values (the number is between -1 and 1). </p> -<h3 id='-----.ray_test-point,-line-'><code>.ray_test point, line</code></h3> +<h3 id='------ray_test-point--line-'><code>.ray_test point, line</code></h3> <p> Given a point and a line, <code>:on</code>, <code>:left</code>, or <code>:right</code> which represents the location of the point relative to the line. </p> -<h3 id='-----.line_rect-line-'><code>.line_rect line</code></h3> +<h3 id='------line_rect-line-'><code>.line_rect line</code></h3> <p> Returns the bounding rectangle for a line. </p> -<h3 id='-----.line_intersect-line_one,-line_two-'><code>.line_intersect line_one, line_two</code></h3> +<h3 id='------line_intersect-line_one--line_two-'><code>.line_intersect line_one, line_two</code></h3> <p> Returns a point that represents the intersection of the lines. </p> -<h3 id='-----.distance-point_one,-point_two-'><code>.distance point_one, point_two</code></h3> +<h3 id='------distance-point_one--point_two-'><code>.distance point_one, point_two</code></h3> <p> Returns the distance between two points. </p> -<h3 id='-----.cubic_bezier-t,-a,-b,-c,-d-'><code>.cubic_bezier t, a, b, c, d</code></h3> +<h3 id='------cubic_bezier-t--a--b--c--d-'><code>.cubic_bezier t, a, b, c, d</code></h3> <p> Returns the cubic bezier function for tick_count <code>t</code> with anchors <code>a</code>, <code>b</code>, <code>c</code>, and <code>d</code>. </p> -<h2 id='----args.easing-'><code>args.easing</code></h2> +<h2 id='----args-easing-'><code>args.easing</code></h2> <p> A set of functions that allow you to determine the current progression of an easing function. </p> -<h3 id='-----.ease-start_tick,-current_tick,-duration,-easing_functions-'><code>.ease start_tick, current_tick, duration, easing_functions</code></h3> +<h3 id='------ease-start_tick--current_tick--duration--easing_functions-'><code>.ease start_tick, current_tick, duration, easing_functions</code></h3> <p> Given a start, current, duration, and easing function names, <code>ease</code> returns a number between 0 and 1 that represents the progress of an easing function. </p> @@ -1485,7 +1529,7 @@ This example will move a box at a linear speed from 0 to 1280. args.outputs.solids << { x: 1280 * current_progress, y: 360, w: 10, h: 10 } end </code></pre> -<h3 id='-----.ease_spline-start_tick,-current_tick,-duration,-spline-'><code>.ease_spline start_tick, current_tick, duration, spline</code></h3> +<h3 id='------ease_spline-start_tick--current_tick--duration--spline-'><code>.ease_spline start_tick, current_tick, duration, spline</code></h3> <p> Given a start, current, duration, and a multiple bezier values, this function returns a number between 0 and 1 that represents the progress of an easing function. </p> @@ -1506,11 +1550,11 @@ This example will move a box at a linear speed from 0 to 1280 and then back to 0 args.outputs.solids << { x: 1280 * current_progress, y: 360, w: 10, h: 10 } end </code></pre> -<h2 id='----args.string-'><code>args.string</code></h2> +<h2 id='----args-string-'><code>args.string</code></h2> <p> Useful string functions not included in Ruby core libraries. </p> -<h3 id='-----.wrapped_lines-string,-max_character_length-'><code>.wrapped_lines string, max_character_length</code></h3> +<h3 id='------wrapped_lines-string--max_character_length-'><code>.wrapped_lines string, max_character_length</code></h3> <p> This function will return a collection of strings given an input <code>string</code> and <code>max_character_length</code>. The collection of strings returned will split the input string into strings of <code>length <= max_character_length</code>. </p> @@ -1527,175 +1571,175 @@ teger dolor velit, ultricies vitae libero vel, aliquam imperdiet enim." end end </code></pre> -<h2 id='----args.grid-'><code>args.grid</code></h2> +<h2 id='----args-grid-'><code>args.grid</code></h2> <p> Returns the virtual grid for the game. </p> -<h3 id='-----.name-'><code>.name</code></h3> +<h3 id='------name-'><code>.name</code></h3> <p> Returns either <code>:origin_bottom_left</code> or <code>:origin_center</code>. </p> -<h3 id='-----.bottom-'><code>.bottom</code></h3> +<h3 id='------bottom-'><code>.bottom</code></h3> <p> Returns the <code>y</code> value that represents the bottom of the grid. </p> -<h3 id='-----.top-'><code>.top</code></h3> +<h3 id='------top-'><code>.top</code></h3> <p> Returns the <code>y</code> value that represents the top of the grid. </p> -<h3 id='-----.left-'><code>.left</code></h3> +<h3 id='------left-'><code>.left</code></h3> <p> Returns the <code>x</code> value that represents the left of the grid. </p> -<h3 id='-----.right-'><code>.right</code></h3> +<h3 id='------right-'><code>.right</code></h3> <p> Returns the <code>x</code> value that represents the right of the grid. </p> -<h3 id='-----.rect-'><code>.rect</code></h3> +<h3 id='------rect-'><code>.rect</code></h3> <p> Returns a rectangle Primitive that represents the grid. </p> -<h3 id='-----.origin_bottom_left!-'><code>.origin_bottom_left!</code></h3> +<h3 id='------origin_bottom_left!-'><code>.origin_bottom_left!</code></h3> <p> Change the grids coordinate system to 0, 0 at the bottom left corner. </p> -<h3 id='-----.origin_center!-'><code>.origin_center!</code></h3> +<h3 id='------origin_center!-'><code>.origin_center!</code></h3> <p> Change the grids coordinate system to 0, 0 at the center of the screen. </p> -<h3 id='-----.w-'><code>.w</code></h3> +<h3 id='------w-'><code>.w</code></h3> <p> Returns the grid's width (always 1280). </p> -<h3 id='-----.h-'><code>.h</code></h3> +<h3 id='------h-'><code>.h</code></h3> <p> Returns the grid's height (always 720). </p> -<h2 id='----args.gtk-'><code>args.gtk</code></h2> +<h2 id='----args-gtk-'><code>args.gtk</code></h2> <p> This represents the DragonRuby Game Toolkit's Runtime Environment and can be accessed via <code>args.gtk.METHOD</code>. </p> -<h3 id='-----.argv-'><code>.argv</code></h3> +<h3 id='------argv-'><code>.argv</code></h3> <p> Returns a <code>String</code> that represents the parameters passed into the <code>./dragonruby</code> binary. </p> -<h3 id='-----.platform-'><code>.platform</code></h3> +<h3 id='------platform-'><code>.platform</code></h3> <p> Returns a <code>String</code> representing the operating system the game is running on. </p> -<h3 id='-----.request_quit-'><code>.request_quit</code></h3> +<h3 id='------request_quit-'><code>.request_quit</code></h3> <p> Request that the runtime quit the game. </p> -<h3 id='-----.write_file-path,-contents-'><code>.write_file path, contents</code></h3> +<h3 id='------write_file-path--contents-'><code>.write_file path, contents</code></h3> <p> Writes/overwrites a file within the game directory + path. </p> -<h3 id='-----.write_file_root-'><code>.write_file_root</code></h3> +<h3 id='------write_file_root-'><code>.write_file_root</code></h3> <p> Writes/overwrites a file within the root dragonruby binary directory + path. </p> -<h3 id='-----.append_file-path,-contents-'><code>.append_file path, contents</code></h3> +<h3 id='------append_file-path--contents-'><code>.append_file path, contents</code></h3> <p> Append content to a file located at the game directory + path. </p> -<h3 id='-----.append_file_root-path,-contents-'><code>.append_file_root path, contents</code></h3> +<h3 id='------append_file_root-path--contents-'><code>.append_file_root path, contents</code></h3> <p> Append content to a file located at the root dragonruby binary directory + path. </p> -<h3 id='-----.read_file-path-'><code>.read_file path</code></h3> +<h3 id='------read_file-path-'><code>.read_file path</code></h3> <p> Reads a file from the sandboxed file system. </p> -<h3 id='-----.parse_xml-string,-parse_xml_file-path-'><code>.parse_xml string, parse_xml_file path</code></h3> +<h3 id='------parse_xml-string--parse_xml_file-path-'><code>.parse_xml string, parse_xml_file path</code></h3> <p> Returns a <code>Hash</code> for a <code>String</code> that represents XML. </p> -<h3 id='-----.parse_json-string,-parse_json_file-path-'><code>.parse_json string, parse_json_file path</code></h3> +<h3 id='------parse_json-string--parse_json_file-path-'><code>.parse_json string, parse_json_file path</code></h3> <p> Returns a <code>Hash</code> for a <code>String</code> that represents JSON. </p> -<h3 id='-----.http_get-url,-extra_headers-=-{}-'><code>.http_get url, extra_headers = {}</code></h3> +<h3 id='------http_get-url--extra_headers-=-{}-'><code>.http_get url, extra_headers = {}</code></h3> <p> Creates an async task to perform an HTTP GET. </p> -<h3 id='-----.http_post-url,-form_fields-=-{},-extra_headers-=-{}-'><code>.http_post url, form_fields = {}, extra_headers = {}</code></h3> +<h3 id='------http_post-url--form_fields-=-{}--extra_headers-=-{}-'><code>.http_post url, form_fields = {}, extra_headers = {}</code></h3> <p> Creates an async task to perform an HTTP POST. </p> -<h3 id='-----.reset-'><code>.reset</code></h3> +<h3 id='------reset-'><code>.reset</code></h3> <p> Resets the game by deleting all data in <code>args.state</code> and setting <code>args.state.tick_count</code> back to <code>0</code>. </p> -<h3 id='-----.stop_music-'><code>.stop_music</code></h3> +<h3 id='------stop_music-'><code>.stop_music</code></h3> <p> Stops all background music. </p> -<h3 id='-----.calcstringbox-str,-size_enum,-font-'><code>.calcstringbox str, size_enum, font</code></h3> +<h3 id='------calcstringbox-str--size_enum--font-'><code>.calcstringbox str, size_enum, font</code></h3> <p> Returns a tuple with width and height of a string being rendered. </p> -<h3 id='-----.slowmo!-factor-'><code>.slowmo! factor</code></h3> +<h3 id='------slowmo!-factor-'><code>.slowmo! factor</code></h3> <p> Slows the game down by the factor provided. </p> -<h3 id='-----.notify!-string-'><code>.notify! string</code></h3> +<h3 id='------notify!-string-'><code>.notify! string</code></h3> <p> Renders a toast message at the bottom of the screen. </p> -<h3 id='-----.system-'><code>.system</code></h3> +<h3 id='------system-'><code>.system</code></h3> <p> Invokes a shell command and prints the result to the console. </p> -<h3 id='-----.exec-'><code>.exec</code></h3> +<h3 id='------exec-'><code>.exec</code></h3> <p> Invokes a shell command and returns a <code>String</code> that represents the result. </p> -<h3 id='-----.save_state-'><code>.save_state</code></h3> +<h3 id='------save_state-'><code>.save_state</code></h3> <p> Saves the game state to <code>game_state.txt</code>. </p> -<h3 id='-----.load_state-'><code>.load_state</code></h3> +<h3 id='------load_state-'><code>.load_state</code></h3> <p> Load <code>args.state</code> from <code>game_state.txt</code>. </p> -<h3 id='-----.serialize_state-file,-state-'><code>.serialize_state file, state</code></h3> +<h3 id='------serialize_state-file--state-'><code>.serialize_state file, state</code></h3> <p> Saves entity state to a file. If only one parameter is provided a string is returned for state instead of writing to a file. </p> -<h3 id='-----.deserialize_state-file-'><code>.deserialize_state file</code></h3> +<h3 id='------deserialize_state-file-'><code>.deserialize_state file</code></h3> <p> Returns entity state from a file or serialization data represented as a <code>String</code>. </p> -<h3 id='-----.reset_sprite-path-'><code>.reset_sprite path</code></h3> +<h3 id='------reset_sprite-path-'><code>.reset_sprite path</code></h3> <p> Invalids the texture cache of a sprite. </p> -<h3 id='-----.show_cursor-'><code>.show_cursor</code></h3> +<h3 id='------show_cursor-'><code>.show_cursor</code></h3> <p> Shows the mouse cursor. </p> -<h3 id='-----.hide_cursor-'><code>.hide_cursor</code></h3> +<h3 id='------hide_cursor-'><code>.hide_cursor</code></h3> <p> Hides the mouse cursor. </p> -<h3 id='-----.cursor_shown?-'><code>.cursor_shown?</code></h3> +<h3 id='------cursor_shown--'><code>.cursor_shown?</code></h3> <p> Returns <code>true</code> if the mouse cursor is shown. </p> -<h3 id='-----.set_window_fullscreen-enabled-'><code>.set_window_fullscreen enabled</code></h3> +<h3 id='------set_window_fullscreen-enabled-'><code>.set_window_fullscreen enabled</code></h3> <p> Sets the game to either fullscreen (<code>enabled=true</code>) or windowed (<code>enabled=false)</code>. </p> -<h3 id='-----.openurl-url-'><code>.openurl url</code></h3> +<h3 id='------openurl-url-'><code>.openurl url</code></h3> <p> Opens a url using the Operating System's default browser. </p> -<h3 id='-----.get_base_dir-'><code>.get_base_dir</code></h3> +<h3 id='------get_base_dir-'><code>.get_base_dir</code></h3> <p> Returns the full path of the DragonRuby binary directory. </p> -<h3 id='-----.get_game_dir-'><code>.get_game_dir</code></h3> +<h3 id='------get_game_dir-'><code>.get_game_dir</code></h3> <p> Returns the full path of the game directory in its sandboxed environment. </p> @@ -1722,6 +1766,33 @@ This function takes in two parameters. The first parameter is the file path and end end </code></pre> +<h1 id='--docs---gtk--runtime#benchmark-'>DOCS: <code>GTK::Runtime#benchmark</code></h1> +<p> +You can use this function to compare the relative performance of methods. +</p> +<pre><code class="language-ruby">def tick args + # press r to run benchmark + if args.inputs.keyboard.key_down.r + args.gtk.console.show + args.gtk.benchmark iterations: 1000, # number of iterations + # label for experiment + using_numeric_map: -> () { + # experiment body + v = 100.map do |i| + i * 100 + end + }, + # label for experiment + using_numeric_times: -> () { + # experiment body + v = [] + 100.times do |i| + v << i * 100 + end + } + end +end +</code></pre> <h1 id='--docs---array-'>DOCS: <code>Array</code></h1> <p> 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. @@ -1869,11 +1940,11 @@ Assuming the array is an array of arrays, Given a block, each 2D array index inv puts occupied_tiles end </code></pre> -<h1 id='--docs---array#include_any?-'>DOCS: <code>Array#include_any?</code></h1> +<h1 id='--docs---array#include_any--'>DOCS: <code>Array#include_any?</code></h1> <p> Given a collection of items, the function will return <code>true</code> if any of <code>self</code>'s items exists in the collection of items passed in: </p> -<h1 id='--docs---array#any_intersect_rect?-'>DOCS: <code>Array#any_intersect_rect?</code></h1> +<h1 id='--docs---array#any_intersect_rect--'>DOCS: <code>Array#any_intersect_rect?</code></h1> <p> Assuming the array contains objects that respond to <code>left</code>, <code>right</code>, <code>top</code>, <code>bottom</code>, this method returns <code>true</code> if any of the elements within the array intersect the object being passed in. You are given an optional parameter called <code>tolerance</code> which informs how close to the other rectangles the elements need to be for it to be considered intersecting. </p> @@ -2018,7 +2089,7 @@ end </code></pre> <h2 id='---rendering-a-solid-using-an-array-with-colors-and-alpha'>Rendering a solid using an Array with colors and alpha</h2> <p> -The value for the color and alpha is an number between <code>0</code> and <code>255</code>. The alpha property is optional and will be set to <code>255</code> if not specified. +The value for the color and alpha is a number between <code>0</code> and <code>255</code>. The alpha property is optional and will be set to <code>255</code> if not specified. </p> <p> Creates a green solid rectangle with an opacity of 50%. @@ -2047,7 +2118,7 @@ end </code></pre> <h2 id='---rendering-a-solid-using-a-class'>Rendering a solid using a Class</h2> <p> -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 <code>primitive_marker</code> must be defined on the class. +You can also create a class with solid/border properties and render it as a primitive. ALL properties must be on the class. *Additionally*, a method called <code>primitive_marker</code> must be defined on the class. </p> <p> Here is an example: @@ -2100,6 +2171,93 @@ You have to use <code>args.outputs.borders</code>: args.outputs.borders << [100, 100, 160, 90] end </code></pre> +<h1 id='--docs---gtk--outputs#sprites-'>DOCS: <code>GTK::Outputs#sprites</code></h1> +<p> +Add primitives to this collection to render a sprite to the screen. +</p> +<h2 id='---rendering-a-sprite-using-an-array'>Rendering a sprite using an Array</h2> +<p> +Creates a sprite of a white circle located at 100, 100. 160 pixels wide and 90 pixels tall. +</p> +<pre><code class="language-ruby">def tick args + # X Y WIDTH HEIGHT PATH + args.outputs.sprites << [100, 100, 160, 90, "sprites/circle/white.png] +end +</code></pre> +<h2 id='---rendering-a-sprite-using-an-array-with-colors-and-alpha'>Rendering a sprite using an Array with colors and alpha</h2> +<p> +The value for the color and alpha is a number between <code>0</code> and <code>255</code>. The alpha property is optional and will be set to <code>255</code> if not specified. +</p> +<p> +Creates a green circle sprite with an opacity of 50%. +</p> +<pre><code class="language-ruby">def tick args + # X Y WIDTH HEIGHT PATH ANGLE ALPHA RED GREEN BLUE + args.outputs.sprites << [100, 100, 160, 90, "sprites/circle/white.png", 0, 128, 0, 255, 0] +end +</code></pre> +<h2 id='---rendering-a-sprite-using-a-hash'>Rendering a sprite using a Hash</h2> +<p> +If you want a more readable 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. +</p> +<pre><code class="language-ruby">def tick args + args.outputs.sprites << { + x: 0, + y: 0, + w: 100, + h: 100, + path: "sprites/circle/white.png", + angle: 0, + a: 255, + r: 0, + g: 255, + b: 0 + } +end +</code></pre> +<h2 id='---rendering-a-solid-using-a-class'>Rendering a solid using a Class</h2> +<p> +You can also create a class with solid/border properties and render it as a primitive. ALL properties must be on the class. *Additionally*, a method called <code>primitive_marker</code> must be defined on the class. +</p> +<p> +Here is an example: +</p> +<pre><code class="language-ruby"># Create type with ALL sprite properties AND primitive_marker +class Sprite + attr_accessor :x, :y, :w, :h, :path, :angle, :angle_anchor_x, :angle_anchor_y, :tile_x, :tile_y, :tile_w, :tile_h, :source_x, :source_y, :source_w, :source_h, :flip_horizontally, :flip_vertically, :a, :r, :g, :b + + 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 serlialize + {x:self.x, y:self.y, w:self.w, h:self.h, path:self.path} + end + + def inspect + serlialize.to_s + end + + def to_s + serlialize.to_s + end +end +def tick args + # render circle sprite + args.outputs.sprites << Circle.new(10, 10, 32,"sprites/circle/white.png") +end +</code></pre> <h1 id='--docs---gtk--outputs#screenshots-'>DOCS: <code>GTK::Outputs#screenshots</code></h1> <p> 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. @@ -2160,12 +2318,12 @@ The <code>GTK::MousePoint</code> has the following properties. <li><code>x</code>: Integer representing the mouse's x.</li> <li><code>y</code>: Integer representing the mouse's y.</li> <li><code>point</code>: Array with the <code>x</code> and <code>y</code> values.</li> -<li><code>w</code>: Width of the point that always returns <code>0</code> (included so that it can seemlessly work with <code>GTK::Geometry</code> functions).</li> -<li><code>h</code>: Height of the point that always returns <code>0</code> (included so that it can seemlessly work with <code>GTK::Geometry</code> functions).</li> -<li><code>left</code>: This value is the same as <code>x</code> (included so that it can seemlessly work with <code>GTK::Geometry</code> functions).</li> -<li><code>right</code>: This value is the same as <code>x</code> (included so that it can seemlessly work with <code>GTK::Geometry</code> functions).</li> -<li><code>top</code>: This value is the same as <code>y</code> (included so that it can seemlessly work with <code>GTK::Geometry</code> functions).</li> -<li><code>bottom</code>: This value is the same as <code>y</code> (included so that it can seemlessly work with <code>GTK::Geometry</code> functions).</li> +<li><code>w</code>: Width of the point that always returns <code>0</code> (included so that it can seamlessly work with <code>GTK::Geometry</code> functions).</li> +<li><code>h</code>: Height of the point that always returns <code>0</code> (included so that it can seamlessly work with <code>GTK::Geometry</code> functions).</li> +<li><code>left</code>: This value is the same as <code>x</code> (included so that it can seamlessly work with <code>GTK::Geometry</code> functions).</li> +<li><code>right</code>: This value is the same as <code>x</code> (included so that it can seamlessly work with <code>GTK::Geometry</code> functions).</li> +<li><code>top</code>: This value is the same as <code>y</code> (included so that it can seamlessly work with <code>GTK::Geometry</code> functions).</li> +<li><code>bottom</code>: This value is the same as <code>y</code> (included so that it can seamlessly work with <code>GTK::Geometry</code> functions).</li> <li><code>created_at</code>: The tick (<code>args.state.tick_count</code>) that this structure was created.</li> <li><code>global_created_at</code>: The global tick (<code>Kernel.global_tick_count</code>) that this structure was created.</li> </ul> @@ -2322,7 +2480,7 @@ And here is an example where the override parameter is passed in: end end </code></pre> -<h1 id='--docs---numeric#elapsed?-'>DOCS: <code>Numeric#elapsed?</code></h1> +<h1 id='--docs---numeric#elapsed--'>DOCS: <code>Numeric#elapsed?</code></h1> <p> Returns true if <code>Numeric#elapsed_time</code> is greater than the number. An optional parameter can be passed into <code>elapsed?</code> which is added to the number before evaluating whether <code>elapsed?</code> is true. </p> @@ -2386,7 +2544,7 @@ Example usage (with optional parameter): args.state.box_queue -= boxes_to_destroy end </code></pre> -<h1 id='--docs---numeric#created?-'>DOCS: <code>Numeric#created?</code></h1> +<h1 id='--docs---numeric#created--'>DOCS: <code>Numeric#created?</code></h1> <p> Returns true if <code>Numeric#elapsed_time == 0</code>. Essentially communicating that number is equal to the current frame. </p> @@ -2455,7 +2613,7 @@ end Follows is a source code listing for all files that have been open sourced. This code can be found in the <code>./samples</code> directory. </p> <h2 id='---samples'>Samples</h2> -<h3 id='----learn-ruby-optional---beginner-ruby-primer---automation.rb'>Learn Ruby Optional - Beginner Ruby Primer - automation.rb</h3> +<h3 id='----learn-ruby-optional---beginner-ruby-primer---automation-rb'>Learn Ruby Optional - Beginner Ruby Primer - automation.rb</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_beginner_ruby_primer/app/automation.rb # ========================================================================== # _ _ ________ __ _ _____ _____ _______ ______ _ _ _ _ _ _ @@ -2579,7 +2737,7 @@ $gtk.schedule_callback 400 do end </code></pre> -<h3 id='----learn-ruby-optional---beginner-ruby-primer---main.rb'>Learn Ruby Optional - Beginner Ruby Primer - main.rb</h3> +<h3 id='----learn-ruby-optional---beginner-ruby-primer---main-rb'>Learn Ruby Optional - Beginner Ruby Primer - main.rb</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_beginner_ruby_primer/app/main.rb # ========================================================================== # _ _ ________ __ _ _____ _____ _______ ______ _ _ _ _ _ _ @@ -2900,7 +3058,7 @@ def outputs end </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---printing.txt'>Learn Ruby Optional - Intermediate Ruby Primer - printing.txt</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---printing-txt'>Learn Ruby Optional - Intermediate Ruby Primer - printing.txt</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/01_printing.txt # ==================================================================================== # Commenting Code @@ -2935,7 +3093,7 @@ repl do end </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---strings.txt'>Learn Ruby Optional - Intermediate Ruby Primer - strings.txt</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---strings-txt'>Learn Ruby Optional - Intermediate Ruby Primer - strings.txt</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/02_strings.txt # ==================================================================================== # Strings @@ -2954,7 +3112,7 @@ repl do end </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---numbers.txt'>Learn Ruby Optional - Intermediate Ruby Primer - numbers.txt</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---numbers-txt'>Learn Ruby Optional - Intermediate Ruby Primer - numbers.txt</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/03_numbers.txt # ==================================================================================== # Numerics @@ -2979,7 +3137,7 @@ repl do end </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---booleans.txt'>Learn Ruby Optional - Intermediate Ruby Primer - booleans.txt</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---booleans-txt'>Learn Ruby Optional - Intermediate Ruby Primer - booleans.txt</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/04_booleans.txt # ==================================================================================== # Booleans @@ -3015,7 +3173,7 @@ repl do end </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---conditionals.txt'>Learn Ruby Optional - Intermediate Ruby Primer - conditionals.txt</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---conditionals-txt'>Learn Ruby Optional - Intermediate Ruby Primer - conditionals.txt</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/05_conditionals.txt # ==================================================================================== # Conditionals @@ -3133,7 +3291,7 @@ repl do end </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---looping.txt'>Learn Ruby Optional - Intermediate Ruby Primer - looping.txt</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---looping-txt'>Learn Ruby Optional - Intermediate Ruby Primer - looping.txt</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/06_looping.txt # ==================================================================================== # Looping @@ -3192,7 +3350,7 @@ repl do end </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---functions.txt'>Learn Ruby Optional - Intermediate Ruby Primer - functions.txt</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---functions-txt'>Learn Ruby Optional - Intermediate Ruby Primer - functions.txt</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/07_functions.txt # ==================================================================================== # Functions @@ -3265,7 +3423,7 @@ repl do end </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---arrays.txt'>Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---arrays-txt'>Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/08_arrays.txt # ==================================================================================== # Arrays @@ -3479,14 +3637,14 @@ end # ==================================================================================== </code></pre> -<h3 id='----learn-ruby-optional---intermediate-ruby-primer---main.rb'>Learn Ruby Optional - Intermediate Ruby Primer - main.rb</h3> +<h3 id='----learn-ruby-optional---intermediate-ruby-primer---main-rb'>Learn Ruby Optional - Intermediate Ruby Primer - main.rb</h3> <pre><code class="language-ruby"># ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/main.rb def tick args args.outputs.labels << [640, 380, "Open repl.rb in the text editor of your choice and follow the document.", 0, 1] end </code></pre> -<h3 id='----rendering-basics---labels---main.rb'>Rendering Basics - Labels - main.rb</h3> +<h3 id='----rendering-basics---labels---main-rb'>Rendering Basics - Labels - main.rb</h3> <pre><code class="language-ruby"># ./samples/01_rendering_basics/01_labels/app/main.rb =begin @@ -3557,7 +3715,7 @@ def tick args g: 0, b: 200, a: 255, - font: "manaspc.ttf" }.label + font: "manaspc.ttf" }.label! # Primitives can hold anything, and can be given a label in the following forms args.outputs.primitives << [690 + 150, 330 - 80, "Custom font (.primitives Array)", 0, 1, 125, 0, 200, 255, "manaspc.ttf" ].label @@ -3571,7 +3729,7 @@ def tick args g: 0, b: 200, a: 255, - font: "manaspc.ttf" }.label + font: "manaspc.ttf" }.label! end def tick_instructions args, text, y = 715 @@ -3589,7 +3747,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----rendering-basics---lines---main.rb'>Rendering Basics - Lines - main.rb</h3> +<h3 id='----rendering-basics---lines---main-rb'>Rendering Basics - Lines - main.rb</h3> <pre><code class="language-ruby"># ./samples/01_rendering_basics/02_lines/app/main.rb =begin @@ -3647,7 +3805,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----rendering-basics---solids-borders---main.rb'>Rendering Basics - Solids Borders - main.rb</h3> +<h3 id='----rendering-basics---solids-borders---main-rb'>Rendering Basics - Solids Borders - main.rb</h3> <pre><code class="language-ruby"># ./samples/01_rendering_basics/03_solids_borders/app/main.rb =begin @@ -3717,7 +3875,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----rendering-basics---sprites---main.rb'>Rendering Basics - Sprites - main.rb</h3> +<h3 id='----rendering-basics---sprites---main-rb'>Rendering Basics - Sprites - main.rb</h3> <pre><code class="language-ruby"># ./samples/01_rendering_basics/04_sprites/app/main.rb =begin @@ -3764,7 +3922,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----rendering-basics---sounds---main.rb'>Rendering Basics - Sounds - main.rb</h3> +<h3 id='----rendering-basics---sounds---main-rb'>Rendering Basics - Sounds - main.rb</h3> <pre><code class="language-ruby"># ./samples/01_rendering_basics/05_sounds/app/main.rb =begin @@ -3799,768 +3957,7 @@ def tick args end </code></pre> -<h3 id='----rendering-basics---audio-mixer---main.rb'>Rendering Basics - Audio Mixer - main.rb</h3> -<pre><code class="language-ruby"># ./samples/01_rendering_basics/06_audio_mixer/app/main.rb -$gtk.reset - -$boxsize = 30 - -def render_sources args - mouse_in_panel = (args.state.selected != 0) && args.inputs.mouse.position.inside_rect?([900, 450, 340, 250]) - mouse_new_down = (args.state.mouse_held == 1) - - if (mouse_new_down && !mouse_in_panel) - args.state.selected = 0 # will reset below if we hit something. - end - - args.audio.keys.each { |k| - s = args.audio[k] - - if (mouse_new_down) && !mouse_in_panel && args.inputs.mouse.position.inside_rect?([s[:screenx], s[:screeny], $boxsize, $boxsize]) - args.state.selected = k - args.state.dragging_source = true - end - - isselected = (k == args.state.selected) - - if isselected && args.state.dragging_source - # you can hang anything on the audio hashes you want, so we store the - # actual screen position so it doesn't scale weirdly vs your mouse. - s[:screenx] = args.inputs.mouse.x - ($boxsize / 2) - s[:screeny] = args.inputs.mouse.y - ($boxsize / 2) - - s[:screeny] = 50 if s[:screeny] < 50 - s[:screeny] = (719 - $boxsize) if s[:screeny] > (719 - $boxsize) - s[:screenx] = 0 if s[:screenx] < 0 - s[:screenx] = (1279 - $boxsize) if s[:screenx] > (1279 - $boxsize) - - s[:x] = ((s[:screenx] / 1279.0) * 2.0) - 1.0 # scale to -1.0 - 1.0 range - s[:y] = ((s[:screeny] / 719.0) * 2.0) - 1.0 # scale to -1.0 - 1.0 range - end - - color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ] - args.outputs.primitives << [s[:screenx], s[:screeny], $boxsize, $boxsize, *color].solid - } -end - -def render_panel args - s = args.audio[args.state.selected] - return if s.nil? - mouse_down = (args.state.mouse_held > 0) - - args.outputs.primitives << [900, 450, 340, 250, 127, 127, 200, 255].solid - args.outputs.primitives << [1075, 690, "Source ##{args.state.selected}", 3, 1, 255, 255, 255].label - args.outputs.primitives << [910, 660, 1230, 660, 255, 255, 255].line - args.outputs.primitives << [910, 650, "screen: (#{s[:screenx].to_i}, #{s[:screeny].to_i})", 0, 0, 255, 255, 255].label - args.outputs.primitives << [910, 625, "position: (#{s[:x].round(5).to_s[0..6]}, #{s[:y].round(5).to_s[0..6]})", 0, 0, 255, 255, 255].label - - slider = [1022, 586, 200, 7] - if mouse_down && args.inputs.mouse.position.inside_rect?(slider) - s[:pitch] = ((args.inputs.mouse.x - slider[0]).to_f / (slider[2]-1.0)) * 2.0 - end - slidercolor = (s[:pitch] / 2.0) * 255 - args.outputs.primitives << [*slider, slidercolor, slidercolor, slidercolor, 255].solid - args.outputs.primitives << [910, 600, "pitch: #{s[:pitch].round(3).to_s[0..2]}", 0, 0, 255, 255, 255].label - - slider = [1022, 561, 200, 7] - if mouse_down && args.inputs.mouse.position.inside_rect?(slider) - s[:gain] = (args.inputs.mouse.x - slider[0]).to_f / (slider[2]-1.0) - end - slidercolor = s[:gain] * 255 - args.outputs.primitives << [*slider, slidercolor, slidercolor, slidercolor, 255].solid - args.outputs.primitives << [910, 575, "gain: #{s[:gain].round(3).to_s[0..2]}", 0, 0, 255, 255, 255].label - - checkbox = [1022, 533, 10, 12] - if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox) - s[:looping] = !s[:looping] - end - checkboxcolor = s[:looping] ? 255 : 0 - args.outputs.primitives << [*checkbox, checkboxcolor, checkboxcolor, checkboxcolor, 255].solid - args.outputs.primitives << [910, 550, "looping:", 0, 0, 255, 255, 255].label - - checkbox = [1022, 508, 10, 12] - if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox) - s[:paused] = !s[:paused] - end - checkboxcolor = s[:paused] ? 255 : 0 - args.outputs.primitives << [*checkbox, checkboxcolor, checkboxcolor, checkboxcolor, 255].solid - args.outputs.primitives << [910, 525, "paused:", 0, 0, 255, 255, 255].label - - button = [910, 460, 320, 20] - if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(button) - args.audio.delete(args.state.selected) - args.state.selected = 0 - end - args.outputs.primitives << [*button, 255, 0, 0, 255].solid - args.outputs.primitives << [button[0] + (button[2] / 2), button[1]+20, "DELETE SOURCE", 0, 1, 255, 255, 0].label -end - -def spawn_new_sound args, num - input = nil - input = "sounds/#{num}.#{(num == 6) ? 'ogg' : 'wav'}" - - # Spawn randomly in an area that won't be covered by UI. - screenx = (rand * 600.0) + 200.0 - screeny = (rand * 400.0) + 100.0 - - args.state.next_sound_index += 1 - - # you can hang anything on the audio hashes you want, so we store the - # actual screen position in here for convenience. - args.audio[args.state.next_sound_index] = { - input: input, - screenx: screenx, - screeny: screeny, - x: ((screenx / 1279.0) * 2.0) - 1.0, # scale to -1.0 - 1.0 range - y: ((screeny / 719.0) * 2.0) - 1.0, # scale to -1.0 - 1.0 range - z: 0.0, - gain: 1.0, - pitch: 1.0, - looping: true, - paused: false - } - - args.state.selected = args.state.next_sound_index -end - -def render_launcher args - total = 6 - x = (1280 - (total * $boxsize * 3)) / 2 - y = 10 - args.outputs.primitives << [0, 0, 1280, ((y*2) + $boxsize), 127, 127, 127, 255].solid - for i in 1..total - args.outputs.primitives << [x, y, $boxsize, $boxsize, 255, 255, 255, 255].solid - args.outputs.primitives << [x+8, y+28, i.to_s, 3, 0, 0, 0, 255, 255].label - if args.inputs.mouse.click && args.inputs.mouse.click.point.inside_rect?([x, y, $boxsize, $boxsize]) - spawn_new_sound args, i - end - x = x + ($boxsize * 3) - end -end - -def render_ui args - render_launcher args - render_panel args -end - -def tick args - args.state.mouse_held ||= 0 - args.state.dragging_source ||= false - args.state.selected ||= 0 - args.state.next_sound_index ||= 0 - - if args.inputs.mouse.up - args.state.mouse_held = 0 - args.state.dragging_source = false - elsif args.inputs.mouse.down || (args.state.mouse_held > 0) - args.state.mouse_held += 1 - else - end - - args.outputs.background_color = [ 0, 0, 0, 255 ] - render_sources args - render_ui args -end - -</code></pre> -<h3 id='----rendering-basics---sound-synthesis---main.rb'>Rendering Basics - Sound Synthesis - main.rb</h3> -<pre><code class="language-ruby"># ./samples/01_rendering_basics/07_sound_synthesis/app/main.rb -begin # region: top level tick methods - def tick args - defaults args - render args - input args - process_audio_queue args - end - - def defaults args - args.state.sine_waves ||= {} - args.state.square_waves ||= {} - args.state.saw_tooth_waves ||= {} - args.state.triangle_waves ||= {} - args.state.audio_queue ||= [] - args.state.buttons ||= [ - (frequency_buttons args), - (sine_wave_note_buttons args), - (bell_buttons args), - (square_wave_note_buttons args), - (saw_tooth_wave_note_buttons args), - (triangle_wave_note_buttons args), - ].flatten - end - - def render args - args.outputs.borders << args.state.buttons.map { |b| b[:border] } - args.outputs.labels << args.state.buttons.map { |b| b[:label] } - args.outputs.labels << args.layout - .rect(row: 0, col: 11.5) - .yield_self { |r| r.merge y: r.y + r.h } - .merge(text: "This is a Pro only feature. Click here to watch the YouTube video if you are on the Standard License.", - alignment_enum: 1) - end - - - def input args - args.state.buttons.each do |b| - if args.inputs.mouse.click && (args.inputs.mouse.click.inside_rect? b[:rect]) - parameter_string = (b.slice :frequency, :note, :octave).map { |k, v| "#{k}: #{v}" }.join ", " - args.gtk.notify! "#{b[:method_to_call]} #{parameter_string}" - send b[:method_to_call], args, b - end - end - - if args.inputs.mouse.click && (args.inputs.mouse.click.inside_rect? (args.layout.rect(row: 0).yield_self { |r| r.merge y: r.y + r.h.half, h: r.h.half })) - args.gtk.openurl 'https://www.youtube.com/watch?v=zEzovM5jT-k&ab_channel=AmirRajan' - end - end - - def process_audio_queue args - to_queue = args.state.audio_queue.find_all { |v| v[:queue_at] <= args.tick_count } - args.state.audio_queue -= to_queue - to_queue.each { |a| args.audio[a[:id]] = a } - - args.audio.find_all { |k, v| v[:decay_rate] } - .each { |k, v| v[:gain] -= v[:decay_rate] } - - sounds_to_stop = args.audio - .find_all { |k, v| v[:stop_at] && args.state.tick_count >= v[:stop_at] } - .map { |k, v| k } - - sounds_to_stop.each { |k| args.audio.delete k } - end -end - -begin # region: button definitions, ui layout, callback functions - def button args, opts - button_def = opts.merge rect: (args.layout.rect (opts.merge w: 2, h: 1)) - - button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0 - - label_offset_x = 5 - label_offset_y = 30 - - button_def[:label] = button_def[:rect].merge text: opts[:text], - size_enum: -2.5, - x: button_def[:rect].x + label_offset_x, - y: button_def[:rect].y + label_offset_y - - button_def - end - - def play_sine_wave args, sender - queue_sine_wave args, - frequency: sender[:frequency], - duration: 1.seconds, - fade_out: true - end - - def play_note args, sender - method_to_call = :queue_sine_wave - method_to_call = :queue_square_wave if sender[:type] == :square - method_to_call = :queue_saw_tooth_wave if sender[:type] == :saw_tooth - method_to_call = :queue_triangle_wave if sender[:type] == :triangle - method_to_call = :queue_bell if sender[:type] == :bell - - send method_to_call, args, - frequency: (frequency_for note: sender[:note], octave: sender[:octave]), - duration: 1.seconds, - fade_out: true - end - - def frequency_buttons args - [ - (button args, - row: 4.0, col: 0, text: "300hz", - frequency: 300, - method_to_call: :play_sine_wave), - (button args, - row: 5.0, col: 0, text: "400hz", - frequency: 400, - method_to_call: :play_sine_wave), - (button args, - row: 6.0, col: 0, text: "500hz", - frequency: 500, - method_to_call: :play_sine_wave), - ] - end - - def sine_wave_note_buttons args - [ - (button args, - row: 1.5, col: 2, text: "Sine C4", - note: :c, octave: 4, type: :sine, method_to_call: :play_note), - (button args, - row: 2.5, col: 2, text: "Sine D4", - note: :d, octave: 4, type: :sine, method_to_call: :play_note), - (button args, - row: 3.5, col: 2, text: "Sine E4", - note: :e, octave: 4, type: :sine, method_to_call: :play_note), - (button args, - row: 4.5, col: 2, text: "Sine F4", - note: :f, octave: 4, type: :sine, method_to_call: :play_note), - (button args, - row: 5.5, col: 2, text: "Sine G4", - note: :g, octave: 4, type: :sine, method_to_call: :play_note), - (button args, - row: 6.5, col: 2, text: "Sine A5", - note: :a, octave: 5, type: :sine, method_to_call: :play_note), - (button args, - row: 7.5, col: 2, text: "Sine B5", - note: :b, octave: 5, type: :sine, method_to_call: :play_note), - (button args, - row: 8.5, col: 2, text: "Sine C5", - note: :c, octave: 5, type: :sine, method_to_call: :play_note), - ] - end - - def square_wave_note_buttons args - [ - (button args, - row: 1.5, col: 6, text: "Square C4", - note: :c, octave: 4, type: :square, method_to_call: :play_note), - (button args, - row: 2.5, col: 6, text: "Square D4", - note: :d, octave: 4, type: :square, method_to_call: :play_note), - (button args, - row: 3.5, col: 6, text: "Square E4", - note: :e, octave: 4, type: :square, method_to_call: :play_note), - (button args, - row: 4.5, col: 6, text: "Square F4", - note: :f, octave: 4, type: :square, method_to_call: :play_note), - (button args, - row: 5.5, col: 6, text: "Square G4", - note: :g, octave: 4, type: :square, method_to_call: :play_note), - (button args, - row: 6.5, col: 6, text: "Square A5", - note: :a, octave: 5, type: :square, method_to_call: :play_note), - (button args, - row: 7.5, col: 6, text: "Square B5", - note: :b, octave: 5, type: :square, method_to_call: :play_note), - (button args, - row: 8.5, col: 6, text: "Square C5", - note: :c, octave: 5, type: :square, method_to_call: :play_note), - ] - end - def saw_tooth_wave_note_buttons args - [ - (button args, - row: 1.5, col: 8, text: "Saw C4", - note: :c, octave: 4, type: :saw_tooth, method_to_call: :play_note), - (button args, - row: 2.5, col: 8, text: "Saw D4", - note: :d, octave: 4, type: :saw_tooth, method_to_call: :play_note), - (button args, - row: 3.5, col: 8, text: "Saw E4", - note: :e, octave: 4, type: :saw_tooth, method_to_call: :play_note), - (button args, - row: 4.5, col: 8, text: "Saw F4", - note: :f, octave: 4, type: :saw_tooth, method_to_call: :play_note), - (button args, - row: 5.5, col: 8, text: "Saw G4", - note: :g, octave: 4, type: :saw_tooth, method_to_call: :play_note), - (button args, - row: 6.5, col: 8, text: "Saw A5", - note: :a, octave: 5, type: :saw_tooth, method_to_call: :play_note), - (button args, - row: 7.5, col: 8, text: "Saw B5", - note: :b, octave: 5, type: :saw_tooth, method_to_call: :play_note), - (button args, - row: 8.5, col: 8, text: "Saw C5", - note: :c, octave: 5, type: :saw_tooth, method_to_call: :play_note), - ] - end - - def triangle_wave_note_buttons args - [ - (button args, - row: 1.5, col: 10, text: "Triangle C4", - note: :c, octave: 4, type: :triangle, method_to_call: :play_note), - (button args, - row: 2.5, col: 10, text: "Triangle D4", - note: :d, octave: 4, type: :triangle, method_to_call: :play_note), - (button args, - row: 3.5, col: 10, text: "Triangle E4", - note: :e, octave: 4, type: :triangle, method_to_call: :play_note), - (button args, - row: 4.5, col: 10, text: "Triangle F4", - note: :f, octave: 4, type: :triangle, method_to_call: :play_note), - (button args, - row: 5.5, col: 10, text: "Triangle G4", - note: :g, octave: 4, type: :triangle, method_to_call: :play_note), - (button args, - row: 6.5, col: 10, text: "Triangle A5", - note: :a, octave: 5, type: :triangle, method_to_call: :play_note), - (button args, - row: 7.5, col: 10, text: "Triangle B5", - note: :b, octave: 5, type: :triangle, method_to_call: :play_note), - (button args, - row: 8.5, col: 10, text: "Triangle C5", - note: :c, octave: 5, type: :triangle, method_to_call: :play_note), - ] - end - - def bell_buttons args - [ - (button args, - row: 1.5, col: 4, text: "Bell C4", - note: :c, octave: 4, type: :bell, method_to_call: :play_note), - (button args, - row: 2.5, col: 4, text: "Bell D4", - note: :d, octave: 4, type: :bell, method_to_call: :play_note), - (button args, - row: 3.5, col: 4, text: "Bell E4", - note: :e, octave: 4, type: :bell, method_to_call: :play_note), - (button args, - row: 4.5, col: 4, text: "Bell F4", - note: :f, octave: 4, type: :bell, method_to_call: :play_note), - (button args, - row: 5.5, col: 4, text: "Bell G4", - note: :g, octave: 4, type: :bell, method_to_call: :play_note), - (button args, - row: 6.5, col: 4, text: "Bell A5", - note: :a, octave: 5, type: :bell, method_to_call: :play_note), - (button args, - row: 7.5, col: 4, text: "Bell B5", - note: :b, octave: 5, type: :bell, method_to_call: :play_note), - (button args, - row: 8.5, col: 4, text: "Bell C5", - note: :c, octave: 5, type: :bell, method_to_call: :play_note), - ] - end -end - -begin # region: wave generation - begin # sine wave - def defaults_sine_wave_for - { frequency: 440, sample_rate: 48000 } - end - - def sine_wave_for opts = {} - opts = defaults_sine_wave_for.merge opts - frequency = opts[:frequency] - sample_rate = opts[:sample_rate] - period_size = (sample_rate.fdiv frequency).ceil - period_size.map_with_index do |i| - Math::sin((2.0 * Math::PI) / (sample_rate.to_f / frequency.to_f) * i) - end.to_a - end - - def defaults_queue_sine_wave - { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 } - end - - def queue_sine_wave args, opts = {} - opts = defaults_queue_sine_wave.merge opts - frequency = opts[:frequency] - sample_rate = 48000 - - sine_wave = sine_wave_for frequency: frequency, sample_rate: sample_rate - args.state.sine_waves[frequency] ||= sine_wave_for frequency: frequency, sample_rate: sample_rate - - proc = lambda do - generate_audio_data args.state.sine_waves[frequency], sample_rate - end - - audio_state = new_audio_state args, opts - audio_state[:input] = [1, sample_rate, proc] - queue_audio args, audio_state: audio_state, wave: sine_wave - end - end - - begin # region: square wave - def defaults_square_wave_for - { frequency: 440, sample_rate: 48000 } - end - - def square_wave_for opts = {} - opts = defaults_square_wave_for.merge opts - sine_wave = sine_wave_for opts - sine_wave.map do |v| - if v >= 0 - 1.0 - else - -1.0 - end - end.to_a - end - - def defaults_queue_square_wave - { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 } - end - - def queue_square_wave args, opts = {} - opts = defaults_queue_square_wave.merge opts - frequency = opts[:frequency] - sample_rate = 48000 - - square_wave = square_wave_for frequency: frequency, sample_rate: sample_rate - args.state.square_waves[frequency] ||= square_wave_for frequency: frequency, sample_rate: sample_rate - - proc = lambda do - generate_audio_data args.state.square_waves[frequency], sample_rate - end - - audio_state = new_audio_state args, opts - audio_state[:input] = [1, sample_rate, proc] - queue_audio args, audio_state: audio_state, wave: square_wave - end - end - - begin # region: saw tooth wave - def defaults_saw_tooth_wave_for - { frequency: 440, sample_rate: 48000 } - end - - def saw_tooth_wave_for opts = {} - opts = defaults_saw_tooth_wave_for.merge opts - sine_wave = sine_wave_for opts - period_size = sine_wave.length - sine_wave.map_with_index do |v, i| - (((i % period_size).fdiv period_size) * 2) - 1 - end - end - - def defaults_queue_saw_tooth_wave - { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 } - end - - def queue_saw_tooth_wave args, opts = {} - opts = defaults_queue_saw_tooth_wave.merge opts - frequency = opts[:frequency] - sample_rate = 48000 - - saw_tooth_wave = saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate - args.state.saw_tooth_waves[frequency] ||= saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate - - proc = lambda do - generate_audio_data args.state.saw_tooth_waves[frequency], sample_rate - end - - audio_state = new_audio_state args, opts - audio_state[:input] = [1, sample_rate, proc] - queue_audio args, audio_state: audio_state, wave: saw_tooth_wave - end - end - - begin # region: triangle wave - def defaults_triangle_wave_for - { frequency: 440, sample_rate: 48000 } - end - - def triangle_wave_for opts = {} - opts = defaults_saw_tooth_wave_for.merge opts - sine_wave = sine_wave_for opts - period_size = sine_wave.length - sine_wave.map_with_index do |v, i| - ratio = (i.fdiv period_size) - if ratio <= 0.5 - (ratio * 4) - 1 - else - ratio -= 0.5 - 1 - (ratio * 4) - end - end - end - - def defaults_queue_triangle_wave - { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 } - end - - def queue_triangle_wave args, opts = {} - opts = defaults_queue_triangle_wave.merge opts - frequency = opts[:frequency] - sample_rate = 48000 - - triangle_wave = triangle_wave_for frequency: frequency, sample_rate: sample_rate - args.state.triangle_waves[frequency] ||= triangle_wave_for frequency: frequency, sample_rate: sample_rate - - proc = lambda do - generate_audio_data args.state.triangle_waves[frequency], sample_rate - end - - audio_state = new_audio_state args, opts - audio_state[:input] = [1, sample_rate, proc] - queue_audio args, audio_state: audio_state, wave: triangle_wave - end - end - - begin # region: bell - def defaults_queue_bell - { frequency: 440, duration: 1.seconds, queue_in: 0 } - end - - def queue_bell args, opts = {} - (bell_to_sine_waves (defaults_queue_bell.merge opts)).each { |b| queue_sine_wave args, b } - end - - def bell_harmonics - [ - { frequency_ratio: 0.5, duration_ratio: 1.00 }, - { frequency_ratio: 1.0, duration_ratio: 0.80 }, - { frequency_ratio: 2.0, duration_ratio: 0.60 }, - { frequency_ratio: 3.0, duration_ratio: 0.40 }, - { frequency_ratio: 4.2, duration_ratio: 0.25 }, - { frequency_ratio: 5.4, duration_ratio: 0.20 }, - { frequency_ratio: 6.8, duration_ratio: 0.15 } - ] - end - - def defaults_bell_to_sine_waves - { frequency: 440, duration: 1.seconds, queue_in: 0 } - end - - def bell_to_sine_waves opts = {} - opts = defaults_bell_to_sine_waves.merge opts - bell_harmonics.map do |b| - { - frequency: opts[:frequency] * b[:frequency_ratio], - duration: opts[:duration] * b[:duration_ratio], - queue_in: opts[:queue_in], - gain: (1.fdiv bell_harmonics.length), - fade_out: true - } - end - end - end - - begin # audio entity construction - def generate_audio_data sine_wave, sample_rate - sample_size = (sample_rate.fdiv (1000.fdiv 60)).ceil - copy_count = (sample_size.fdiv sine_wave.length).ceil - sine_wave * copy_count - end - - def defaults_new_audio_state - { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 } - end - - def new_audio_state args, opts = {} - opts = defaults_new_audio_state.merge opts - decay_rate = 0 - decay_rate = 1.fdiv(opts[:duration]) * opts[:gain] if opts[:fade_out] - frequency = opts[:frequency] - sample_rate = 48000 - - { - id: (new_id! args), - frequency: frequency, - sample_rate: 48000, - stop_at: args.tick_count + opts[:queue_in] + opts[:duration], - gain: opts[:gain].to_f, - queue_at: args.state.tick_count + opts[:queue_in], - decay_rate: decay_rate, - pitch: 1.0, - looping: true, - paused: false - } - end - - def queue_audio args, opts = {} - graph_wave args, opts[:wave], opts[:audio_state][:frequency] - args.state.audio_queue << opts[:audio_state] - end - - def new_id! args - args.state.audio_id ||= 0 - args.state.audio_id += 1 - end - - def graph_wave args, wave, frequency - if args.state.tick_count != args.state.graphed_at - args.outputs.static_lines.clear - args.outputs.static_sprites.clear - end - - wave = wave - - r, g, b = frequency.to_i % 85, - frequency.to_i % 170, - frequency.to_i % 255 - - starting_rect = args.layout.rect(row: 5, col: 13) - x_scale = 10 - y_scale = 100 - max_points = 25 - - points = wave - if wave.length > max_points - resolution = wave.length.idiv max_points - points = wave.find_all.with_index { |y, i| (i % resolution == 0) } - end - - args.outputs.static_lines << points.map_with_index do |y, x| - next_y = points[x + 1] - - if next_y - { - x: starting_rect.x + (x * x_scale), - y: starting_rect.y + starting_rect.h.half + y_scale * y, - x2: starting_rect.x + ((x + 1) * x_scale), - y2: starting_rect.y + starting_rect.h.half + y_scale * next_y, - r: r, - g: g, - b: b - } - end - end - - args.outputs.static_sprites << points.map_with_index do |y, x| - { - x: (starting_rect.x + (x * x_scale)) - 2, - y: (starting_rect.y + starting_rect.h.half + y_scale * y) - 2, - w: 4, - h: 4, - path: 'sprites/square-white.png', - r: r, - g: g, - b: b - } - end - - args.state.graphed_at = args.state.tick_count - end - end - - begin # region: musical note mapping - def defaults_frequency_for - { note: :a, octave: 5, sharp: false, flat: false } - end - - def frequency_for opts = {} - opts = defaults_frequency_for.merge opts - octave_offset_multiplier = opts[:octave] - 5 - note = note_frequencies_octave_5[opts[:note]] - if octave_offset_multiplier < 0 - note = note * 1 / (octave_offset_multiplier.abs + 1) - elsif octave_offset_multiplier > 0 - note = note * (octave_offset_multiplier.abs + 1) / 1 - end - note - end - - def note_frequencies_octave_5 - { - a: 440.0, - a_sharp: 466.16, b_flat: 466.16, - b: 493.88, - c: 523.25, - c_sharp: 554.37, d_flat: 587.33, - d: 587.33, - d_sharp: 622.25, e_flat: 659.25, - e: 659.25, - f: 698.25, - f_sharp: 739.99, g_flat: 739.99, - g: 783.99, - g_sharp: 830.61, a_flat: 830.61 - } - end - end -end - -$gtk.reset - -</code></pre> -<h3 id='----input-basics---keyboard---main.rb'>Input Basics - Keyboard - main.rb</h3> +<h3 id='----input-basics---keyboard---main-rb'>Input Basics - Keyboard - main.rb</h3> <pre><code class="language-ruby"># ./samples/02_input_basics/01_keyboard/app/main.rb =begin @@ -4589,9 +3986,9 @@ APIs listing that haven't been encountered in a previous sample apps: def tick args tick_instructions args, "Sample app shows how keyboard events are registered and accessed.", 360 # Notice how small_font accounts for all the remaining parameters - args.outputs.labels << [460, row_to_px(args, 0), "Current game time: #{args.state.tick_count}", small_font] - args.outputs.labels << [460, row_to_px(args, 2), "Keyboard input: args.inputs.keyboard.key_up.h", small_font] - args.outputs.labels << [460, row_to_px(args, 3), "Press \"h\" on the keyboard.", small_font] + args.outputs.labels << { x: 460, y: row_to_px(args, 0), text: "Current game time: #{args.state.tick_count}", size_enum: -1 } + args.outputs.labels << { x: 460, y: row_to_px(args, 2), text: "Keyboard input: args.inputs.keyboard.key_up.h", size_enum: -1 } + args.outputs.labels << { x: 460, y: row_to_px(args, 3), text: "Press \"h\" on the keyboard.", size_enum: -1 } # Input on a specifc key can be found through args.inputs.keyboard.key_up followed by the key if args.inputs.keyboard.key_up.h @@ -4602,27 +3999,19 @@ def tick args args.state.h_pressed_at ||= false if args.state.h_pressed_at - args.outputs.labels << [460, row_to_px(args, 4), "\"h\" was pressed at time: #{args.state.h_pressed_at}", small_font] + args.outputs.labels << { x: 460, y: row_to_px(args, 4), text: "\"h\" was pressed at time: #{args.state.h_pressed_at}", size_enum: -1 } else - args.outputs.labels << [460, row_to_px(args, 4), "\"h\" has never been pressed.", small_font] + args.outputs.labels << { x: 460, y: row_to_px(args, 4), text: "\"h\" has never been pressed.", size_enum: -1 } end tick_help_text args end -def small_font - # This method provides some values for the construction of labels - # Specifically, Size, Alignment, & RGBA - # This makes it so that custom parameters don't have to be repeatedly typed. - # Additionally "small_font" provides programmers with more information than some numbers - [-2, 0, 0, 0, 0, 255] -end - -def row_to_px args, row_number +def row_to_px args, row_number, y_offset = 20 # This takes a row_number and converts it to pixels DragonRuby understands. # Row 0 starts 5 units below the top of the grid # Each row afterward is 20 units lower - args.grid.top.shift_down(5).shift_down(20 * row_number) + args.grid.top - 5 - (y_offset * row_number) end # Don't worry about understanding the code within this method just yet. @@ -4652,17 +4041,17 @@ def tick_help_text args end end - args.outputs.labels << [10, row_to_px(args, 6), "Advanced Help:", small_font] + args.outputs.labels << { x: 10, y: row_to_px(args, 6), text: "This is the api for the keys you've pressed:", size_enum: -1, r: 180 } if !args.state.help_available args.outputs.labels << [10, row_to_px(args, 7), "Press a key and I'll show code to access the key and what value will be returned if you used the code.", small_font] return end - args.outputs.labels << [10 , row_to_px(args, 7), "args.inputs.keyboard", small_font] - args.outputs.labels << [330, row_to_px(args, 7), "args.inputs.keyboard.key_down", small_font] - args.outputs.labels << [650, row_to_px(args, 7), "args.inputs.keyboard.key_held", small_font] - args.outputs.labels << [990, row_to_px(args, 7), "args.inputs.keyboard.key_up", small_font] + args.outputs.labels << { x: 10 , y: row_to_px(args, 7), text: "args.inputs.keyboard", size_enum: -2 } + args.outputs.labels << { x: 330, y: row_to_px(args, 7), text: "args.inputs.keyboard.key_down", size_enum: -2 } + args.outputs.labels << { x: 650, y: row_to_px(args, 7), text: "args.inputs.keyboard.key_held", size_enum: -2 } + args.outputs.labels << { x: 990, y: row_to_px(args, 7), text: "args.inputs.keyboard.key_up", size_enum: -2 } fill_history args, :key_value_history, :down_or_held, nil fill_history args, :key_down_value_history, :down, :key_down @@ -4708,12 +4097,8 @@ def render_help_labels args, history_key, state_key, keyboard_method, x end idx += 2 [ - [x, row_to_px(args, idx - 2), - " .#{k} is #{current_value || "nil"}", - small_font], - [x, row_to_px(args, idx - 1), - " was #{v}", - small_font] + { x: x, y: row_to_px(args, idx + 0, 16), text: " .#{k} is #{current_value || "nil"}", size_enum: -2 }, + { x: x, y: row_to_px(args, idx + 1, 16), text: " was #{v}", size_enum: -2 } ] end end @@ -4728,13 +4113,49 @@ def tick_instructions args, text, y = 715 args.state.key_event_occurred = true end - args.outputs.debug << [0, y - 50, 1280, 60].solid - args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label - args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label + args.outputs.debug << { x: 0, y: y - 50, w: 1280, h: 60 }.solid! + args.outputs.debug << { x: 640, y: y, text: text, + size_enum: 1, alignment_enum: 1, r: 255, g: 255, b: 255 }.label! + args.outputs.debug << { x: 640, y: y - 25, text: "(click to dismiss instructions)", + size_enum: -2, alignment_enum: 1, r: 255, g: 255, b: 255 }.label! end </code></pre> -<h3 id='----input-basics---mouse---main.rb'>Input Basics - Mouse - main.rb</h3> +<h3 id='----input-basics---moving-a-sprite---main-rb'>Input Basics - Moving A Sprite - main.rb</h3> +<pre><code class="language-ruby"># ./samples/02_input_basics/01_moving_a_sprite/app/main.rb +def tick args + # create a player and set default values + # for the player's x, y, w (width), and h (height) + args.state.player.x ||= 100 + args.state.player.y ||= 100 + args.state.player.w ||= 50 + args.state.player.h ||= 50 + + # render the player to the screen + args.outputs.sprites << { x: args.state.player.x, + y: args.state.player.y, + w: args.state.player.w, + h: args.state.player.h, + path: 'sprites/square/green.png' } + + # move the player around using the keyboard + if args.inputs.up + args.state.player.y += 10 + elsif args.inputs.down + args.state.player.y -= 10 + end + + if args.inputs.left + args.state.player.x -= 10 + elsif args.inputs.right + args.state.player.x += 10 + end +end + +$gtk.reset + +</code></pre> +<h3 id='----input-basics---mouse---main-rb'>Input Basics - Mouse - main.rb</h3> <pre><code class="language-ruby"># ./samples/02_input_basics/02_mouse/app/main.rb =begin @@ -4770,7 +4191,7 @@ Reminder: # Use args.inputs.mouse.click.created_at # To see how many frames its been since the click occurred -# Use args.inputs.mouse.click.creat_at_elapsed +# Use args.inputs.mouse.click.created_at_elapsed # Saving the click in args.state can be quite useful @@ -4799,11 +4220,7 @@ def small_label args, x, row, message # This method effectively combines the row_to_px and small_font methods # It changes the given row value to a DragonRuby pixel value # and adds the customization parameters - [x, row_to_px(args, row), message, small_font] -end - -def small_font - [-2, 0, 0, 0, 0, 255] + { x: x, y: row_to_px(args, row), text: message, alignment_enum: -2 } end def row_to_px args, row_number @@ -4819,13 +4236,13 @@ def tick_instructions args, text, y = 715 args.state.key_event_occurred = true end - args.outputs.debug << [0, y - 50, 1280, 60].solid - args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label - args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label + args.outputs.debug << { x: 0, y: y - 50, w: 1280, h: 60 }.solid! + args.outputs.debug << { x: 640, y: y, text: text, size_enum: 1, alignment_enum: 1, r: 255, g: 255, b: 255 }.label! + args.outputs.debug << { x: 640, y: y - 25, text: "(click to dismiss instructions)", size_enum: -2, alignment_enum: 1, r: 255, g: 255, b: 255 }.label! end </code></pre> -<h3 id='----input-basics---mouse-point-to-rect---main.rb'>Input Basics - Mouse Point To Rect - main.rb</h3> +<h3 id='----input-basics---mouse-point-to-rect---main-rb'>Input Basics - Mouse Point To Rect - main.rb</h3> <pre><code class="language-ruby"># ./samples/02_input_basics/03_mouse_point_to_rect/app/main.rb =begin @@ -4871,7 +4288,7 @@ def tick args args.outputs.labels << small_label(args, x, 15, "Click inside the blue box maybe ---->") - box = [785, 370, 50, 50, 0, 0, 170] + box = { x: 785, y: 370, w: 50, h: 50, r: 0, g: 0, b: 170 } args.outputs.borders << box # Saves the most recent click into args.state @@ -4893,11 +4310,7 @@ def tick args end def small_label args, x, row, message - [x, row_to_px(args, row), message, small_font] -end - -def small_font - [-2, 0, 0, 0, 0, 255] + { x: x, y: row_to_px(args, row), text: message, size_enum: -2 } end def row_to_px args, row_number @@ -4913,13 +4326,13 @@ def tick_instructions args, text, y = 715 args.state.key_event_occurred = true end - args.outputs.debug << [0, y - 50, 1280, 60].solid - args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label - args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label + args.outputs.debug << { x: 0, y: y - 50, w: 1280, h: 60 }.solid! + args.outputs.debug << { x: 640, y: y, text: text, size_enum: 1, alignment_enum: 1, r: 255, g: 255, b: 255 }.label! + args.outputs.debug << { x: 640, y: y - 25, text: "(click to dismiss instructions)", size_enum: -2, alignment_enum: 1, r: 255, g: 255, b: 255 }.label! end </code></pre> -<h3 id='----input-basics---mouse-rect-to-rect---main.rb'>Input Basics - Mouse Rect To Rect - main.rb</h3> +<h3 id='----input-basics---mouse-rect-to-rect---main-rb'>Input Basics - Mouse Rect To Rect - main.rb</h3> <pre><code class="language-ruby"># ./samples/02_input_basics/04_mouse_rect_to_rect/app/main.rb =begin @@ -4965,9 +4378,15 @@ def tick args # They are stored in game so that they do not get reset every tick if args.inputs.mouse.click if !args.state.box_collision_one - args.state.box_collision_one = [args.inputs.mouse.click.point.x - 25, args.inputs.mouse.click.point.y - 25, 125, 125, 180, 0, 0, 180] + args.state.box_collision_one = { x: args.inputs.mouse.click.point.x - 25, + y: args.inputs.mouse.click.point.y - 25, + w: 125, h: 125, + r: 180, g: 0, b: 0, a: 180 } elsif !args.state.box_collision_two - args.state.box_collision_two = [args.inputs.mouse.click.point.x - 25, args.inputs.mouse.click.point.y - 25, 125, 125, 0, 0, 180, 180] + args.state.box_collision_two = { x: args.inputs.mouse.click.point.x - 25, + y: args.inputs.mouse.click.point.y - 25, + w: 125, h: 125, + r: 0, g: 0, b: 180, a: 180 } else args.state.box_collision_one = nil args.state.box_collision_two = nil @@ -4994,15 +4413,11 @@ def tick args end def small_label args, x, row, message - [x, row_to_px(args, row), message, small_font] -end - -def small_font - [-2, 0, 0, 0, 0, 255] + { x: x, y: row_to_px(args, row), text: message, size_enum: -2 } end def row_to_px args, row_number - args.grid.top.shift_down(5).shift_down(20 * row_number) + args.grid.top - 5 - (20 * row_number) end def tick_instructions args, text, y = 715 @@ -5020,7 +4435,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----input-basics---controller---main.rb'>Input Basics - Controller - main.rb</h3> +<h3 id='----input-basics---controller---main-rb'>Input Basics - Controller - main.rb</h3> <pre><code class="language-ruby"># ./samples/02_input_basics/05_controller/app/main.rb =begin @@ -5066,57 +4481,51 @@ class ControllerDemo def process_inputs state.buttons = [] - state.buttons << [100, 500, inputs.controller_one.key_held.l1, "L1"] - state.buttons << [100, 600, inputs.controller_one.key_held.l2, "L2"] - - state.buttons << [1100, 500, inputs.controller_one.key_held.r1, "R1"] - state.buttons << [1100, 600, inputs.controller_one.key_held.r2, "R2"] - - state.buttons << [540, 450, inputs.controller_one.key_held.select, "Select"] - state.buttons << [660, 450, inputs.controller_one.key_held.start, "Start"] - - state.buttons << [200, 300, inputs.controller_one.key_held.left, "Left"] - state.buttons << [300, 400, inputs.controller_one.key_held.up, "Up"] - state.buttons << [400, 300, inputs.controller_one.key_held.right, "Right"] - state.buttons << [300, 200, inputs.controller_one.key_held.down, "Down"] - - state.buttons << [800, 300, inputs.controller_one.key_held.x, "X"] - state.buttons << [900, 400, inputs.controller_one.key_held.y, "Y"] - state.buttons << [1000, 300, inputs.controller_one.key_held.a, "A"] - state.buttons << [900, 200, inputs.controller_one.key_held.b, "B"] - - state.buttons << [450 + inputs.controller_one.left_analog_x_perc * 100, - 100 + inputs.controller_one.left_analog_y_perc * 100, - inputs.controller_one.key_held.l3, - "L3"] - - state.buttons << [750 + inputs.controller_one.right_analog_x_perc * 100, - 100 + inputs.controller_one.right_analog_y_perc * 100, - inputs.controller_one.key_held.r3, - "R3"] + state.buttons << { x: 100, y: 500, active: inputs.controller_one.key_held.l1, text: "L1"} + state.buttons << { x: 100, y: 600, active: inputs.controller_one.key_held.l2, text: "L2"} + state.buttons << { x: 1100, y: 500, active: inputs.controller_one.key_held.r1, text: "R1"} + state.buttons << { x: 1100, y: 600, active: inputs.controller_one.key_held.r2, text: "R2"} + state.buttons << { x: 540, y: 450, active: inputs.controller_one.key_held.select, text: "Select"} + state.buttons << { x: 660, y: 450, active: inputs.controller_one.key_held.start, text: "Start"} + state.buttons << { x: 200, y: 300, active: inputs.controller_one.key_held.left, text: "Left"} + state.buttons << { x: 300, y: 400, active: inputs.controller_one.key_held.up, text: "Up"} + state.buttons << { x: 400, y: 300, active: inputs.controller_one.key_held.right, text: "Right"} + state.buttons << { x: 300, y: 200, active: inputs.controller_one.key_held.down, text: "Down"} + state.buttons << { x: 800, y: 300, active: inputs.controller_one.key_held.x, text: "X"} + state.buttons << { x: 900, y: 400, active: inputs.controller_one.key_held.y, text: "Y"} + state.buttons << { x: 1000, y: 300, active: inputs.controller_one.key_held.a, text: "A"} + state.buttons << { x: 900, y: 200, active: inputs.controller_one.key_held.b, text: "B"} + state.buttons << { x: 450 + inputs.controller_one.left_analog_x_perc * 100, + y: 100 + inputs.controller_one.left_analog_y_perc * 100, + active: inputs.controller_one.key_held.l3, + text: "L3" } + state.buttons << { x: 750 + inputs.controller_one.right_analog_x_perc * 100, + y: 100 + inputs.controller_one.right_analog_y_perc * 100, + active: inputs.controller_one.key_held.r3, + text: "R3" } end # Gives each button a square shape. # If the button is being pressed or held (which means it is considered active), # the square is filled in. Otherwise, the button simply has a border. def render - state.buttons.each do |x, y, active, text| - rect = [x, y, 75, 75] + state.buttons.each do |b| + rect = { x: b.x, y: b.y, w: 75, h: 75 } - if active # if button is pressed + if b.active # if button is pressed outputs.solids << rect # rect is output as solid (filled in) else outputs.borders << rect # otherwise, output as border end # Outputs the text of each button using labels. - outputs.labels << [x, y + 95, text] # add 95 to place label above button + outputs.labels << { x: b.x, y: b.y + 95, text: b.text } # add 95 to place label above button end - outputs.labels << [10, 60, "Left Analog x: #{inputs.controller_one.left_analog_x_raw} (#{inputs.controller_one.left_analog_x_perc * 100}%)"] - outputs.labels << [10, 30, "Left Analog y: #{inputs.controller_one.left_analog_y_raw} (#{inputs.controller_one.left_analog_y_perc * 100}%)"] - outputs.labels << [900, 60, "Right Analog x: #{inputs.controller_one.right_analog_x_raw} (#{inputs.controller_one.right_analog_x_perc * 100}%)"] - outputs.labels << [900, 30, "Right Analog y: #{inputs.controller_one.right_analog_y_raw} (#{inputs.controller_one.right_analog_y_perc * 100}%)"] + outputs.labels << { x: 10, y: 60, text: "Left Analog x: #{inputs.controller_one.left_analog_x_raw} (#{inputs.controller_one.left_analog_x_perc * 100}%)" } + outputs.labels << { x: 10, y: 30, text: "Left Analog y: #{inputs.controller_one.left_analog_y_raw} (#{inputs.controller_one.left_analog_y_perc * 100}%)" } + outputs.labels << { x: 900, y: 60, text: "Right Analog x: #{inputs.controller_one.right_analog_x_raw} (#{inputs.controller_one.right_analog_x_perc * 100}%)" } + outputs.labels << { x: 900, y: 30, text: "Right Analog y: #{inputs.controller_one.right_analog_y_raw} (#{inputs.controller_one.right_analog_y_perc * 100}%)" } end end @@ -5150,7 +4559,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----input-basics---touch---main.rb'>Input Basics - Touch - main.rb</h3> +<h3 id='----input-basics---touch---main-rb'>Input Basics - Touch - main.rb</h3> <pre><code class="language-ruby"># ./samples/02_input_basics/06_touch/app/main.rb def tick args args.outputs.background_color = [ 0, 0, 0 ] @@ -5165,10 +4574,12 @@ def tick args # the next new touch will be finger_one again, but until then, new touches # don't fill in earlier slots. if !args.inputs.finger_one.nil? - args.outputs.primitives << [640, 650, "Finger #1 is touching at (#{args.inputs.finger_one.x}, #{args.inputs.finger_one.y}).", 5, 1, 255, 255, 255].label + args.outputs.primitives << { x: 640, y: 650, text: "Finger #1 is touching at (#{args.inputs.finger_one.x}, #{args.inputs.finger_one.y}).", + size_enum: 5, alignment_enum: 1, r: 255, g: 255, b: 255 }.label! end if !args.inputs.finger_two.nil? - args.outputs.primitives << [640, 600, "Finger #2 is touching at (#{args.inputs.finger_two.x}, #{args.inputs.finger_two.y}).", 5, 1, 255, 255, 255].label + args.outputs.primitives << { x: 640, y: 600, text: "Finger #2 is touching at (#{args.inputs.finger_two.x}, #{args.inputs.finger_two.y}).", + size_enum: 5, alignment_enum: 1, r: 255, g: 255, b: 255 }.label! end # Here's the more flexible interface: this will report as many simultaneous @@ -5189,14 +4600,13 @@ def tick args r = (color & 0xFF0000) >> 16 g = (color & 0x00FF00) >> 8 b = (color & 0x0000FF) - args.outputs.primitives << [v.x - (size / 2), v.y + (size / 2), size, size, r, g, b, 255].solid - args.outputs.primitives << [v.x, v.y + size, k.to_s, 0, 1, 0, 0, 0].label + args.outputs.primitives << { x: v.x - (size / 2), y: v.y + (size / 2), w: size, h: size, r: r, g: g, b: b, a: 255 }.solid! + args.outputs.primitives << { x: v.x, y: v.y + size, text: k.to_s, alignment_enum: 1 }.label! } end - </code></pre> -<h3 id='----rendering-sprites---animation-using-separate-pngs---main.rb'>Rendering Sprites - Animation Using Separate Pngs - main.rb</h3> +<h3 id='----rendering-sprites---animation-using-separate-pngs---main-rb'>Rendering Sprites - Animation Using Separate Pngs - main.rb</h3> <pre><code class="language-ruby"># ./samples/03_rendering_sprites/01_animation_using_separate_pngs/app/main.rb =begin @@ -5231,6 +4641,8 @@ end # in this tick "entry point": `looping_animation`, and the # second method is `one_time_animation`. def tick args + # uncomment the line below to see animation play out in slow motion + # args.gtk.slowmo! 6 looping_animation args one_time_animation args end @@ -5263,22 +4675,22 @@ def looping_animation args does_sprite_loop # Now that we have `sprite_index, we can present the correct file. - args.outputs.sprites << [100, 100, 100, 100, "sprites/dragon_fly_#{sprite_index}.png"] + args.outputs.sprites << { x: 100, y: 100, w: 100, h: 100, path: "sprites/dragon_fly_#{sprite_index}.png" } # Try changing the numbers below to see how the animation changes: - args.outputs.sprites << [100, 200, 100, 100, "sprites/dragon_fly_#{0.frame_index 6, 4, true}.png"] + args.outputs.sprites << { x: 100, y: 200, w: 100, h: 100, path: "sprites/dragon_fly_#{0.frame_index 6, 4, true}.png" } end # This function shows how to animate a sprite that executes # only once when the "f" key is pressed. def one_time_animation args # This is just a label the shows instructions within the game. - args.outputs.labels << [220, 350, "(press f to animate)"] + args.outputs.labels << { x: 220, y: 350, text: "(press f to animate)" } # If "f" is pressed on the keyboard... if args.inputs.keyboard.key_down.f # Print the frame that "f" was pressed on. - puts "Hello from main.rb! The \"f\" key was in the down state on frame: #{args.inputs.keyboard.key_down.f}" + puts "Hello from main.rb! The \"f\" key was in the down state on frame: #{args.state.tick_count}" # And MOST IMPORTANTLY set the point it time to start the animation, # equal to "now" which is represented as args.state.tick_count. @@ -5311,7 +4723,7 @@ def one_time_animation args sprite_index ||= 0 # Present the sprite. - args.outputs.sprites << [100, 300, 100, 100, "sprites/dragon_fly_#{sprite_index}.png"] + args.outputs.sprites << { x: 100, y: 300, w: 100, h: 100, path: "sprites/dragon_fly_#{sprite_index}.png" } tick_instructions args, "Sample app shows how to use Numeric#frame_index and string interpolation to animate a sprite over time." end @@ -5331,7 +4743,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----rendering-sprites---animation-using-sprite-sheet---main.rb'>Rendering Sprites - Animation Using Sprite Sheet - main.rb</h3> +<h3 id='----rendering-sprites---animation-using-sprite-sheet---main-rb'>Rendering Sprites - Animation Using Sprite Sheet - main.rb</h3> <pre><code class="language-ruby"># ./samples/03_rendering_sprites/02_animation_using_sprite_sheet/app/main.rb def tick args args.state.player.x ||= 100 @@ -5433,7 +4845,7 @@ def running_sprite args end </code></pre> -<h3 id='----rendering-sprites---animation-states---main.rb'>Rendering Sprites - Animation States - main.rb</h3> +<h3 id='----rendering-sprites---animation-states---main-rb'>Rendering Sprites - Animation States - main.rb</h3> <pre><code class="language-ruby"># ./samples/03_rendering_sprites/03_animation_states/app/main.rb class Game attr_gtk @@ -5620,7 +5032,7 @@ end $gtk.reset </code></pre> -<h3 id='----rendering-sprites---color-and-rotation---main.rb'>Rendering Sprites - Color And Rotation - main.rb</h3> +<h3 id='----rendering-sprites---color-and-rotation---main-rb'>Rendering Sprites - Color And Rotation - main.rb</h3> <pre><code class="language-ruby"># ./samples/03_rendering_sprites/04_color_and_rotation/app/main.rb =begin APIs listing that haven't been encountered in previous sample apps: @@ -5850,7 +5262,7 @@ def source_rect state end </code></pre> -<h3 id='----physics-and-collisions---simple---main.rb'>Physics And Collisions - Simple - main.rb</h3> +<h3 id='----physics-and-collisions---simple---main-rb'>Physics And Collisions - Simple - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/01_simple/app/main.rb =begin @@ -5962,7 +5374,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----physics-and-collisions---moving-objects---main.rb'>Physics And Collisions - Moving Objects - main.rb</h3> +<h3 id='----physics-and-collisions---moving-objects---main-rb'>Physics And Collisions - Moving Objects - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/02_moving_objects/app/main.rb =begin @@ -6266,7 +5678,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----physics-and-collisions---entities---main.rb'>Physics And Collisions - Entities - main.rb</h3> +<h3 id='----physics-and-collisions---entities---main-rb'>Physics And Collisions - Entities - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/03_entities/app/main.rb =begin @@ -6421,7 +5833,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----physics-and-collisions---box-collision---main.rb'>Physics And Collisions - Box Collision - main.rb</h3> +<h3 id='----physics-and-collisions---box-collision---main-rb'>Physics And Collisions - Box Collision - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/04_box_collision/app/main.rb =begin @@ -6762,7 +6174,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----physics-and-collisions---box-collision-2---main.rb'>Physics And Collisions - Box Collision 2 - main.rb</h3> +<h3 id='----physics-and-collisions---box-collision-2---main-rb'>Physics And Collisions - Box Collision 2 - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/05_box_collision_2/app/main.rb =begin APIs listing that haven't been encountered in previous sample apps: @@ -7236,7 +6648,7 @@ def tick args end </code></pre> -<h3 id='----physics-and-collisions---box-collision-3---main.rb'>Physics And Collisions - Box Collision 3 - main.rb</h3> +<h3 id='----physics-and-collisions---box-collision-3---main-rb'>Physics And Collisions - Box Collision 3 - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/06_box_collision_3/app/main.rb class Game attr_gtk @@ -7286,9 +6698,9 @@ class Game mouse_overlay = mouse_overlay.merge r: 255 if state.delete_mode if state.mouse_held - outputs.primitives << mouse_overlay.border + outputs.primitives << mouse_overlay.border! else - outputs.primitives << mouse_overlay.solid + outputs.primitives << mouse_overlay.solid! end end @@ -7352,7 +6764,7 @@ class Game def calc_below return unless player.dy <= 0 - tiles_below = find_tiles { |t| t.rect.top <= player.y } + tiles_below = find_tiles { |t| t.rect.top <= player.prev_rect.y } collision = find_colliding_tile tiles_below, (player.rect.merge y: player.next_rect.y) return unless collision if collision.neighbors.b == :none && player.jumped_down_at.elapsed_time < 10 @@ -7383,7 +6795,7 @@ class Game def calc_above return unless player.dy > 0 - tiles_above = find_tiles { |t| t.rect.y >= player.y } + tiles_above = find_tiles { |t| t.rect.y >= player.prev_rect.y } collision = find_colliding_tile tiles_above, (player.rect.merge y: player.next_rect.y) return unless collision return if collision.neighbors.t == :none @@ -7392,17 +6804,17 @@ class Game end def calc_player_dx - player.y += player.dy - player.dy += state.gravity - player.dy += player.dy * state.drag ** 2 * -1 - end - - def calc_player_dy player.dx = player.dx.clamp(-5, 5) player.dx *= 0.9 player.x += player.dx end + def calc_player_dy + player.y += player.dy + player.dy += state.gravity + player.dy += player.dy * state.drag ** 2 * -1 + end + def reset_player player.x = 100 player.y = 720 @@ -7495,7 +6907,7 @@ def tick args end </code></pre> -<h3 id='----physics-and-collisions---jump-physics---main.rb'>Physics And Collisions - Jump Physics - main.rb</h3> +<h3 id='----physics-and-collisions---jump-physics---main-rb'>Physics And Collisions - Jump Physics - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/07_jump_physics/app/main.rb =begin @@ -7544,8 +6956,7 @@ class VerticalPlatformer input end - # Sets default values - def defaults + def init_game s.platforms ||= [ # initializes platforms collection with two platforms using hashes new_platform(x: 0, y: 0, w: 700, h: 32, dx: 1, speed: 0, rect: nil), new_platform(x: 0, y: 300, w: 700, h: 32, dx: 1, speed: 0, rect: nil), # 300 pixels higher @@ -7568,6 +6979,11 @@ class VerticalPlatformer s.camera ||= { y: -100 } # shows view on screen (as the player moves upward, the camera does too) end + # Sets default values + def defaults + init_game + end + # Outputs objects onto the screen def render outputs.solids << s.platforms.map do |p| # outputs platforms onto screen @@ -7655,7 +7071,9 @@ class VerticalPlatformer rect: nil) end else + # game over s.as_hash.clear # otherwise clear the hash (no new platform is necessary) + init_game end end @@ -7695,7 +7113,7 @@ def tick args end </code></pre> -<h3 id='----physics-and-collisions---bouncing-on-collision---ball.rb'>Physics And Collisions - Bouncing On Collision - ball.rb</h3> +<h3 id='----physics-and-collisions---bouncing-on-collision---ball-rb'>Physics And Collisions - Bouncing On Collision - ball.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/ball.rb GRAVITY = -0.08 @@ -7786,7 +7204,7 @@ class Ball end </code></pre> -<h3 id='----physics-and-collisions---bouncing-on-collision---block.rb'>Physics And Collisions - Bouncing On Collision - block.rb</h3> +<h3 id='----physics-and-collisions---bouncing-on-collision---block-rb'>Physics And Collisions - Bouncing On Collision - block.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/block.rb DEGREES_TO_RADIANS = Math::PI / 180 @@ -7949,7 +7367,7 @@ class Block end </code></pre> -<h3 id='----physics-and-collisions---bouncing-on-collision---cannon.rb'>Physics And Collisions - Bouncing On Collision - cannon.rb</h3> +<h3 id='----physics-and-collisions---bouncing-on-collision---cannon-rb'>Physics And Collisions - Bouncing On Collision - cannon.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/cannon.rb class Cannon def initialize args @@ -7973,7 +7391,7 @@ class Cannon end </code></pre> -<h3 id='----physics-and-collisions---bouncing-on-collision---main.rb'>Physics And Collisions - Bouncing On Collision - main.rb</h3> +<h3 id='----physics-and-collisions---bouncing-on-collision---main-rb'>Physics And Collisions - Bouncing On Collision - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/main.rb INFINITY= 10**10 @@ -8094,7 +7512,7 @@ def tick args end </code></pre> -<h3 id='----physics-and-collisions---bouncing-on-collision---peg.rb'>Physics And Collisions - Bouncing On Collision - peg.rb</h3> +<h3 id='----physics-and-collisions---bouncing-on-collision---peg-rb'>Physics And Collisions - Bouncing On Collision - peg.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/peg.rb class Peg def initialize(x, y, block_size) @@ -8280,7 +7698,7 @@ class Peg end </code></pre> -<h3 id='----physics-and-collisions---bouncing-on-collision---vector2d.rb'>Physics And Collisions - Bouncing On Collision - vector2d.rb</h3> +<h3 id='----physics-and-collisions---bouncing-on-collision---vector2d-rb'>Physics And Collisions - Bouncing On Collision - vector2d.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/vector2d.rb class Vector2d attr_accessor :x, :y @@ -8332,7 +7750,7 @@ class Vector2d end end </code></pre> -<h3 id='----physics-and-collisions---arbitrary-collision---ball.rb'>Physics And Collisions - Arbitrary Collision - ball.rb</h3> +<h3 id='----physics-and-collisions---arbitrary-collision---ball-rb'>Physics And Collisions - Arbitrary Collision - ball.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/09_arbitrary_collision/app/ball.rb class Ball @@ -8502,7 +7920,7 @@ class Ball end </code></pre> -<h3 id='----physics-and-collisions---arbitrary-collision---blocks.rb'>Physics And Collisions - Arbitrary Collision - blocks.rb</h3> +<h3 id='----physics-and-collisions---arbitrary-collision---blocks-rb'>Physics And Collisions - Arbitrary Collision - blocks.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/09_arbitrary_collision/app/blocks.rb MAX_COUNT=100 @@ -9124,7 +8542,7 @@ class Line end </code></pre> -<h3 id='----physics-and-collisions---arbitrary-collision---linear_collider.rb'>Physics And Collisions - Arbitrary Collision - linear_collider.rb</h3> +<h3 id='----physics-and-collisions---arbitrary-collision---linear_collider-rb'>Physics And Collisions - Arbitrary Collision - linear_collider.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/09_arbitrary_collision/app/linear_collider.rb COLLISIONWIDTH=8 @@ -9308,7 +8726,7 @@ class LinearCollider end </code></pre> -<h3 id='----physics-and-collisions---arbitrary-collision---main.rb'>Physics And Collisions - Arbitrary Collision - main.rb</h3> +<h3 id='----physics-and-collisions---arbitrary-collision---main-rb'>Physics And Collisions - Arbitrary Collision - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/09_arbitrary_collision/app/main.rb INFINITY= 10**10 MAX_VELOCITY = 8.0 @@ -9483,7 +8901,7 @@ def tick args end </code></pre> -<h3 id='----physics-and-collisions---arbitrary-collision---paddle.rb'>Physics And Collisions - Arbitrary Collision - paddle.rb</h3> +<h3 id='----physics-and-collisions---arbitrary-collision---paddle-rb'>Physics And Collisions - Arbitrary Collision - paddle.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/09_arbitrary_collision/app/paddle.rb class Paddle attr_accessor :enabled @@ -9540,7 +8958,7 @@ class Paddle end </code></pre> -<h3 id='----physics-and-collisions---arbitrary-collision---rectangle.rb'>Physics And Collisions - Arbitrary Collision - rectangle.rb</h3> +<h3 id='----physics-and-collisions---arbitrary-collision---rectangle-rb'>Physics And Collisions - Arbitrary Collision - rectangle.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/09_arbitrary_collision/app/rectangle.rb class Rectangle def initialize args @@ -9634,7 +9052,7 @@ class Rectangle end </code></pre> -<h3 id='----physics-and-collisions---arbitrary-collision---square_collider.rb'>Physics And Collisions - Arbitrary Collision - square_collider.rb</h3> +<h3 id='----physics-and-collisions---arbitrary-collision---square_collider-rb'>Physics And Collisions - Arbitrary Collision - square_collider.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/09_arbitrary_collision/app/square_collider.rb class SquareCollider @@ -9667,7 +9085,7 @@ class SquareCollider end </code></pre> -<h3 id='----physics-and-collisions---arbitrary-collision---vector2d.rb'>Physics And Collisions - Arbitrary Collision - vector2d.rb</h3> +<h3 id='----physics-and-collisions---arbitrary-collision---vector2d-rb'>Physics And Collisions - Arbitrary Collision - vector2d.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/09_arbitrary_collision/app/vector2d.rb class Vector2d attr_accessor :x, :y @@ -9719,7 +9137,7 @@ class Vector2d end end </code></pre> -<h3 id='----physics-and-collisions---collision-with-object-removal---ball.rb'>Physics And Collisions - Collision With Object Removal - ball.rb</h3> +<h3 id='----physics-and-collisions---collision-with-object-removal---ball-rb'>Physics And Collisions - Collision With Object Removal - ball.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/ball.rb class Ball #TODO limit accessors? @@ -9754,7 +9172,7 @@ class Ball end </code></pre> -<h3 id='----physics-and-collisions---collision-with-object-removal---linear_collider.rb'>Physics And Collisions - Collision With Object Removal - linear_collider.rb</h3> +<h3 id='----physics-and-collisions---collision-with-object-removal---linear_collider-rb'>Physics And Collisions - Collision With Object Removal - linear_collider.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/linear_collider.rb #The LinearCollider (theoretically) produces collisions upon a line segment defined point.y two x,y cordinates @@ -9924,7 +9342,7 @@ class LinearCollider end </code></pre> -<h3 id='----physics-and-collisions---collision-with-object-removal---main.rb'>Physics And Collisions - Collision With Object Removal - main.rb</h3> +<h3 id='----physics-and-collisions---collision-with-object-removal---main-rb'>Physics And Collisions - Collision With Object Removal - main.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/main.rb # coding: utf-8 INFINITY= 10**10 @@ -10117,7 +9535,7 @@ def tick args end </code></pre> -<h3 id='----physics-and-collisions---collision-with-object-removal---paddle.rb'>Physics And Collisions - Collision With Object Removal - paddle.rb</h3> +<h3 id='----physics-and-collisions---collision-with-object-removal---paddle-rb'>Physics And Collisions - Collision With Object Removal - paddle.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/paddle.rb class Paddle attr_accessor :enabled @@ -10174,7 +9592,7 @@ class Paddle end </code></pre> -<h3 id='----physics-and-collisions---collision-with-object-removal---tests.rb'>Physics And Collisions - Collision With Object Removal - tests.rb</h3> +<h3 id='----physics-and-collisions---collision-with-object-removal---tests-rb'>Physics And Collisions - Collision With Object Removal - tests.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/tests.rb # For advanced users: # You can put some quick verification tests here, any method @@ -10207,7 +9625,7 @@ $gtk.log_level = :off $gtk.tests.start </code></pre> -<h3 id='----physics-and-collisions---collision-with-object-removal---vector2d.rb'>Physics And Collisions - Collision With Object Removal - vector2d.rb</h3> +<h3 id='----physics-and-collisions---collision-with-object-removal---vector2d-rb'>Physics And Collisions - Collision With Object Removal - vector2d.rb</h3> <pre><code class="language-ruby"># ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/vector2d.rb class Vector2d @@ -10261,7 +9679,7 @@ class Vector2d end </code></pre> -<h3 id='----mouse---mouse-click---main.rb'>Mouse - Mouse Click - main.rb</h3> +<h3 id='----mouse---mouse-click---main-rb'>Mouse - Mouse Click - main.rb</h3> <pre><code class="language-ruby"># ./samples/05_mouse/01_mouse_click/app/main.rb =begin @@ -10314,12 +9732,23 @@ class TicTacToe # Starts the game with player x's turn and creates an array (to_a) for space combinations. # Calls methods necessary for the game to run properly. def tick - state.current_turn ||= :x - state.space_combinations = [-1, 0, 1].product([-1, 0, 1]).to_a + init_new_game render_board input_board end + def init_new_game + state.current_turn ||= :x + state.space_combinations ||= [-1, 0, 1].product([-1, 0, 1]).to_a + + state.spaces ||= {} + + state.space_combinations.each do |x, y| + state.spaces[x] ||= {} + state.spaces[x][y] ||= state.new_entity(:space) + end + end + # Uses borders to create grid squares for the game's board. Also outputs the game pieces using labels. def render_board square_size = 80 @@ -10395,6 +9824,7 @@ class TicTacToe def input_restart_game return unless state.game_over gtk.reset + init_new_game end # Checks if x or o won the game. @@ -10509,7 +9939,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----mouse---mouse-move---main.rb'>Mouse - Mouse Move - main.rb</h3> +<h3 id='----mouse---mouse-move---main-rb'>Mouse - Mouse Move - main.rb</h3> <pre><code class="language-ruby"># ./samples/05_mouse/02_mouse_move/app/main.rb =begin @@ -10709,7 +10139,7 @@ class ProtectThePuppiesFromTheZombies def calc_kill_zombie # Find all zombies that intersect with the player. They are considered killed. - killed_this_frame = state.zombies.find_all { |z| z.sprite.intersect_rect? state.player_sprite } + killed_this_frame = state.zombies.find_all { |z| z.sprite && (z.sprite.intersect_rect? state.player_sprite) } state.zombies = state.zombies - killed_this_frame # remove newly killed zombies from zombies collection state.killed_zombies += killed_this_frame # add newly killed zombies to killed zombies @@ -10809,7 +10239,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----mouse---mouse-move-paint-app---main.rb'>Mouse - Mouse Move Paint App - main.rb</h3> +<h3 id='----mouse---mouse-move-paint-app---main-rb'>Mouse - Mouse Move Paint App - main.rb</h3> <pre><code class="language-ruby"># ./samples/05_mouse/03_mouse_move_paint_app/app/main.rb =begin @@ -11053,7 +10483,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----mouse---coordinate-systems---main.rb'>Mouse - Coordinate Systems - main.rb</h3> +<h3 id='----mouse---coordinate-systems---main-rb'>Mouse - Coordinate Systems - main.rb</h3> <pre><code class="language-ruby"># ./samples/05_mouse/04_coordinate_systems/app/main.rb =begin @@ -11137,7 +10567,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----save-load---save-load-game---main.rb'>Save Load - Save Load Game - main.rb</h3> +<h3 id='----save-load---save-load-game---main-rb'>Save Load - Save Load Game - main.rb</h3> <pre><code class="language-ruby"># ./samples/06_save_load/01_save_load_game/app/main.rb =begin @@ -11530,7 +10960,1117 @@ def tick args end </code></pre> -<h3 id='----advanced-rendering---simple-render-targets---main.rb'>Advanced Rendering - Simple Render Targets - main.rb</h3> +<h3 id='----advanced-audio---audio-mixer---main-rb'>Advanced Audio - Audio Mixer - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_audio/01_audio_mixer/app/main.rb +# these are the properties that you can sent on args.audio +def spawn_new_sound args, name, path + # Spawn randomly in an area that won't be covered by UI. + screenx = (rand * 600.0) + 200.0 + screeny = (rand * 400.0) + 100.0 + + id = new_sound_id! args + # you can hang anything on the audio hashes you want, so we store the + # actual screen position in here for convenience. + args.audio[id] = { + name: name, + input: path, + screenx: screenx, + screeny: screeny, + x: ((screenx / 1279.0) * 2.0) - 1.0, # scale to -1.0 - 1.0 range + y: ((screeny / 719.0) * 2.0) - 1.0, # scale to -1.0 - 1.0 range + z: 0.0, + gain: 1.0, + pitch: 1.0, + looping: true, + paused: false + } + + args.state.selected = id +end + +# these are values you can change on the ~args.audio~ data structure +def input_panel args + return unless args.state.panel + return if args.state.dragging + + audio_entry = args.audio[args.state.selected] + results = args.state.panel + + if args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.pitch_slider_rect.rect) + audio_entry.pitch = 2.0 * ((args.inputs.mouse.x - results.pitch_slider_rect.x).to_f / (results.pitch_slider_rect.w - 1.0)) + elsif args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.playtime_slider_rect.rect) + audio_entry.playtime = audio_entry.length_ * ((args.inputs.mouse.x - results.playtime_slider_rect.x).to_f / (results.playtime_slider_rect.w - 1.0)) + elsif args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.gain_slider_rect.rect) + audio_entry.gain = (args.inputs.mouse.x - results.gain_slider_rect.x).to_f / (results.gain_slider_rect.w - 1.0) + elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.looping_checkbox_rect.rect) + audio_entry.looping = !audio_entry.looping + elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.paused_checkbox_rect.rect) + audio_entry.paused = !audio_entry.paused + elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.delete_button_rect.rect) + args.audio.delete args.state.selected + end +end + +def render_sources args + args.outputs.primitives << args.audio.keys.map do |k| + s = args.audio[k] + + isselected = (k == args.state.selected) + + color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ] + [ + [s.screenx, s.screeny, args.state.boxsize, args.state.boxsize, *color].solid, + + { + x: s.screenx + args.state.boxsize.half, + y: s.screeny, + text: s.name, + r: 255, + g: 255, + b: 255, + alignment_enum: 1 + }.label! + ] + end +end + +def playtime_str t + return "" unless t + minutes = (t / 60.0).floor + seconds = t - (minutes * 60.0).to_f + return minutes.to_s + ':' + seconds.floor.to_s + ((seconds - seconds.floor).to_s + "000")[1..3] +end + +def label_with_drop_shadow x, y, text + [ + { x: x + 1, y: y + 1, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r: 0, g: 0, b: 0 }.label!, + { x: x + 2, y: y + 0, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r: 0, g: 0, b: 0 }.label!, + { x: x + 0, y: y + 1, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r: 200, g: 200, b: 200 }.label! + ] +end + +def check_box opts = {} + checkbox_template = opts.args.layout.rect(w: 0.5, h: 0.5, col: 2) + final_rect = checkbox_template.center_inside_rect_y(opts.args.layout.rect(row: opts.row, col: opts.col)) + color = { r: 0, g: 0, b: 0 } + color = { r: 255, g: 255, b: 255 } if opts.checked + + { + rect: final_rect, + primitives: [ + (final_rect.to_solid color) + ] + } +end + +def progress_bar opts = {} + outer_rect = opts.args.layout.rect(row: opts.row, col: opts.col, w: 5, h: 1) + color = opts.percentage * 255 + baseline_progress_bar = opts.args + .layout + .rect(w: 5, h: 0.5) + + final_rect = baseline_progress_bar.center_inside_rect(outer_rect) + center = final_rect.rect_center_point + + { + rect: final_rect, + primitives: [ + final_rect.merge(r: color, g: color, b: color, a: 128).solid!, + label_with_drop_shadow(center.x, center.y, opts.text) + ] + } +end + +def panel_primitives args, audio_entry + results = { primitives: [] } + + return results unless audio_entry + + # this uses DRGTK's layout apis to layout the controls + # imagine the screen is split into equal cells (24 cells across, 12 cells up and down) + # args.layout.rect returns a hash which we merge values with to create primitives + # using args.layout.rect removes the need for pixel pushing + + # args.outputs.debug << args.layout.debug_primitives(r: 255, g: 255, b: 255) + + white_color = { r: 255, g: 255, b: 255 } + label_style = white_color.merge(vertical_alignment_enum: 1) + + # panel background + results.primitives << args.layout.rect(row: 0, col: 0, w: 7, h: 6, include_col_gutter: true, include_row_gutter: true) + .border!(r: 255, g: 255, b: 255) + + # title + results.primitives << args.layout.point(row: 0, col: 3.5, row_anchor: 0.5) + .merge(label_style) + .merge(text: "Source #{args.state.selected} (#{args.audio[args.state.selected].name})", + size_enum: 3, + alignment_enum: 1) + + # seperator line + results.primitives << args.layout.rect(row: 1, col: 0, w: 7, h: 0) + .line!(white_color) + + # screen location + results.primitives << args.layout.point(row: 1.0, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "screen:") + + results.primitives << args.layout.point(row: 1.0, col: 2, row_anchor: 0.5) + .merge(label_style) + .merge(text: "(#{audio_entry.screenx.to_i}, #{audio_entry.screeny.to_i})") + + # position + results.primitives << args.layout.point(row: 1.5, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "position:") + + results.primitives << args.layout.point(row: 1.5, col: 2, row_anchor: 0.5) + .merge(label_style) + .merge(text: "(#{audio_entry[:x].round(5).to_s[0..6]}, #{audio_entry[:y].round(5).to_s[0..6]})") + + results.primitives << args.layout.point(row: 2.0, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "pitch:") + + results.pitch_slider_rect = progress_bar(row: 2.0, col: 2, + percentage: audio_entry.pitch / 2.0, + text: "#{audio_entry.pitch.to_sf}", + args: args) + + results.primitives << results.pitch_slider_rect.primitives + + results.primitives << args.layout.point(row: 2.5, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "playtime:") + + results.playtime_slider_rect = progress_bar(args: args, + row: 2.5, + col: 2, + percentage: (audio_entry.playtime || 1) / (audio_entry.length_ || 1), + text: "#{playtime_str(audio_entry.playtime)} / #{playtime_str(audio_entry.length_)}") + + results.primitives << results.playtime_slider_rect.primitives + + results.primitives << args.layout.point(row: 3.0, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "gain:") + + results.gain_slider_rect = progress_bar(args: args, + row: 3.0, + col: 2, + percentage: audio_entry.gain, + text: "#{audio_entry.gain.to_sf}") + + results.primitives << results.gain_slider_rect.primitives + + + results.primitives << args.layout.point(row: 3.5, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "looping:") + + checkbox_template = args.layout.rect(w: 0.5, h: 0.5, col: 2) + + results.looping_checkbox_rect = check_box(args: args, row: 3.5, col: 2, checked: audio_entry.looping) + results.primitives << results.looping_checkbox_rect.primitives + + results.primitives << args.layout.point(row: 4.0, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "paused:") + + checkbox_template = args.layout.rect(w: 0.5, h: 0.5, col: 2) + + results.paused_checkbox_rect = check_box(args: args, row: 4.0, col: 2, checked: !audio_entry.paused) + results.primitives << results.paused_checkbox_rect.primitives + + results.delete_button_rect = { rect: args.layout.rect(row: 5, col: 0, w: 7, h: 1) } + + results.primitives << results.delete_button_rect.to_solid(r: 180) + + results.primitives << args.layout.point(row: 5, col: 3.5, row_anchor: 0.5) + .merge(label_style) + .merge(text: "DELETE", alignment_enum: 1) + + return results +end + +def render_panel args + args.state.panel = nil + audio_entry = args.audio[args.state.selected] + return unless audio_entry + + mouse_down = (args.state.mouse_held >= 0) + args.state.panel = panel_primitives args, audio_entry + args.outputs.primitives << args.state.panel.primitives +end + +def new_sound_id! args + args.state.sound_id ||= 0 + args.state.sound_id += 1 + args.state.sound_id +end + +def render_launcher args + args.outputs.primitives << args.state.spawn_sound_buttons.map(&:primitives) +end + +def render_ui args + render_launcher args + render_panel args +end + +def tick args + defaults args + render args + input args +end + +def input args + if !args.audio[args.state.selected] + args.state.selected = nil + args.state.dragging = nil + end + + # spawn button and node interaction + if args.inputs.mouse.click + spawn_sound_button = args.state.spawn_sound_buttons.find { |b| args.inputs.mouse.inside_rect? b.rect } + + audio_click_key, audio_click_value = args.audio.find do |k, v| + args.inputs.mouse.inside_rect? [v.screenx, v.screeny, args.state.boxsize, args.state.boxsize] + end + + if spawn_sound_button + args.state.selected = nil + spawn_new_sound args, spawn_sound_button.name, spawn_sound_button.path + elsif audio_click_key + args.state.selected = audio_click_key + end + end + + if args.state.mouse_state == :held && args.state.selected + v = args.audio[args.state.selected] + if args.inputs.mouse.inside_rect? [v.screenx, v.screeny, args.state.boxsize, args.state.boxsize] + args.state.dragging = args.state.selected + end + + if args.state.dragging + s = args.audio[args.state.selected] + # you can hang anything on the audio hashes you want, so we store the + # actual screen position so it doesn't scale weirdly vs your mouse. + s.screenx = args.inputs.mouse.x - (args.state.boxsize / 2) + s.screeny = args.inputs.mouse.y - (args.state.boxsize / 2) + + s.screeny = 50 if s.screeny < 50 + s.screeny = (719 - args.state.boxsize) if s.screeny > (719 - args.state.boxsize) + s.screenx = 0 if s.screenx < 0 + s.screenx = (1279 - args.state.boxsize) if s.screenx > (1279 - args.state.boxsize) + + s.x = ((s.screenx / 1279.0) * 2.0) - 1.0 # scale to -1.0 - 1.0 range + s.y = ((s.screeny / 719.0) * 2.0) - 1.0 # scale to -1.0 - 1.0 range + end + elsif args.state.mouse_state == :released + args.state.dragging = nil + end + + input_panel args +end + +def defaults args + args.state.mouse_state ||= :released + args.state.dragging_source ||= false + args.state.selected ||= 0 + args.state.next_sound_index ||= 0 + args.state.boxsize ||= 30 + args.state.sound_files ||= [ + { name: :tada, path: "sounds/tada.wav" }, + { name: :splash, path: "sounds/splash.wav" }, + { name: :drum, path: "sounds/drum.wav" }, + { name: :spring, path: "sounds/spring.wav" }, + { name: :music, path: "sounds/music.ogg" } + ] + + # generate buttons based off the sound collection above + args.state.spawn_sound_buttons ||= begin + # create a group of buttons + # column centered (using col_offset to calculate the column offset) + # where each item is 2 columns apart + rects = args.layout.rect_group row: 11, + col_offset: { + count: args.state.sound_files.length, + w: 2 + }, + dcol: 2, + w: 2, + h: 1, + group: args.state.sound_files + + # now that you have the rects + # construct the metadata for the buttons + rects.map do |rect| + { + rect: rect, + name: rect.name, + path: rect.path, + primitives: [ + rect.to_border(r: 255, g: 255, b: 255), + rect.to_label(x: rect.center_x, + y: rect.center_y, + text: "#{rect.name}", + alignment_enum: 1, + vertical_alignment_enum: 1, + r: 255, g: 255, b: 255) + ] + } + end + end + + if args.inputs.mouse.up + args.state.mouse_state = :released + args.state.dragging_source = false + elsif args.inputs.mouse.down + args.state.mouse_state = :held + end + + args.outputs.background_color = [ 0, 0, 0, 255 ] +end + +def render args + render_ui args + render_sources args +end + +</code></pre> +<h3 id='----advanced-audio---audio-mixer---server_ip_address-txt'>Advanced Audio - Audio Mixer - server_ip_address.txt</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_audio/01_audio_mixer/app/server_ip_address.txt +192.168.1.65 +</code></pre> +<h3 id='----advanced-audio---sound-synthesis---main-rb'>Advanced Audio - Sound Synthesis - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_audio/02_sound_synthesis/app/main.rb +begin # region: top level tick methods + def tick args + defaults args + render args + input args + process_audio_queue args + end + + def defaults args + args.state.sine_waves ||= {} + args.state.square_waves ||= {} + args.state.saw_tooth_waves ||= {} + args.state.triangle_waves ||= {} + args.state.audio_queue ||= [] + args.state.buttons ||= [ + (frequency_buttons args), + (sine_wave_note_buttons args), + (bell_buttons args), + (square_wave_note_buttons args), + (saw_tooth_wave_note_buttons args), + (triangle_wave_note_buttons args), + ].flatten + end + + def render args + args.outputs.borders << args.state.buttons.map { |b| b[:border] } + args.outputs.labels << args.state.buttons.map { |b| b[:label] } + args.outputs.labels << args.layout + .rect(row: 0, col: 11.5) + .yield_self { |r| r.merge y: r.y + r.h } + .merge(text: "This is a Pro only feature. Click here to watch the YouTube video if you are on the Standard License.", + alignment_enum: 1) + end + + + def input args + args.state.buttons.each do |b| + if args.inputs.mouse.click && (args.inputs.mouse.click.inside_rect? b[:rect]) + parameter_string = (b.slice :frequency, :note, :octave).map { |k, v| "#{k}: #{v}" }.join ", " + args.gtk.notify! "#{b[:method_to_call]} #{parameter_string}" + send b[:method_to_call], args, b + end + end + + if args.inputs.mouse.click && (args.inputs.mouse.click.inside_rect? (args.layout.rect(row: 0).yield_self { |r| r.merge y: r.y + r.h.half, h: r.h.half })) + args.gtk.openurl 'https://www.youtube.com/watch?v=zEzovM5jT-k&ab_channel=AmirRajan' + end + end + + def process_audio_queue args + to_queue = args.state.audio_queue.find_all { |v| v[:queue_at] <= args.tick_count } + args.state.audio_queue -= to_queue + to_queue.each { |a| args.audio[a[:id]] = a } + + args.audio.find_all { |k, v| v[:decay_rate] } + .each { |k, v| v[:gain] -= v[:decay_rate] } + + sounds_to_stop = args.audio + .find_all { |k, v| v[:stop_at] && args.state.tick_count >= v[:stop_at] } + .map { |k, v| k } + + sounds_to_stop.each { |k| args.audio.delete k } + end +end + +begin # region: button definitions, ui layout, callback functions + def button args, opts + button_def = opts.merge rect: (args.layout.rect (opts.merge w: 2, h: 1)) + + button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0 + + label_offset_x = 5 + label_offset_y = 30 + + button_def[:label] = button_def[:rect].merge text: opts[:text], + size_enum: -2.5, + x: button_def[:rect].x + label_offset_x, + y: button_def[:rect].y + label_offset_y + + button_def + end + + def play_sine_wave args, sender + queue_sine_wave args, + frequency: sender[:frequency], + duration: 1.seconds, + fade_out: true + end + + def play_note args, sender + method_to_call = :queue_sine_wave + method_to_call = :queue_square_wave if sender[:type] == :square + method_to_call = :queue_saw_tooth_wave if sender[:type] == :saw_tooth + method_to_call = :queue_triangle_wave if sender[:type] == :triangle + method_to_call = :queue_bell if sender[:type] == :bell + + send method_to_call, args, + frequency: (frequency_for note: sender[:note], octave: sender[:octave]), + duration: 1.seconds, + fade_out: true + end + + def frequency_buttons args + [ + (button args, + row: 4.0, col: 0, text: "300hz", + frequency: 300, + method_to_call: :play_sine_wave), + (button args, + row: 5.0, col: 0, text: "400hz", + frequency: 400, + method_to_call: :play_sine_wave), + (button args, + row: 6.0, col: 0, text: "500hz", + frequency: 500, + method_to_call: :play_sine_wave), + ] + end + + def sine_wave_note_buttons args + [ + (button args, + row: 1.5, col: 2, text: "Sine C4", + note: :c, octave: 4, type: :sine, method_to_call: :play_note), + (button args, + row: 2.5, col: 2, text: "Sine D4", + note: :d, octave: 4, type: :sine, method_to_call: :play_note), + (button args, + row: 3.5, col: 2, text: "Sine E4", + note: :e, octave: 4, type: :sine, method_to_call: :play_note), + (button args, + row: 4.5, col: 2, text: "Sine F4", + note: :f, octave: 4, type: :sine, method_to_call: :play_note), + (button args, + row: 5.5, col: 2, text: "Sine G4", + note: :g, octave: 4, type: :sine, method_to_call: :play_note), + (button args, + row: 6.5, col: 2, text: "Sine A5", + note: :a, octave: 5, type: :sine, method_to_call: :play_note), + (button args, + row: 7.5, col: 2, text: "Sine B5", + note: :b, octave: 5, type: :sine, method_to_call: :play_note), + (button args, + row: 8.5, col: 2, text: "Sine C5", + note: :c, octave: 5, type: :sine, method_to_call: :play_note), + ] + end + + def square_wave_note_buttons args + [ + (button args, + row: 1.5, col: 6, text: "Square C4", + note: :c, octave: 4, type: :square, method_to_call: :play_note), + (button args, + row: 2.5, col: 6, text: "Square D4", + note: :d, octave: 4, type: :square, method_to_call: :play_note), + (button args, + row: 3.5, col: 6, text: "Square E4", + note: :e, octave: 4, type: :square, method_to_call: :play_note), + (button args, + row: 4.5, col: 6, text: "Square F4", + note: :f, octave: 4, type: :square, method_to_call: :play_note), + (button args, + row: 5.5, col: 6, text: "Square G4", + note: :g, octave: 4, type: :square, method_to_call: :play_note), + (button args, + row: 6.5, col: 6, text: "Square A5", + note: :a, octave: 5, type: :square, method_to_call: :play_note), + (button args, + row: 7.5, col: 6, text: "Square B5", + note: :b, octave: 5, type: :square, method_to_call: :play_note), + (button args, + row: 8.5, col: 6, text: "Square C5", + note: :c, octave: 5, type: :square, method_to_call: :play_note), + ] + end + def saw_tooth_wave_note_buttons args + [ + (button args, + row: 1.5, col: 8, text: "Saw C4", + note: :c, octave: 4, type: :saw_tooth, method_to_call: :play_note), + (button args, + row: 2.5, col: 8, text: "Saw D4", + note: :d, octave: 4, type: :saw_tooth, method_to_call: :play_note), + (button args, + row: 3.5, col: 8, text: "Saw E4", + note: :e, octave: 4, type: :saw_tooth, method_to_call: :play_note), + (button args, + row: 4.5, col: 8, text: "Saw F4", + note: :f, octave: 4, type: :saw_tooth, method_to_call: :play_note), + (button args, + row: 5.5, col: 8, text: "Saw G4", + note: :g, octave: 4, type: :saw_tooth, method_to_call: :play_note), + (button args, + row: 6.5, col: 8, text: "Saw A5", + note: :a, octave: 5, type: :saw_tooth, method_to_call: :play_note), + (button args, + row: 7.5, col: 8, text: "Saw B5", + note: :b, octave: 5, type: :saw_tooth, method_to_call: :play_note), + (button args, + row: 8.5, col: 8, text: "Saw C5", + note: :c, octave: 5, type: :saw_tooth, method_to_call: :play_note), + ] + end + + def triangle_wave_note_buttons args + [ + (button args, + row: 1.5, col: 10, text: "Triangle C4", + note: :c, octave: 4, type: :triangle, method_to_call: :play_note), + (button args, + row: 2.5, col: 10, text: "Triangle D4", + note: :d, octave: 4, type: :triangle, method_to_call: :play_note), + (button args, + row: 3.5, col: 10, text: "Triangle E4", + note: :e, octave: 4, type: :triangle, method_to_call: :play_note), + (button args, + row: 4.5, col: 10, text: "Triangle F4", + note: :f, octave: 4, type: :triangle, method_to_call: :play_note), + (button args, + row: 5.5, col: 10, text: "Triangle G4", + note: :g, octave: 4, type: :triangle, method_to_call: :play_note), + (button args, + row: 6.5, col: 10, text: "Triangle A5", + note: :a, octave: 5, type: :triangle, method_to_call: :play_note), + (button args, + row: 7.5, col: 10, text: "Triangle B5", + note: :b, octave: 5, type: :triangle, method_to_call: :play_note), + (button args, + row: 8.5, col: 10, text: "Triangle C5", + note: :c, octave: 5, type: :triangle, method_to_call: :play_note), + ] + end + + def bell_buttons args + [ + (button args, + row: 1.5, col: 4, text: "Bell C4", + note: :c, octave: 4, type: :bell, method_to_call: :play_note), + (button args, + row: 2.5, col: 4, text: "Bell D4", + note: :d, octave: 4, type: :bell, method_to_call: :play_note), + (button args, + row: 3.5, col: 4, text: "Bell E4", + note: :e, octave: 4, type: :bell, method_to_call: :play_note), + (button args, + row: 4.5, col: 4, text: "Bell F4", + note: :f, octave: 4, type: :bell, method_to_call: :play_note), + (button args, + row: 5.5, col: 4, text: "Bell G4", + note: :g, octave: 4, type: :bell, method_to_call: :play_note), + (button args, + row: 6.5, col: 4, text: "Bell A5", + note: :a, octave: 5, type: :bell, method_to_call: :play_note), + (button args, + row: 7.5, col: 4, text: "Bell B5", + note: :b, octave: 5, type: :bell, method_to_call: :play_note), + (button args, + row: 8.5, col: 4, text: "Bell C5", + note: :c, octave: 5, type: :bell, method_to_call: :play_note), + ] + end +end + +begin # region: wave generation + begin # sine wave + def defaults_sine_wave_for + { frequency: 440, sample_rate: 48000 } + end + + def sine_wave_for opts = {} + opts = defaults_sine_wave_for.merge opts + frequency = opts[:frequency] + sample_rate = opts[:sample_rate] + period_size = (sample_rate.fdiv frequency).ceil + period_size.map_with_index do |i| + Math::sin((2.0 * Math::PI) / (sample_rate.to_f / frequency.to_f) * i) + end.to_a + end + + def defaults_queue_sine_wave + { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 } + end + + def queue_sine_wave args, opts = {} + opts = defaults_queue_sine_wave.merge opts + frequency = opts[:frequency] + sample_rate = 48000 + + sine_wave = sine_wave_for frequency: frequency, sample_rate: sample_rate + args.state.sine_waves[frequency] ||= sine_wave_for frequency: frequency, sample_rate: sample_rate + + proc = lambda do + generate_audio_data args.state.sine_waves[frequency], sample_rate + end + + audio_state = new_audio_state args, opts + audio_state[:input] = [1, sample_rate, proc] + queue_audio args, audio_state: audio_state, wave: sine_wave + end + end + + begin # region: square wave + def defaults_square_wave_for + { frequency: 440, sample_rate: 48000 } + end + + def square_wave_for opts = {} + opts = defaults_square_wave_for.merge opts + sine_wave = sine_wave_for opts + sine_wave.map do |v| + if v >= 0 + 1.0 + else + -1.0 + end + end.to_a + end + + def defaults_queue_square_wave + { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 } + end + + def queue_square_wave args, opts = {} + opts = defaults_queue_square_wave.merge opts + frequency = opts[:frequency] + sample_rate = 48000 + + square_wave = square_wave_for frequency: frequency, sample_rate: sample_rate + args.state.square_waves[frequency] ||= square_wave_for frequency: frequency, sample_rate: sample_rate + + proc = lambda do + generate_audio_data args.state.square_waves[frequency], sample_rate + end + + audio_state = new_audio_state args, opts + audio_state[:input] = [1, sample_rate, proc] + queue_audio args, audio_state: audio_state, wave: square_wave + end + end + + begin # region: saw tooth wave + def defaults_saw_tooth_wave_for + { frequency: 440, sample_rate: 48000 } + end + + def saw_tooth_wave_for opts = {} + opts = defaults_saw_tooth_wave_for.merge opts + sine_wave = sine_wave_for opts + period_size = sine_wave.length + sine_wave.map_with_index do |v, i| + (((i % period_size).fdiv period_size) * 2) - 1 + end + end + + def defaults_queue_saw_tooth_wave + { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 } + end + + def queue_saw_tooth_wave args, opts = {} + opts = defaults_queue_saw_tooth_wave.merge opts + frequency = opts[:frequency] + sample_rate = 48000 + + saw_tooth_wave = saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate + args.state.saw_tooth_waves[frequency] ||= saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate + + proc = lambda do + generate_audio_data args.state.saw_tooth_waves[frequency], sample_rate + end + + audio_state = new_audio_state args, opts + audio_state[:input] = [1, sample_rate, proc] + queue_audio args, audio_state: audio_state, wave: saw_tooth_wave + end + end + + begin # region: triangle wave + def defaults_triangle_wave_for + { frequency: 440, sample_rate: 48000 } + end + + def triangle_wave_for opts = {} + opts = defaults_saw_tooth_wave_for.merge opts + sine_wave = sine_wave_for opts + period_size = sine_wave.length + sine_wave.map_with_index do |v, i| + ratio = (i.fdiv period_size) + if ratio <= 0.5 + (ratio * 4) - 1 + else + ratio -= 0.5 + 1 - (ratio * 4) + end + end + end + + def defaults_queue_triangle_wave + { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 } + end + + def queue_triangle_wave args, opts = {} + opts = defaults_queue_triangle_wave.merge opts + frequency = opts[:frequency] + sample_rate = 48000 + + triangle_wave = triangle_wave_for frequency: frequency, sample_rate: sample_rate + args.state.triangle_waves[frequency] ||= triangle_wave_for frequency: frequency, sample_rate: sample_rate + + proc = lambda do + generate_audio_data args.state.triangle_waves[frequency], sample_rate + end + + audio_state = new_audio_state args, opts + audio_state[:input] = [1, sample_rate, proc] + queue_audio args, audio_state: audio_state, wave: triangle_wave + end + end + + begin # region: bell + def defaults_queue_bell + { frequency: 440, duration: 1.seconds, queue_in: 0 } + end + + def queue_bell args, opts = {} + (bell_to_sine_waves (defaults_queue_bell.merge opts)).each { |b| queue_sine_wave args, b } + end + + def bell_harmonics + [ + { frequency_ratio: 0.5, duration_ratio: 1.00 }, + { frequency_ratio: 1.0, duration_ratio: 0.80 }, + { frequency_ratio: 2.0, duration_ratio: 0.60 }, + { frequency_ratio: 3.0, duration_ratio: 0.40 }, + { frequency_ratio: 4.2, duration_ratio: 0.25 }, + { frequency_ratio: 5.4, duration_ratio: 0.20 }, + { frequency_ratio: 6.8, duration_ratio: 0.15 } + ] + end + + def defaults_bell_to_sine_waves + { frequency: 440, duration: 1.seconds, queue_in: 0 } + end + + def bell_to_sine_waves opts = {} + opts = defaults_bell_to_sine_waves.merge opts + bell_harmonics.map do |b| + { + frequency: opts[:frequency] * b[:frequency_ratio], + duration: opts[:duration] * b[:duration_ratio], + queue_in: opts[:queue_in], + gain: (1.fdiv bell_harmonics.length), + fade_out: true + } + end + end + end + + begin # audio entity construction + def generate_audio_data sine_wave, sample_rate + sample_size = (sample_rate.fdiv (1000.fdiv 60)).ceil + copy_count = (sample_size.fdiv sine_wave.length).ceil + sine_wave * copy_count + end + + def defaults_new_audio_state + { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 } + end + + def new_audio_state args, opts = {} + opts = defaults_new_audio_state.merge opts + decay_rate = 0 + decay_rate = 1.fdiv(opts[:duration]) * opts[:gain] if opts[:fade_out] + frequency = opts[:frequency] + sample_rate = 48000 + + { + id: (new_id! args), + frequency: frequency, + sample_rate: 48000, + stop_at: args.tick_count + opts[:queue_in] + opts[:duration], + gain: opts[:gain].to_f, + queue_at: args.state.tick_count + opts[:queue_in], + decay_rate: decay_rate, + pitch: 1.0, + looping: true, + paused: false + } + end + + def queue_audio args, opts = {} + graph_wave args, opts[:wave], opts[:audio_state][:frequency] + args.state.audio_queue << opts[:audio_state] + end + + def new_id! args + args.state.audio_id ||= 0 + args.state.audio_id += 1 + end + + def graph_wave args, wave, frequency + if args.state.tick_count != args.state.graphed_at + args.outputs.static_lines.clear + args.outputs.static_sprites.clear + end + + wave = wave + + r, g, b = frequency.to_i % 85, + frequency.to_i % 170, + frequency.to_i % 255 + + starting_rect = args.layout.rect(row: 5, col: 13) + x_scale = 10 + y_scale = 100 + max_points = 25 + + points = wave + if wave.length > max_points + resolution = wave.length.idiv max_points + points = wave.find_all.with_index { |y, i| (i % resolution == 0) } + end + + args.outputs.static_lines << points.map_with_index do |y, x| + next_y = points[x + 1] + + if next_y + { + x: starting_rect.x + (x * x_scale), + y: starting_rect.y + starting_rect.h.half + y_scale * y, + x2: starting_rect.x + ((x + 1) * x_scale), + y2: starting_rect.y + starting_rect.h.half + y_scale * next_y, + r: r, + g: g, + b: b + } + end + end + + args.outputs.static_sprites << points.map_with_index do |y, x| + { + x: (starting_rect.x + (x * x_scale)) - 2, + y: (starting_rect.y + starting_rect.h.half + y_scale * y) - 2, + w: 4, + h: 4, + path: 'sprites/square-white.png', + r: r, + g: g, + b: b + } + end + + args.state.graphed_at = args.state.tick_count + end + end + + begin # region: musical note mapping + def defaults_frequency_for + { note: :a, octave: 5, sharp: false, flat: false } + end + + def frequency_for opts = {} + opts = defaults_frequency_for.merge opts + octave_offset_multiplier = opts[:octave] - 5 + note = note_frequencies_octave_5[opts[:note]] + if octave_offset_multiplier < 0 + note = note * 1 / (octave_offset_multiplier.abs + 1) + elsif octave_offset_multiplier > 0 + note = note * (octave_offset_multiplier.abs + 1) / 1 + end + note + end + + def note_frequencies_octave_5 + { + a: 440.0, + a_sharp: 466.16, b_flat: 466.16, + b: 493.88, + c: 523.25, + c_sharp: 554.37, d_flat: 587.33, + d: 587.33, + d_sharp: 622.25, e_flat: 659.25, + e: 659.25, + f: 698.25, + f_sharp: 739.99, g_flat: 739.99, + g: 783.99, + g_sharp: 830.61, a_flat: 830.61 + } + end + end +end + +$gtk.reset + +</code></pre> +<h3 id='----advanced-rendering---labels-with-wrapped-text---main-rb'>Advanced Rendering - Labels With Wrapped Text - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_rendering/00_labels_with_wrapped_text/app/main.rb +def tick args + # defaults + args.state.scroll_location ||= 0 + args.state.textbox.messages ||= [] + args.state.textbox.scroll ||= 0 + + # render + args.outputs.background_color = [0, 0, 0, 255] + render_messages args + render_instructions args + + # inputs + if args.inputs.keyboard.key_down.one + queue_message args, "Hello there neighbour! my name is mark, how is your day today?" + end + + if args.inputs.keyboard.key_down.two + queue_message args, "I'm doing great sir, actually I'm having a picnic today" + end + + if args.inputs.keyboard.key_down.three + queue_message args, "Well that sounds wonderful!" + end + + if args.inputs.keyboard.key_down.home + args.state.scroll_location = 1 + end + + if args.inputs.keyboard.key_down.delete + clear_message_queue args + end +end + +def queue_message args, msg + args.state.textbox.messages.concat msg.wrapped_lines 50 +end + +def clear_message_queue args + args.state.textbox.messages = nil + args.state.textbox.scroll = 0 +end + +def render_messages args + args.outputs[:textbox].w = 400 + args.outputs[:textbox].h = 720 + + args.outputs.primitives << args.state.textbox.messages.each_with_index.map do |s, idx| + { + x: 0, + y: 20 * (args.state.textbox.messages.size - idx) + args.state.textbox.scroll * 20, + text: s, + size_enum: -3, + alignment_enum: 0, + r: 255, g:255, b: 255, a: 255 + } + end + + args.outputs[:textbox].labels << args.state.textbox.messages.each_with_index.map do |s, idx| + { + x: 0, + y: 20 * (args.state.textbox.messages.size - idx) + args.state.textbox.scroll * 20, + text: s, + size_enum: -3, + alignment_enum: 0, + r: 255, g:255, b: 255, a: 255 + } + end + + args.outputs[:textbox].borders << [0, 0, args.outputs[:textbox].w, 720] + + args.state.textbox.scroll += args.inputs.mouse.wheel.y unless args.inputs.mouse.wheel.nil? + + if args.state.scroll_location > 0 + args.state.textbox.scroll = 0 + args.state.scroll_location = 0 + end + + args.outputs.sprites << [900, 0, args.outputs[:textbox].w, 720, :textbox] +end + +def render_instructions args + args.outputs.labels << [30, + 30.from_top, + "press 1, 2, 3 to display messages, MOUSE WHEEL to scroll, HOME to go to top, BACKSPACE to delete.", + 0, 255, 255] + + args.outputs.primitives << [0, 55.from_top, 1280, 30, :pixel, 0, 255, 0, 0, 0].sprite +end + +</code></pre> +<h3 id='----advanced-rendering---rotating-label---main-rb'>Advanced Rendering - Rotating Label - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_rendering/00_rotating_label/app/main.rb +def tick args + # set the render target width and height to match the label + args.outputs[:scene].w = 220 + args.outputs[:scene].h = 30 + + + # make the background transparent + args.outputs[:scene].background_color = [255, 255, 255, 0] + + # set the blendmode of the label to 0 (no blending) + # center it inside of the scene + # set the vertical_alignment_enum to 1 (center) + args.outputs[:scene].labels << { x: 0, + y: 15, + text: "label in render target", + blendmode_enum: 0, + vertical_alignment_enum: 1 } + + # add a border to the render target + args.outputs[:scene].borders << { x: 0, + y: 0, + w: args.outputs[:scene].w, + h: args.outputs[:scene].h } + + # add the rendertarget to the main output as a sprite + args.outputs.sprites << { x: 640 - args.outputs[:scene].w.half, + y: 360 - args.outputs[:scene].h.half, + w: args.outputs[:scene].w, + h: args.outputs[:scene].h, + angle: args.state.tick_count, + path: :scene } +end + +</code></pre> +<h3 id='----advanced-rendering---simple-render-targets---main-rb'>Advanced Rendering - Simple Render Targets - main.rb</h3> <pre><code class="language-ruby"># ./samples/07_advanced_rendering/01_simple_render_targets/app/main.rb def tick args # args.outputs.render_targets are really really powerful. @@ -11586,7 +12126,7 @@ end $gtk.reset </code></pre> -<h3 id='----advanced-rendering---render-targets-with-tile-manipulation---main.rb'>Advanced Rendering - Render Targets With Tile Manipulation - main.rb</h3> +<h3 id='----advanced-rendering---render-targets-with-tile-manipulation---main-rb'>Advanced Rendering - Render Targets With Tile Manipulation - main.rb</h3> <pre><code class="language-ruby"># ./samples/07_advanced_rendering/02_render_targets_with_tile_manipulation/app/main.rb # This sample is meant to show you how to do that dripping transition thing # at the start of the original Doom. Most of this file is here to animate @@ -11685,7 +12225,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----advanced-rendering---render-target-viewports---main.rb'>Advanced Rendering - Render Target Viewports - main.rb</h3> +<h3 id='----advanced-rendering---render-target-viewports---main-rb'>Advanced Rendering - Render Target Viewports - main.rb</h3> <pre><code class="language-ruby"># ./samples/07_advanced_rendering/03_render_target_viewports/app/main.rb =begin @@ -12157,7 +12697,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----advanced-rendering---render-primitive-hierarchies---main.rb'>Advanced Rendering - Render Primitive Hierarchies - main.rb</h3> +<h3 id='----advanced-rendering---render-primitive-hierarchies---main-rb'>Advanced Rendering - Render Primitive Hierarchies - main.rb</h3> <pre><code class="language-ruby"># ./samples/07_advanced_rendering/04_render_primitive_hierarchies/app/main.rb =begin @@ -12333,7 +12873,7 @@ def collection_of_sprites args end </code></pre> -<h3 id='----advanced-rendering---render-primitives-as-hash---main.rb'>Advanced Rendering - Render Primitives As Hash - main.rb</h3> +<h3 id='----advanced-rendering---render-primitives-as-hash---main-rb'>Advanced Rendering - Render Primitives As Hash - main.rb</h3> <pre><code class="language-ruby"># ./samples/07_advanced_rendering/05_render_primitives_as_hash/app/main.rb =begin @@ -12473,7 +13013,7 @@ def tick args flip_horizontally: false, angle_anchor_x: 0.5, # rotation center set to middle angle_anchor_y: 0.5 - }.sprite + }.sprite! # Outputs label as primitive using a hash args.outputs.primitives << { @@ -12487,7 +13027,7 @@ def tick args b: 50, a: 255, # transparency font: "fonts/manaspc.ttf" # font style - }.label + }.label! # Outputs solid as primitive using a hash args.outputs.primitives << { @@ -12499,7 +13039,7 @@ def tick args g: 50, b: 50, a: 255 # transparency - }.solid + }.solid! # Outputs border as primitive using a hash # Same parameters as solid @@ -12512,7 +13052,7 @@ def tick args g: 50, b: 50, a: 255 # transparency - }.border + }.border! # Outputs line as primitive using a hash args.outputs.primitives << { @@ -12524,11 +13064,11 @@ def tick args g: 50, b: 50, a: 255 # transparency - }.line + }.line! end </code></pre> -<h3 id='----advanced-rendering---pixel-arrays---main.rb'>Advanced Rendering - Pixel Arrays - main.rb</h3> +<h3 id='----advanced-rendering---pixel-arrays---main-rb'>Advanced Rendering - Pixel Arrays - main.rb</h3> <pre><code class="language-ruby"># ./samples/07_advanced_rendering/06_pixel_arrays/app/main.rb $gtk.reset @@ -12573,8 +13113,105 @@ end </code></pre> -<h3 id='----advanced-rendering---splitscreen-camera---main.rb'>Advanced Rendering - Splitscreen Camera - main.rb</h3> -<pre><code class="language-ruby"># ./samples/07_advanced_rendering/07_splitscreen_camera/app/main.rb +<h3 id='----advanced-rendering---simple-camera---main-rb'>Advanced Rendering - Simple Camera - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_rendering/07_simple_camera/app/main.rb +def tick args + # variables you can play around with + args.state.world.w ||= 1280 + args.state.world.h ||= 720 + + args.state.player.x ||= 0 + args.state.player.y ||= 0 + args.state.player.size ||= 32 + + args.state.enemy.x ||= 700 + args.state.enemy.y ||= 700 + args.state.enemy.size ||= 16 + + args.state.camera.x ||= 640 + args.state.camera.y ||= 300 + args.state.camera.scale ||= 1.0 + args.state.camera.show_empty_space ||= :yes + + # instructions + args.outputs.primitives << { x: 0, y: 80.from_top, w: 360, h: 80, r: 0, g: 0, b: 0, a: 128 }.solid! + args.outputs.primitives << { x: 10, y: 10.from_top, text: "arrow keys to move around", r: 255, g: 255, b: 255}.label! + args.outputs.primitives << { x: 10, y: 30.from_top, text: "+/- to change zoom of camera", r: 255, g: 255, b: 255}.label! + args.outputs.primitives << { x: 10, y: 50.from_top, text: "tab to change camera edge behavior", r: 255, g: 255, b: 255}.label! + + # render scene + args.outputs[:scene].w = args.state.world.w + args.outputs[:scene].h = args.state.world.h + + args.outputs[:scene].solids << { x: 0, y: 0, w: args.state.world.w, h: args.state.world.h, r: 20, g: 60, b: 80 } + args.outputs[:scene].solids << { x: args.state.player.x, y: args.state.player.y, + w: args.state.player.size, h: args.state.player.size, r: 80, g: 155, b: 80 } + args.outputs[:scene].solids << { x: args.state.enemy.x, y: args.state.enemy.y, + w: args.state.enemy.size, h: args.state.enemy.size, r: 155, g: 80, b: 80 } + + # render camera + scene_position = calc_scene_position args + args.outputs.sprites << { x: scene_position.x, + y: scene_position.y, + w: scene_position.w, + h: scene_position.h, + path: :scene } + + # move player + if args.inputs.directional_angle + args.state.player.x += args.inputs.directional_angle.vector_x * 5 + args.state.player.y += args.inputs.directional_angle.vector_y * 5 + args.state.player.x = args.state.player.x.clamp(0, args.state.world.w - args.state.player.size) + args.state.player.y = args.state.player.y.clamp(0, args.state.world.h - args.state.player.size) + end + + # +/- to zoom in and out + if args.inputs.keyboard.plus && args.state.tick_count.zmod?(3) + args.state.camera.scale += 0.05 + elsif args.inputs.keyboard.hyphen && args.state.tick_count.zmod?(3) + args.state.camera.scale -= 0.05 + elsif args.inputs.keyboard.key_down.tab + if args.state.camera.show_empty_space == :yes + args.state.camera.show_empty_space = :no + else + args.state.camera.show_empty_space = :yes + end + end + + args.state.camera.scale = args.state.camera.scale.greater(0.1) +end + +def calc_scene_position args + result = { x: args.state.camera.x - (args.state.player.x * args.state.camera.scale), + y: args.state.camera.y - (args.state.player.y * args.state.camera.scale), + w: args.state.world.w * args.state.camera.scale, + h: args.state.world.h * args.state.camera.scale, + scale: args.state.camera.scale } + + return result if args.state.camera.show_empty_space == :yes + + if result.w < args.grid.w + result.merge!(x: (args.grid.w - result.w).half) + elsif (args.state.player.x * result.scale) < args.grid.w.half + result.merge!(x: 10) + elsif (result.x + result.w) < args.grid.w + result.merge!(x: - result.w + (args.grid.w - 10)) + end + + if result.h < args.grid.h + result.merge!(y: (args.grid.h - result.h).half) + elsif (result.y) > 10 + result.merge!(y: 10) + elsif (result.y + result.h) < args.grid.h + result.merge!(y: - result.h + (args.grid.h - 10)) + end + + result +end + +</code></pre> +<h3 id='----advanced-rendering---splitscreen-camera---main-rb'>Advanced Rendering - Splitscreen Camera - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_rendering/08_splitscreen_camera/app/main.rb class CameraMovement attr_accessor :state, :inputs, :outputs, :grid @@ -12671,6 +13308,7 @@ class CameraMovement default_camera(0,0,1280,720) end + def new_room default_floor_tile(0,0,1024,1024,'sprites/rooms/camera_room.png') end @@ -12972,8 +13610,8 @@ def tick args end </code></pre> -<h3 id='----advanced-rendering---z-targeting-camera---main.rb'>Advanced Rendering - Z Targeting Camera - main.rb</h3> -<pre><code class="language-ruby"># ./samples/07_advanced_rendering/08_z_targeting_camera/app/main.rb +<h3 id='----advanced-rendering---z-targeting-camera---main-rb'>Advanced Rendering - Z Targeting Camera - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_rendering/09_z_targeting_camera/app/main.rb class Game attr_gtk @@ -13082,7 +13720,111 @@ end $gtk.reset </code></pre> -<h3 id='----tweening-lerping-easing-functions---easing-functions---main.rb'>Tweening Lerping Easing Functions - Easing Functions - main.rb</h3> +<h3 id='----advanced-rendering---blend-modes---main-rb'>Advanced Rendering - Blend Modes - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_rendering/10_blend_modes/app/main.rb +$gtk.reset + +def draw_blendmode args, mode + w = 160 + h = w + args.state.x += (1280-w) / (args.state.blendmodes.length + 1) + x = args.state.x + y = (720 - h) / 2 + s = 'sprites/blue-feathered.png' + args.outputs.sprites << { blendmode_enum: mode.value, x: x, y: y, w: w, h: h, path: s } + args.outputs.labels << [x + (w/2), y, mode.name.to_s, 1, 1, 255, 255, 255] +end + +def tick args + + # Different blend modes do different things, depending on what they + # blend against (in this case, the pixels of the background color). + args.state.bg_element ||= 1 + args.state.bg_color ||= 255 + args.state.bg_color_direction ||= 1 + bg_r = (args.state.bg_element == 1) ? args.state.bg_color : 0 + bg_g = (args.state.bg_element == 2) ? args.state.bg_color : 0 + bg_b = (args.state.bg_element == 3) ? args.state.bg_color : 0 + args.state.bg_color += args.state.bg_color_direction + if (args.state.bg_color_direction > 0) && (args.state.bg_color >= 255) + args.state.bg_color_direction = -1 + args.state.bg_color = 255 + elsif (args.state.bg_color_direction < 0) && (args.state.bg_color <= 0) + args.state.bg_color_direction = 1 + args.state.bg_color = 0 + args.state.bg_element += 1 + if args.state.bg_element >= 4 + args.state.bg_element = 1 + end + end + + args.outputs.background_color = [ bg_r, bg_g, bg_b, 255 ] + + args.state.blendmodes ||= [ + { name: :none, value: 0 }, + { name: :blend, value: 1 }, + { name: :add, value: 2 }, + { name: :mod, value: 3 }, + { name: :mul, value: 4 } + ] + + args.state.x = 0 # reset this, draw_blendmode will increment it. + args.state.blendmodes.each { |blendmode| draw_blendmode args, blendmode } +end + +</code></pre> +<h3 id='----advanced-rendering---render-target-noclear---main-rb'>Advanced Rendering - Render Target Noclear - main.rb</h3> +<pre><code class="language-ruby"># ./samples/07_advanced_rendering/11_render_target_noclear/app/main.rb +def tick args + args.state.x ||= 500 + args.state.y ||= 350 + args.state.xinc ||= 7 + args.state.yinc ||= 7 + args.state.bgcolor ||= 1 + args.state.bginc ||= 1 + + # clear the render target on the first tick, and then never again. Draw + # another box to it every tick, accumulating over time. + clear_target = (args.state.tick_count == 0) || (args.inputs.keyboard.key_down.space) + args.render_target(:accumulation).background_color = [ 0, 0, 0, 0 ]; + args.render_target(:accumulation).clear_before_render = clear_target + args.render_target(:accumulation).solids << [args.state.x, args.state.y, 25, 25, 255, 0, 0, 255]; + args.state.x += args.state.xinc + args.state.y += args.state.yinc + args.state.bgcolor += args.state.bginc + + # animation upkeep...change where we draw the next box and what color the + # window background will be. + if args.state.xinc > 0 && args.state.x >= 1280 + args.state.xinc = -7 + elsif args.state.xinc < 0 && args.state.x < 0 + args.state.xinc = 7 + end + + if args.state.yinc > 0 && args.state.y >= 720 + args.state.yinc = -7 + elsif args.state.yinc < 0 && args.state.y < 0 + args.state.yinc = 7 + end + + if args.state.bginc > 0 && args.state.bgcolor >= 255 + args.state.bginc = -1 + elsif args.state.bginc < 0 && args.state.bgcolor <= 0 + args.state.bginc = 1 + end + + # clear the screen to a shade of blue and draw the render target, which + # is not clearing every frame, on top of it. Note that you can NOT opt to + # skip clearing the screen, only render targets. The screen clears every + # frame; double-buffering would prevent correct updates between frames. + args.outputs.background_color = [ 0, 0, args.state.bgcolor, 255 ] + args.outputs.sprites << [ 0, 0, 1280, 720, :accumulation ] +end + +$gtk.reset + +</code></pre> +<h3 id='----tweening-lerping-easing-functions---easing-functions---main-rb'>Tweening Lerping Easing Functions - Easing Functions - main.rb</h3> <pre><code class="language-ruby"># ./samples/08_tweening_lerping_easing_functions/01_easing_functions/app/main.rb def tick args # STOP! Watch the following presentation first!!!! @@ -13218,7 +13960,7 @@ module Easing end </code></pre> -<h3 id='----tweening-lerping-easing-functions---cubic-bezier---main.rb'>Tweening Lerping Easing Functions - Cubic Bezier - main.rb</h3> +<h3 id='----tweening-lerping-easing-functions---cubic-bezier---main-rb'>Tweening Lerping Easing Functions - Cubic Bezier - main.rb</h3> <pre><code class="language-ruby"># ./samples/08_tweening_lerping_easing_functions/02_cubic_bezier/app/main.rb def tick args args.outputs.background_color = [33, 33, 33] @@ -13283,7 +14025,7 @@ def pow n, to end </code></pre> -<h3 id='----tweening-lerping-easing-functions---easing-using-spline---main.rb'>Tweening Lerping Easing Functions - Easing Using Spline - main.rb</h3> +<h3 id='----tweening-lerping-easing-functions---easing-using-spline---main-rb'>Tweening Lerping Easing Functions - Easing Using Spline - main.rb</h3> <pre><code class="language-ruby"># ./samples/08_tweening_lerping_easing_functions/03_easing_using_spline/app/main.rb def tick args args.state.duration = 10.seconds @@ -13305,7 +14047,7 @@ def tick args end </code></pre> -<h3 id='----tweening-lerping-easing-functions---parametric-enemy-movement---main.rb'>Tweening Lerping Easing Functions - Parametric Enemy Movement - main.rb</h3> +<h3 id='----tweening-lerping-easing-functions---parametric-enemy-movement---main-rb'>Tweening Lerping Easing Functions - Parametric Enemy Movement - main.rb</h3> <pre><code class="language-ruby"># ./samples/08_tweening_lerping_easing_functions/04_parametric_enemy_movement/app/main.rb def new_star args { x: 1280.randomize(:ratio), @@ -13522,7 +14264,7 @@ def tick args end </code></pre> -<h3 id='----performance---sprites-as-hash---main.rb'>Performance - Sprites As Hash - main.rb</h3> +<h3 id='----performance---sprites-as-hash---main-rb'>Performance - Sprites As Hash - main.rb</h3> <pre><code class="language-ruby"># ./samples/09_performance/01_sprites_as_hash/app/main.rb # Sprites represented as Hashes using the queue ~args.outputs.sprites~ @@ -13565,7 +14307,11 @@ def tick args # sets console command when sample app initially opens if Kernel.global_tick_count == 0 - puts "* INFO - Please specify the number of sprites to render." + puts "" + puts "" + puts "=========================================================" + puts "* INFO: Sprites, Hashes" + puts "* INFO: Please specify the number of sprites to render." args.gtk.console.set_command "reset_with count: 100" end @@ -13590,7 +14336,7 @@ def reset_with count: count end </code></pre> -<h3 id='----performance---sprites-as-entities---main.rb'>Performance - Sprites As Entities - main.rb</h3> +<h3 id='----performance---sprites-as-entities---main-rb'>Performance - Sprites As Entities - main.rb</h3> <pre><code class="language-ruby"># ./samples/09_performance/02_sprites_as_entities/app/main.rb # Sprites represented as Entities using the queue ~args.outputs.sprites~ # yields nicer access apis over Hashes, but require a bit more code upfront. @@ -13633,7 +14379,97 @@ def tick args # sets console command when sample app initially opens if Kernel.global_tick_count == 0 - puts "* INFO - Please specify the number of sprites to render." + puts "" + puts "" + puts "=========================================================" + puts "* INFO: Sprites, Open Entities" + puts "* INFO: Please specify the number of sprites to render." + args.gtk.console.set_command "reset_with count: 100" + end + + # init + if args.state.tick_count == 0 + args.state.stars = args.state.star_count.map { |i| new_star args } + end + + # update + args.state.stars.each { |s| move_star args, s } + + # render + args.outputs.sprites << args.state.stars + args.outputs.background_color = [0, 0, 0] + args.outputs.primitives << args.gtk.current_framerate_primitives +end + +# resets game, and assigns star count given by user +def reset_with count: count + $gtk.reset + $gtk.args.state.star_count = count +end + +</code></pre> +<h3 id='----performance---sprites-as-struct---main-rb'>Performance - Sprites As Struct - main.rb</h3> +<pre><code class="language-ruby"># ./samples/09_performance/03_sprites_as_struct/app/main.rb +# create a Struct variant that allows for named parameters on construction. +class NamedStruct < Struct + def initialize **opts + super(*members.map { |k| opts[k] }) + end +end + +# create a Star NamedStruct +Star = NamedStruct.new(:x, :y, :w, :h, :path, :s, + :angle, :angle_anchor_x, :angle_anchor_y, + :r, :g, :b, :a, + :tile_x, :tile_y, + :tile_w, :tile_h, + :source_x, :source_y, + :source_w, :source_h, + :flip_horizontally, :flip_vertically, + :blendmode_enum) + +# Sprites represented as Structs. They require a little bit more code than Hashes, +# but are the a little faster to render too. +def random_x args + (args.grid.w.randomize :ratio) * -1 +end + +def random_y args + (args.grid.h.randomize :ratio) * -1 +end + +def random_speed + 1 + (4.randomize :ratio) +end + +def new_star args + Star.new x: (random_x args), + y: (random_y args), + w: 4, h: 4, + path: 'sprites/tiny-star.png', + s: random_speed +end + +def move_star args, star + star.x += star[:s] + star.y += star[:s] + if star.x > args.grid.w || star.y > args.grid.h + star.x = (random_x args) + star.y = (random_y args) + star[:s] = random_speed + end +end + +def tick args + args.state.star_count ||= 0 + + # sets console command when sample app initially opens + if Kernel.global_tick_count == 0 + puts "" + puts "" + puts "=========================================================" + puts "* INFO: Sprites, Structs" + puts "* INFO: Please specify the number of sprites to render." args.gtk.console.set_command "reset_with count: 100" end @@ -13658,8 +14494,8 @@ def reset_with count: count end </code></pre> -<h3 id='----performance---sprites-as-strict-entities---main.rb'>Performance - Sprites As Strict Entities - main.rb</h3> -<pre><code class="language-ruby"># ./samples/09_performance/03_sprites_as_strict_entities/app/main.rb +<h3 id='----performance---sprites-as-strict-entities---main-rb'>Performance - Sprites As Strict Entities - main.rb</h3> +<pre><code class="language-ruby"># ./samples/09_performance/04_sprites_as_strict_entities/app/main.rb # Sprites represented as StrictEntities using the queue ~args.outputs.sprites~ # yields apis access similar to Entities, but all properties that can be set on the # entity must be predefined with a default value. Strict entities do not support the @@ -13705,7 +14541,11 @@ def tick args # sets console command when sample app initially opens if Kernel.global_tick_count == 0 - puts "* INFO - Please specify the number of sprites to render." + puts "" + puts "" + puts "=========================================================" + puts "* INFO: Sprites, Strict Entities" + puts "* INFO: Please specify the number of sprites to render." args.gtk.console.set_command "reset_with count: 100" end @@ -13730,8 +14570,8 @@ def reset_with count: count end </code></pre> -<h3 id='----performance---sprites-as-classes---main.rb'>Performance - Sprites As Classes - main.rb</h3> -<pre><code class="language-ruby"># ./samples/09_performance/04_sprites_as_classes/app/main.rb +<h3 id='----performance---sprites-as-classes---main-rb'>Performance - Sprites As Classes - main.rb</h3> +<pre><code class="language-ruby"># ./samples/09_performance/05_sprites_as_classes/app/main.rb # Sprites represented as Classes using the queue ~args.outputs.sprites~. # gives you full control of property declaration and method invocation. # They are more performant than OpenEntities and StrictEntities, but more code upfront. @@ -13760,6 +14600,11 @@ end def tick args # sets console command when sample app initially opens if Kernel.global_tick_count == 0 + puts "" + puts "" + puts "=========================================================" + puts "* INFO: Sprites, Classes" + puts "* INFO: Please specify the number of sprites to render." args.gtk.console.set_command "reset_with count: 100" end @@ -13784,8 +14629,8 @@ def reset_with count: count end </code></pre> -<h3 id='----performance---static-sprites-as-classes---main.rb'>Performance - Static Sprites As Classes - main.rb</h3> -<pre><code class="language-ruby"># ./samples/09_performance/05_static_sprites_as_classes/app/main.rb +<h3 id='----performance---static-sprites-as-classes---main-rb'>Performance - Static Sprites As Classes - main.rb</h3> +<pre><code class="language-ruby"># ./samples/09_performance/06_static_sprites_as_classes/app/main.rb # Sprites represented as Classes using the queue ~args.outputs.static_sprites~. # bypasses the queue behavior of ~args.outputs.sprites~. All instances are held # by reference. You get better performance, but you are mutating state of held objects @@ -13815,19 +14660,24 @@ end def tick args # sets console command when sample app initially opens if Kernel.global_tick_count == 0 + puts "" + puts "" + puts "=========================================================" + puts "* INFO: Static Sprites, Classes" + puts "* INFO: Please specify the number of sprites to render." args.gtk.console.set_command "reset_with count: 100" end # init if args.state.tick_count == 0 args.state.stars = args.state.star_count.map { |i| Star.new args.grid } + args.outputs.static_sprites << args.state.stars end # update args.state.stars.each(&:move) # render - args.outputs.sprites << args.state.stars args.outputs.background_color = [0, 0, 0] args.outputs.primitives << args.gtk.current_framerate_primitives end @@ -13839,8 +14689,8 @@ def reset_with count: count end </code></pre> -<h3 id='----performance---static-sprites-as-classes-with-custom-drawing---main.rb'>Performance - Static Sprites As Classes With Custom Drawing - main.rb</h3> -<pre><code class="language-ruby"># ./samples/09_performance/06_static_sprites_as_classes_with_custom_drawing/app/main.rb +<h3 id='----performance---static-sprites-as-classes-with-custom-drawing---main-rb'>Performance - Static Sprites As Classes With Custom Drawing - main.rb</h3> +<pre><code class="language-ruby"># ./samples/09_performance/07_static_sprites_as_classes_with_custom_drawing/app/main.rb # Sprites represented as Classes, with a draw_override method, and using the queue ~args.outputs.static_sprites~. # is the fastest approach. This is comparable to what other game engines set as the default behavior. # There are tradeoffs for all this speed if the creation of a full blown class, and bypassing @@ -13883,10 +14733,21 @@ class Star # path, # angle, # alpha, red_saturation, green_saturation, blue_saturation + # tile_x, tile_y, tile_w, tile_h, # flip_horizontally, flip_vertically, - # tile_x, tile_y, tile_w, tile_h # angle_anchor_x, angle_anchor_y, # source_x, source_y, source_w, source_h + + # The argument order for ffi_draw.draw_sprite_4 is: + # x, y, w, h, + # path, + # angle, + # alpha, red_saturation, green_saturation, blue_saturation + # tile_x, tile_y, tile_w, tile_h, + # flip_horizontally, flip_vertically, + # angle_anchor_x, angle_anchor_y, + # source_x, source_y, source_w, source_h, + # blendmode_enum end end @@ -13894,6 +14755,11 @@ end def tick args # sets console command when sample app initially opens if Kernel.global_tick_count == 0 + puts "" + puts "" + puts "=========================================================" + puts "* INFO: Static Sprites, Classes, Draw Override" + puts "* INFO: Please specify the number of sprites to render." args.gtk.console.set_command "reset_with count: 100" end @@ -13915,8 +14781,8 @@ def reset_with count: count end </code></pre> -<h3 id='----performance---collision-limits---main.rb'>Performance - Collision Limits - main.rb</h3> -<pre><code class="language-ruby"># ./samples/09_performance/07_collision_limits/app/main.rb +<h3 id='----performance---collision-limits---main-rb'>Performance - Collision Limits - main.rb</h3> +<pre><code class="language-ruby"># ./samples/09_performance/08_collision_limits/app/main.rb =begin Reminders: @@ -13974,7 +14840,30 @@ end $gtk.reset </code></pre> -<h3 id='----advanced-debugging---trace-debugging---main.rb'>Advanced Debugging - Trace Debugging - main.rb</h3> +<h3 id='----advanced-debugging---logging---main-rb'>Advanced Debugging - Logging - main.rb</h3> +<pre><code class="language-ruby"># ./samples/10_advanced_debugging/00_logging/app/main.rb +def tick args + args.outputs.background_color = [255, 255, 255, 0] + if args.state.tick_count == 0 + args.gtk.log_spam "log level spam" + args.gtk.log_debug "log level debug" + args.gtk.log_info "log level info" + args.gtk.log_warn "log level warn" + args.gtk.log_error "log level error" + args.gtk.log_unfiltered "log level unfiltered" + puts "This is a puts call" + args.gtk.console.show + end + + if args.state.tick_count == 60 + puts "This is a puts call on tick 60" + elsif args.state.tick_count == 120 + puts "This is a puts call on tick 120" + end +end + +</code></pre> +<h3 id='----advanced-debugging---trace-debugging---main-rb'>Advanced Debugging - Trace Debugging - main.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/01_trace_debugging/app/main.rb class Game attr_gtk @@ -14031,7 +14920,7 @@ def tick args end </code></pre> -<h3 id='----advanced-debugging---trace-debugging-classes---main.rb'>Advanced Debugging - Trace Debugging Classes - main.rb</h3> +<h3 id='----advanced-debugging---trace-debugging-classes---main-rb'>Advanced Debugging - Trace Debugging Classes - main.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/02_trace_debugging_classes/app/main.rb class Foobar def initialize @@ -14057,7 +14946,54 @@ def tick args end </code></pre> -<h3 id='----advanced-debugging---unit-tests---exception_raising_tests.rb'>Advanced Debugging - Unit Tests - exception_raising_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---benchmark_api_tests-rb'>Advanced Debugging - Unit Tests - benchmark_api_tests.rb</h3> +<pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/benchmark_api_tests.rb +def test_benchmark_api args, assert + result = args.gtk.benchmark iterations: 100, + only_one: -> () { + r = 0 + (1..100).each do |i| + r += 1 + end + } + + assert.equal! result.first_place.name, :only_one + + result = args.gtk.benchmark iterations: 100, + iterations_100: -> () { + r = 0 + (1..100).each do |i| + r += 1 + end + }, + iterations_50: -> () { + r = 0 + (1..50).each do |i| + r += 1 + end + } + + assert.equal! result.first_place.name, :iterations_50 + + result = args.gtk.benchmark iterations: 1, + iterations_100: -> () { + r = 0 + (1..100).each do |i| + r += 1 + end + }, + iterations_50: -> () { + r = 0 + (1..50).each do |i| + r += 1 + end + } + + assert.equal! result.too_small_to_measure, true +end + +</code></pre> +<h3 id='----advanced-debugging---unit-tests---exception_raising_tests-rb'>Advanced Debugging - Unit Tests - exception_raising_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/exception_raising_tests.rb begin :shared class ExceptionalClass @@ -14080,7 +15016,7 @@ $gtk.reset 100 $gtk.log_level = :off </code></pre> -<h3 id='----advanced-debugging---unit-tests---fn_tests.rb'>Advanced Debugging - Unit Tests - fn_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---fn_tests-rb'>Advanced Debugging - Unit Tests - fn_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/fn_tests.rb def infinity 1 / 0 @@ -14264,13 +15200,13 @@ def test_array_hash args, assert end </code></pre> -<h3 id='----advanced-debugging---unit-tests---gen_docs.rb'>Advanced Debugging - Unit Tests - gen_docs.rb</h3> +<h3 id='----advanced-debugging---unit-tests---gen_docs-rb'>Advanced Debugging - Unit Tests - gen_docs.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/gen_docs.rb # ./dragonruby mygame --eval samples/99_zz_gtk_unit_tests/gen_docs.rb --no-tick Kernel.export_docs! </code></pre> -<h3 id='----advanced-debugging---unit-tests---geometry_tests.rb'>Advanced Debugging - Unit Tests - geometry_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---geometry_tests-rb'>Advanced Debugging - Unit Tests - geometry_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/geometry_tests.rb begin :shared def primitive_representations x, y, w, h @@ -14388,7 +15324,7 @@ $gtk.reset 100 $gtk.log_level = :off </code></pre> -<h3 id='----advanced-debugging---unit-tests---http_tests.rb'>Advanced Debugging - Unit Tests - http_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---http_tests-rb'>Advanced Debugging - Unit Tests - http_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/http_tests.rb def try_assert_or_schedule args, assert if $result[:complete] @@ -14414,7 +15350,7 @@ $gtk.reset 100 $gtk.log_level = :off </code></pre> -<h3 id='----advanced-debugging---unit-tests---nil_coercion_tests.rb'>Advanced Debugging - Unit Tests - nil_coercion_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---nil_coercion_tests-rb'>Advanced Debugging - Unit Tests - nil_coercion_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/nil_coercion_tests.rb # numbers def test_open_entity_add_number args, assert @@ -14510,7 +15446,7 @@ def test_open_entity_nil_bug args, assert end </code></pre> -<h3 id='----advanced-debugging---unit-tests---object_to_primitive_tests.rb'>Advanced Debugging - Unit Tests - object_to_primitive_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---object_to_primitive_tests-rb'>Advanced Debugging - Unit Tests - object_to_primitive_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/object_to_primitive_tests.rb class PlayerSpriteForTest end @@ -14530,7 +15466,7 @@ $gtk.reset 100 $gtk.log_level = :off </code></pre> -<h3 id='----advanced-debugging---unit-tests---parsing_tests.rb'>Advanced Debugging - Unit Tests - parsing_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---parsing_tests-rb'>Advanced Debugging - Unit Tests - parsing_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/parsing_tests.rb def test_parse_json args, assert result = args.gtk.parse_json '{ "name": "John Doe", "aliases": ["JD"] }' @@ -14561,7 +15497,141 @@ $gtk.reset 100 $gtk.log_level = :off </code></pre> -<h3 id='----advanced-debugging---unit-tests---require_tests.rb'>Advanced Debugging - Unit Tests - require_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---pretty_format_tests-rb'>Advanced Debugging - Unit Tests - pretty_format_tests.rb</h3> +<pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/pretty_format_tests.rb +def H opts + opts +end + +def A *opts + opts +end + +def assert_format args, assert, hash, expected + actual = args.fn.pretty_format hash + assert.are_equal! actual, expected +end + +def test_pretty_print args, assert + # ============================= + # hash with single value + # ============================= + input = (H first_name: "John") + expected = <<-S +{:first_name "John"} +S + (assert_format args, assert, input, expected) + + # ============================= + # hash with two values + # ============================= + input = (H first_name: "John", last_name: "Smith") + expected = <<-S +{:first_name "John" + :last_name "Smith"} +S + + (assert_format args, assert, input, expected) + + # ============================= + # hash with inner hash + # ============================= + input = (H first_name: "John", + last_name: "Smith", + middle_initial: "I", + so: (H first_name: "Pocahontas", + last_name: "Tsenacommacah"), + friends: (A (H first_name: "Side", last_name: "Kick"), + (H first_name: "Tim", last_name: "Wizard"))) + expected = <<-S +{:first_name "John" + :last_name "Smith" + :middle_initial "I" + :so {:first_name "Pocahontas" + :last_name "Tsenacommacah"} + :friends [{:first_name "Side" + :last_name "Kick"} + {:first_name "Tim" + :last_name "Wizard"}]} +S + + (assert_format args, assert, input, expected) + + # ============================= + # array with one value + # ============================= + input = (A 1) + expected = <<-S +[1] +S + (assert_format args, assert, input, expected) + + # ============================= + # array with multiple values + # ============================= + input = (A 1, 2, 3) + expected = <<-S +[1 + 2 + 3] +S + (assert_format args, assert, input, expected) + + # ============================= + # array with multiple values hashes + # ============================= + input = (A (H first_name: "Side", last_name: "Kick"), + (H first_name: "Tim", last_name: "Wizard")) + expected = <<-S +[{:first_name "Side" + :last_name "Kick"} + {:first_name "Tim" + :last_name "Wizard"}] +S + + (assert_format args, assert, input, expected) +end + +def test_nested_nested args, assert + # ============================= + # nested array in nested hash + # ============================= + input = (H type: :root, + text: "Root", + children: (A (H level: 1, + text: "Level 1", + children: (A (H level: 2, + text: "Level 2", + children: []))))) + + expected = <<-S +{:type :root + :text "Root" + :children [{:level 1 + :text "Level 1" + :children [{:level 2 + :text "Level 2" + :children []}]}]} + +S + + (assert_format args, assert, input, expected) +end + +def test_scene args, assert + script = <<-S +* Scene 1 +** Narrator +They say happy endings don't exist. +** Narrator +They say true love is a lie. +S + input = parse_org args, script + puts (args.fn.pretty_format input) +end + +</code></pre> +<h3 id='----advanced-debugging---unit-tests---require_tests-rb'>Advanced Debugging - Unit Tests - require_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/require_tests.rb def write_src path, src $gtk.write_file path, src @@ -14603,18 +15673,18 @@ def test_require args, assert end </code></pre> -<h3 id='----advanced-debugging---unit-tests---serialize_deserialize_tests.rb'>Advanced Debugging - Unit Tests - serialize_deserialize_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---serialize_deserialize_tests-rb'>Advanced Debugging - Unit Tests - serialize_deserialize_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/serialize_deserialize_tests.rb def test_serialize args, assert GTK::Entity.__reset_id__! args.state.player_one = "test" result = args.gtk.serialize_state args.state - assert.equal! result, "{:entity_id=>3, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" + assert.equal! result, "{:entity_id=>4, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" GTK::Entity.__reset_id__! args.gtk.write_file 'state.txt', '' result = args.gtk.serialize_state 'state.txt', args.state - assert.equal! result, "{:entity_id=>3, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" + assert.equal! result, "{:entity_id=>4, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" end def test_deserialize args, assert @@ -14663,7 +15733,7 @@ def test_strict_entity_serialization_with_nil args, assert args.state.player_two = args.state.new_entity_strict(:player_strict, name: "Ken", blood_type: nil) serialized_state = args.gtk.serialize_state args.state - assert.equal! serialized_state, '{:entity_id=>9, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_keys_by_ref=>{}, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>2, :entity_name=>:player_strict, :entity_type=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :entity_keys_by_ref=>{:entity_type=>:entity_name, :global_created_at_elapsed=>:created_at}, :name=>"Ken", :blood_type=>nil}}' + assert.equal! serialized_state, '{:entity_id=>7, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_keys_by_ref=>{}, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>2, :entity_name=>:player_strict, :entity_type=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :entity_keys_by_ref=>{:entity_type=>:entity_name, :global_created_at_elapsed=>:created_at}, :name=>"Ken", :blood_type=>nil}}' GTK::Entity.__reset_id__! deserialized_state = args.gtk.deserialize_state serialized_state @@ -14721,8 +15791,20 @@ def test_by_reference_state_strict_entities args, assert assert.equal! deserialized_state.strict_entity.one, deserialized_state.strict_entity.two end +def test_serialization_excludes_thrash_count args, assert + GTK::Entity.__reset_id__! + args.state.player.name = "Ryu" + # force a nil pun + if args.state.player.age > 30 + end + assert.equal! args.state.player.as_hash[:__thrash_count__][:>], 1 + result = args.gtk.serialize_state args.state + assert.false! (result.include? "__thrash_count__"), + "The __thrash_count__ key exists in state when it shouldn't have." +end + </code></pre> -<h3 id='----advanced-debugging---unit-tests---state_serialization_experimental_tests.rb'>Advanced Debugging - Unit Tests - state_serialization_experimental_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---state_serialization_experimental_tests-rb'>Advanced Debugging - Unit Tests - state_serialization_experimental_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/state_serialization_experimental_tests.rb MAX_CODE_GEN_LENGTH = 50 @@ -14832,7 +15914,7 @@ $gtk.reset 100 $gtk.log_level = :off </code></pre> -<h3 id='----advanced-debugging---unit-tests---suggest_autocompletion_tests.rb'>Advanced Debugging - Unit Tests - suggest_autocompletion_tests.rb</h3> +<h3 id='----advanced-debugging---unit-tests---suggest_autocompletion_tests-rb'>Advanced Debugging - Unit Tests - suggest_autocompletion_tests.rb</h3> <pre><code class="language-ruby"># ./samples/10_advanced_debugging/03_unit_tests/suggest_autocompletion_tests.rb def default_suggest_autocompletion args { @@ -14874,13 +15956,15 @@ S end </code></pre> -<h3 id='----http---retrieve-images---main.rb'>Http - Retrieve Images - main.rb</h3> +<h3 id='----http---retrieve-images---main-rb'>Http - Retrieve Images - main.rb</h3> <pre><code class="language-ruby"># ./samples/11_http/01_retrieve_images/app/main.rb +$gtk.register_cvar 'app.warn_seconds', "seconds to wait before starting", :uint, 11 + def tick args args.outputs.background_color = [0, 0, 0] # Show a warning at the start. - args.state.warning_debounce ||= 11 * 60 + args.state.warning_debounce ||= args.cvars['app.warn_seconds'].value * 60 if args.state.warning_debounce > 0 args.state.warning_debounce -= 1 args.outputs.labels << [640, 600, "This app shows random images from the Internet.", 10, 1, 255, 255, 255] @@ -14931,10 +16015,11 @@ def tick args end </code></pre> -<h3 id='----http---web-server---main.rb'>Http - Web Server - main.rb</h3> -<pre><code class="language-ruby"># ./samples/11_http/02_web_server/app/main.rb +<h3 id='----http---in-game-web-server-http-get---main-rb'>Http - In Game Web Server Http Get - main.rb</h3> +<pre><code class="language-ruby"># ./samples/11_http/02_in_game_web_server_http_get/app/main.rb def tick args args.state.port ||= 3000 + args.state.reqnum ||= 0 # by default the embedded webserver runs on port 9001 (the port number is over 9000) and is disabled in a production build # to enable the http server in a production build, you need to manually start # the server up: @@ -14962,7 +16047,83 @@ def tick args end </code></pre> -<h3 id='----c-extensions---basics---main.rb'>C Extensions - Basics - main.rb</h3> +<h3 id='----http---in-game-web-server-http-post---main-rb'>Http - In Game Web Server Http Post - main.rb</h3> +<pre><code class="language-ruby"># ./samples/11_http/03_in_game_web_server_http_post/app/main.rb +def tick args + # defaults + args.state.post_button = args.layout.rect(row: 0, col: 0, w: 5, h: 1).merge(text: "execute http_post") + args.state.post_body_button = args.layout.rect(row: 1, col: 0, w: 5, h: 1).merge(text: "execute http_post_body") + args.state.request_to_s ||= "" + args.state.request_body ||= "" + + # render + args.state.post_button.yield_self do |b| + args.outputs.borders << b + args.outputs.labels << b.merge(text: b.text, + y: b.y + 30, + x: b.x + 10) + end + + args.state.post_body_button.yield_self do |b| + args.outputs.borders << b + args.outputs.labels << b.merge(text: b.text, + y: b.y + 30, + x: b.x + 10) + end + + draw_label args, 0, 6, "Request:", args.state.request_to_s + draw_label args, 0, 14, "Request Body Unaltered:", args.state.request_body + + # input + if args.inputs.mouse.click + # ============= HTTP_POST ============= + if (args.inputs.mouse.inside_rect? args.state.post_button) + # ========= DATA TO SEND =========== + form_fields = { "userId" => "#{Time.now.to_i}" } + # ================================== + + args.gtk.http_post "http://localhost:9001/testing", + form_fields, + ["Content-Type: application/x-www-form-urlencoded"] + + args.gtk.notify! "http_post" + end + + # ============= HTTP_POST_BODY ============= + if (args.inputs.mouse.inside_rect? args.state.post_body_button) + # =========== DATA TO SEND ============== + json = "{ \"userId\": \"#{Time.now.to_i}\"}" + # ================================== + + args.gtk.http_post_body "http://localhost:9001/testing", + json, + ["Content-Type: application/json", "Content-Length: #{json.length}"] + + args.gtk.notify! "http_post_body" + end + end + + # calc + args.inputs.http_requests.each do |r| + puts "#{r}" + if r.uri == "/testing" + puts r + args.state.request_to_s = "#{r}" + args.state.request_body = r.raw_body + r.respond 200, "ok" + end + end +end + +def draw_label args, row, col, header, text + label_pos = args.layout.rect(row: row, col: col, w: 0, h: 0) + args.outputs.labels << "#{header}\n\n#{text}".wrapped_lines(80).map_with_index do |l, i| + { x: label_pos.x, y: label_pos.y - (i * 15), text: l, size_enum: -2 } + end +end + +</code></pre> +<h3 id='----c-extensions---basics---main-rb'>C Extensions - Basics - main.rb</h3> <pre><code class="language-ruby"># ./samples/12_c_extensions/01_basics/app/main.rb $gtk.ffi_misc.gtk_dlopen("ext") include FFI::CExt @@ -14976,7 +16137,7 @@ end </code></pre> -<h3 id='----c-extensions---intermediate---main.rb'>C Extensions - Intermediate - main.rb</h3> +<h3 id='----c-extensions---intermediate---main-rb'>C Extensions - Intermediate - main.rb</h3> <pre><code class="language-ruby"># ./samples/12_c_extensions/02_intermediate/app/main.rb $gtk.ffi_misc.gtk_dlopen("ext") include FFI::RE @@ -14999,7 +16160,7 @@ def tick args end </code></pre> -<h3 id='----c-extensions---native-pixel-arrays---main.rb'>C Extensions - Native Pixel Arrays - main.rb</h3> +<h3 id='----c-extensions---native-pixel-arrays---main-rb'>C Extensions - Native Pixel Arrays - main.rb</h3> <pre><code class="language-ruby"># ./samples/12_c_extensions/03_native_pixel_arrays/app/main.rb $gtk.ffi_misc.gtk_dlopen("ext") include FFI::CExt @@ -15025,7 +16186,7 @@ end </code></pre> -<h3 id='----path-finding-algorithms---breadth-first-search---main.rb'>Path Finding Algorithms - Breadth First Search - main.rb</h3> +<h3 id='----path-finding-algorithms---breadth-first-search---main-rb'>Path Finding Algorithms - Breadth First Search - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/01_breadth_first_search/app/main.rb # A visual demonstration of a breadth first search # Inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html @@ -15721,7 +16882,7 @@ def reset end </code></pre> -<h3 id='----path-finding-algorithms---detailed-breadth-first-search---main.rb'>Path Finding Algorithms - Detailed Breadth First Search - main.rb</h3> +<h3 id='----path-finding-algorithms---detailed-breadth-first-search---main-rb'>Path Finding Algorithms - Detailed Breadth First Search - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/02_detailed_breadth_first_search/app/main.rb # A visual demonstration of a breadth first search # Inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html @@ -16371,7 +17532,7 @@ def reset end </code></pre> -<h3 id='----path-finding-algorithms---breadcrumbs---main.rb'>Path Finding Algorithms - Breadcrumbs - main.rb</h3> +<h3 id='----path-finding-algorithms---breadcrumbs---main-rb'>Path Finding Algorithms - Breadcrumbs - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/03_breadcrumbs/app/main.rb class Breadcrumbs attr_gtk @@ -16895,7 +18056,7 @@ def tick args end # Every tick, new args are passed, and the Breadth First Search tick is called - $breadcrumbs ||= Breadcrumbs.new(args) + $breadcrumbs ||= Breadcrumbs.new $breadcrumbs.args = args $breadcrumbs.tick end @@ -16920,7 +18081,7 @@ end # end </code></pre> -<h3 id='----path-finding-algorithms---early-exit---main.rb'>Path Finding Algorithms - Early Exit - main.rb</h3> +<h3 id='----path-finding-algorithms---early-exit---main-rb'>Path Finding Algorithms - Early Exit - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/04_early_exit/app/main.rb # Comparison of a breadth first search with and without early exit # Inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html @@ -17544,7 +18705,7 @@ def tick args end # Every tick, new args are passed, and the Breadth First Search tick is called - $early_exit_breadth_first_search ||= EarlyExitBreadthFirstSearch.new(args) + $early_exit_breadth_first_search ||= EarlyExitBreadthFirstSearch.new $early_exit_breadth_first_search.args = args $early_exit_breadth_first_search.tick end @@ -17555,7 +18716,7 @@ def reset end </code></pre> -<h3 id='----path-finding-algorithms---dijkstra---main.rb'>Path Finding Algorithms - Dijkstra - main.rb</h3> +<h3 id='----path-finding-algorithms---dijkstra---main-rb'>Path Finding Algorithms - Dijkstra - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/05_dijkstra/app/main.rb # Demonstrates how Dijkstra's Algorithm allows movement costs to be considered @@ -18392,7 +19553,7 @@ def tick args end # Every tick, new args are passed, and the Dijkstra tick method is called - $movement_costs ||= Movement_Costs.new(args) + $movement_costs ||= Movement_Costs.new $movement_costs.args = args $movement_costs.tick end @@ -18403,7 +19564,7 @@ def reset end </code></pre> -<h3 id='----path-finding-algorithms---heuristic---main.rb'>Path Finding Algorithms - Heuristic - main.rb</h3> +<h3 id='----path-finding-algorithms---heuristic---main-rb'>Path Finding Algorithms - Heuristic - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/06_heuristic/app/main.rb # This program is inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html @@ -19376,7 +20537,7 @@ def tick args end # Every tick, new args are passed, and the Breadth First Search tick is called - $heuristic_with_walls ||= Heuristic_With_Walls.new(args) + $heuristic_with_walls ||= Heuristic_With_Walls.new $heuristic_with_walls.args = args $heuristic_with_walls.tick end @@ -19387,7 +20548,7 @@ def reset end </code></pre> -<h3 id='----path-finding-algorithms---heuristic-with-walls---main.rb'>Path Finding Algorithms - Heuristic With Walls - main.rb</h3> +<h3 id='----path-finding-algorithms---heuristic-with-walls---main-rb'>Path Finding Algorithms - Heuristic With Walls - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/07_heuristic_with_walls/app/main.rb # This program is inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html # The effectiveness of the Heuristic search algorithm is shown through this demonstration. @@ -20393,7 +21554,7 @@ def tick args end # Every tick, new args are passed, and the Breadth First Search tick is called - $heuristic ||= Heuristic.new(args) + $heuristic ||= Heuristic.new $heuristic.args = args $heuristic.tick end @@ -20404,7 +21565,7 @@ def reset end </code></pre> -<h3 id='----path-finding-algorithms---a-star---main.rb'>Path Finding Algorithms - A Star - main.rb</h3> +<h3 id='----path-finding-algorithms---a-star---main-rb'>Path Finding Algorithms - A Star - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/08_a_star/app/main.rb # This program is inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html @@ -21426,7 +22587,7 @@ def tick args end # Every tick, new args are passed, and the Breadth First Search tick is called - $a_star_algorithm ||= A_Star_Algorithm.new(args) + $a_star_algorithm ||= A_Star_Algorithm.new $a_star_algorithm.args = args $a_star_algorithm.tick end @@ -21437,7 +22598,7 @@ def reset end </code></pre> -<h3 id='----path-finding-algorithms---tower-defense---main.rb'>Path Finding Algorithms - Tower Defense - main.rb</h3> +<h3 id='----path-finding-algorithms---tower-defense---main-rb'>Path Finding Algorithms - Tower Defense - main.rb</h3> <pre><code class="language-ruby"># ./samples/13_path_finding_algorithms/09_tower_defense/app/main.rb # An example of some major components in a tower defence game # The pathing of the tanks is determined by A* algorithm -- try editing the walls @@ -21742,7 +22903,7 @@ def a_star_color end </code></pre> -<h3 id='----3d---3d-cube---main.rb'>3d - 3d Cube - main.rb</h3> +<h3 id='----3d---3d-cube---main-rb'>3d - 3d Cube - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_3d/01_3d_cube/app/main.rb STARTX = 0.0 STARTY = 0.0 @@ -21796,7 +22957,7 @@ end $gtk.reset </code></pre> -<h3 id='----3d---wireframe---main.rb'>3d - Wireframe - main.rb</h3> +<h3 id='----3d---wireframe---main-rb'>3d - Wireframe - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_3d/02_wireframe/app/main.rb def tick args args.state.model ||= Object3D.new('data/shuttle.off') @@ -21949,11 +23110,366 @@ class Vertex end end </code></pre> -<h3 id='----3d---wireframe---data---what-is-this.txt'>3d - Wireframe - Data - what-is-this.txt</h3> +<h3 id='----3d---wireframe---data---what-is-this-txt'>3d - Wireframe - Data - what-is-this.txt</h3> <pre><code class="language-ruby"># ./samples/99_genre_3d/02_wireframe/data/what-is-this.txt https://en.wikipedia.org/wiki/OFF_(file_format) </code></pre> -<h3 id='----arcade---bullet-hell---main.rb'>Arcade - Bullet Hell - main.rb</h3> +<h3 id='----3d---yaw-pitch-roll---main-rb'>3d - Yaw Pitch Roll - main.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_3d/03_yaw_pitch_roll/app/main.rb +class Game + attr_gtk + + def tick + defaults + render + input + end + + def matrix_mul m, v + (hmap x: ((m.x.x * v.x) + (m.x.y * v.y) + (m.x.z * v.z) + (m.x.w * v.w)), + y: ((m.y.x * v.x) + (m.y.y * v.y) + (m.y.z * v.z) + (m.y.w * v.w)), + z: ((m.z.x * v.x) + (m.z.y * v.y) + (m.z.z * v.z) + (m.z.w * v.w)), + w: ((m.w.x * v.x) + (m.w.y * v.y) + (m.w.z * v.z) + (m.w.w * v.w))) + end + + def player_ship + [ + # engine back + { x: -1, y: -1, z: 1, w: 0 }, + { x: -1, y: 1, z: 1, w: 0 }, + + { x: -1, y: 1, z: 1, w: 0 }, + { x: 1, y: 1, z: 1, w: 0 }, + + { x: 1, y: 1, z: 1, w: 0 }, + { x: 1, y: -1, z: 1, w: 0 }, + + { x: 1, y: -1, z: 1, w: 0 }, + { x: -1, y: -1, z: 1, w: 0 }, + + # engine front + { x: -1, y: -1, z: -1, w: 0 }, + { x: -1, y: 1, z: -1, w: 0 }, + + { x: -1, y: 1, z: -1, w: 0 }, + { x: 1, y: 1, z: -1, w: 0 }, + + { x: 1, y: 1, z: -1, w: 0 }, + { x: 1, y: -1, z: -1, w: 0 }, + + { x: 1, y: -1, z: -1, w: 0 }, + { x: -1, y: -1, z: -1, w: 0 }, + + # engine left + { x: -1, z: -1, y: -1, w: 0 }, + { x: -1, z: -1, y: 1, w: 0 }, + + { x: -1, z: -1, y: 1, w: 0 }, + { x: -1, z: 1, y: 1, w: 0 }, + + { x: -1, z: 1, y: 1, w: 0 }, + { x: -1, z: 1, y: -1, w: 0 }, + + { x: -1, z: 1, y: -1, w: 0 }, + { x: -1, z: -1, y: -1, w: 0 }, + + # engine right + { x: 1, z: -1, y: -1, w: 0 }, + { x: 1, z: -1, y: 1, w: 0 }, + + { x: 1, z: -1, y: 1, w: 0 }, + { x: 1, z: 1, y: 1, w: 0 }, + + { x: 1, z: 1, y: 1, w: 0 }, + { x: 1, z: 1, y: -1, w: 0 }, + + { x: 1, z: 1, y: -1, w: 0 }, + { x: 1, z: -1, y: -1, w: 0 }, + + # top front of engine to front of ship + { x: 1, y: 1, z: 1, w: 0 }, + { x: 0, y: -1, z: 9, w: 0 }, + + { x: 0, y: -1, z: 9, w: 0 }, + { x: -1, y: 1, z: 1, w: 0 }, + + # bottom front of engine + { x: 1, y: -1, z: 1, w: 0 }, + { x: 0, y: -1, z: 9, w: 0 }, + + { x: -1, y: -1, z: 1, w: 0 }, + { x: 0, y: -1, z: 9, w: 0 }, + + # right wing + # front of wing + { x: 1, y: 0.10, z: 1, w: 0 }, + { x: 9, y: 0.10, z: -1, w: 0 }, + + { x: 9, y: 0.10, z: -1, w: 0 }, + { x: 10, y: 0.10, z: -2, w: 0 }, + + # back of wing + { x: 1, y: 0.10, z: -1, w: 0 }, + { x: 9, y: 0.10, z: -1, w: 0 }, + + { x: 10, y: 0.10, z: -2, w: 0 }, + { x: 8, y: 0.10, z: -1, w: 0 }, + + # front of wing + { x: 1, y: -0.10, z: 1, w: 0 }, + { x: 9, y: -0.10, z: -1, w: 0 }, + + { x: 9, y: -0.10, z: -1, w: 0 }, + { x: 10, y: -0.10, z: -2, w: 0 }, + + # back of wing + { x: 1, y: -0.10, z: -1, w: 0 }, + { x: 9, y: -0.10, z: -1, w: 0 }, + + { x: 10, y: -0.10, z: -2, w: 0 }, + { x: 8, y: -0.10, z: -1, w: 0 }, + + # left wing + # front of wing + { x: -1, y: 0.10, z: 1, w: 0 }, + { x: -9, y: 0.10, z: -1, w: 0 }, + + { x: -9, y: 0.10, z: -1, w: 0 }, + { x: -10, y: 0.10, z: -2, w: 0 }, + + # back of wing + { x: -1, y: 0.10, z: -1, w: 0 }, + { x: -9, y: 0.10, z: -1, w: 0 }, + + { x: -10, y: 0.10, z: -2, w: 0 }, + { x: -8, y: 0.10, z: -1, w: 0 }, + + # front of wing + { x: -1, y: -0.10, z: 1, w: 0 }, + { x: -9, y: -0.10, z: -1, w: 0 }, + + { x: -9, y: -0.10, z: -1, w: 0 }, + { x: -10, y: -0.10, z: -2, w: 0 }, + + # back of wing + { x: -1, y: -0.10, z: -1, w: 0 }, + { x: -9, y: -0.10, z: -1, w: 0 }, + + { x: -10, y: -0.10, z: -2, w: 0 }, + { x: -8, y: -0.10, z: -1, w: 0 }, + + # left fin + # top + { x: -1, y: 0.10, z: 1, w: 0 }, + { x: -1, y: 3, z: -3, w: 0 }, + + { x: -1, y: 0.10, z: -1, w: 0 }, + { x: -1, y: 3, z: -3, w: 0 }, + + { x: -1.1, y: 0.10, z: 1, w: 0 }, + { x: -1.1, y: 3, z: -3, w: 0 }, + + { x: -1.1, y: 0.10, z: -1, w: 0 }, + { x: -1.1, y: 3, z: -3, w: 0 }, + + # bottom + { x: -1, y: -0.10, z: 1, w: 0 }, + { x: -1, y: -2, z: -2, w: 0 }, + + { x: -1, y: -0.10, z: -1, w: 0 }, + { x: -1, y: -2, z: -2, w: 0 }, + + { x: -1.1, y: -0.10, z: 1, w: 0 }, + { x: -1.1, y: -2, z: -2, w: 0 }, + + { x: -1.1, y: -0.10, z: -1, w: 0 }, + { x: -1.1, y: -2, z: -2, w: 0 }, + + # right fin + { x: 1, y: 0.10, z: 1, w: 0 }, + { x: 1, y: 3, z: -3, w: 0 }, + + { x: 1, y: 0.10, z: -1, w: 0 }, + { x: 1, y: 3, z: -3, w: 0 }, + + { x: 1.1, y: 0.10, z: 1, w: 0 }, + { x: 1.1, y: 3, z: -3, w: 0 }, + + { x: 1.1, y: 0.10, z: -1, w: 0 }, + { x: 1.1, y: 3, z: -3, w: 0 }, + + # bottom + { x: 1, y: -0.10, z: 1, w: 0 }, + { x: 1, y: -2, z: -2, w: 0 }, + + { x: 1, y: -0.10, z: -1, w: 0 }, + { x: 1, y: -2, z: -2, w: 0 }, + + { x: 1.1, y: -0.10, z: 1, w: 0 }, + { x: 1.1, y: -2, z: -2, w: 0 }, + + { x: 1.1, y: -0.10, z: -1, w: 0 }, + { x: 1.1, y: -2, z: -2, w: 0 }, + ] + end + + def defaults + state.points ||= player_ship + state.shifted_points ||= state.points.map { |point| point } + + state.scale ||= 1 + state.angle_x ||= 0 + state.angle_y ||= 0 + state.angle_z ||= 0 + end + + def matrix_new x0, y0, z0, w0, x1, y1, z1, w1, x2, y2, z2, w2, x3, y3, z3, w3 + (hmap x: (hmap x: x0, y: y0, z: z0, w: w0), + y: (hmap x: x1, y: y1, z: z1, w: w1), + z: (hmap x: x2, y: y2, z: z2, w: w2), + w: (hmap x: x3, y: y3, z: z3, w: w3)) + end + + def angle_z_matrix degrees + cos_t = Math.cos degrees.to_radians + sin_t = Math.sin degrees.to_radians + (matrix_new cos_t, -sin_t, 0, 0, + sin_t, cos_t, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1) + end + + def angle_y_matrix degrees + cos_t = Math.cos degrees.to_radians + sin_t = Math.sin degrees.to_radians + (matrix_new cos_t, 0, sin_t, 0, + 0, 1, 0, 0, + -sin_t, 0, cos_t, 0, + 0, 0, 0, 1) + end + + def angle_x_matrix degrees + cos_t = Math.cos degrees.to_radians + sin_t = Math.sin degrees.to_radians + (matrix_new 1, 0, 0, 0, + 0, cos_t, -sin_t, 0, + 0, sin_t, cos_t, 0, + 0, 0, 0, 1) + end + + def scale_matrix factor + (matrix_new factor, 0, 0, 0, + 0, factor, 0, 0, + 0, 0, factor, 0, + 0, 0, 0, 1) + end + + def input + if (inputs.keyboard.shift && inputs.keyboard.p) + state.scale -= 0.1 + elsif inputs.keyboard.p + state.scale += 0.1 + end + + if inputs.mouse.wheel + state.scale += inputs.mouse.wheel.y + end + + state.scale = state.scale.clamp(0.1, 1000) + + if (inputs.keyboard.shift && inputs.keyboard.y) || inputs.keyboard.right + state.angle_y += 1 + elsif (inputs.keyboard.y) || inputs.keyboard.left + state.angle_y -= 1 + end + + if (inputs.keyboard.shift && inputs.keyboard.x) || inputs.keyboard.down + state.angle_x -= 1 + elsif (inputs.keyboard.x || inputs.keyboard.up) + state.angle_x += 1 + end + + if inputs.keyboard.shift && inputs.keyboard.z + state.angle_z += 1 + elsif inputs.keyboard.z + state.angle_z -= 1 + end + + if inputs.keyboard.zero + state.angle_x = 0 + state.angle_y = 0 + state.angle_z = 0 + end + + angle_x = state.angle_x + angle_y = state.angle_y + angle_z = state.angle_z + scale = state.scale + + s_matrix = scale_matrix state.scale + x_matrix = angle_z_matrix angle_z + y_matrix = angle_y_matrix angle_y + z_matrix = angle_x_matrix angle_x + + state.shifted_points = state.points.map do |point| + (matrix_mul s_matrix, + (matrix_mul z_matrix, + (matrix_mul x_matrix, + (matrix_mul y_matrix, point)))).merge(original: point) + end + end + + def thick_line line + [ + line.merge(y: line.y - 1, y2: line.y2 - 1, r: 0, g: 0, b: 0), + line.merge(x: line.x - 1, x2: line.x2 - 1, r: 0, g: 0, b: 0), + line.merge(x: line.x - 0, x2: line.x2 - 0, r: 0, g: 0, b: 0), + line.merge(y: line.y + 1, y2: line.y2 + 1, r: 0, g: 0, b: 0), + line.merge(x: line.x + 1, x2: line.x2 + 1, r: 0, g: 0, b: 0) + ] + end + + def render + outputs.lines << state.shifted_points.each_slice(2).map do |(p1, p2)| + perc = 0 + thick_line({ x: p1.x.*(10) + 640, y: p1.y.*(10) + 320, + x2: p2.x.*(10) + 640, y2: p2.y.*(10) + 320, + r: 255 * perc, + g: 255 * perc, + b: 255 * perc }) + end + + outputs.labels << [ 10, 700, "angle_x: #{state.angle_x.to_sf}", 0] + outputs.labels << [ 10, 670, "x, shift+x", 0] + + outputs.labels << [210, 700, "angle_y: #{state.angle_y.to_sf}", 0] + outputs.labels << [210, 670, "y, shift+y", 0] + + outputs.labels << [410, 700, "angle_z: #{state.angle_z.to_sf}", 0] + outputs.labels << [410, 670, "z, shift+z", 0] + + outputs.labels << [610, 700, "scale: #{state.scale.to_sf}", 0] + outputs.labels << [610, 670, "p, shift+p", 0] + end +end + +$game = Game.new + +def tick args + $game.args = args + $game.tick +end + +def set_angles x, y, z + $game.state.angle_x = x + $game.state.angle_y = y + $game.state.angle_z = z +end + +$gtk.reset + +</code></pre> +<h3 id='----arcade---bullet-hell---main-rb'>Arcade - Bullet Hell - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/bullet_hell/app/main.rb def tick args args.state.base_columns ||= 10.times.map { |n| 50 * n + 1280 / 2 - 5 * 50 + 5 } @@ -22146,7 +23662,7 @@ def update_enemy_positions args end </code></pre> -<h3 id='----arcade---dueling-starships---main.rb'>Arcade - Dueling Starships - main.rb</h3> +<h3 id='----arcade---dueling-starships---main-rb'>Arcade - Dueling Starships - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/dueling_starships/app/main.rb class DuelingSpaceships attr_accessor :state, :inputs, :outputs, :grid @@ -22515,14 +24031,14 @@ def tick args end </code></pre> -<h3 id='----arcade/flappy-dragon/credits.txt'>arcade/flappy dragon/credits.txt</h3> +<h3 id='----arcade/flappy-dragon/credits-txt'>arcade/flappy dragon/credits.txt</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/flappy_dragon/CREDITS.txt code: Amir Rajan, https://twitter.com/amirrajan graphics and audio: Nick Culbertson, https://twitter.com/MobyPixel </code></pre> -<h3 id='----arcade/flappy-dragon/main.rb'>arcade/flappy dragon/main.rb</h3> +<h3 id='----arcade/flappy-dragon/main-rb'>arcade/flappy dragon/main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/flappy_dragon/app/main.rb class FlappyDragon attr_accessor :grid, :inputs, :state, :outputs @@ -22564,31 +24080,36 @@ class FlappyDragon end def render_score - outputs.primitives << [10, 710, "HI SCORE: #{state.hi_score}", large_white_typeset].label - outputs.primitives << [10, 680, "SCORE: #{state.score}", large_white_typeset].label - outputs.primitives << [10, 650, "DIFFICULTY: #{state.difficulty.upcase}", large_white_typeset].label + outputs.primitives << { x: 10, y: 710, text: "HI SCORE: #{state.hi_score}", **large_white_typeset } + outputs.primitives << { x: 10, y: 680, text: "SCORE: #{state.score}", **large_white_typeset } + outputs.primitives << { x: 10, y: 650, text: "DIFFICULTY: #{state.difficulty.upcase}", **large_white_typeset } end def render_menu return unless state.scene == :menu render_overlay - outputs.labels << [640, 700, "Flappy Dragon", 50, 1, 255, 255, 255] - outputs.labels << [640, 500, "Instructions: Press Spacebar to flap. Don't die.", 4, 1, 255, 255, 255] - outputs.labels << [430, 430, "[Tab] Change difficulty", 4, 0, 255, 255, 255] - outputs.labels << [430, 400, "[Enter] Start at New Difficulty ", 4, 0, 255, 255, 255] - outputs.labels << [430, 370, "[Escape] Cancel/Resume ", 4, 0, 255, 255, 255] - outputs.labels << [640, 300, "(mouse, touch, and game controllers work, too!) ", 4, 1, 255, 255, 255] - outputs.labels << [640, 200, "Difficulty: #{state.new_difficulty.capitalize}", 4, 1, 255, 255, 255] + outputs.labels << { x: 640, y: 700, text: "Flappy Dragon", size_enum: 50, alignment_enum: 1, **white } + outputs.labels << { x: 640, y: 500, text: "Instructions: Press Spacebar to flap. Don't die.", size_enum: 4, alignment_enum: 1, **white } + outputs.labels << { x: 430, y: 430, text: "[Tab] Change difficulty", size_enum: 4, alignment_enum: 0, **white } + outputs.labels << { x: 430, y: 400, text: "[Enter] Start at New Difficulty ", size_enum: 4, alignment_enum: 0, **white } + outputs.labels << { x: 430, y: 370, text: "[Escape] Cancel/Resume ", size_enum: 4, alignment_enum: 0, **white } + outputs.labels << { x: 640, y: 300, text: "(mouse, touch, and game controllers work, too!) ", size_enum: 4, alignment_enum: 1, **white } + outputs.labels << { x: 640, y: 200, text: "Difficulty: #{state.new_difficulty.capitalize}", size_enum: 4, alignment_enum: 1, **white } - outputs.labels << [10, 100, "Code: @amirrajan", 255, 255, 255] - outputs.labels << [10, 80, "Art: @mobypixel", 255, 255, 255] - outputs.labels << [10, 60, "Music: @mobypixel", 255, 255, 255] - outputs.labels << [10, 40, "Engine: DragonRuby GTK", 255, 255, 255] + outputs.labels << { x: 10, y: 100, text: "Code: @amirrajan", **white } + outputs.labels << { x: 10, y: 80, text: "Art: @mobypixel", **white } + outputs.labels << { x: 10, y: 60, text: "Music: @mobypixel", **white } + outputs.labels << { x: 10, y: 40, text: "Engine: DragonRuby GTK", **white } end def render_overlay - outputs.primitives << [grid.rect.scale_rect(1.1, 0, 0), 0, 0, 0, 230].solid + overlay_rect = grid.rect.scale_rect(1.1, 0, 0) + outputs.primitives << { x: overlay_rect.x, + y: overlay_rect.y, + w: overlay_rect.w, + h: overlay_rect.h, + r: 0, g: 0, b: 0, a: 230 }.solid! end def render_game @@ -22601,14 +24122,14 @@ class FlappyDragon def render_game_over return unless state.scene == :game - outputs.labels << [638, 358, score_text, 20, 1] - outputs.labels << [635, 360, score_text, 20, 1, 255, 255, 255] - outputs.labels << [638, 428, countdown_text, 20, 1] - outputs.labels << [635, 430, countdown_text, 20, 1, 255, 255, 255] + outputs.labels << { x: 638, y: 358, text: score_text, size_enum: 20, alignment_enum: 1 } + outputs.labels << { x: 635, y: 360, text: score_text, size_enum: 20, alignment_enum: 1, r: 255, g: 255, b: 255 } + outputs.labels << { x: 638, y: 428, text: countdown_text, size_enum: 20, alignment_enum: 1 } + outputs.labels << { x: 635, y: 430, text: countdown_text, size_enum: 20, alignment_enum: 1, r: 255, g: 255, b: 255 } end def render_background - outputs.sprites << [0, 0, 1280, 720, 'sprites/background.png'] + outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: 'sprites/background.png' } scroll_point_at = state.tick_count scroll_point_at = state.scene_at if state.scene == :menu @@ -22620,11 +24141,18 @@ class FlappyDragon outputs.sprites << scrolling_background(scroll_point_at, 'sprites/parallax_front.png', 1.00, -80) end + def scrolling_background at, path, rate, y = 0 + [ + { x: 0 - at.*(rate) % 1440, y: y, w: 1440, h: 720, path: path }, + { x: 1440 - at.*(rate) % 1440, y: y, w: 1440, h: 720, path: path } + ] + end + def render_walls state.walls.each do |w| w.sprites = [ - [w.x, w.bottom_height - 720, 100, 720, 'sprites/wall.png', 180], - [w.x, w.top_y, 100, 720, 'sprites/wallbottom.png', 0] + { x: w.x, y: w.bottom_height - 720, w: 100, h: 720, path: 'sprites/wall.png', angle: 180 }, + { x: w.x, y: w.top_y, w: 100, h: 720, path: 'sprites/wallbottom.png', angle: 0 } ] end outputs.sprites << state.walls.map(&:sprites) @@ -22633,15 +24161,13 @@ class FlappyDragon def render_dragon state.show_death = true if state.countdown == 3.seconds - render_debug_hitbox false - if state.show_death == false || !state.death_at animation_index = state.flapped_at.frame_index 6, 2, false if state.flapped_at sprite_name = "sprites/dragon_fly#{animation_index.or(0) + 1}.png" - state.dragon_sprite = [state.x, state.y, 100, 80, sprite_name, state.dy * 1.2] + state.dragon_sprite = { x: state.x, y: state.y, w: 100, h: 80, path: sprite_name, angle: state.dy * 1.2 } else sprite_name = "sprites/dragon_die.png" - state.dragon_sprite = [state.x, state.y, 100, 80, sprite_name, state.dy * 1.2] + state.dragon_sprite = { x: state.x, y: state.y, w: 100, h: 80, path: sprite_name, angle: state.dy * 1.2 } sprite_changed_elapsed = state.death_at.elapsed_time - 1.seconds state.dragon_sprite.angle += (sprite_changed_elapsed ** 1.3) * state.death_fall_direction * -1 state.dragon_sprite.x += (sprite_changed_elapsed ** 1.2) * state.death_fall_direction @@ -22651,20 +24177,12 @@ class FlappyDragon outputs.sprites << state.dragon_sprite end - def render_debug_hitbox show - return unless show - outputs.borders << [dragon_collision_box.rect, 255, 0, 0] if state.dragon_sprite - outputs.borders << state.walls.flat_map do |w| - w.sprites.map { |s| [s.rect, 255, 0, 0] } - end - end - def render_flash return unless state.flash_at - outputs.primitives << [grid.rect, - white, - 255 * state.flash_at.ease(20, :flip)].solid + outputs.primitives << { **grid.rect.to_hash, + **white, + a: 255 * state.flash_at.ease(20, :flip) }.solid! state.flash_at = 0 if state.flash_at.elapsed_time > 20 end @@ -22784,19 +24302,12 @@ class FlappyDragon end end - def scrolling_background at, path, rate, y = 0 - [ - [ 0 - at.*(rate) % 1440, y, 1440, 720, path], - [1440 - at.*(rate) % 1440, y, 1440, 720, path] - ] - end - def white - [255, 255, 255] + { r: 255, g: 255, b: 255 } end def large_white_typeset - [5, 0, 255, 255, 255] + { size_enum: 5, alignment_enum: 0, r: 255, g: 255, b: 255 } end def at_beginning? @@ -22805,9 +24316,9 @@ class FlappyDragon def dragon_collision_box state.dragon_sprite - .scale_rect(1.0 - collision_forgiveness, 0.5, 0.5) - .rect_shift_right(10) - .rect_shift_up(state.dy * 2) + .scale_rect(1.0 - collision_forgiveness, 0.5, 0.5) + .rect_shift_right(10) + .rect_shift_up(state.dy * 2) end def game_over? @@ -22816,7 +24327,7 @@ class FlappyDragon state.walls .flat_map { |w| w.sprites } .any? do |s| - s.intersect_rect?(dragon_collision_box) + s && s.intersect_rect?(dragon_collision_box) end end @@ -22886,7 +24397,7 @@ def tick args end </code></pre> -<h3 id='----arcade---pong---main.rb'>Arcade - Pong - main.rb</h3> +<h3 id='----arcade---pong---main-rb'>Arcade - Pong - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/pong/app/main.rb def tick args defaults args @@ -23049,7 +24560,7 @@ begin :assets end </code></pre> -<h3 id='----arcade---snakemoji---main.rb'>Arcade - Snakemoji - main.rb</h3> +<h3 id='----arcade---snakemoji---main-rb'>Arcade - Snakemoji - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/snakemoji/app/main.rb # coding: utf-8 ################################ @@ -23218,7 +24729,7 @@ def defaults 🎮 end </code></pre> -<h3 id='----arcade---solar-system---main.rb'>Arcade - Solar System - main.rb</h3> +<h3 id='----arcade---solar-system---main-rb'>Arcade - Solar System - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/solar_system/app/main.rb # Focused tutorial video: https://s3.amazonaws.com/s3.dragonruby.org/dragonruby-nddnug-workshop.mp4 # Workshop/Presentation which provides motivation for creating a game engine: https://www.youtube.com/watch?v=S3CFce1arC8 @@ -23330,7 +24841,7 @@ def r end </code></pre> -<h3 id='----arcade---sound-golf---main.rb'>Arcade - Sound Golf - main.rb</h3> +<h3 id='----arcade---sound-golf---main-rb'>Arcade - Sound Golf - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/sound_golf/app/main.rb =begin @@ -23524,7 +25035,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----arcade---twinstick---main.rb'>Arcade - Twinstick - main.rb</h3> +<h3 id='----arcade---twinstick---main-rb'>Arcade - Twinstick - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_arcade/twinstick/app/main.rb def tick args args.state.player ||= {x: 600, y: 320, w: 80, h: 80, path: 'sprites/circle-white.png', vx: 0, vy: 0, health: 10, cooldown: 0, score: 0} @@ -23678,7 +25189,7 @@ def shoot_directional_vector args [dx, dy] end </code></pre> -<h3 id='----crafting---craft-game-starting-point---main.rb'>Crafting - Craft Game Starting Point - main.rb</h3> +<h3 id='----crafting---craft-game-starting-point---main-rb'>Crafting - Craft Game Starting Point - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_crafting/craft_game_starting_point/app/main.rb # ================================================== # A NOTE TO JAM CRAFT PARTICIPANTS: @@ -24105,7 +25616,128 @@ end $gtk.reset </code></pre> -<h3 id='----dev-tools---add-buttons-to-console---main.rb'>Dev Tools - Add Buttons To Console - main.rb</h3> +<h3 id='----crafting---farming-game-starting-point---main-rb'>Crafting - Farming Game Starting Point - main.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_crafting/farming_game_starting_point/app/main.rb +def tick args + args.state.tile_size = 80 + args.state.player_speed = 4 + args.state.player ||= tile(args, 7, 3, 0, 128, 180) + generate_map args + #press j to plant a green onion + if args.inputs.keyboard.j + #change this part you can change what you want to plant + args.state.walls << tile(args, ((args.state.player.x+80)/args.state.tile_size), ((args.state.player.y)/args.state.tile_size), 255, 255, 255) + args.state.plants << tile(args, ((args.state.player.x+80)/args.state.tile_size), ((args.state.player.y+80)/args.state.tile_size), 0, 160, 0) + end + # Adds walls, background, and player to args.outputs.solids so they appear on screen + args.outputs.solids << [0,0,1280,720, 237,189,101] + args.outputs.sprites << [0, 0, 1280, 720, 'sprites/background.png'] + args.outputs.solids << args.state.walls + args.outputs.solids << args.state.player + args.outputs.solids << args.state.plants + args.outputs.labels << [320, 640, "press J to plant", 3, 1, 255, 0, 0, 200] + + move_player args, -1, 0 if args.inputs.keyboard.left # x position decreases by 1 if left key is pressed + move_player args, 1, 0 if args.inputs.keyboard.right # x position increases by 1 if right key is pressed + move_player args, 0, 1 if args.inputs.keyboard.up # y position increases by 1 if up is pressed + move_player args, 0, -1 if args.inputs.keyboard.down # y position decreases by 1 if down is pressed +end + +# Sets position, size, and color of the tile +def tile args, x, y, *color + [x * args.state.tile_size, # sets definition for array using method parameters + y * args.state.tile_size, # multiplying by tile_size sets x and y to correct position using pixel values + args.state.tile_size, + args.state.tile_size, + *color] +end + +# Creates map by adding tiles to the wall, as well as a goal (that the player needs to reach) +def generate_map args + return if args.state.area + + # Creates the area of the map. There are 9 rows running horizontally across the screen + # and 16 columns running vertically on the screen. Any spot with a "1" is not + # open for the player to move into (and is green), and any spot with a "0" is available + # for the player to move in. + args.state.area = [ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], + ].reverse # reverses the order of the area collection + + # By reversing the order, the way that the area appears above is how it appears + # on the screen in the game. If we did not reverse, the map would appear inverted. + + #The wall starts off with no tiles. + args.state.walls = [] + args.state.plants = [] + + # If v is 1, a green tile is added to args.state.walls. + # If v is 2, a black tile is created as the goal. + args.state.area.map_2d do |y, x, v| + if v == 1 + args.state.walls << tile(args, x, y, 255, 160, 156) # green tile + end + end +end + +# Allows the player to move their box around the screen +def move_player args, *vector + box = args.state.player.shift_rect(vector) # box is able to move at an angle + + # If the player's box hits a wall, it is not able to move further in that direction + return if args.state.walls + .any_intersect_rect?(box) + + # Player's box is able to move at angles (not just the four general directions) fast + args.state.player = + args.state.player + .shift_rect(vector.x * args.state.player_speed, # if we don't multiply by speed, then + vector.y * args.state.player_speed) # the box will move extremely slow +end + +</code></pre> +<h3 id='----crafting---farming-game-starting-point---tests-rb'>Crafting - Farming Game Starting Point - tests.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_crafting/farming_game_starting_point/app/tests.rb +# For advanced users: +# You can put some quick verification tests here, any method +# that starts with the `test_` will be run when you save this file. + +# Here is an example test and game + +# To run the test: ./dragonruby mygame --eval app/tests.rb --no-tick + +class MySuperHappyFunGame + attr_gtk + + def tick + outputs.solids << [100, 100, 300, 300] + end +end + +def test_universe args, assert + game = MySuperHappyFunGame.new + game.args = args + game.tick + assert.true! args.outputs.solids.length == 1, "failure: a solid was not added after tick" + assert.false! 1 == 2, "failure: some how, 1 equals 2, the world is ending" + puts "test_universe completed successfully" +end + +puts "running tests" +$gtk.reset 100 +$gtk.log_level = :off +$gtk.tests.start + +</code></pre> +<h3 id='----dev-tools---add-buttons-to-console---main-rb'>Dev Tools - Add Buttons To Console - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_dev_tools/add_buttons_to_console/app/main.rb # You can customize the buttons that show up in the Console. class GTK::Console::Menu @@ -24167,7 +25799,7 @@ def tick args end </code></pre> -<h3 id='----dev-tools---animation-creator-starting-point---main.rb'>Dev Tools - Animation Creator Starting Point - main.rb</h3> +<h3 id='----dev-tools---animation-creator-starting-point---main-rb'>Dev Tools - Animation Creator Starting Point - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_dev_tools/animation_creator_starting_point/app/main.rb class OneBitLowrezPaint attr_gtk @@ -24272,12 +25904,12 @@ class OneBitLowrezPaint label = { x: b.x + state.buttons_frame_selection.size.half, y: b.y, text: "#{i + 1}", r: 180, g: 180, b: 180, - size_enum: -4, alignment_enum: 1 }.label + size_enum: -4, alignment_enum: 1 }.label! - selection_border = b.merge(r: 40, g: 40, b: 40).border + selection_border = b.merge(r: 40, g: 40, b: 40).border! if i == state.animation_frames_selected_index - selection_border = b.merge(r: 40, g: 230, b: 200).border + selection_border = b.merge(r: 40, g: 230, b: 200).border! end [selection_border, label] @@ -24616,7 +26248,7 @@ end # $gtk.reset </code></pre> -<h3 id='----dev-tools---tile-editor-starting-point---main.rb'>Dev Tools - Tile Editor Starting Point - main.rb</h3> +<h3 id='----dev-tools---tile-editor-starting-point---main-rb'>Dev Tools - Tile Editor Starting Point - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_dev_tools/tile_editor_starting_point/app/main.rb =begin @@ -25011,7 +26643,525 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----lowrez---nokia-3310---main.rb'>Lowrez - Nokia 3310 - main.rb</h3> +<h3 id='----dungeon-crawl---classics-jam---main-rb'>Dungeon Crawl - Classics Jam - main.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_dungeon_crawl/classics_jam/app/main.rb +class Game + attr_gtk + + def tick + defaults + render + input + calc + end + + def defaults + player.x ||= 640 + player.y ||= 360 + player.w ||= 16 + player.h ||= 16 + player.attacked_at ||= -1 + player.angle ||= 0 + player.future_player ||= future_player_position 0, 0 + player.projectiles ||= [] + player.damage ||= 0 + state.level ||= create_level level_one_template + end + + def render + outputs.sprites << level.walls.map do |w| + w.merge(path: 'sprites/square/gray.png') + end + + outputs.sprites << level.spawn_locations.map do |s| + s.merge(path: 'sprites/square/blue.png') + end + + outputs.sprites << player.projectiles.map do |p| + p.merge(path: 'sprites/square/blue.png') + end + + outputs.sprites << level.enemies.map do |e| + e.merge(path: 'sprites/square/red.png') + end + + outputs.sprites << player.merge(path: 'sprites/circle/green.png', angle: player.angle) + + outputs.labels << { x: 30, y: 30.from_top, text: "damage: #{player.damage || 0}" } + end + + def input + player.angle = inputs.directional_angle || player.angle + if inputs.controller_one.key_down.a || inputs.keyboard.key_down.space + player.attacked_at = state.tick_count + end + end + + def calc + calc_player + calc_projectiles + calc_enemies + calc_spawn_locations + end + + def calc_player + if player.attacked_at == state.tick_count + player.projectiles << { at: state.tick_count, + x: player.x, + y: player.y, + angle: player.angle, + w: 4, + h: 4 }.center_inside_rect(player) + end + + if player.attacked_at.elapsed_time > 5 + future_player = future_player_position inputs.left_right * 2, inputs.up_down * 2 + future_player_collision = future_collision player, future_player, level.walls + player.x = future_player_collision.x if !future_player_collision.dx_collision + player.y = future_player_collision.y if !future_player_collision.dy_collision + end + end + + def calc_projectile_collisions entities + entities.each do |e| + e.damage ||= 0 + player.projectiles.each do |p| + if !p.collided && (p.intersect_rect? e) + p.collided = true + e.damage += 1 + end + end + end + end + + def calc_projectiles + player.projectiles.map! do |p| + dx, dy = p.angle.vector 10 + p.merge(x: p.x + dx, y: p.y + dy) + end + + calc_projectile_collisions level.walls + level.enemies + level.spawn_locations + player.projectiles.reject! { |p| p.at.elapsed_time > 10000 } + player.projectiles.reject! { |p| p.collided } + level.enemies.reject! { |e| e.damage > e.hp } + level.spawn_locations.reject! { |s| s.damage > s.hp } + end + + def calc_enemies + level.enemies.map! do |e| + dx = 0 + dx = 1 if e.x < player.x + dx = -1 if e.x > player.x + dy = 0 + dy = 1 if e.y < player.y + dy = -1 if e.y > player.y + future_e = future_entity_position dx, dy, e + future_e_collision = future_collision e, future_e, level.enemies + level.walls + e.next_x = e.x + e.next_y = e.y + e.next_x = future_e_collision.x if !future_e_collision.dx_collision + e.next_y = future_e_collision.y if !future_e_collision.dy_collision + e + end + + level.enemies.map! do |e| + e.x = e.next_x + e.y = e.next_y + e + end + + level.enemies.each do |e| + player.damage += 1 if e.intersect_rect? player + end + end + + def calc_spawn_locations + level.spawn_locations.map! do |s| + s.merge(countdown: s.countdown - 1) + end + level.spawn_locations + .find_all { |s| s.countdown.neg? } + .each do |s| + s.countdown = s.rate + s.merge(countdown: s.rate) + new_enemy = create_enemy s + if !(level.enemies.find { |e| e.intersect_rect? new_enemy }) + level.enemies << new_enemy + end + end + end + + def create_enemy spawn_location + to_cell(spawn_location.ordinal_x, spawn_location.ordinal_y).merge hp: 2 + end + + def create_level level_template + { + walls: level_template.walls.map { |w| to_cell(w.ordinal_x, w.ordinal_y).merge(w) }, + enemies: [], + spawn_locations: level_template.spawn_locations.map { |s| to_cell(s.ordinal_x, s.ordinal_y).merge(s) } + } + end + + def level_one_template + { + walls: [{ ordinal_x: 25, ordinal_y: 20}, + { ordinal_x: 25, ordinal_y: 21}, + { ordinal_x: 25, ordinal_y: 22}, + { ordinal_x: 25, ordinal_y: 23}], + spawn_locations: [{ ordinal_x: 10, ordinal_y: 10, rate: 120, countdown: 0, hp: 5 }] + } + end + + def player + state.player ||= {} + end + + def level + state.level ||= {} + end + + def future_collision entity, future_entity, others + dx_collision = others.find { |o| o != entity && (o.intersect_rect? future_entity.dx) } + dy_collision = others.find { |o| o != entity && (o.intersect_rect? future_entity.dy) } + + { + dx_collision: dx_collision, + x: future_entity.dx.x, + dy_collision: dy_collision, + y: future_entity.dy.y + } + end + + def future_entity_position dx, dy, entity + { + dx: entity.merge(x: entity.x + dx), + dy: entity.merge(y: entity.y + dy), + both: entity.merge(x: entity.x + dx, y: entity.y + dy) + } + end + + def future_player_position dx, dy + future_entity_position dx, dy, player + end + + def to_cell ordinal_x, ordinal_y + { x: ordinal_x * 16, y: ordinal_y * 16, w: 16, h: 16 } + end +end + +def tick args + $game ||= Game.new + $game.args = args + $game.tick +end + +$gtk.reset +$game = nil + +</code></pre> +<h3 id='----fighting---special-move-inputs---main-rb'>Fighting - Special Move Inputs - main.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_fighting/01_special_move_inputs/app/main.rb +def tick args + #tick_instructions args, "Use LEFT and RIGHT arrow keys to move and SPACE to jump." + defaults args + render args + input args + calc args +end + +# sets default values and creates empty collections +# initialization only happens in the first frame +def defaults args + fiddle args + + args.state.tick_count = args.state.tick_count + args.state.bridge_top = 128 + args.state.player.x ||= 0 # initializes player's properties + args.state.player.y ||= args.state.bridge_top + args.state.player.w ||= 64 + args.state.player.h ||= 64 + args.state.player.dy ||= 0 + args.state.player.dx ||= 0 + args.state.player.r ||= 0 + args.state.game_over_at ||= 0 + args.state.animation_time ||=0 + + args.state.timeleft ||=0 + args.state.timeright ||=0 + args.state.lastpush ||=0 + + args.state.inputlist ||= ["j","k","l"] +end + +# sets enemy, player, hammer values +def fiddle args + args.state.gravity = -0.5 + args.state.player_jump_power = 10 # sets player values + args.state.player_jump_power_duration = 5 + args.state.player_max_run_speed = 20 + args.state.player_speed_slowdown_rate = 0.9 + args.state.player_acceleration = 0.9 +end + +# outputs objects onto the screen +def render args + if (args.state.player.dx < 0.01) && (args.state.player.dx > -0.01) + args.state.player.dx = 0 + end + + #move list + (args.layout.rect_group row: 0, col_from_right: 8, drow: 0.3, + merge: { vertical_alignment_enum: 0, size_enum: -2 }, + group: [ + { text: "move: WASD" }, + { text: "jump: Space" }, + { text: "attack forwards: J (while on ground" }, + { text: "attack upwards: K (while on groud)" }, + { text: "attack backwards: J (while on ground and holding A)" }, + { text: "attack downwards: K (while in air)" }, + { text: "dash attack: J, K in quick succession." }, + { text: "shield: hold J, K at the same time." }, + { text: "dash backwards: A, A in quick succession." }, + ]).into args.outputs.labels + + # registered moves + args.outputs.labels << { x: 0.to_layout_col, + y: 0.to_layout_row, + text: "input history", + size_enum: -2, + vertical_alignment_enum: 0 } + + (args.state.inputlist.take(5)).map do |s| + { text: s, size_enum: -2, vertical_alignment_enum: 0 } + end.yield_self do |group| + (args.layout.rect_group row: 0.3, col: 0, drow: 0.3, group: group).into args.outputs.labels + end + + + #sprites + player = [args.state.player.x, args.state.player.y, + args.state.player.w, args.state.player.h, + "sprites/square/white.png", + args.state.player.r] + + playershield = [args.state.player.x - 20, args.state.player.y - 10, + args.state.player.w + 20, args.state.player.h + 20, + "sprites/square/blue.png", + args.state.player.r, + 0] + + playerjab = [args.state.player.x + 32, args.state.player.y, + args.state.player.w, args.state.player.h, + "sprites/isometric/indigo.png", + args.state.player.r, + 0] + + playerupper = [args.state.player.x, args.state.player.y + 32, + args.state.player.w, args.state.player.h, + "sprites/isometric/indigo.png", + args.state.player.r+90, + 0] + + if ((args.state.tick_count - args.state.lastpush) <= 15) + if (args.state.inputlist[0] == "<<") + player = [args.state.player.x, args.state.player.y, + args.state.player.w, args.state.player.h, + "sprites/square/yellow.png", args.state.player.r] + end + + if (args.state.inputlist[0] == "shield") + player = [args.state.player.x, args.state.player.y, + args.state.player.w, args.state.player.h, + "sprites/square/indigo.png", args.state.player.r] + + playershield = [args.state.player.x - 10, args.state.player.y - 10, + args.state.player.w + 20, args.state.player.h + 20, + "sprites/square/blue.png", args.state.player.r, 50] + end + + if (args.state.inputlist[0] == "back-attack") + playerjab = [args.state.player.x - 20, args.state.player.y, + args.state.player.w - 10, args.state.player.h, + "sprites/isometric/indigo.png", args.state.player.r, 255] + end + + if (args.state.inputlist[0] == "forward-attack") + playerjab = [args.state.player.x + 32, args.state.player.y, + args.state.player.w, args.state.player.h, + "sprites/isometric/indigo.png", args.state.player.r, 255] + end + + if (args.state.inputlist[0] == "up-attack") + playerupper = [args.state.player.x, args.state.player.y + 32, + args.state.player.w, args.state.player.h, + "sprites/isometric/indigo.png", args.state.player.r + 90, 255] + end + + if (args.state.inputlist[0] == "dair") + playerupper = [args.state.player.x, args.state.player.y - 32, + args.state.player.w, args.state.player.h, + "sprites/isometric/indigo.png", args.state.player.r + 90, 255] + end + + if (args.state.inputlist[0] == "dash-attack") + playerupper = [args.state.player.x, args.state.player.y + 32, + args.state.player.w, args.state.player.h, + "sprites/isometric/violet.png", args.state.player.r + 90, 255] + + playerjab = [args.state.player.x + 32, args.state.player.y, + args.state.player.w, args.state.player.h, + "sprites/isometric/violet.png", args.state.player.r, 255] + end + end + + args.outputs.sprites << playerjab + args.outputs.sprites << playerupper + args.outputs.sprites << player + args.outputs.sprites << playershield + + args.outputs.solids << 20.map_with_index do |i| # uses 20 squares to form bridge + [i * 64, args.state.bridge_top - 64, 64, 64] + end +end + +# Performs calculations to move objects on the screen +def calc args + # Since velocity is the change in position, the change in x increases by dx. Same with y and dy. + args.state.player.x += args.state.player.dx + args.state.player.y += args.state.player.dy + + # Since acceleration is the change in velocity, the change in y (dy) increases every frame + args.state.player.dy += args.state.gravity + + # player's y position is either current y position or y position of top of + # bridge, whichever has a greater value + # ensures that the player never goes below the bridge + args.state.player.y = args.state.player.y.greater(args.state.bridge_top) + + # player's x position is either the current x position or 0, whichever has a greater value + # ensures that the player doesn't go too far left (out of the screen's scope) + args.state.player.x = args.state.player.x.greater(0) + + # player is not falling if it is located on the top of the bridge + args.state.player.falling = false if args.state.player.y == args.state.bridge_top + #args.state.player.rect = [args.state.player.x, args.state.player.y, args.state.player.h, args.state.player.w] # sets definition for player +end + +# Resets the player by changing its properties back to the values they had at initialization +def reset_player args + args.state.player.x = 0 + args.state.player.y = args.state.bridge_top + args.state.player.dy = 0 + args.state.player.dx = 0 + args.state.enemy.hammers.clear # empties hammer collection + args.state.enemy.hammer_queue.clear # empties hammer_queue + args.state.game_over_at = args.state.tick_count # game_over_at set to current frame (or passage of time) +end + +# Processes input from the user to move the player +def input args + if args.state.inputlist.length > 5 + args.state.inputlist.pop + end + + should_process_special_move = (args.inputs.keyboard.key_down.j) || + (args.inputs.keyboard.key_down.k) || + (args.inputs.keyboard.key_down.a) || + (args.inputs.keyboard.key_down.d) || + (args.inputs.controller_one.key_down.y) || + (args.inputs.controller_one.key_down.x) || + (args.inputs.controller_one.key_down.left) || + (args.inputs.controller_one.key_down.right) + + if (should_process_special_move) + if (args.inputs.keyboard.key_down.j && args.inputs.keyboard.key_down.k) || + (args.inputs.controller_one.key_down.x && args.inputs.controller_one.key_down.y) + args.state.inputlist.unshift("shield") + elsif (args.inputs.keyboard.key_down.k || args.inputs.controller_one.key_down.y) && + (args.state.inputlist[0] == "forward-attack") && ((args.state.tick_count - args.state.lastpush) <= 15) + args.state.inputlist.unshift("dash-attack") + args.state.player.dx = 20 + elsif (args.inputs.keyboard.key_down.j && args.inputs.keyboard.a) || + (args.inputs.controller_one.key_down.x && args.inputs.controller_one.key_down.left) + args.state.inputlist.unshift("back-attack") + elsif ( args.inputs.controller_one.key_down.x || args.inputs.keyboard.key_down.j) + args.state.inputlist.unshift("forward-attack") + elsif (args.inputs.keyboard.key_down.k || args.inputs.controller_one.key_down.y) && + (args.state.player.y > 128) + args.state.inputlist.unshift("dair") + elsif (args.inputs.keyboard.key_down.k || args.inputs.controller_one.key_down.y) + args.state.inputlist.unshift("up-attack") + elsif (args.inputs.controller_one.key_down.left || args.inputs.keyboard.key_down.a) && + (args.state.inputlist[0] == "<") && + ((args.state.tick_count - args.state.lastpush) <= 10) + args.state.inputlist.unshift("<<") + args.state.player.dx = -15 + elsif (args.inputs.controller_one.key_down.left || args.inputs.keyboard.key_down.a) + args.state.inputlist.unshift("<") + args.state.timeleft = args.state.tick_count + elsif (args.inputs.controller_one.key_down.right || args.inputs.keyboard.key_down.d) + args.state.inputlist.unshift(">") + end + + args.state.lastpush = args.state.tick_count + end + + if args.inputs.keyboard.space || args.inputs.controller_one.r2 # if the user presses the space bar + args.state.player.jumped_at ||= args.state.tick_count # jumped_at is set to current frame + + # if the time that has passed since the jump is less than the player's jump duration and + # the player is not falling + if args.state.player.jumped_at.elapsed_time < args.state.player_jump_power_duration && !args.state.player.falling + args.state.player.dy = args.state.player_jump_power # change in y is set to power of player's jump + end + end + + # if the space bar is in the "up" state (or not being pressed down) + if args.inputs.keyboard.key_up.space || args.inputs.controller_one.key_up.r2 + args.state.player.jumped_at = nil # jumped_at is empty + args.state.player.falling = true # the player is falling + end + + if args.inputs.left # if left key is pressed + if args.state.player.dx < -5 + args.state.player.dx = args.state.player.dx + else + args.state.player.dx = -5 + end + + elsif args.inputs.right # if right key is pressed + if args.state.player.dx > 5 + args.state.player.dx = args.state.player.dx + else + args.state.player.dx = 5 + end + else + args.state.player.dx *= args.state.player_speed_slowdown_rate # dx is scaled down + end + + if ((args.state.player.dx).abs > 5) #&& ((args.state.tick_count - args.state.lastpush) <= 10) + args.state.player.dx *= 0.95 + end +end + +def tick_instructions args, text, y = 715 + return if args.state.key_event_occurred + if args.inputs.mouse.click || + args.inputs.keyboard.directional_vector || + args.inputs.keyboard.key_down.enter || + args.inputs.keyboard.key_down.space || + args.inputs.keyboard.key_down.escape + args.state.key_event_occurred = true + end + + args.outputs.debug << [0, y - 50, 1280, 60].solid + args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label + args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label +end + +</code></pre> +<h3 id='----lowrez---nokia-3310---main-rb'>Lowrez - Nokia 3310 - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_lowrez/nokia_3310/app/main.rb require 'app/nokia.rb' @@ -25620,7 +27770,7 @@ def render_debug args text: text, size_enum: -1.5, r: 255, g: 255, b: 255 - }.label + }.label! end args.outputs.debug << { @@ -25630,7 +27780,7 @@ def render_debug args size_enum: -0.5, alignment_enum: 1, r: 255, g: 255, b: 255 - }.label + }.label! end def snake_demo args @@ -25640,7 +27790,7 @@ end $gtk.reset </code></pre> -<h3 id='----lowrez---nokia-3310---nokia.rb'>Lowrez - Nokia 3310 - nokia.rb</h3> +<h3 id='----lowrez---nokia-3310---nokia-rb'>Lowrez - Nokia 3310 - nokia.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_lowrez/nokia_3310/app/nokia.rb # Emulation of a 64x64 canvas. Don't change this file unless you know what you're doing :-) # Head over to main.rb and study the code there. @@ -25880,7 +28030,7 @@ module GTK g: 240, b: 216, a: 100 - }.line + }.line! end (NOKIA_WIDTH + 1).map_with_index do |i| @@ -25893,7 +28043,7 @@ module GTK g: 240, b: 216, a: 100 - }.line + }.line! end @args.state.overlay_rendered = true @@ -25903,7 +28053,7 @@ module GTK end </code></pre> -<h3 id='----lowrez---resolution-64x64---lowrez.rb'>Lowrez - Resolution 64x64 - lowrez.rb</h3> +<h3 id='----lowrez---resolution-64x64---lowrez-rb'>Lowrez - Resolution 64x64 - lowrez.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_lowrez/resolution_64x64/app/lowrez.rb # Emulation of a 64x64 canvas. Don't change this file unless you know what you're doing :-) # Head over to main.rb and study the code there. @@ -26077,7 +28227,7 @@ module GTK end </code></pre> -<h3 id='----lowrez---resolution-64x64---main.rb'>Lowrez - Resolution 64x64 - main.rb</h3> +<h3 id='----lowrez---resolution-64x64---main-rb'>Lowrez - Resolution 64x64 - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_lowrez/resolution_64x64/app/main.rb require 'app/lowrez.rb' @@ -26641,7 +28791,7 @@ def render_debug args g: 128, b: 128, a: 80 - }.line + }.line! args.outputs.static_debug << { x: LOWREZ_X_OFFSET + (i * 10), @@ -26652,7 +28802,7 @@ def render_debug args g: 128, b: 128, a: 80 - }.line + }.line! end end @@ -26679,7 +28829,7 @@ def render_debug args y: 720 - (i * 20), text: text, size_enum: -1.5 - }.label + }.label! end args.outputs.debug << { @@ -26688,15 +28838,382 @@ def render_debug args text: "INFO: dev mode is currently enabled. Comment out the invocation of ~render_debug~ within the ~tick~ method to hide the debug layer.", size_enum: -0.5, alignment_enum: 1 - }.label + }.label! +end + +$gtk.reset + +</code></pre> +<h3 id='----mario---jumping---main-rb'>Mario - Jumping - main.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_mario/01_jumping/app/main.rb +def tick args + defaults args + render args + input args + calc args +end + +def defaults args + args.state.player.x ||= args.grid.w.half + args.state.player.y ||= 0 + args.state.player.size ||= 100 + args.state.player.dy ||= 0 + args.state.player.action ||= :jumping + args.state.jump.power = 20 + args.state.jump.increase_frames = 10 + args.state.jump.increase_power = 1 + args.state.gravity = -1 +end + +def render args + args.outputs.sprites << { + x: args.state.player.x - + args.state.player.size.half, + y: args.state.player.y, + w: args.state.player.size, + h: args.state.player.size, + path: 'sprites/square/red.png' + } +end + +def input args + if args.inputs.keyboard.key_down.space + if args.state.player.action == :standing + args.state.player.action = :jumping + args.state.player.dy = args.state.jump.power + + # record when the action took place + current_frame = args.state.tick_count + args.state.player.action_at = current_frame + end + end + + # if the space bar is being held + if args.inputs.keyboard.key_held.space + # is the player jumping + is_jumping = args.state.player.action == :jumping + + # when was the jump performed + time_of_jump = args.state.player.action_at + + # how much time has passed since the jump + jump_elapsed_time = time_of_jump.elapsed_time + + # how much time is allowed for increasing power + time_allowed = args.state.jump.increase_frames + + # if the player is jumping + # and the elapsed time is less than + # the allowed time + if is_jumping && jump_elapsed_time < time_allowed + # increase the dy by the increase power + power_to_add = args.state.jump.increase_power + args.state.player.dy += power_to_add + end + end +end + +def calc args + if args.state.player.action == :jumping + args.state.player.y += args.state.player.dy + args.state.player.dy += args.state.gravity + end + + if args.state.player.y < 0 + args.state.player.y = 0 + args.state.player.action = :standing + end +end + +</code></pre> +<h3 id='----mario---jumping-and-collisions---main-rb'>Mario - Jumping And Collisions - main.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_mario/02_jumping_and_collisions/app/main.rb +class Game + attr_gtk + + def tick + defaults + render + input + calc + end + + def defaults + return if state.tick_count != 0 + + player.x = 64 + player.y = 800 + player.size = 50 + player.dx = 0 + player.dy = 0 + player.action = :falling + + player.max_speed = 20 + player.jump_power = 15 + player.jump_air_time = 15 + player.jump_increase_power = 1 + + state.gravity = -1 + state.drag = 0.001 + state.tile_size = 64 + state.tiles ||= [ + { ordinal_x: 0, ordinal_y: 0 }, + { ordinal_x: 1, ordinal_y: 0 }, + { ordinal_x: 2, ordinal_y: 0 }, + { ordinal_x: 3, ordinal_y: 0 }, + { ordinal_x: 4, ordinal_y: 0 }, + { ordinal_x: 5, ordinal_y: 0 }, + { ordinal_x: 6, ordinal_y: 0 }, + { ordinal_x: 7, ordinal_y: 0 }, + { ordinal_x: 8, ordinal_y: 0 }, + { ordinal_x: 9, ordinal_y: 0 }, + { ordinal_x: 10, ordinal_y: 0 }, + { ordinal_x: 11, ordinal_y: 0 }, + { ordinal_x: 12, ordinal_y: 0 }, + + { ordinal_x: 9, ordinal_y: 3 }, + { ordinal_x: 10, ordinal_y: 3 }, + { ordinal_x: 11, ordinal_y: 3 }, + ] + + tiles.each do |t| + t.rect = { x: t.ordinal_x * 64, + y: t.ordinal_y * 64, + w: 64, + h: 64 } + end + end + + def render + render_player + render_tiles + # render_grid + end + + def input + input_jump + input_move + end + + def calc + calc_player_rect + calc_left + calc_right + calc_below + calc_above + calc_player_dy + calc_player_dx + calc_game_over + end + + def render_player + outputs.sprites << { + x: player.x, + y: player.y, + w: player.size, + h: player.size, + path: 'sprites/square/red.png' + } + end + + def render_tiles + outputs.sprites << state.tiles.map do |t| + t.merge path: 'sprites/square/white.png', + x: t.ordinal_x * 64, + y: t.ordinal_y * 64, + w: 64, + h: 64 + end + end + + def render_grid + if state.tick_count == 0 + outputs[:grid].background_color = [0, 0, 0, 0] + outputs[:grid].borders << available_brick_locations + outputs[:grid].labels << available_brick_locations.map do |b| + [ + b.merge(text: "#{b.ordinal_x},#{b.ordinal_y}", + x: b.x + 2, + y: b.y + 2, + size_enum: -3, + vertical_alignment_enum: 0, + blendmode_enum: 0), + b.merge(text: "#{b.x},#{b.y}", + x: b.x + 2, + y: b.y + 2 + 20, + size_enum: -3, + vertical_alignment_enum: 0, + blendmode_enum: 0) + ] + end + end + + outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: :grid } + end + + def input_jump + if inputs.keyboard.key_down.space + player_jump + end + + if inputs.keyboard.key_held.space + player_jump_increase_air_time + end + end + + def input_move + if player.dx.abs < 20 + if inputs.keyboard.left + player.dx -= 2 + elsif inputs.keyboard.right + player.dx += 2 + end + end + end + + def calc_game_over + if player.y < -64 + player.x = 64 + player.y = 800 + player.dx = 0 + player.dy = 0 + end + end + + def calc_player_rect + player.rect = player_current_rect + player.next_rect = player_next_rect + player.prev_rect = player_prev_rect + end + + def calc_player_dx + player.dx = player_next_dx + player.x += player.dx + end + + def calc_player_dy + player.y += player.dy + player.dy = player_next_dy + end + + def calc_below + return unless player.dy < 0 + tiles_below = tiles_find { |t| t.rect.top <= player.prev_rect.y } + collision = tiles_find_colliding tiles_below, (player.rect.merge y: player.next_rect.y) + if collision + player.y = collision.rect.y + state.tile_size + player.dy = 0 + player.action = :standing + else + player.action = :falling + end + end + + def calc_left + return unless player.dx < 0 && player_next_dx < 0 + tiles_left = tiles_find { |t| t.rect.right <= player.prev_rect.left } + collision = tiles_find_colliding tiles_left, (player.rect.merge x: player.next_rect.x) + return unless collision + player.x = collision.rect.right + player.dx = 0 + end + + def calc_right + return unless player.dx > 0 && player_next_dx > 0 + tiles_right = tiles_find { |t| t.rect.left >= player.prev_rect.right } + collision = tiles_find_colliding tiles_right, (player.rect.merge x: player.next_rect.x) + return unless collision + player.x = collision.rect.left - player.rect.w + player.dx = 0 + end + + def calc_above + return unless player.dy > 0 + tiles_above = tiles_find { |t| t.rect.y >= player.prev_rect.y } + collision = tiles_find_colliding tiles_above, (player.rect.merge y: player.next_rect.y) + return unless collision + player.dy = 0 + player.y = collision.rect.bottom - player.rect.h + end + + def player_current_rect + { x: player.x, y: player.y, w: player.size, h: player.size } + end + + def available_brick_locations + (0..19).to_a + .product(0..11) + .map do |(ordinal_x, ordinal_y)| + { ordinal_x: ordinal_x, + ordinal_y: ordinal_y, + x: ordinal_x * 64, + y: ordinal_y * 64, + w: 64, + h: 64 } + end + end + + def player + state.player ||= args.state.new_entity :player + end + + def player_next_dy + player.dy + state.gravity + state.drag ** 2 * -1 + end + + def player_next_dx + player.dx * 0.8 + end + + def player_next_rect + player.rect.merge x: player.x + player_next_dx, + y: player.y + player_next_dy + end + + def player_prev_rect + player.rect.merge x: player.x - player.dx, + y: player.y - player.dy + end + + def player_jump + return if player.action != :standing + player.action = :jumping + player.dy = state.player.jump_power + current_frame = state.tick_count + player.action_at = current_frame + end + + def player_jump_increase_air_time + return if player.action != :jumping + return if player.action_at.elapsed_time >= player.jump_air_time + player.dy += player.jump_increase_power + end + + def tiles + state.tiles + end + + def tiles_find_colliding tiles, target + tiles.find { |t| t.rect.intersect_rect? target } + end + + def tiles_find &block + tiles.find_all(&block) + end +end + +def tick args + $game ||= Game.new + $game.args = args + $game.tick end $gtk.reset </code></pre> -<h3 id='----platformer---clepto-frog---main.rb'>Platformer - Clepto Frog - main.rb</h3> +<h3 id='----platformer---clepto-frog---main-rb'>Platformer - Clepto Frog - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/clepto_frog/app/main.rb -MAP_FILE_PATH = 'app/map.txt' +MAP_FILE_PATH = 'map.txt' require 'app/map.rb' @@ -26751,35 +29268,7 @@ class CleptoFrog def render_intro outputs.labels << [640, 700, "Clepto Frog", 4, 1] - if state.tick_count >= 120 - outputs.labels << [640, 620, "\"Uh... your office has a pet frog?\" - New Guy", - 4, 1, 0, 0, 0, 255 * 120.ease(60)] - end - - if state.tick_count >= 240 - outputs.labels << [640, 580, "\"Yep! His name is Clepto.\" - Jim", - 4, 1, 0, 0, 0, 255 * 240.ease(60)] - end - - if state.tick_count >= 360 - outputs.labels << [640, 540, "\"Uh...\" - New Guy", - 4, 1, 0, 0, 0, 255 * 360.ease(60)] - end - - if state.tick_count >= 480 - outputs.labels << [640, 500, "\"He steals mugs while we're away...\" - Jim", - 4, 1, 0, 0, 0, 255 * 480.ease(60)] - end - - if state.tick_count >= 600 - outputs.labels << [640, 460, "\"It's not a big deal, we take them back in the morning.\" - Jim", - 4, 1, 0, 0, 0, 255 * 600.ease(60)] - end - - outputs.sprites << [640 - 50, 360 - 50, 100, 100, - "sprites/square-green.png"] - - if state.tick_count == 800 + if state.tick_count == 120 state.scene = :game state.game_start_at = state.tick_count end @@ -26787,7 +29276,7 @@ class CleptoFrog def tick defaults - if state.scene == :intro && state.tick_count <= 800 + if state.scene == :intro && state.tick_count <= 120 render_intro elsif state.scene == :ending render_ending @@ -26890,15 +29379,15 @@ class CleptoFrog if state.god_mode # SHOW HIDE COLLISIONS - outputs.sprites << state.world.map do |x, y, w, h| - x = vx(x) - y = vy(y) + outputs.sprites << state.world.map do |rect| + x = vx(rect.x) + y = vy(rect.y) if x > -80 && x < 1280 && y > -80 && y < 720 { x: x, y: y, - w: vw(w || state.tile_size), - h: vh(h || state.tile_size), + w: vw(rect.w || state.tile_size), + h: vh(rect.h || state.tile_size), path: 'sprites/square-gray.png', a: 128 } @@ -26921,8 +29410,10 @@ class CleptoFrog # Creates sprite following mouse to help indicate which sprite you have selected - outputs.primitives << [inputs.mouse.position.x, inputs.mouse.position.y, - state.tile_size, state.tile_size, 'sprites/square-indigo.png', 0, 100].sprite + outputs.primitives << [inputs.mouse.position.x - 32 * state.camera_scale, + inputs.mouse.position.y - 32 * state.camera_scale, + state.tile_size * state.camera_scale, + state.tile_size * state.camera_scale, 'sprites/square-indigo.png', 0, 100].sprite end render_mini_map @@ -27003,6 +29494,29 @@ class CleptoFrog set_camera_scale 1 end + if inputs.mouse.click + state.id_seed += 1 + id = state.id_seed + x = state.camera_x + (inputs.mouse.click.x.fdiv(state.camera_scale) - 32) + y = state.camera_y + (inputs.mouse.click.y.fdiv(state.camera_scale) - 32) + x = ((x + 2).idiv 4) * 4 + y = ((y + 2).idiv 4) * 4 + w = 64 + h = 64 + candidate_rect = { id: id, x: x, y: y, w: w, h: h } + scaled_candidate_rect = { x: x + 30, y: y + 30, w: w - 60, h: h - 60 } + to_remove = state.world.find { |r| r.intersect_rect? scaled_candidate_rect } + if to_remove && args.inputs.keyboard.x + state.world.reject! { |r| r.id == to_remove.id } + else + state.world << candidate_rect + end + export_map + state.world_lookup = {} + state.world_collision_rects = nil + calc_world_lookup + end + if input_up? state.y += 10 state.dy = 0 @@ -27024,12 +29538,6 @@ class CleptoFrog if state.scene == :game process_inputs_player_movement process_inputs_god_mode - elsif state.scene == :intro - if args.inputs.keyboard.key_down.enter || args.inputs.keyboard.key_down.space - if Kernel.tick_count < 600 - Kernel.tick_count = 600 - end - end end end @@ -27127,17 +29635,6 @@ class CleptoFrog end end - def add_floors - # floors - state.world += [ - [0, 0, 10000, 40], - [0, 1670, 3250, 60], - [6691, 1653, 3290, 60], - [1521, 3792, 7370, 60], - [0, 5137, 3290, 60] - ] - end - def attempt_load_world_from_file return if state.world # exported_world = gtk.read_file(MAP_FILE_PATH) @@ -27145,26 +29642,11 @@ class CleptoFrog state.objects = [] if $collisions - $collisions.map do |x, y, w, h| - state.world << [x, y, w, h] + state.id_seed ||= 0 + $collisions.each do |x, y, w, h| + state.id_seed += 1 + state.world << { id: state.id_seed, x: x, y: y, w: w, h: h } end - - add_floors - # elsif exported_world - # exported_world.each_line.map do |l| - # tokens = l.strip.split(',') - # x = tokens[0].to_i - # y = tokens[1].to_i - # type = tokens[2].to_i - # if type == 1 - # state.world << [x, y, state.tile_size, state.tile_size] - # elsif type == 2 - # w, h, path = tokens[3..-1] - # state.objects << [x, y, w.to_i, h.to_i, path] - # end - # end - - # add_floors end if $mugs @@ -27185,23 +29667,24 @@ class CleptoFrog # Searches through the world and finds the cordinates that exist state.world_lookup = {} - state.world.each do |x, y, w, h| - state.world_lookup[[x, y, w || state.tile_size, h || state.tile_size]] = true + state.world.each do |rect| + state.world_lookup[rect.id] = rect end # Assigns collision rects for every sprite drawn state.world_collision_rects = state.world_lookup .keys - .map do |x, y, w, h| + .map do |key| + rect = state.world_lookup[key] s = state.tile_size - w ||= s - h ||= s + rect.w ||= s + rect.h ||= s { - args: [x, y, w, h], - left_right: [x, y + 4, w, h - 6], - top: [x + 4, y + 6, w - 8, h - 6], - bottom: [x + 1, y - 1, w - 2, h - 8], + args: rect, + left_right: { x: rect.x, y: rect.y + 4, w: rect.w, h: rect.h - 6 }, + top: { x: rect.x + 4, y: rect.y + 6, w: rect.w - 8, h: rect.h - 6 }, + bottom: { x: rect.x + 1, y: rect.y - 1, w: rect.w - 2, h: rect.h - 8 }, } end @@ -27257,12 +29740,21 @@ class CleptoFrog def end_of_tongue p = state.tongue_angle.vector(state.tongue_length) - [start_of_tongue.x + p.x, start_of_tongue.y + p.y] + { x: start_of_tongue.x + p.x, y: start_of_tongue.y + p.y } end def calc_shooting + calc_shooting_increment + calc_shooting_increment + calc_shooting_increment + calc_shooting_increment + calc_shooting_increment + calc_shooting_increment + end + + def calc_shooting_increment return unless state.action == :shooting - state.tongue_length += 30 + state.tongue_length += 5 potential_anchor = end_of_tongue if potential_anchor.x <= 0 state.anchor_point = potential_anchor @@ -27281,9 +29773,9 @@ class CleptoFrog state.action = :anchored outputs.sounds << 'sounds/attached.wav' else - anchor_rect = [potential_anchor.x - 5, potential_anchor.y - 5, 10, 10] + anchor_rect = { x: potential_anchor.x - 5, y: potential_anchor.y - 5, w: 10, h: 10 } collision = state.world_collision_rects.find_all do |v| - [v[:args].x, v[:args].y, v[:args].w, v[:args].h].intersect_rect?(anchor_rect) + v[:args].intersect_rect?(anchor_rect) end.first if collision state.anchor_point = potential_anchor @@ -27379,7 +29871,7 @@ class CleptoFrog .first return unless left_side_collisions - state.x = left_side_collisions[:left_right].right + state.x = left_side_collisions[:left_right].right + 1 state.dx = state.dy.abs * 0.8 state.collision_on_x = true end @@ -27394,7 +29886,7 @@ class CleptoFrog .first return unless right_side_collisions - state.x = right_side_collisions[:left_right].left - state.tile_size + state.x = right_side_collisions[:left_right].left - state.tile_size - 1 state.dx = state.dx.abs * 0.8 * -1 state.collision_on_x = true end @@ -27410,7 +29902,7 @@ class CleptoFrog .first return unless ceil_collisions - state.y = ceil_collisions[:bottom].y - state.tile_size + state.y = ceil_collisions[:bottom].y - state.tile_size - 1 state.dy = state.dy.abs * 0.8 * -1 state.collision_on_y = true end @@ -27423,13 +29915,17 @@ class CleptoFrog end def export_map - export_string = state.world.map do |x, y| - "#{x},#{y},1" - end + export_string = "$collisions = [\n" + export_string += state.world.map do |rect| + "[#{rect.x},#{rect.y},#{rect.w},#{rect.h}]," + end.join "\n" + export_string += "\n]\n\n" + export_string += "$mugs = [\n" export_string += state.objects.map do |x, y, w, h, path| - "#{x},#{y},2,#{w},#{h},#{path}" - end - gtk.write_file(MAP_FILE_PATH, export_string.join("\n")) + "[#{x},#{y},#{w},#{h},'#{path}']," + end.join "\n" + export_string += "\n]\n\n" + gtk.write_file(MAP_FILE_PATH, export_string) state.map_saved_at = state.tick_count end @@ -27562,7 +30058,7 @@ def tick_instructions args, text, y = 715 end </code></pre> -<h3 id='----platformer---clepto-frog---map.rb'>Platformer - Clepto Frog - map.rb</h3> +<h3 id='----platformer---clepto-frog---map-rb'>Platformer - Clepto Frog - map.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/clepto_frog/app/map.rb $collisions = [ [326, 463, 64, 64], @@ -28555,6 +31051,11 @@ $collisions = [ [4459, 3997, 64, 64], [76, 5215, 64, 64], [39, 5217, 64, 64], + [0, 0, 10000, 40], + [0, 1670, 3250, 60], + [6691, 1653, 3290, 60], + [1521, 3792, 7370, 60], + [0, 5137, 3290, 60] ] $mugs = [ @@ -28591,14 +31092,14 @@ $mugs = [ ] </code></pre> -<h3 id='----platformer---gorillas-basic---credits.txt'>Platformer - Gorillas Basic - credits.txt</h3> +<h3 id='----platformer---gorillas-basic---credits-txt'>Platformer - Gorillas Basic - credits.txt</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/gorillas_basic/CREDITS.txt code: Amir Rajan, https://twitter.com/amirrajan graphics: Nick Culbertson, https://twitter.com/MobyPixel </code></pre> -<h3 id='----platformer---gorillas-basic---main.rb'>Platformer - Gorillas Basic - main.rb</h3> +<h3 id='----platformer---gorillas-basic---main-rb'>Platformer - Gorillas Basic - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/gorillas_basic/app/main.rb class YouSoBasicGorillas attr_accessor :outputs, :grid, :state, :inputs @@ -28975,7 +31476,7 @@ def tick args end </code></pre> -<h3 id='----platformer---gorillas-basic---tests.rb'>Platformer - Gorillas Basic - tests.rb</h3> +<h3 id='----platformer---gorillas-basic---tests-rb'>Platformer - Gorillas Basic - tests.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/gorillas_basic/app/tests.rb $gtk.reset 100 $gtk.supress_framerate_warning = true @@ -28983,7 +31484,7 @@ $gtk.require 'app/tests/building_generation_tests.rb' $gtk.tests.start </code></pre> -<h3 id='----platformer---gorillas-basic---tests---building_generation_tests.rb'>Platformer - Gorillas Basic - Tests - building_generation_tests.rb</h3> +<h3 id='----platformer---gorillas-basic---tests---building_generation_tests-rb'>Platformer - Gorillas Basic - Tests - building_generation_tests.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/gorillas_basic/app/tests/building_generation_tests.rb def test_solids args, assert game = YouSoBasicGorillas.new @@ -29002,7 +31503,7 @@ def test_solids args, assert end </code></pre> -<h3 id='----platformer---the-little-probe---main.rb'>Platformer - The Little Probe - main.rb</h3> +<h3 id='----platformer---the-little-probe---main-rb'>Platformer - The Little Probe - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/the_little_probe/app/main.rb class FallingCircle attr_gtk @@ -29400,6 +31901,7 @@ class FallingCircle end def load_lines file + return unless state.snaps data = gtk.read_file(file) || "" data.each_line .reject { |l| l.strip.length == 0 } @@ -29458,10 +31960,10 @@ class FallingCircle results[:point] = { x: x, y: y } results[:rect] = { x: x - radius, y: y - radius, w: radius * 2, h: radius * 2 } results[:trajectory] = trajectory(results) - results[:impacts] = terrain.find_all { |t| line_near_rect? results[:rect], t }.map do |t| + results[:impacts] = terrain.find_all { |t| t && (line_near_rect? results[:rect], t) }.map do |t| { terrain: t, - point: geometry.line_intersect(results[:trajectory], t), + point: geometry.line_intersect(results[:trajectory], t, replace_infinity: 1000), type: :terrain } end.reject { |t| !point_within_line? t[:point], t[:terrain] } @@ -29469,10 +31971,10 @@ class FallingCircle results[:impacts] += lava.find_all { |t| line_near_rect? results[:rect], t }.map do |t| { terrain: t, - point: geometry.line_intersect(results[:trajectory], t), + point: geometry.line_intersect(results[:trajectory], t, replace_infinity: 1000), type: :lava } - end.reject { |t| !point_within_line? t[:point], t[:terrain] } + end.reject { |t| !t || (!point_within_line? t[:point], t[:terrain]) } results end @@ -29485,6 +31987,7 @@ class FallingCircle end def calc_terrains_to_monitor + return unless circle.impacts circle.impact = nil circle.impacts.each do |i| circle.terrains_to_monitor[i[:terrain]] ||= { @@ -29893,7 +32396,7 @@ def reset end </code></pre> -<h3 id='----platformer---the-little-probe---data---level.txt'>Platformer - The Little Probe - Data - level.txt</h3> +<h3 id='----platformer---the-little-probe---data---level-txt'>Platformer - The Little Probe - Data - level.txt</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/the_little_probe/data/level.txt 640,8840,1180,8840 -60,10220,0,9960 @@ -31377,7 +33880,7 @@ end 1910,11280,2200,11180 923.0029599285435,11398.99893503157,1264.002959928544,11351.99893503157 </code></pre> -<h3 id='----platformer---the-little-probe---data---level_lava.txt'>Platformer - The Little Probe - Data - level_lava.txt</h3> +<h3 id='----platformer---the-little-probe---data---level_lava-txt'>Platformer - The Little Probe - Data - level_lava.txt</h3> <pre><code class="language-ruby"># ./samples/99_genre_platformer/the_little_probe/data/level_lava.txt 100,10740,500,10780 500,10780,960,10760 @@ -31615,7 +34118,7 @@ end 2200,11130,2360,11090 1840,11230,2200,11130 </code></pre> -<h3 id='----rpg-narrative---choose-your-own-adventure---decision.rb'>Rpg Narrative - Choose Your Own Adventure - decision.rb</h3> +<h3 id='----rpg-narrative---choose-your-own-adventure---decision-rb'>Rpg Narrative - Choose Your Own Adventure - decision.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/choose_your_own_adventure/app/decision.rb # Hey there! Welcome to Four Decisions. Here is how you # create your decision tree. Remove =being and =end from the text to @@ -31656,7 +34159,7 @@ def game end </code></pre> -<h3 id='----rpg-narrative---choose-your-own-adventure---main.rb'>Rpg Narrative - Choose Your Own Adventure - main.rb</h3> +<h3 id='----rpg-narrative---choose-your-own-adventure---main-rb'>Rpg Narrative - Choose Your Own Adventure - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/choose_your_own_adventure/app/main.rb =begin @@ -31792,7 +34295,7 @@ end $gtk.reset </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---lowrez_simulator.rb'>Rpg Narrative - Return Of Serenity - lowrez_simulator.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---lowrez_simulator-rb'>Rpg Narrative - Return Of Serenity - lowrez_simulator.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/lowrez_simulator.rb ################################################################################### # YOU CAN PLAY AROUND WITH THE CODE BELOW, BUT USE CAUTION AS THIS IS WHAT EMULATES @@ -31887,7 +34390,7 @@ def render_gridlines_if_needed args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---main.rb'>Rpg Narrative - Return Of Serenity - main.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---main-rb'>Rpg Narrative - Return Of Serenity - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/main.rb require 'app/require.rb' @@ -32364,7 +34867,7 @@ def player_xs args, x, y end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---require.rb'>Rpg Narrative - Return Of Serenity - require.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---require-rb'>Rpg Narrative - Return Of Serenity - require.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/require.rb require 'app/lowrez_simulator.rb' require 'app/storyline_day_one.rb' @@ -32379,7 +34882,7 @@ require 'app/storyline_final_decision.rb' require 'app/storyline.rb' </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline.rb'>Rpg Narrative - Return Of Serenity - storyline.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline-rb'>Rpg Narrative - Return Of Serenity - storyline.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline.rb def hotspot_top [4, 61, 56, 3] @@ -32529,7 +35032,7 @@ def reload_current_scene end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_anka.rb'>Rpg Narrative - Return Of Serenity - storyline_anka.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_anka-rb'>Rpg Narrative - Return Of Serenity - storyline_anka.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_anka.rb def anka_inside_room args { @@ -32660,7 +35163,7 @@ def replied_to_anka_back_home args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_blinking_light.rb'>Rpg Narrative - Return Of Serenity - storyline_blinking_light.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_blinking_light-rb'>Rpg Narrative - Return Of Serenity - storyline_blinking_light.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_blinking_light.rb def the_blinking_light args { @@ -32739,7 +35242,7 @@ def blinking_light_inside_mainframe args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_day_one.rb'>Rpg Narrative - Return Of Serenity - storyline_day_one.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_day_one-rb'>Rpg Narrative - Return Of Serenity - storyline_day_one.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_day_one.rb def day_one_beginning args { @@ -32949,7 +35452,7 @@ def explaining_the_special_power_inside_computer args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_final_decision.rb'>Rpg Narrative - Return Of Serenity - storyline_final_decision.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_final_decision-rb'>Rpg Narrative - Return Of Serenity - storyline_final_decision.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_final_decision.rb def final_decision_side_of_home args { @@ -33089,7 +35592,7 @@ def final_decision_ship_status_shared args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_final_message.rb'>Rpg Narrative - Return Of Serenity - storyline_final_message.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_final_message-rb'>Rpg Narrative - Return Of Serenity - storyline_final_message.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_final_message.rb def final_message_sad args { @@ -33309,7 +35812,7 @@ def final_message_summary args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_serenity_alive.rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_alive.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_serenity_alive-rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_alive.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_serenity_alive.rb def serenity_alive_side_of_home args { @@ -33532,7 +36035,7 @@ def serenity_alive_current_message args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_serenity_bio.rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_bio.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_serenity_bio-rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_bio.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_serenity_bio.rb def serenity_bio_infront_of_home args { @@ -33687,7 +36190,7 @@ def bad_dream_last_reply args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_serenity_introduction.rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_introduction.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_serenity_introduction-rb'>Rpg Narrative - Return Of Serenity - storyline_serenity_introduction.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_serenity_introduction.rb # decision_graph "Message from Sasha", # "I should reply.", @@ -33786,7 +36289,7 @@ def replied_to_introduction_side_of_home args end </code></pre> -<h3 id='----rpg-narrative---return-of-serenity---storyline_speed_of_light.rb'>Rpg Narrative - Return Of Serenity - storyline_speed_of_light.rb</h3> +<h3 id='----rpg-narrative---return-of-serenity---storyline_speed_of_light-rb'>Rpg Narrative - Return Of Serenity - storyline_speed_of_light.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_speed_of_light.rb def speed_of_light_front_of_home args { @@ -33894,7 +36397,7 @@ def speed_of_light_end_of_day args end </code></pre> -<h3 id='----rpg-roguelike---roguelike-starting-point---constants.rb'>Rpg Roguelike - Roguelike Starting Point - constants.rb</h3> +<h3 id='----rpg-roguelike---roguelike-starting-point---constants-rb'>Rpg Roguelike - Roguelike Starting Point - constants.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_roguelike/01_roguelike_starting_point/app/constants.rb SHOW_LEGEND = true SOURCE_TILE_SIZE = 16 @@ -33906,7 +36409,7 @@ TILE_B = 0 TILE_A = 255 </code></pre> -<h3 id='----rpg-roguelike---roguelike-starting-point---legend.rb'>Rpg Roguelike - Roguelike Starting Point - legend.rb</h3> +<h3 id='----rpg-roguelike---roguelike-starting-point---legend-rb'>Rpg Roguelike - Roguelike Starting Point - legend.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_roguelike/01_roguelike_starting_point/app/legend.rb def tick_legend args return unless SHOW_LEGEND @@ -33975,7 +36478,7 @@ def tick_legend args end </code></pre> -<h3 id='----rpg-roguelike---roguelike-starting-point---main.rb'>Rpg Roguelike - Roguelike Starting Point - main.rb</h3> +<h3 id='----rpg-roguelike---roguelike-starting-point---main-rb'>Rpg Roguelike - Roguelike Starting Point - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_roguelike/01_roguelike_starting_point/app/main.rb require 'app/constants.rb' require 'app/sprite_lookup.rb' @@ -34076,7 +36579,7 @@ def tile_in_game x, y, tile_key end </code></pre> -<h3 id='----rpg-roguelike---roguelike-starting-point---sprite_lookup.rb'>Rpg Roguelike - Roguelike Starting Point - sprite_lookup.rb</h3> +<h3 id='----rpg-roguelike---roguelike-starting-point---sprite_lookup-rb'>Rpg Roguelike - Roguelike Starting Point - sprite_lookup.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_roguelike/01_roguelike_starting_point/app/sprite_lookup.rb def sprite_lookup { @@ -34204,7 +36707,7 @@ end $gtk.args.state.reserved.sprite_lookup = sprite_lookup </code></pre> -<h3 id='----rpg-roguelike---roguelike-line-of-sight---main.rb'>Rpg Roguelike - Roguelike Line Of Sight - main.rb</h3> +<h3 id='----rpg-roguelike---roguelike-line-of-sight---main-rb'>Rpg Roguelike - Roguelike Line Of Sight - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_roguelike/02_roguelike_line_of_sight/app/main.rb =begin @@ -34647,7 +37150,7 @@ def tick args end </code></pre> -<h3 id='----rpg-tactical---hexagonal-grid---main.rb'>Rpg Tactical - Hexagonal Grid - main.rb</h3> +<h3 id='----rpg-tactical---hexagonal-grid---main-rb'>Rpg Tactical - Hexagonal Grid - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_tactical/hexagonal_grid/app/main.rb class HexagonTileGame attr_gtk @@ -34719,7 +37222,7 @@ end $gtk.reset </code></pre> -<h3 id='----rpg-tactical---isometric-grid---main.rb'>Rpg Tactical - Isometric Grid - main.rb</h3> +<h3 id='----rpg-tactical---isometric-grid---main-rb'>Rpg Tactical - Isometric Grid - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_tactical/isometric_grid/app/main.rb class Isometric attr_accessor :grid, :inputs, :state, :outputs @@ -34985,7 +37488,150 @@ def tick args end </code></pre> -<h3 id='----rpg-topdown---topdown-starting-point---main.rb'>Rpg Topdown - Topdown Starting Point - main.rb</h3> +<h3 id='----rpg-topdown---topdown-casino---main-rb'>Rpg Topdown - Topdown Casino - main.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_rpg_topdown/topdown_casino/app/main.rb +$gtk.reset + +def coinflip + rand < 0.5 +end + +class Game + attr_accessor :args + + def text_font + return nil #"rpg.ttf" + end + + def text_color + [ 255, 255, 255, 255 ] + end + + def set_gem_values + @args.state.gem0 = ((coinflip) ? 100 : 20) + @args.state.gem1 = ((coinflip) ? -10 : -50) + @args.state.gem2 = ((coinflip) ? -10 : -30) + if coinflip + tmp = @args.state.gem0 + @args.state.gem0 = @args.state.gem1 + @args.state.gem1 = tmp + end + if coinflip + tmp = @args.state.gem1 + @args.state.gem1 = @args.state.gem2 + @args.state.gem2 = tmp + end + if coinflip + tmp = @args.state.gem0 + @args.state.gem0 = @args.state.gem2 + @args.state.gem2 = tmp + end + end + + def initialize args + @args = args + @args.state.animticks = 0 + @args.state.score = 0 + @args.state.gem_chosen = false + @args.state.round_finished = false + @args.state.gem0_x = 197 + @args.state.gem0_y = 720-274 + @args.state.gem1_x = 623 + @args.state.gem1_y = 720-274 + @args.state.gem2_x = 1049 + @args.state.gem2_y = 720-274 + @args.state.hero_sprite = "sprites/herodown100.png" + @args.state.hero_x = 608 + @args.state.hero_y = 720-656 + set_gem_values + end + + def render_gem_value x, y, gem + if @args.state.gem_chosen + @args.outputs.labels << [ x, y + 96, gem.to_s, 1, 1, *text_color, text_font ] + end + end + + def render + gemsprite = ((@args.state.animticks % 400) < 200) ? 'sprites/gem200.png' : 'sprites/gem400.png' + @args.outputs.background_color = [ 0, 0, 0, 255 ] + @args.outputs.sprites << [608, 720-150, 64, 64, 'sprites/oldman.png'] + @args.outputs.sprites << [300, 720-150, 64, 64, 'sprites/fire.png'] + @args.outputs.sprites << [900, 720-150, 64, 64, 'sprites/fire.png'] + @args.outputs.sprites << [@args.state.gem0_x, @args.state.gem0_y, 32, 64, gemsprite] + @args.outputs.sprites << [@args.state.gem1_x, @args.state.gem1_y, 32, 64, gemsprite] + @args.outputs.sprites << [@args.state.gem2_x, @args.state.gem2_y, 32, 64, gemsprite] + @args.outputs.sprites << [@args.state.hero_x, @args.state.hero_y, 64, 64, @args.state.hero_sprite] + + @args.outputs.labels << [ 630, 720-30, "IT'S A SECRET TO EVERYONE.", 1, 1, *text_color, text_font ] + @args.outputs.labels << [ 50, 720-85, @args.state.score.to_s, 1, 1, *text_color, text_font ] + render_gem_value @args.state.gem0_x, @args.state.gem0_y, @args.state.gem0 + render_gem_value @args.state.gem1_x, @args.state.gem1_y, @args.state.gem1 + render_gem_value @args.state.gem2_x, @args.state.gem2_y, @args.state.gem2 + end + + def calc + @args.state.animticks += 16 + + return unless @args.state.gem_chosen + @args.state.round_finished_debounce ||= 60 * 3 + @args.state.round_finished_debounce -= 1 + return if @args.state.round_finished_debounce > 0 + + @args.state.gem_chosen = false + @args.state.hero.sprite[0] = 'sprites/herodown100.png' + @args.state.hero.sprite[1] = 608 + @args.state.hero.sprite[2] = 656 + @args.state.round_finished_debounce = nil + set_gem_values + end + + def walk xdir, ydir, anim + @args.state.hero_sprite = "sprites/#{anim}#{(((@args.state.animticks % 200) < 100) ? '100' : '200')}.png" + @args.state.hero_x += 5 * xdir + @args.state.hero_y += 5 * ydir + end + + def check_gem_touching gem_x, gem_y, gem + return if @args.state.gem_chosen + herorect = [ @args.state.hero_x, @args.state.hero_y, 64, 64 ] + return if !herorect.intersect_rect?([gem_x, gem_y, 32, 64]) + @args.state.gem_chosen = true + @args.state.score += gem + @args.outputs.sounds << ((gem < 0) ? 'sounds/lose.wav' : 'sounds/win.wav') + end + + def input + if @args.inputs.keyboard.key_held.left + walk(-1.0, 0.0, 'heroleft') + elsif @args.inputs.keyboard.key_held.right + walk(1.0, 0.0, 'heroright') + elsif @args.inputs.keyboard.key_held.up + walk(0.0, 1.0, 'heroup') + elsif @args.inputs.keyboard.key_held.down + walk(0.0, -1.0, 'herodown') + end + + check_gem_touching(@args.state.gem0_x, @args.state.gem0_y, @args.state.gem0) + check_gem_touching(@args.state.gem1_x, @args.state.gem1_y, @args.state.gem1) + check_gem_touching(@args.state.gem2_x, @args.state.gem2_y, @args.state.gem2) + end + + def tick + input + calc + render + end +end + +def tick args + args.state.game ||= Game.new args + args.state.game.args = args + args.state.game.tick +end + +</code></pre> +<h3 id='----rpg-topdown---topdown-starting-point---main-rb'>Rpg Topdown - Topdown Starting Point - main.rb</h3> <pre><code class="language-ruby"># ./samples/99_genre_rpg_topdown/topdown_starting_point/app/main.rb =begin @@ -35097,11 +37743,190 @@ def move_player args, *vector end </code></pre> +<h3 id='----teenytiny---teenytiny-starting-point---main-rb'>Teenytiny - Teenytiny Starting Point - main.rb</h3> +<pre><code class="language-ruby"># ./samples/99_genre_teenytiny/teenytiny_starting_point/app/main.rb +# full documenation is at http://docs.dragonruby.org +# be sure to come to the discord if you hit any snags: http://discord.dragonruby.org +def tick args + # ==================================================== + # initialize default variables + # ==================================================== + + # ruby has an operator called ||= which means "only initialize this if it's nil" + args.state.count_down ||= 20 * 60 # set the count down to 20 seconds + # set the initial position of the target + args.state.target ||= { x: args.grid.w.half, + y: args.grid.h.half, + w: 20, + h: 20 } + + # set the initial position of the player + args.state.player ||= { x: 50, + y: 50, + w: 20, + h: 20 } + + # set the player movement speed + args.state.player_speed ||= 5 + + # set the score + args.state.score ||= 0 + args.state.teleports ||= 3 + + # set the instructions + args.state.instructions ||= "Get to the red goal! Use arrow keys to move. Spacebar to teleport (use them carefully)!" + + # ==================================================== + # render the game + # ==================================================== + args.outputs.labels << { x: args.grid.w.half, y: args.grid.h - 10, + text: args.state.instructions, + alignment_enum: 1 } + + # check if it's game over. if so, then render game over + # otherwise render the current time left + if game_over? args + args.outputs.labels << { x: args.grid.w.half, + y: args.grid.h - 40, + text: "game over! (press r to start over)", + alignment_enum: 1 } + else + args.outputs.labels << { x: args.grid.w.half, + y: args.grid.h - 40, + text: "time left: #{(args.state.count_down.idiv 60) + 1}", + alignment_enum: 1 } + end + + # render the score + args.outputs.labels << { x: args.grid.w.half, + y: args.grid.h - 70, + text: "score: #{args.state.score}", + alignment_enum: 1 } + + # render the player with teleport count + args.outputs.sprites << { x: args.state.player.x, + y: args.state.player.y, + w: args.state.player.w, + h: args.state.player.h, + path: 'sprites/square-green.png' } + + args.outputs.labels << { x: args.state.player.x + 10, + y: args.state.player.y + 40, + text: "teleports: #{args.state.teleports}", + alignment_enum: 1, size_enum: -2 } + + # render the target + args.outputs.sprites << { x: args.state.target.x, + y: args.state.target.y, + w: args.state.target.w, + h: args.state.target.h, + path: 'sprites/square-red.png' } + + # ==================================================== + # run simulation + # ==================================================== + + # count down calculation + args.state.count_down -= 1 + args.state.count_down = -1 if args.state.count_down < -1 + + # ==================================================== + # process player input + # ==================================================== + # if it isn't game over let them move + if !game_over? args + dir_y = 0 + dir_x = 0 + + # determine the change horizontally + if args.inputs.keyboard.up + dir_y += args.state.player_speed + elsif args.inputs.keyboard.down + dir_y -= args.state.player_speed + end + + # determine the change vertically + if args.inputs.keyboard.left + dir_x -= args.state.player_speed + elsif args.inputs.keyboard.right + dir_x += args.state.player_speed + end + + # determine if teleport can be used + if args.inputs.keyboard.key_down.space && args.state.teleports > 0 + args.state.teleports -= 1 + dir_x *= 20 + dir_y *= 20 + end + + # apply change to player + args.state.player.x += dir_x + args.state.player.y += dir_y + else + # if r is pressed, reset the game + if args.inputs.keyboard.key_down.r + $gtk.reset + return + end + end + + # ==================================================== + # determine score + # ==================================================== + + # calculate new score if the player is at goal + if !game_over? args + + # if the player is at the goal, then move the goal + if args.state.player.intersect_rect? args.state.target + # increment the goal + args.state.score += 1 + + # move the goal to a random location + args.state.target = { x: (rand args.grid.w), y: (rand args.grid.h), w: 20, h: 20 } + + # make sure the goal is inside the view area + if args.state.target.x < 0 + args.state.target.x += 20 + elsif args.state.target.x > 1280 + args.state.target.x -= 20 + end + + # make sure the goal is inside the view area + if args.state.target.y < 0 + args.state.target.y += 20 + elsif args.state.target.y > 720 + args.state.target.y -= 20 + end + end + end +end + +def game_over? args + args.state.count_down < 0 +end + +$gtk.reset + +</code></pre> +<h3 id='----teenytiny---teenytiny-starting-point---license-txt'>Teenytiny - Teenytiny Starting Point - license.txt</h3> +<pre><code class="language-ruby"># ./samples/99_genre_teenytiny/teenytiny_starting_point/license.txt +Copyright 2019 DragonRuby LLC + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +</code></pre> <h2 id='---oss'>OSS</h2> <p> Follows is a source code listing for all files that have been open sourced. This code can be found online at <a href='https://github.com/DragonRuby/dragonruby-game-toolkit-contrib/'>https://github.com/DragonRuby/dragonruby-game-toolkit-contrib/</a>. </p> -<h3 id='----args.rb'>args.rb</h3> +<h3 id='----args-rb'>args.rb</h3> <pre><code class="language-ruby"># ./dragon/args.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -35115,66 +37940,32 @@ module GTK class Args include ArgsDeprecated include Serialize - - # Contains information related to input devices and input events. - # - # @return [Inputs] + attr_accessor :cvars attr_accessor :inputs - - # Contains the means to interact with output devices such as the screen. - # - # @return [Outputs] attr_accessor :outputs - - # Contains the means to interact with the audio mixer. - # - # @return [Hash] attr_accessor :audio - - # Contains display size information to assist in positioning things on the screen. - # - # @return [Grid] attr_accessor :grid - - # Provides access to game play recording facilities within Game Toolkit. - # - # @return [Recording] attr_accessor :recording - - # Gives you access to geometry related functions. - # - # @return [Geometry] attr_accessor :geometry - attr_accessor :fn - - # This is where you'll put state associated with your video game. - # - # @return [OpenEntity] attr_accessor :state - - # Gives you access to the top level DragonRuby runtime. - # - # @return [Runtime] + attr_accessor :temp_state attr_accessor :runtime alias_method :gtk, :runtime - attr_accessor :passes - attr_accessor :wizards - attr_accessor :layout - attr_accessor :easing - attr_accessor :string def initialize runtime, recording @inputs = Inputs.new @outputs = Outputs.new args: self + @cvars = {} @audio = {} @passes = [] @state = OpenEntity.new + @temp_state = OpenEntity.new @state.tick_count = -1 @runtime = runtime @recording = recording @@ -35204,11 +37995,12 @@ module GTK def serialize { - state: state.as_hash, - inputs: inputs.serialize, - passes: passes.serialize, - outputs: outputs.serialize, - grid: grid.serialize + state: state.as_hash, + temp_state: temp_state.as_hash, + inputs: inputs.serialize, + passes: passes.serialize, + outputs: outputs.serialize, + grid: grid.serialize } end @@ -35340,11 +38132,28 @@ module GTK def autocomplete_methods [:inputs, :outputs, :gtk, :state, :geometry, :audio, :grid, :layout, :fn] end + + def method_missing name, *args, &block + if (args.length <= 1) && (@state.as_hash.key? name) + raise <<-S +* ERROR - :#{name} method missing on ~#{self.class.name}~. +The method + :#{name} +with args + #{args} +doesn't exist on #{inspect}. +** POSSIBLE SOLUTION - ~args.state.#{name}~ exists. +Did you forget ~.state~ before ~.#{name}~? +S + end + + super + end end end </code></pre> -<h3 id='----assert.rb'>assert.rb</h3> +<h3 id='----assert-rb'>assert.rb</h3> <pre><code class="language-ruby"># ./dragon/assert.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -35372,10 +38181,10 @@ To add an assertion open up this class and write: class Assert def custom_assertion actual, expected, message = nil - # this tell Game Toolkit that an assertion was performed (so that the test isn't marked inconclusive). + # this tells Game Toolkit that an assertion was performed (so that the test isn't marked inconclusive). @assertion_performed = true - # perform your custom logic here and rais an exception to denote a failure. + # perform your custom logic here and raise an exception to denote a failure. raise "Some Error. #{message}." end @@ -35385,14 +38194,14 @@ end attr :assertion_performed =begin -Us this if you are throwing your own exceptions and you want to mark the tests as ran (so that it wont be marked as inconclusive). +Use this if you are throwing your own exceptions and you want to mark the tests as ran (so that it wont be marked as inconclusive). =end def ok! @assertion_performed = true end =begin -Assert if a value is a thruthy value. All assert method take an optional final parameter that is the message to display to the user. +Assert if a value is a truthy value. All assert methods take an optional final parameter that is the message to display to the user. @example @@ -35446,7 +38255,7 @@ end @assertion_performed = true if actual != expected actual_string = "#{actual}#{actual.nil? ? " (nil) " : " " }".strip - message = "actual:\n#{actual_string}\n\ndid not equal\n\nexpected:\n#{expected}.\n#{message}" + message = "actual:\n#{actual_string}\n\ndid not equal\n\nexpected:\n#{expected}\n#{message}" raise message end nil @@ -35456,7 +38265,7 @@ end @assertion_performed = true if actual == expected actual_string = "#{actual}#{actual.nil? ? " (nil) " : " " }".strip - message = "actual:\n#{actual_string}\n\nequaled\n\nexpected:\n#{expected}.\n#{message}" + message = "actual:\n#{actual_string}\n\nequaled\n\nexpected:\n#{expected}\n#{message}" raise message end nil @@ -35486,7 +38295,7 @@ end end </code></pre> -<h3 id='----attr_gtk.rb'>attr_gtk.rb</h3> +<h3 id='----attr_gtk-rb'>attr_gtk.rb</h3> <pre><code class="language-ruby"># ./dragon/attr_gtk.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -35509,6 +38318,10 @@ module AttrGTK args.state end + def temp_state + args.temp_state + end + def inputs args.inputs end @@ -35536,11 +38349,20 @@ module AttrGTK def layout args.layout end + + def new_entity entity_type, init_hash = nil, &block + args.state.new_entity entity_type, init_hash, &block + end + + def new_entity_strict entity_type, init_hash = nil, &block + args.state.new_entity_strict entity_type, init_hash, &block + end end </code></pre> -<h3 id='----attr_sprite.rb'>attr_sprite.rb</h3> +<h3 id='----attr_sprite-rb'>attr_sprite.rb</h3> <pre><code class="language-ruby"># ./dragon/attr_sprite.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # attr_sprite.rb has been released under MIT (*only this file*). @@ -35580,7 +38402,7 @@ module AttrSprite 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, - :source_x, :source_y, :source_w, :source_h + :source_x, :source_y, :source_w, :source_h, :blendmode_enum def primitive_marker :sprite @@ -35600,8 +38422,9 @@ module AttrSprite end </code></pre> -<h3 id='----console.rb'>console.rb</h3> +<h3 id='----console-rb'>console.rb</h3> <pre><code class="language-ruby"># ./dragon/console.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console.rb has been released under MIT (*only this file*). @@ -35613,17 +38436,20 @@ module GTK class Console include ConsoleDeprecated - attr_accessor :show_reason, :log, :logo, :background_color, - :text_color, :animation_duration, + attr_accessor :show_reason, :log, :logo, + :animation_duration, :max_log_lines, :max_history, :log, - :last_command_errored, :last_command, :error_color, :shown_at, - :header_color, :archived_log, :last_log_lines, :last_log_lines_count, + :last_command_errored, :last_command, :shown_at, + :archived_log, :last_log_lines, :last_log_lines_count, :suppress_left_arrow_behavior, :command_set_at, :toast_ids, :bottom, - :font_style, :menu + :font_style, :menu, + :background_color, :spam_color, :text_color, :warn_color, + :error_color, :header_color, :code_color, :comment_color, + :debug_color, :unfiltered_color def initialize - @font_style = FontStyle.new(font: 'font.ttf', size_enum: -1, line_height: 1.1) + @font_style = FontStyle.new(font: 'font.ttf', size_enum: -1.5, line_height: 1.1) @menu = Menu.new self @disabled = false @log_offset = 0 @@ -35638,13 +38464,22 @@ module GTK @command_history_index = -1 @nonhistory_input = '' @logo = 'console-logo.png' - @history_fname = 'console_history.txt' + @history_fname = 'logs/console_history.txt' @background_color = Color.new [0, 0, 0, 224] - @text_color = Color.new [255, 255, 255] - @error_color = Color.new [200, 50, 50] @header_color = Color.new [100, 200, 220] + @code_color = Color.new [210, 168, 255] + @comment_color = Color.new [0, 200, 100] @animation_duration = 1.seconds @shown_at = -1 + + # these are the colors for text at various log levels. + @spam_color = Color.new [160, 160, 160] + @debug_color = Color.new [0, 255, 0] + @text_color = Color.new [255, 255, 255] + @warn_color = Color.new [255, 255, 0] + @error_color = Color.new [200, 50, 50] + @unfiltered_color = Color.new [0, 255, 255] + load_history end @@ -35710,7 +38545,13 @@ module GTK nil end - def add_text obj + def add_text obj, loglevel=-1 + # loglevel is one of the values of LogLevel in logging.h, or -1 to say "we don't care, colorize it with your special string parsing magic" + loglevel = -1 if loglevel < 0 + loglevel = 5 if loglevel > 5 # 5 == unfiltered (it's 0x7FFFFFFE in C, clamp it down) + loglevel = 2 if (loglevel == -1) && obj.start_with?('!c!') # oh well + colorstr = (loglevel != -1) ? "!c!#{loglevel}" : nil + @last_log_lines_count ||= 1 @log_invocation_count += 1 @@ -35719,12 +38560,18 @@ module GTK log_lines = [] str.each_line do |s| - s.wrapped_lines(self.console_text_width).each do |l| - log_lines << l + if colorstr.nil? + s.wrapped_lines(self.console_text_width).each do |l| + log_lines << l + end + else + s.wrapped_lines(self.console_text_width).each do |l| + log_lines << "#{colorstr}#{l}" + end end end - if log_lines == @last_log_lines + if log_lines == @last_log_lines && log_lines.length != 0 @last_log_lines_count += 1 new_log_line_with_count = @last_log_lines.last + " (#{@last_log_lines_count})" if log_lines.length > 1 @@ -35917,6 +38764,8 @@ S if cmd == 'quit' || cmd == ':wq' || cmd == ':q!' || cmd == ':q' || cmd == ':wqa' $gtk.request_quit + elsif cmd.start_with? ':' + send ((cmd.gsub '-', '_').gsub ':', '') else puts "-> #{cmd}" begin @@ -35925,13 +38774,19 @@ S if $results.nil? puts "=> nil" elsif $results == :console_silent_eval + # do nothing since the console is silent else puts "=> #{$results}" end @last_command_errored = false rescue Exception => e try_search_docs e - puts "* EXCEPTION: #{e}" + # if an exception is thrown and the bactrace includes something helpful, then show it + if (e.backtrace || []).first && (e.backtrace.first.include? "(eval)") + puts "* EXCEPTION: #{e}" + else + puts "* EXCEPTION: #{e}\n#{e.__backtrace_to_org__}" + end end end end @@ -35991,10 +38846,12 @@ S def mouse_wheel_scroll args @inertia ||= 0 - if args.inputs.mouse.wheel && args.inputs.mouse.wheel.y > 0 - @inertia = 1 - elsif args.inputs.mouse.wheel && args.inputs.mouse.wheel.y < 0 - @inertia = -1 + if args.inputs.mouse.wheel + if args.inputs.mouse.wheel.y > 0 + @inertia = 1 + elsif args.inputs.mouse.wheel.y < 0 + @inertia = -1 + end end if args.inputs.mouse.click @@ -36003,13 +38860,11 @@ S return if @inertia == 0 - if @inertia != 0 - @inertia = (@inertia * 0.7) - if @inertia > 0 - @log_offset -= 1 - elsif @inertia < 0 - @log_offset += 1 - end + @inertia = (@inertia * 0.7) + if @inertia > 0 + @log_offset += 1 + elsif @inertia < 0 + @log_offset -= 1 end if @inertia.abs < 0.01 @@ -36027,6 +38882,7 @@ S if console_toggle_key_down? args args.inputs.text.clear toggle + args.inputs.keyboard.clear if !@visible end return unless visible? @@ -36038,7 +38894,16 @@ S @log_offset = 0 if @log_offset < 0 if args.inputs.keyboard.key_down.enter - eval_the_set_command + if slide_progress > 0.5 + # in the event of an exception, the console window pops up + # and is pre-filled with $gtk.reset. + # there is an annoying scenario where the exception could be thrown + # by pressing enter (while playing the game). if you press enter again + # quickly, then the game is reset which closes the console. + # so enter in the console is only evaluated if the slide_progress + # is atleast half way down the page. + eval_the_set_command + end elsif args.inputs.keyboard.key_down.v if args.inputs.keyboard.key_down.control || args.inputs.keyboard.key_down.meta prompt << $gtk.ffi_misc.getclipboard @@ -36115,7 +38980,7 @@ S def write_line(args, left, y, str, archived: false) color = color_for_log_entry(str) color = color.mult_alpha(0.5) if archived - + str = str[4..-1] if str.start_with?('!c!') # chop off loglevel color args.outputs.reserved << font_style.label(x: left.shift_right(10), y: y, text: str, color: color) end @@ -36158,6 +39023,11 @@ S end render_log_offset args + + args.outputs.reserved << { x: 10.from_right, y: @bottom + 10, + text: "Press CTRL+g or ESCAPE to clear the prompt.", + vertical_alignment_enum: 0, + alignment_enum: 2, r: 80, g: 80, b: 80 }.label! end def render_log_offset args @@ -36180,7 +39050,7 @@ S end def include_subdued_markers? text - include_any_words? text, subdued_markers + (text.start_with? "* INFO: ") && (include_any_words? text, subdued_markers) end def include_any_words? text, words @@ -36326,8 +39196,34 @@ S (log_entry.start_with? "**** ") end - def color_for_log_entry(log_entry) - if include_row_marker? log_entry + def code? log_entry + (just_symbol? log_entry) || (codeblock_marker? log_entry) + end + + def just_symbol? log_entry + scrubbed = log_entry.gsub("*", "").strip + (scrubbed.start_with? ":") && (!scrubbed.include? " ") && (!scrubbed.include? "=>") + end + + def code_comment? log_entry + return true if log_entry.strip.start_with?("# ") + return false + end + + def codeblock_marker? log_entry + return true if log_entry.strip.start_with?("#+begin_src") + return true if log_entry.strip.start_with?("#+end_src") + return false + end + + def color_for_plain_text log_entry + log_entry = log_entry[4..-1] if log_entry.start_with? "!c!" + + if code? log_entry + @code_color + elsif code_comment? log_entry + @comment_color + elsif include_row_marker? log_entry @text_color elsif include_error_marker? log_entry @error_color @@ -36342,6 +39238,29 @@ S end end + def color_for_log_entry(log_entry) + if log_entry.start_with?('!c!') # loglevel color specified. + return case log_entry[3..3].to_i + when 0 # spam + @spam_color + when 1 # debug + @debug_color + #when 2 # info (caught by the `else` block.) + # @text_color + when 3 # warn + @warn_color + when 4 # error + @error_color + when 5 # unfiltered + @unfiltered_color + else + color_for_plain_text log_entry + end + end + + return color_for_plain_text log_entry + end + def prompt @prompt ||= Prompt.new(font_style: font_style, text_color: @text_color, console_text_width: console_text_width) end @@ -36374,8 +39293,9 @@ S end </code></pre> -<h3 id='----console_color.rb'>console_color.rb</h3> +<h3 id='----console_color-rb'>console_color.rb</h3> <pre><code class="language-ruby"># ./dragon/console_color.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console_color.rb has been released under MIT (*only this file*). @@ -36400,6 +39320,10 @@ module GTK @color end + def to_s + "GTK::Console::Color #{to_h}" + end + def to_h { r: @color[0], g: @color[1], b: @color[2], a: @color[3] } end @@ -36408,8 +39332,9 @@ module GTK end </code></pre> -<h3 id='----console_font_style.rb'>console_font_style.rb</h3> +<h3 id='----console_font_style-rb'>console_font_style.rb</h3> <pre><code class="language-ruby"># ./dragon/console_font_style.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console_font_style.rb has been released under MIT (*only this file*). @@ -36445,15 +39370,16 @@ module GTK size_enum: size_enum, alignment_enum: alignment_enum, **color.to_h, - }.label + }.label! end end end end </code></pre> -<h3 id='----console_menu.rb'>console_menu.rb</h3> +<h3 id='----console_menu-rb'>console_menu.rb</h3> <pre><code class="language-ruby"># ./dragon/console_menu.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console_menu.rb has been released under MIT (*only this file*). @@ -36507,7 +39433,7 @@ module GTK def itch_wizard_clicked @console.scroll_to_bottom - $wizards.itch.start + $wizards.itch.restart end def docs_clicked @@ -36532,6 +39458,7 @@ module GTK @buttons = [ (button id: :record, row: 0, col: 9, text: "record gameplay", method: :record_clicked), (button id: :replay, row: 0, col: 10, text: "start replay", method: :replay_clicked), + *custom_buttons ] elsif @menu_shown == :hidden @buttons = [ @@ -36590,8 +39517,8 @@ module GTK method: method }.let do |entity| primitives = [] - primitives << entity[:rect].merge(a: 164).solid - primitives << entity[:rect].merge(r: 255, g: 255, b: 255).border + primitives << entity[:rect].solid!(a: 164) + primitives << entity[:rect].border!(r: 255, g: 255, b: 255) primitives << text.wrapped_lines(5) .map_with_index do |l, i| [ @@ -36615,8 +39542,9 @@ module GTK end </code></pre> -<h3 id='----console_prompt.rb'>console_prompt.rb</h3> +<h3 id='----console_prompt-rb'>console_prompt.rb</h3> <pre><code class="language-ruby"># ./dragon/console_prompt.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console_prompt.rb has been released under MIT (*only this file*). @@ -36776,11 +39704,11 @@ S # partition the original list of items into a string to be printed items.each_slice(columns).each_with_index do |cells, i| - pretty_print_row_seperator string_width, cell_width, column_width, columns + pretty_print_row_separator string_width, cell_width, column_width, columns pretty_print_row cells, string_width, cell_width, column_width, columns end - pretty_print_row_seperator string_width, cell_width, column_width, columns + pretty_print_row_separator string_width, cell_width, column_width, columns end end @@ -36793,17 +39721,17 @@ S "#{" " * (string_width.length - c.length) } #{c} |" end.join - # remove seperators between empty values + # remove separators between empty values formated_row = formated_row.gsub(" | ", " ") puts formated_row end - def pretty_print_row_seperator string_width, cell_width, column_width, columns + def pretty_print_row_separator string_width, cell_width, column_width, columns # this is a joint: +-------- column_joint = "+#{"-" * cell_width}" - # multiple joints create a row seperator: +----+----+ + # multiple joints create a row separator: +----+----+ puts (column_joint * columns) + "+" end @@ -36812,12 +39740,12 @@ S args.outputs.reserved << (@cursor_color.to_h.merge x: x + @cursor_position_px + 0.5, y: y + 5, x2: x + @cursor_position_px + 0.5, - y2: y + @font_style.letter_size.y + 5) + y2: y + @font_style.letter_size.y + 4) args.outputs.reserved << (@cursor_color.to_h.merge x: x + @cursor_position_px + 1, y: y + 5, x2: x + @cursor_position_px + 1, - y2: y + @font_style.letter_size.y + 5) + y2: y + @font_style.letter_size.y + 4) # debugging rectangle for string # args.outputs.reserved << (@cursor_color.to_h.merge x: x, @@ -36892,7 +39820,7 @@ S end </code></pre> -<h3 id='----controller.rb'>controller.rb</h3> +<h3 id='----controller-rb'>controller.rb</h3> <pre><code class="language-ruby"># ./dragon/controller.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -37018,7 +39946,7 @@ end </code></pre> -<h3 id='----controller/config.rb'>controller/config.rb</h3> +<h3 id='----controller/config-rb'>controller/config.rb</h3> <pre><code class="language-ruby"># ./dragon/controller/config.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -37421,7 +40349,7 @@ module GTK end </code></pre> -<h3 id='----controller/keys.rb'>controller/keys.rb</h3> +<h3 id='----controller/keys-rb'>controller/keys.rb</h3> <pre><code class="language-ruby"># ./dragon/controller/keys.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -37439,7 +40367,7 @@ module GTK :l1, :r1, :l2, :r2, :l3, :r3, - :start, :select, + :start, :select, :home, :directional_up, :directional_down, :directional_left, :directional_right ].freeze @@ -37447,6 +40375,22 @@ module GTK attr label end + def back + @select + end + + def back= value + @select = value + end + + def guide + @home + end + + def guide= value + @home = value + end + # Activate a key. # # @return [void] @@ -37476,7 +40420,7 @@ module GTK end </code></pre> -<h3 id='----directional_input_helper_methods.rb'>directional_input_helper_methods.rb</h3> +<h3 id='----directional_input_helper_methods-rb'>directional_input_helper_methods.rb</h3> <pre><code class="language-ruby"># ./dragon/directional_input_helper_methods.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -37493,7 +40437,7 @@ module GTK error_message = <<-S * ERROR -The GTK::DirectionalKeys module should only be included in objects that respond to the following api heirarchy: +The GTK::DirectionalKeys module should only be included in objects that respond to the following api hierarchy: - (#{ directional_methods.join("|") }) - key_held.(#{ directional_methods.join("|") }) @@ -37550,6 +40494,12 @@ S end end + def directional_angle + return nil unless directional_vector + + Math.atan2(up_down, left_right).to_degrees + end + def method_missing m, *args # combine the key with ctrl_ if m.to_s.start_with?("ctrl_") @@ -37572,7 +40522,7 @@ S end </code></pre> -<h3 id='----easing.rb'>easing.rb</h3> +<h3 id='----easing-rb'>easing.rb</h3> <pre><code class="language-ruby"># ./dragon/easing.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -37585,8 +40535,8 @@ module GTK ease_extended start_tick, current_tick, start_tick + duration, - (initial_value *definitions), - (final_value *definitions), + initial_value(*definitions), + final_value(*definitions), *definitions end @@ -37672,7 +40622,7 @@ end Easing = GTK::Easing </code></pre> -<h3 id='----entity.rb'>entity.rb</h3> +<h3 id='----entity-rb'>entity.rb</h3> <pre><code class="language-ruby"># ./dragon/entity.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -37767,7 +40717,7 @@ S end </code></pre> -<h3 id='----geometry.rb'>geometry.rb</h3> +<h3 id='----geometry-rb'>geometry.rb</h3> <pre><code class="language-ruby"># ./dragon/geometry.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -37776,6 +40726,22 @@ end module GTK module Geometry + def self.rotate_point point, angle, around = nil + s = Math.sin angle.to_radians + c = Math.cos angle.to_radians + px = point.x + py = point.y + cx = 0 + cy = 0 + if around + cx = around.x + cy = around.y + end + + point.merge(x: ((px - cx) * c - (py - cy) * s) + cx, + y: ((px - cx) * s + (py - cy) * c) + cy) + end + # Returns f(t) for a cubic Bezier curve. def self.cubic_bezier t, a, b, c, d s = 1 - t @@ -37857,7 +40823,7 @@ module GTK rescue Exception => e raise e, <<-S * ERROR: -center_inside_rect for self #{self} and other_rect #{other_rect}. Failed with exception #{e}. +center_inside_rect for self #{self} and other_rect #{other_rect}.\n#{e}. S end @@ -37874,7 +40840,7 @@ S rescue Exception => e raise e, <<-S * ERROR: -center_inside_rect_x for self #{self} and other_rect #{other_rect}. Failed with exception #{e}. +center_inside_rect_x for self #{self} and other_rect #{other_rect}.\n#{e}. S end @@ -37891,7 +40857,7 @@ S rescue Exception => e raise e, <<-S * ERROR: -center_inside_rect_y for self #{self} and other_rect #{other_rect}. Failed with exception #{e}. +center_inside_rect_y for self #{self} and other_rect #{other_rect}.\n#{e}. S end @@ -37900,7 +40866,7 @@ S end - # Returns a primitive that is anchored/repositioned based off its retangle. + # Returns a primitive that is anchored/repositioned based off its rectangle. # @gtk def anchor_rect anchor_x, anchor_y current_w = self.w @@ -37943,8 +40909,16 @@ S end # @gtk - def self.line_y_intercept line - line.y - line_slope(line) * line.x + def self.line_y_intercept line, replace_infinity: nil + line.y - line_slope(line, replace_infinity: replace_infinity) * line.x + rescue Exception => e +raise <<-S +* ERROR: ~Geometry::line_y_intercept~ +The following exception was thrown for line: #{line} +#{e} + +Consider passing in ~replace_infinity: VALUE~ to handle for vertical lines. +S end # @gtk @@ -38020,14 +40994,22 @@ S end # @gtk - def self.line_intersect line_one, line_two - m1 = line_slope(line_one) - m2 = line_slope(line_two) - b1 = line_y_intercept(line_one) - b2 = line_y_intercept(line_two) + def self.line_intersect line_one, line_two, replace_infinity: nil + m1 = line_slope(line_one, replace_infinity: replace_infinity) + m2 = line_slope(line_two, replace_infinity: replace_infinity) + b1 = line_y_intercept(line_one, replace_infinity: replace_infinity) + b2 = line_y_intercept(line_two, replace_infinity: replace_infinity) x = (b1 - b2) / (m2 - m1) y = (-b2.fdiv(m2) + b1.fdiv(m1)).fdiv(1.fdiv(m1) - 1.fdiv(m2)) [x, y] + rescue Exception => e +raise <<-S +* ERROR: ~Geometry::line_intersect~ +The following exception was thrown for line_one: #{line_one}, line_two: #{line_two} +#{e} + +Consider passing in ~replace_infinity: VALUE~ to handle for vertical lines. +S end def self.contract_intersect_rect? @@ -38036,10 +41018,10 @@ S # @gtk def self.intersect_rect? rect_one, rect_two, tolerance = 0.1 - return false if rect_one.right - tolerance < rect_two.left + tolerance - return false if rect_one.left + tolerance > rect_two.right - tolerance - return false if rect_one.top - tolerance < rect_two.bottom + tolerance - return false if rect_one.bottom + tolerance > rect_two.top - tolerance + return false if ((rect_one.x + rect_one.w) - tolerance) < (rect_two.x + tolerance) + return false if (rect_one.x + tolerance) > ((rect_two.x + rect_two.w) - tolerance) + return false if ((rect_one.y + rect_one.h) - tolerance) < (rect_two.y + tolerance) + return false if (rect_one.y + tolerance) > ((rect_two.y + rect_two.h) - tolerance) return true rescue Exception => e context_help_rect_one = (rect_one.__help_contract_implementation contract_intersect_rect?)[:not_implemented_methods] @@ -38067,6 +41049,7 @@ S - rect_one: #{rect_one} - rect_two: #{rect_two} #{context_help} +\n#{e} S end @@ -38077,14 +41060,14 @@ S y = y.shift_down(size * anchor_y) [x, y, size, size] rescue Exception => e - raise e, ":to_square failed for size: #{size} x: #{x} y: #{y} anchor_x: #{anchor_x} anchor_y: #{anchor_y}." + raise e, ":to_square failed for size: #{size} x: #{x} y: #{y} anchor_x: #{anchor_x} anchor_y: #{anchor_y}.\n#{e}" end # @gtk def self.distance point_one, point_two Math.sqrt((point_two.x - point_one.x)**2 + (point_two.y - point_one.y)**2) rescue Exception => e - raise e, ":distance failed for point_one: #{point_one} point_two #{point_two}." + raise e, ":distance failed for point_one: #{point_one} point_two #{point_two}.\n#{e}" end # @gtk @@ -38093,31 +41076,34 @@ S d_x = end_point.x - start_point.x Math::PI.+(Math.atan2(d_y, d_x)).to_degrees rescue Exception => e - raise e, ":angle_from failed for start_point: #{start_point} end_point: #{end_point}." + raise e, ":angle_from failed for start_point: #{start_point} end_point: #{end_point}.\n#{e}" end # @gtk def self.angle_to start_point, end_point angle_from end_point, start_point rescue Exception => e - raise e, ":angle_to failed for start_point: #{start_point} end_point: #{end_point}." + raise e, ":angle_to failed for start_point: #{start_point} end_point: #{end_point}.\n#{e}" end # @gtk def self.point_inside_circle? point, circle_center_point, radius (point.x - circle_center_point.x) ** 2 + (point.y - circle_center_point.y) ** 2 < radius ** 2 rescue Exception => e - raise e, ":point_inside_circle? failed for point: #{point} circle_center_point: #{circle_center_point} radius: #{radius}" + raise e, ":point_inside_circle? failed for point: #{point} circle_center_point: #{circle_center_point} radius: #{radius}.\n#{e}" end # @gtk def self.inside_rect? inner_rect, outer_rect, tolerance = 0.0 + return nil if !inner_rect + return nil if !outer_rect + inner_rect.x + tolerance >= outer_rect.x - tolerance && - inner_rect.right - tolerance <= outer_rect.right + tolerance && + (inner_rect.x + inner_rect.w) - tolerance <= (outer_rect.x + outer_rect.w) + tolerance && inner_rect.y + tolerance >= outer_rect.y - tolerance && - inner_rect.top - tolerance <= outer_rect.top + tolerance + (inner_rect.y + inner_rect.h) - tolerance <= (outer_rect.y + outer_rect.h) + tolerance rescue Exception => e - raise e, ":inside_rect? failed for inner_rect: #{inner_rect} outer_rect: #{outer_rect}." + raise e, ":inside_rect? failed for inner_rect: #{inner_rect} outer_rect: #{outer_rect}.\n#{e}" end # @gtk @@ -38152,7 +41138,7 @@ S return rect end rescue Exception => e - raise e, ":scale_rect_extended failed for rect: #{rect} percentage_x: #{percentage_x} percentage_y: #{percentage_y} anchors_x: #{anchor_x} anchor_y: #{anchor_y}." + raise e, ":scale_rect_extended failed for rect: #{rect} percentage_x: #{percentage_x} percentage_y: #{percentage_y} anchors_x: #{anchor_x} anchor_y: #{anchor_y}.\n#{e}" end # @gtk @@ -38166,13 +41152,27 @@ S anchor_x: anchor_x, anchor_y: anchor_y rescue Exception => e - raise e, ":scale_rect failed for rect: #{rect} percentage: #{percentage} anchors [#{anchor_x} (x), #{anchor_y} (y)]." + raise e, ":scale_rect failed for rect: #{rect} percentage: #{percentage} anchors [#{anchor_x} (x), #{anchor_y} (y)].\n#{e}" + end + + def self.rect_to_line rect + l = rect.to_hash.line + l.merge(x2: l.x + l.w - 1, + y2: l.y + l.h) + end + + def self.rect_center_point rect + { x: rect.x + rect.w.half, y: rect.y + rect.h.half } + end + + def rect_center_point + Geometry.rect_center_point self end end # module Geometry end # module GTK </code></pre> -<h3 id='----grid.rb'>grid.rb</h3> +<h3 id='----grid-rb'>grid.rb</h3> <pre><code class="language-ruby"># ./dragon/grid.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -38361,11 +41361,19 @@ module GTK def bottom_right [@right, @bottom].point end + + def x + 0 + end + + def y + 0 + end end end </code></pre> -<h3 id='----inputs.rb'>inputs.rb</h3> +<h3 id='----inputs-rb'>inputs.rb</h3> <pre><code class="language-ruby"># ./dragon/inputs.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -38560,6 +41568,30 @@ module GTK } end + def self.method_to_key_hash + return @method_to_key_hash if @method_to_key_hash + @method_to_key_hash = {} + string_representation_overrides ||= { + backspace: '\b' + } + char_to_method_hash.each do |k, v| + v.each do |vi| + t = { char_or_raw_key: k } + + if k.is_a? Numeric + t[:raw_key] = k + t[:string_representation] = "raw_key == #{k}" + else + t[:char] = k + t[:string_representation] = "\"#{k.strip}\"" + end + + @method_to_key_hash[vi] = t + end + end + @method_to_key_hash + end + def self.char_to_method char, int = nil methods = char_to_method_hash[char] || char_to_method_hash[int] methods ? methods.dup : [char.to_sym || int] @@ -38658,7 +41690,8 @@ module GTK value = Kernel.tick_count if value collection.each do |m| - self.instance_variable_set("@#{m.to_s}".to_sym, value) + m_to_s = m.to_s + self.instance_variable_set("@#{m_to_s}".to_sym, value) if m_to_s.strip.length > 0 rescue Exception => e raise e, <<-S * ERROR: @@ -38674,24 +41707,32 @@ S end def method_missing m, *args - begin - define_singleton_method(m) do - r = self.instance_variable_get("@#{m.without_ending_bang}".to_sym) - clear_key m - return r - end + if KeyboardKeys.method_to_key_hash[m.without_ending_bang] + begin + define_singleton_method(m) do + r = self.instance_variable_get("@#{m.without_ending_bang}".to_sym) + clear_key m + return r + end - return self.send m - rescue Exception => e - log_important "#{e}" + return self.send m + rescue Exception => e + log_important "#{e}" + end end + did_you_mean = KeyboardKeys.method_to_key_hash.find_all do |k, v| + k.to_s[0..1] == m.to_s[0..1] + end.map {|k, v| ":#{k} (#{v[:string_representation]})" } + did_you_mean_string = "" + did_you_mean_string = ". Did you mean #{did_you_mean.join ", "}?" + raise <<-S * ERROR: -There is no member on the keyboard called #{m}. Here is a to_s representation of what's available: - -#{KeyboardKeys.char_to_method_hash.map { |k, v| "[#{k} => #{v.join(",")}]" }.join(" ")} +#{KeyboardKeys.method_to_key_hash.map { |k, v| "** :#{k} #{v.string_representation}" }.join("\n")} +There is no key on the keyboard called :#{m}#{did_you_mean_string}. +Full list of available keys =:points_up:=. S end @@ -39075,6 +42116,10 @@ module GTK (controller_one && controller_one.directional_vector) end + def directional_angle + keyboard.directional_angle || (controller_one && controller_one.directional_angle) + end + # Returns a signal indicating right (`1`), left (`-1`), or neither ('0'). # # @return [Integer] @@ -39142,21 +42187,16 @@ module GTK end </code></pre> -<h3 id='----ios_wizard.rb'>ios_wizard.rb</h3> +<h3 id='----ios_wizard-rb'>ios_wizard.rb</h3> <pre><code class="language-ruby"># ./dragon/ios_wizard.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # ios_wizard.rb has been released under MIT (*only this file*). -class WizardException < Exception - attr_accessor :console_primitives - - def initialize *console_primitives - @console_primitives = console_primitives - end -end +# Contributors outside of DragonRuby who also hold Copyright: Michał Dudziński -class IOSWizard +class IOSWizard < Wizard def initialize @doctor_executed_at = 0 end @@ -39169,23 +42209,46 @@ class IOSWizard @steps ||= [] end - def steps_development_build + def prerequisite_steps [ :check_for_xcode, :check_for_brew, :check_for_certs, - :check_for_device, - :check_for_dev_profile, + ] + end + + def app_metadata_retrieval_steps + [ :determine_team_identifier, :determine_app_name, :determine_app_id, - :blow_away_temp, + ] + end + + def steps_development_build + [ + *prerequisite_steps, + + :check_for_device, + :check_for_dev_profile, + + *app_metadata_retrieval_steps, + :determine_devcert, + + :clear_tmp_directory, :stage_app, + :development_write_info_plist, + :write_entitlements_plist, :compile_icons, - :create_payload_directory, + :clear_payload_directory, + + :create_payload_directory_dev, + + :create_payload, :code_sign_payload, + :create_ipa, :deploy ] @@ -39193,20 +42256,28 @@ class IOSWizard def steps_production_build [ - :check_for_xcode, - :check_for_brew, - :check_for_certs, + *prerequisite_steps, + :check_for_distribution_profile, - :determine_team_identifier, - :determine_app_name, - :determine_app_id, - :blow_away_temp, + :determine_app_version, + + *app_metadata_retrieval_steps, + :determine_prodcert, + + :clear_tmp_directory, :stage_app, + :production_write_info_plist, + :write_entitlements_plist, :compile_icons, - :create_payload_directory, + :clear_payload_directory, + + :create_payload_directory_prod, + + :create_payload, :code_sign_payload, + :create_ipa, :print_publish_help ] @@ -39242,6 +42313,8 @@ class IOSWizard @steps = steps_development_build @steps = steps_production_build if @production_build @certificate_name = nil + @app_version = opts[:version] + @app_version = "1.0" if @opts[:env] == :dev && !@app_version init_wizard_status log_info "Starting iOS Wizard so we can deploy to your device." @start_at = Kernel.global_tick_count @@ -39263,8 +42336,10 @@ class IOSWizard log "=" * $console.console_text_width else log_error e.to_s + log e.__backtrace_to_org__ end + init_wizard_status $console.set_command "$wizards.ios.start env: :#{@opts[:env]}" end @@ -39371,13 +42446,70 @@ class IOSWizard return "profiles/development.mobileprovision" end + def ios_metadata_template + <<-S +# ios_metadata.txt is used by the Pro version of DragonRuby Game Toolkit to create iOS apps. +# Information about the Pro version can be found at: http://dragonruby.org/toolkit/game#purchase + +# teamid needs to be set to your assigned Team Id which can be found at https://developer.apple.com/account/#/membership/ +teamid= +# appid needs to be set to your application identifier which can be found at https://developer.apple.com/account/resources/identifiers/list +appid= +# appname is the name you want to show up underneath the app icon on the device. Keep it under 10 characters. +appname= +# devcert is the certificate to use for development/deploying to your local device +devcert= +# prodcert is the certificate to use for distribution to the app store +prodcert= +S + end + + def ios_metadata + contents = $gtk.read_file 'metadata/ios_metadata.txt' + + if !contents + $gtk.write_file 'metadata/ios_metadata.txt', ios_metadata_template + contents = $gtk.read_file 'metadata/ios_metadata.txt' + end + + kvps = contents.each_line + .reject { |l| l.strip.length == 0 || (l.strip.start_with? "#") } + .map do |l| + key, value = l.split("=") + [key.strip.to_sym, value.strip] + end.flatten + Hash[*kvps] + end + + def game_metadata + contents = $gtk.read_file 'metadata/game_metadata.txt' + + kvps = contents.each_line + .reject { |l| l.strip.length == 0 || (l.strip.start_with? "#") } + .map do |l| + key, value = l.split("=") + [key.strip.to_sym, value.strip] + end.flatten + Hash[*kvps] + end + + def raise_ios_metadata_required + raise WizardException.new( + "* mygame/metadata/ios_metadata.txt needs to be filled out.", + "You need to update metadata/ios_metadata.txt with a valid teamid, appname, appid, devcert, and prodcert.", + "Instructions for where the values should come from are within metadata/ios_metadata.txt." + ) + end + def determine_team_identifier - @team_name = (team_identifier_from_provisioning_profile @opts[:env]) - log_info "Team Identifer is: #{@team_name}" + @team_id = (ios_metadata.teamid || "") + raise_ios_metadata_required if @team_id.strip.length == 0 + log_info "Team Identifer is: #{@team_id}" end def determine_app_name - @app_name = (provisioning_profile_xml @opts[:env])[:children].first[:children].first[:children][1][:children].first[:data] + @app_name = (ios_metadata.appname || "") + raise_ios_metadata_required if @app_name.strip.length == 0 log_info "App name is: #{@app_name}." end @@ -39401,37 +42533,22 @@ class IOSWizard $gtk.parse_xml scrubbed end - def app_id_from_provisioning_profile environment - application_identifier_index = (provisioning_profile_xml environment)[:children][0][:children][0][:children][13][:children][0][:children][0][:data] - (provisioning_profile_xml environment)[:children][0][:children][0][:children][13][:children].each.with_index do |node, i| - if node[:children] && node[:children][0] && node[:children][0][:data] == "application-identifier" - application_identifier_index = i - break - end - end - - app_id_with_team_identifier = (provisioning_profile_xml environment)[:children].first[:children].first[:children][13][:children][application_identifier_index + 1][:children].first[:data] - team_identifer = team_identifier_from_provisioning_profile environment - app_id_with_team_identifier.gsub "#{team_identifer}.", "" + def determine_app_id + @app_id = ios_metadata.appid + raise_ios_metadata_required if @app_id.strip.length == 0 + log_info "App Identifier is set to: #{@app_id}" end - def team_identifier_from_provisioning_profile environment - team_identifer_index = (provisioning_profile_xml environment)[:children][0][:children][0][:children][13][:children][0][:children][0][:data] - - (provisioning_profile_xml environment)[:children][0][:children][0][:children][13][:children].each.with_index do |node, i| - if node[:children] && node[:children][0] && node[:children][0][:data] == "com.apple.developer.team-identifier" - team_identifer_index = i - break - end - end - - (provisioning_profile_xml environment)[:children].first[:children].first[:children][13][:children][team_identifer_index + 1][:children].first[:data] + def determine_devcert + @certificate_name = ios_metadata.devcert + raise_ios_metadata_required if @certificate_name.strip.length == 0 + log_info "Dev Certificate is set to: #{@certificate_name}" end - def determine_app_id - @app_id = app_id_from_provisioning_profile @opts[:env] - - log_info "App Identifier is set to : #{@app_id}" + def determine_prodcert + @certificate_name = ios_metadata.prodcert + raise_ios_metadata_required if @certificate_name.strip.length == 0 + log_info "Production (Distribution) Certificate is set to: #{@certificate_name}" end def set_app_name name @@ -39449,16 +42566,10 @@ class IOSWizard end end - def blow_away_temp + def clear_tmp_directory sh "rm -rf #{tmp_directory}" end - def stage_app - log_info "Staging." - sh "mkdir -p #{tmp_directory}" - sh "cp -R #{relative_path}/dragonruby-ios.app \"#{tmp_directory}/#{@app_name}.app\"" - end - def set_app_id id log_info = "App Id set to: #{id}" @app_id = id @@ -39489,34 +42600,13 @@ class IOSWizard def check_for_certs log_info "Attempting to find certificates on your computer." - if !cli_app_exist?(security_cli_app) - raise WizardException.new( - "* It doesn't look like you have #{security_cli_app}.", - "** 1. Open Disk Utility and run First Aid.", - { w: 700, h: 148, path: get_reserved_sprite("disk-utility.png") }, - ) - end - - if valid_certs.length == 0 - raise WizardException.new( - "* It doesn't look like you have any valid certs installed.", - "** 1. Open Xcode.", - "** 2. Log into your developer account. Xcode -> Preferences -> Accounts.", - { w: 700, h: 98, path: get_reserved_sprite("login-xcode.png") }, - "** 3. After loggin in, select Manage Certificates...", - { w: 700, h: 115, path: get_reserved_sprite("manage-certificates.png") }, - "** 4. Add a certificate for Apple Development.", - { w: 700, h: 217, path: get_reserved_sprite("add-cert.png") }, - ) - raise "You do not have any Apple development certs on this computer." - end - if @production_build - @certificate_name = valid_certs.find_all { |f| f[:name].include? "Distribution" }.first[:name] + @certificate_name = ios_metadata[:prodcert] else - @certificate_name = valid_certs.find_all { |f| f[:name].include? "Development" }.first[:name] + @certificate_name = ios_metadata[:devcert] end - log_info "I will be using Certificate: '#{@certificate_name}'." + + log_info "I will be using certificate: '#{@certificate_name}'." end def idevice_id_cli_app @@ -39531,24 +42621,6 @@ class IOSWizard "xcodebuild" end - def valid_certs - certs = sh("#{security_cli_app} -q find-identity -p codesigning -v").each_line.map do |l| - if l.include?(")") && !l.include?("Developer ID") && (l.include?("Development") || l.include?("Distribution")) - l.strip - else - nil - end - end.reject_nil.map do |l| - number, id, name = l.split(' ', 3) - name = name.gsub("\"", "") if name - { - number: 1, - id: id, - name: name - } - end - end - def connected_devices sh("idevice_id -l").strip.each_line.map do |l| l.strip @@ -39590,7 +42662,8 @@ XML log_info "Creating Entitlements.plist" - $gtk.write_file_root "tmp/ios/Entitlements.plist", entitlement_plist_string.gsub(":app_id", "#{@team_name}.#{@app_id}").strip + $gtk.write_file_root "tmp/ios/Entitlements.plist", entitlement_plist_string.gsub(":app_id", "#{@team_id}.#{@app_id}").strip + $gtk.write_file_root "tmp/ios/Entitlements.txt", entitlement_plist_string.gsub(":app_id", "#{@team_id}.#{@app_id}").strip sh "/usr/bin/plutil -convert binary1 \"#{tmp_directory}/Entitlements.plist\"" sh "/usr/bin/plutil -convert xml1 \"#{tmp_directory}/Entitlements.plist\"" @@ -39628,15 +42701,15 @@ XML <key>CFBundleExecutable</key> <string>:app_name</string> <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> + <string>:app_version</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>5.6</string> + <string>:app_version</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> - <string>5.6</string> + <string>:app_version</string> <key>CFBundleIcons</key> <dict> <key>CFBundlePrimaryIcon</key> @@ -39785,13 +42858,13 @@ XML <key>CFBundleIdentifier</key> <string>:app_id</string> <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> + <string>:app_version</string> <key>CFBundleName</key> <string>:app_name</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>5.2</string> + <string>:app_version</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleSupportedPlatforms</key> @@ -39799,7 +42872,7 @@ XML <string>iPhoneOS</string> </array> <key>CFBundleVersion</key> - <string>5.2</string> + <string>:app_version</string> <key>DTCompiler</key> <string>com.apple.compilers.llvm.clang.1_0</string> <key>DTPlatformBuild</key> @@ -39885,6 +42958,7 @@ XML info_plist_string.gsub!(":app_id", @app_id) $gtk.write_file_root "tmp/ios/#{@app_name}.app/Info.plist", info_plist_string.strip + $gtk.write_file_root "tmp/ios/Info.txt", info_plist_string.strip @info_plist_written = true end @@ -39938,13 +43012,13 @@ XML <key>CFBundleIdentifier</key> <string>:app_id</string> <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> + <string>:app_version</string> <key>CFBundleName</key> <string>:app_name</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>5.2</string> + <string>:app_version</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleSupportedPlatforms</key> @@ -39952,7 +43026,7 @@ XML <string>iPhoneOS</string> </array> <key>CFBundleVersion</key> - <string>5.2</string> + <string>:app_version</string> <key>DTCompiler</key> <string>com.apple.compilers.llvm.clang.1_0</string> <key>DTPlatformBuild</key> @@ -40036,8 +43110,10 @@ XML info_plist_string.gsub!(":app_name", @app_name) info_plist_string.gsub!(":app_id", @app_id) + info_plist_string.gsub!(":app_version", @app_version) $gtk.write_file_root "tmp/ios/#{@app_name}.app/Info.plist", info_plist_string.strip + $gtk.write_file_root "tmp/ios/Info.txt", info_plist_string.strip @info_plist_written = true end @@ -40059,28 +43135,56 @@ XML "#{relative_path}/#{$gtk.cli_arguments[:dragonruby]}" end - def write_ip_address + def embed_mobileprovision + sh %Q[cp #{@provisioning_profile_path} "#{app_path}/embedded.mobileprovision"] + sh %Q[/usr/bin/plutil -convert binary1 "#{app_path}/Info.plist"] + end + + def clear_payload_directory + sh %Q[rm "#{@app_name}".ipa] + sh %Q[rm -rf "#{app_path}/app"] + sh %Q[rm -rf "#{app_path}/sounds"] + sh %Q[rm -rf "#{app_path}/sprites"] + sh %Q[rm -rf "#{app_path}/data"] + sh %Q[rm -rf "#{app_path}/fonts"] + end + + def stage_app + log_info "Staging." + sh "mkdir -p #{tmp_directory}" + sh "cp -R #{relative_path}/dragonruby-ios.app \"#{tmp_directory}/#{@app_name}.app\"" + sh %Q[cp -r "#{root_folder}/app/" "#{app_path}/app/"] + sh %Q[cp -r "#{root_folder}/sounds/" "#{app_path}/sounds/"] + sh %Q[cp -r "#{root_folder}/sprites/" "#{app_path}/sprites/"] + sh %Q[cp -r "#{root_folder}/data/" "#{app_path}/data/"] + sh %Q[cp -r "#{root_folder}/fonts/" "#{app_path}/fonts/"] + end + + def create_payload + sh %Q[mkdir -p #{tmp_directory}/ipa_root/Payload] + sh %Q[cp -r "#{app_path}" "#{tmp_directory}/ipa_root/Payload"] + sh %Q[chmod -R 755 "#{tmp_directory}/ipa_root/Payload"] + end + + def create_payload_directory_dev + # write dev machine's ip address for hotloading $gtk.write_file "app/server_ip_address.txt", $gtk.ffi_misc.get_local_ip_address.strip + + embed_mobileprovision + clear_payload_directory + stage_app end - def create_payload_directory - sh "cp #{@provisioning_profile_path} \"#{app_path}/embedded.mobileprovision\"" - sh "/usr/bin/plutil -convert binary1 \"#{app_path}/Info.plist\"" - write_ip_address - sh "rm \"#{@app_name}\".ipa" - sh "rm -rf \"#{app_path}/app\"" - sh "rm -rf \"#{app_path}/sounds\"" - sh "rm -rf \"#{app_path}/sprites\"" - sh "rm -rf \"#{app_path}/data\"" - sh "rm -rf \"#{app_path}/fonts\"" - sh "cp -r \"#{root_folder}/app/\" \"#{app_path}/app/\"" - sh "cp -r \"#{root_folder}/sounds/\" \"#{app_path}/sounds/\"" - sh "cp -r \"#{root_folder}/sprites/\" \"#{app_path}/sprites/\"" - sh "cp -r \"#{root_folder}/data/\" \"#{app_path}/data/\"" - sh "cp -r \"#{root_folder}/fonts/\" \"#{app_path}/fonts/\"" - sh "mkdir -p #{tmp_directory}/ipa_root/Payload" - sh "cp -r \"#{app_path}\" \"#{tmp_directory}/ipa_root/Payload\"" - sh "chmod -R 755 \"#{tmp_directory}/ipa_root/Payload\"" + def create_payload_directory_prod + # production builds does not hotload ip address + sh %Q[rm "#{root_folder}/app/server_ip_address.txt"] + + embed_mobileprovision + stage_app + + # production build marker + sh %Q[mkdir -p "#{app_path}/metadata/"] + sh %Q[touch metadata/DRAGONRUBY_PRODUCTION_BUILD] end def create_ipa @@ -40113,10 +43217,22 @@ SCRIPT end def print_publish_help - log_info "Go to https://appstoreconnect.apple.com/apps and create an App if you haven't already done so." - log_info "Go to https://appleid.apple.com and create a 'Application Specific Password'." - log_info "To upload your app, Download Transporter from the App Store https://apps.apple.com/us/app/transporter/id1450874784?mt=12." - log_info "Your app is located at ./tmp/ios/#{@app_name}.ipa" + has_transporter = (sh "ls /Applications/Transporter.app").include? "Contents" + if !has_transporter + $gtk.openurl "https://apps.apple.com/us/app/transporter/id1450874784?mt=12" + $console.set_command "$wizards.ios.start env: :#{@opts[:env]}, version: \"#{@opts[:version]}\"" + raise WizardException.new( + "* To upload your app, Download Transporter from the App Store https://apps.apple.com/us/app/transporter/id1450874784?mt=12." + ) + else + sh "mkdir ./tmp/ios/intermediary_artifacts" + sh "mv \"#{tmp_directory}/#{@app_name}.app\" #{tmp_directory}/intermediary_artifacts/" + sh "mv \"#{tmp_directory}/do_zip.sh\" #{tmp_directory}/intermediary_artifacts" + sh "mv \"#{tmp_directory}/Entitlements.plist\" #{tmp_directory}/intermediary_artifacts" + sh "mv \"#{tmp_directory}/ipa_root\" #{tmp_directory}/intermediary_artifacts/" + sh "open /Applications/Transporter.app" + sh "open ./tmp/ios/" + end end def compile_icons @@ -40137,47 +43253,44 @@ S sh "cp -r \"#{root_folder}/native/\" \"#{app_path}/native/\"" sh "CODESIGN_ALLOCATE=\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\" /usr/bin/codesign -f -s \"#{@certificate_name}\" --entitlements #{tmp_directory}/Entitlements.plist \"#{tmp_directory}/#{@app_name}.app/native/ios-device/ext.dylib\"" end + + def set_version version + @app_version = version + start env: @opts[:env], version: version + end + + def app_version + log_info "Attempting to retrieve App Version from metadata/ios_metadata.txt." + ios_version_number = (ios_metadata.version || "").strip + if ios_version_number.length == 0 + log_info "Not found. Attempting to retrieve App Version from metadata/game_metadata.txt." + ios_version_number = (game_metadata.version || "").strip + end + ios_version_number + end + + def determine_app_version + @app_version = app_version + return if @app_version + end end </code></pre> -<h3 id='----itch_wizard.rb'>itch_wizard.rb</h3> +<h3 id='----itch_wizard-rb'>itch_wizard.rb</h3> <pre><code class="language-ruby"># ./dragon/itch_wizard.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # itch_wizard.rb has been released under MIT (*only this file*). -class ItchWizard +class ItchWizard < Wizard def steps [ :check_metadata, - :deploy + :deploy, ] end - def metadata_file_path - "metadata/game_metadata.txt" - end - - def get_metadata - metadata = $gtk.read_file metadata_file_path - - if !metadata - write_blank_metadata - metadata = $gtk.read_file metadata_file_path - end - - dev_id, dev_title, game_id, game_title, version, icon = *metadata.each_line.to_a - - { - dev_id: dev_id.strip, - dev_title: dev_title.strip, - game_id: game_id.strip, - game_title: game_title.strip, - version: version.strip, - icon: icon.strip - } - end - def write_blank_metadata $gtk.write_file metadata_file_path, <<-S.strip #devid=myname @@ -40195,7 +43308,7 @@ S write_blank_metadata end - if metadata_text.each_line.to_a.length != 6 + if metadata_text.strip.each_line.to_a.length < 6 write_blank_metadata end @@ -40207,70 +43320,67 @@ S if metadata[:dev_id].start_with?("#") || !@dev_id log "* PROMPT: Please provide your username for Itch." - $console.set_command "$wizards.itch.set_dev_id \"your-itch-username\"" + $console.set_command "$wizards.itch.set_dev_id \"#{metadata[:dev_id]}\"" return :need_dev_id end if metadata[:dev_title].start_with?("#") || !@dev_title log "* PROMPT: Please provide developer's/company's name that you want displayed." - $console.set_command "$wizards.itch.set_dev_title \"Your Name\"" + $console.set_command "$wizards.itch.set_dev_title \"#{metadata[:dev_title]}\"" return :need_dev_title end if metadata[:game_id].start_with?("#") || !@game_id log "* PROMPT: Please provide the id for you game. This is the id you specified when you set up a new game page on Itch." - $console.set_command "$wizards.itch.set_game_id \"your-game-id\"" + $console.set_command "$wizards.itch.set_game_id \"#{metadata[:game_id]}\"" return :need_game_id end if metadata[:game_title].start_with?("#") || !@game_title log "* PROMPT: Please provide the display name for your game. (This can include spaces)" - $console.set_command "$wizards.itch.set_game_title \"Your Game\"" + $console.set_command "$wizards.itch.set_game_title \"#{metadata[:game_title]}\"" return :need_game_title end if metadata[:version].start_with?("#") || !@version log "* PROMPT: Please provide the version for your game." - $console.set_command "$wizards.itch.set_version \"1.0\"" + $console.set_command "$wizards.itch.set_version \"#{metadata[:version]}\"" return :need_version end if metadata[:icon].start_with?("#") || !@icon log "* PROMPT: Please provide icon path for your game." - $console.set_command "$wizards.itch.set_icon \"icon.png\"" + $console.set_command "$wizards.itch.set_icon \"#{metadata[:icon]}\"" return :need_icon end + puts "here!! success!!!" + return :success end def set_dev_id value @dev_id = value - write_metadata start end def set_dev_title value @dev_title = value - write_metadata start end def set_game_id value @game_id = value - write_metadata start end def set_game_title value @game_title = value - write_metadata start end def set_version value @version = value - write_metadata start end @@ -40313,7 +43423,7 @@ S end if @icon - text += "icon=metadata/#{@icon}\n" + text += "icon=#{@icon}\n" else text += "#icon=metadata/icon.png\n" end @@ -40331,10 +43441,25 @@ S def deploy log_info "* Running dragonruby-publish: #{package_command}" - results = $gtk.exec package_command + $gtk.openurl "http://itch.io/dashboard" if $gtk.platform == "Mac OS X" + if $gtk.platform? :mac + $gtk.exec "rm -rf ./builds" + end + results = $gtk.exec "#{package_command} --only-package" + puts File.expand_path("./builds") + log "#+begin_src" log results log "#+end_src" + + if $gtk.platform? :mac + $gtk.exec "open ./builds/" + elsif $gtk.platform? :windows + $gtk.exec "powershell \"ii .\"" + end + + $gtk.openurl "https://itch.io/dashboard" + :success end @@ -40345,7 +43470,7 @@ S steps.each do |m| begin log_info "Running Itch Wizard Step: ~$wizards.itch.#{m}~" - result = (send m) || :success if @wizard_status[m][:result] != :success + result = (send m) || :success @wizard_status[m][:result] = result if result != :success log_info "Exiting wizard. :#{result}" @@ -40412,8 +43537,9 @@ S end </code></pre> -<h3 id='----layout.rb'>layout.rb</h3> +<h3 id='----layout-rb'>layout.rb</h3> <pre><code class="language-ruby"># ./dragon/layout.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # layout.rb has been released under MIT (*only this file*). @@ -40692,10 +43818,18 @@ module GTK device.grid_area.row_count end + def row_max_index + row_count - 1 + end + def col_count device.grid_area.col_count end + def col_max_index + col_count - 1 + end + def gutter_height device.grid_area.gutter end @@ -40718,18 +43852,124 @@ module GTK def rect_defaults { - row: nil, - col: nil, - h: 1, - w: 1, - dx: 0, - dy: 0, - rect: :control_rect + row: nil, + col: nil, + h: 1, + w: 1, + dx: 0, + dx_ratio: 1, + dy: 0, + dy_ratio: 1, + dh_ratio: 1, + dw_ratio: 1, + merge: nil, + rect: :control_rect } end - def rect opts + def row n + (rect row: n, col: 0, w: 0, h: 0).x + end + + def row_from_bottom n + (rect row: row_count - n, col: 0, w: 0, h: 0).x + end + + def col n + (rect row: 0, col: n, w: 0, h: 0).y + end + + def col_from_right n + (rect row: 0, col: col_max_index - n, w: 0, h: 0).y + end + + def w n + (rect row: 0, col: 0, w: n, h: 1).w + end + + def h n + (rect row: 0, col: 0, w: 1, h: n).h + end + + def rect_group opts + group = opts.group + r = opts.row || 0 + r = row_max_index - opts.row_from_bottom if opts.row_from_bottom + c = opts.col || 0 + c = col_max_index - opts.col_from_right if opts.col_from_right + drow = opts.drow || 0 + dcol = opts.dcol || 0 + w = opts.w || 0 + h = opts.h || 0 + merge = opts[:merge] + + running_row = r + running_col = c + + running_col = calc_col_offset(opts.col_offset) if opts.col_offset + running_row = calc_row_offset(opts.row_offset) if opts.row_offset + + group.map do |i| + group_layout_opts = i.layout || {} + group_layout_opts = group_layout_opts.merge row: running_row, + col: running_col, + merge: merge, + w: w, h: h + result = (rect group_layout_opts).merge i + + if (i.is_a? Hash) && (i.primitive_marker == :label) + if i.alignment_enum == 1 + result.x += result.w.half + elsif i.alignment_enum == 2 + result.x += result.w + end + end + + running_row += drow + running_col += dcol + result + end + end + + def calc_row_offset opts = {} + count = opts[:count] || opts[:length] || 0 + h = opts.h || 1 + (row_count - (count * h)) / 2.0 + end + + def calc_col_offset opts = {} + count = opts[:count] || opts[:length] || 0 + w = opts.w || 1 + (col_count - (count * w)) / 2.0 + end + + def point opts = {} + opts.w = 1 + opts.h = 1 + opts.row ||= 0 + opts.col ||= 0 + r = rect opts + r.x += r.w * opts.col_anchor if opts.col_anchor + r.y += r.h * opts.row_anchor if opts.row_anchor + r + end + + def rect *all_opts + if all_opts.length == 1 + opts = all_opts.first + else + opts = {} + all_opts.each do |o| + opts.merge! o + end + end + + opts.row = row_max_index - opts.row_from_bottom if opts.row_from_bottom + opts.col = col_max_index - opts.col_from_right if opts.col_from_right opts = rect_defaults.merge opts + opts.row ||= 0 + opts.col ||= 0 + result = send opts[:rect] if opts[:row] && opts[:col] && opts[:w] && opts[:h] col = rect_col opts[:col], opts[:w] @@ -40737,7 +43977,9 @@ module GTK result = control_rect.merge x: col.x, y: row.y, w: col.w, - h: row.h + h: row.h, + center_x: col.center_x, + center_y: row.center_y elsif opts[:row] && !opts[:col] result = rect_row opts[:row], opts[:h] elsif !opts[:row] && opts[:col] @@ -40775,13 +44017,21 @@ module GTK result[:h] += device.grid_area.gutter * 2 end - result[:x] += opts[:dx] if opts[:dx] - result[:y] += opts[:dy] if opts[:dy] - result[:w] += opts[:dw] if opts[:dw] - result[:h] += opts[:dh] if opts[:dh] + result[:x] += opts[:dx] if opts[:dx] + result[:x] *= opts[:dx_ratio] if opts[:dx_ratio] + result[:y] += opts[:dy] if opts[:dy] + result[:y] *= opts[:dy_ratio] if opts[:dy_ratio] + result[:w] += opts[:dw] if opts[:dw] + result[:w] *= opts[:dw_ratio] if opts[:dw_ratio] + result[:h] += opts[:dh] if opts[:dh] + result[:h] *= opts[:dh_ratio] if opts[:dh_ratio] + result.merge! opts[:merge] if opts[:merge] result[:row] = opts[:row] result[:col] = opts[:col] + result[:h] = result[:h].clamp 0 + result[:w] = result[:w].clamp 0 + if $gtk.args.grid.name == :center result[:x] -= 640 result[:y] -= 360 @@ -40816,7 +44066,7 @@ module GTK row_y = device.h - row_y - row_h - result = control_rect.merge y: row_y, h: row_h + result = control_rect.merge y: row_y, h: row_h, center_y: (row_y + row_h.half) @rect_cache[:row][index][h] = result @rect_cache[:row][index][h] end @@ -40839,7 +44089,7 @@ module GTK col_w = col_w.to_i col_w -= 1 if col_w.odd? - result = control_rect.merge x: col_x, w: col_w + result = control_rect.merge x: col_x, w: col_w, center_x: (col_x + col_w.half) @rect_cache[:col][index][w] = result @rect_cache[:col][index][w] end @@ -40890,6 +44140,26 @@ module GTK @device end + def debug_primitives opts = {} + @primitives ||= col_count.map_with_index do |col| + row_count.map_with_index do |row| + cell = rect row: row, col: col + center = Geometry.rect_center_point cell + [ + cell.merge(opts).border, + cell.merge(opts) + .label!(x: center.x, + y: center.y, + text: "#{row},#{col}", + size_enum: -3, + vertical_alignment_enum: 1, + alignment_enum: 1) + ] + end + end + @primitives + end + def serialize { device: @device.serialize, @@ -40903,11 +44173,18 @@ module GTK def to_s serialize.to_s end + + def reset + @primitives = nil + @rect_cache ||= {} + @rect_cache.clear + end + end end </code></pre> -<h3 id='----log.rb'>log.rb</h3> +<h3 id='----log-rb'>log.rb</h3> <pre><code class="language-ruby"># ./dragon/log.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -41040,6 +44317,11 @@ module GTK self.puts message end + def self.reset + @once = {} + nil + end + def self.puts_once *ids, message id = "#{ids}" @once ||= {} @@ -41175,7 +44457,54 @@ class Object end </code></pre> -<h3 id='----numeric.rb'>numeric.rb</h3> +<h3 id='----metadata-rb'>metadata.rb</h3> +<pre><code class="language-ruby"># ./dragon/metadata.rb +# coding: utf-8 +# Copyright 2021 DragonRuby LLC +# MIT License +# metadata.rb has been released under MIT (*only this file*). + +# Contributors outside of DragonRuby who also hold Copyright: Michał Dudziński + +module Metadata + def metadata_file_path + "metadata/game_metadata.txt" + end + + def get_metadata + metadata = $gtk.read_file metadata_file_path + + if !metadata + write_blank_metadata + metadata = $gtk.read_file metadata_file_path + end + + dev_id, dev_title, game_id, game_title, version, icon = *metadata.each_line.to_a + + { + dev_id: dev_id.strip, + dev_title: dev_title.strip, + game_id: game_id.strip, + game_title: game_title.strip, + version: version.strip, + icon: icon.strip + } + end + + def write_blank_metadata + $gtk.write_file metadata_file_path, <<-S.strip +#devid=myname +#devtitle=My Name +#gameid=mygame +#gametitle=My Game +#version=0.1 +#icon=metadata/icon.png +S + end +end + +</code></pre> +<h3 id='----numeric-rb'>numeric.rb</h3> <pre><code class="language-ruby"># ./dragon/numeric.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -41192,6 +44521,35 @@ class Numeric alias_method :lte, :<= alias_method :__original_eq_eq__, :== unless Numeric.instance_methods.include? :__original_eq_eq__ + def to_layout_row opts = {} + $layout.rect(row: self, + col: opts.col || 0, + w: opts.w || 0, + h: opts.h || 0).y + end + + def to_layout_col opts = {} + $layout.rect(row: 0, + col: self, + w: opts.w || 0, + h: opts.h || 0).x + end + + def to_layout_w + $layout.rect(row: 0, col: 0, w: self, h: 1).w + end + + def to_layout_h + $layout.rect(row: 0, col: 0, w: 1, h: self).h + end + + def to_layout_row_from_bottom opts = {} + ($layout.row_max_index - self).to_layout_row opts + end + + def to_layout_col_from_right opts = {} + ($layout.col_max_index - self).to_layout_col opts + end # Converts a numeric value representing seconds into frames. # @@ -41207,10 +44565,26 @@ class Numeric self / 2.0 end + def third + self / 3.0 + end + + def quarter + self / 4.0 + end + def to_byte clamp(0, 255).to_i end + def clamp *opts + min = (opts.at 0) + max = (opts.at 1) + return min if min && self < min + return max if max && self > max + return self + end + def clamp_wrap min, max max, min = min, max if min > max return self if self >= min && self <= max @@ -41435,7 +44809,7 @@ S self * Math::PI.fdiv(180) end - # Converts a number representing an angle in radians to degress. + # Converts a number representing an angle in radians to degrees. # # @gtk def to_degrees @@ -41452,21 +44826,21 @@ S GTK::Geometry.to_square(self, x, y, anchor_x, anchor_y) end - # Returns a normal vector for a number that represents an angle in degress. + # Returns a normal vector for a number that represents an angle in degrees. # # @gtk def vector max_value = 1 [vector_x(max_value), vector_y(max_value)] end - # Returns the y component of a normal vector for a number that represents an angle in degress. + # Returns the y component of a normal vector for a number that represents an angle in degrees. # # @gtk def vector_y max_value = 1 max_value * Math.sin(self.to_radians) end - # Returns the x component of a normal vector for a number that represents an angle in degress. + # Returns the x component of a normal vector for a number that represents an angle in degrees. # # @gtk def vector_x max_value = 1 @@ -41493,6 +44867,18 @@ S (self % n) == 0 end + def multiply n + self * n + end + + def fmult n + self * n.to_f + end + + def imult n + (self * n).to_i + end + def mult n self * n end @@ -41594,32 +44980,6 @@ S (0..self).to_a end - def >= other - return false if !other - return gte other - end - - def > other - return false if !other - return gt other - end - - def <= other - return false if !other - return lte other - end - - def < other - return false if !other - return gt other - end - - def == other - return true if __original_eq_eq__ other - return __original_eq_eq__ other.entity_id if other.is_a? OpenEntity - return false - end - # @gtk def map unless block_given? @@ -41705,34 +45065,6 @@ The object above is not a Numeric. S end - def - other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :-, e - end - - def + other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :+, e - end - - def * other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :*, e - end - - def / other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :/, e - end - def serialize self end @@ -41758,6 +45090,10 @@ S def self.clamp n, min, max n.clamp min, max end + + def mid? l, r + (between? l, r) || (between? r, l) + end end class Fixnum @@ -41783,40 +45119,6 @@ class Fixnum return !even? end - def + other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :+, e - end - - def * other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :*, e - end - - def / other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :/, e - end - - def - other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :-, e - end - - def == other - return true if __original_eq_eq__ other - return __original_eq_eq__ other.entity_id if other.is_a? GTK::OpenEntity - return false - end - # Returns `-1` if the number is less than `0`. `+1` if the number # is greater than `0`. Returns `0` if the number is equal to `0`. # @@ -41872,34 +45174,6 @@ class Float alias_method :__original_multiply__, :* unless Float.instance_methods.include? :__original_multiply__ alias_method :__original_divide__, :- unless Float.instance_methods.include? :__original_divide__ - def - other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :-, e - end - - def + other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :+, e - end - - def * other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :*, e - end - - def / other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :/, e - end - def serialize self end @@ -41941,10 +45215,278 @@ class Integer def nan? false end + + def center other + (self - other).abs.fdiv(2) + end +end + +</code></pre> +<h3 id='----recording-rb'>recording.rb</h3> +<pre><code class="language-ruby"># ./dragon/recording.rb +# coding: utf-8 +# Copyright 2019 DragonRuby LLC +# MIT License +# recording.rb has been released under MIT (*only this file*). + +module GTK + # FIXME: Gross + # @gtk + class Replay + # @gtk + def self.start file_name = nil + $recording.start_replay file_name + end + + # @gtk + def self.stop + $recording.stop_replay + end + end + + # @gtk + class Recording + def initialize runtime + @runtime = runtime + @tick_count = 0 + @global_input_order = 1 + end + + def tick + @tick_count += 1 + end + + def start_recording seed_number = nil + if !seed_number + log <<-S +* ERROR: +To start recording, you must provide an integer value to +seed random number generation. +S + $console.set_command "$recording.start SEED_NUMBER" + return + end + + if @is_recording + log <<-S +* ERROR: +You are already recording, first cancel (or stop) the current recording. +S + $console.set_command "$recording.cancel" + return + end + + if @is_replaying + log <<-S +* ERROR: +You are currently replaying a recording, first stop the replay. +S + return + end + + log_info <<-S +Recording has begun with RNG seed value set to #{seed_number}. +To stop recording use stop_recording(filename). +The recording will stop without saving a file if a filename is nil. +S + + $console.set_command "$recording.stop 'replay.txt'" + @runtime.__reset__ + @seed_number = seed_number + @runtime.set_rng seed_number + + @tick_count = 0 + @global_input_order = 1 + @is_recording = true + @input_history = [] + @runtime.notify! "Recording started. When completed, open the console to save it using $recording.stop FILE_NAME (or cancel).", 300 + end + + # @gtk + def start seed_number = nil + start_recording seed_number + end + + def is_replaying? + @is_replaying + end + + def is_recording? + @is_recording + end + + # @gtk + def stop file_name = nil + stop_recording file_name + end + + # @gtk + def cancel + stop_recording_core + @runtime.notify! "Recording cancelled." + end + + def stop_recording file_name = nil + if !file_name + log <<-S +* ERROR: +To please specify a file name when calling: +$recording.stop FILE_NAME + +If you do NOT want to save the recording, call: +$recording.cancel +S + $console.set_command "$recording.stop 'replay.txt'" + return + end + + if !@is_recording + log_info "You are not currently recording. Use start_recording(seed_number) to start recording." + $console.set_command "$recording.start" + return + end + + if file_name + text = "replay_version 2.0\n" + text << "stopped_at #{@tick_count}\n" + text << "seed #{@seed_number}\n" + text << "recorded_at #{Time.now.to_s}\n" + @input_history.each do |items| + text << "#{items}\n" + end + @runtime.write_file file_name, text + @runtime.write_file 'last_replay.txt', text + log_info "The recording has been saved successfully at #{file_name}. You can use start_replay(\"#{file_name}\") to replay the recording." + end + + $console.set_command "$replay.start '#{file_name}'" + stop_recording_core + @runtime.notify! "Recording saved to #{file_name}. To replay it: $replay.start \"#{file_name}\"." + log_info "You can run the replay later on startup using: ./dragonruby mygame --replay #{@replay_file_name}" + nil + end + + def stop_recording_core + @is_recording = false + @input_history = nil + @last_history = nil + @runtime.__reset__ + end + + def start_replay file_name = nil + if !file_name + log <<-S +* ERROR: +Please provide a file name to $recording.start. +S + $console.set_command "$replay.start 'replay.txt'" + return + end + + text = @runtime.read_file file_name + return false unless text + + if text.each_line.first.strip != "replay_version 2.0" + raise "The replay file #{file_name} is not compatible with this version of DragonRuby Game Toolkit. Please recreate the replay (sorry)." + end + + @replay_file_name = file_name + + $replay_data = { input_history: { } } + text.each_line do |l| + if l.strip.length == 0 + next + elsif l.start_with? 'replay_version' + next + elsif l.start_with? 'seed' + $replay_data[:seed] = l.split(' ').last.to_i + elsif l.start_with? 'stopped_at' + $replay_data[:stopped_at] = l.split(' ').last.to_i + elsif l.start_with? 'recorded_at' + $replay_data[:recorded_at] = l.split(' ')[1..-1].join(' ') + elsif l.start_with? '[' + name, value_1, value_2, value_count, id, tick_count = l.strip.gsub('[', '').gsub(']', '').split(',') + $replay_data[:input_history][tick_count.to_i] ||= [] + $replay_data[:input_history][tick_count.to_i] << { + id: id.to_i, + name: name.gsub(':', '').to_sym, + value_1: value_1.to_f, + value_2: value_2.to_f, + value_count: value_count.to_i + } + else + raise "Replay data seems corrupt. I don't know how to parse #{l}." + end + end + + $replay_data[:input_history].keys.each do |key| + $replay_data[:input_history][key] = $replay_data[:input_history][key].sort_by {|input| input[:id]} + end + + @runtime.__reset__ + @runtime.set_rng $replay_data[:seed] + @tick_count = 0 + @is_replaying = true + log_info "Replay has been started." + @runtime.notify! "Replay started [#{@replay_file_name}]." + end + + def stop_replay notification_message = "Replay has been stopped." + if !is_replaying? + log <<-S +* ERROR: +No replay is currently running. Call $replay.start FILE_NAME to start a replay. +S + + $console.set_command "$replay.start 'replay.txt'" + return + end + log_info notification_message + @is_replaying = false + $replay_data = nil + @tick_count = 0 + @global_input_order = 1 + $console.set_command_silent "$replay.start '#{@replay_file_name}'" + @runtime.__reset__ + @runtime.notify! notification_message + end + + def record_input_history name, value_1, value_2, value_count, clear_cache = false + return if @is_replaying + return unless @is_recording + @input_history << [name, value_1, value_2, value_count, @global_input_order, @tick_count] + @global_input_order += 1 + end + + def stage_replay_values + return unless @is_replaying + return unless $replay_data + + if $replay_data[:stopped_at] <= @tick_count + stop_replay "Replay completed [#{@replay_file_name}]. To rerun, bring up the Console and press enter." + return + end + + inputs_this_tick = $replay_data[:input_history][@tick_count] + + if @tick_count.zmod? 60 + log_info "Replay ends in #{($replay_data[:stopped_at] - @tick_count).idiv 60} second(s)." + end + + return unless inputs_this_tick + inputs_this_tick.each do |v| + args = [] + args << v[:value_1] if v[:value_count] >= 1 + args << v[:value_2] if v[:value_count] >= 2 + args << :replay + $gtk.send v[:name], *args + end + end + end end </code></pre> -<h3 id='----remote_hotload_client.rb'>remote_hotload_client.rb</h3> +<h3 id='----remote_hotload_client-rb'>remote_hotload_client.rb</h3> <pre><code class="language-ruby"># ./dragon/remote_hotload_client.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -41995,7 +45537,7 @@ module GTK def local_state @local_state ||= OpenEntity.new @local_state.hotload_client ||= @local_state.new_entity(:hotload_client, - notes: "This enitity is used by DragonRuby Game Toolkit to provide you hotloading on remote machines.", + notes: "This entity is used by DragonRuby Game Toolkit to provide you hotloading on remote machines.", changes: { }, changes_queue: [], reloaded_files_times: []) @@ -42141,8 +45683,9 @@ module GTK end </code></pre> -<h3 id='----runtime/autocomplete.rb'>runtime/autocomplete.rb</h3> +<h3 id='----runtime/autocomplete-rb'>runtime/autocomplete.rb</h3> <pre><code class="language-ruby"># ./dragon/runtime/autocomplete.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # autocomplete.rb has been released under MIT (*only this file*). @@ -42169,7 +45712,7 @@ module GTK sub_index = index - previous_line[:sum] word = (cursor_line[:line][0..sub_index - 1]).strip token = (word.split " ")[-1] - dots = (token.split ".") + dots = (token.split ".").flat_map { |s| s.split "[" }.flat_map { |s| s.split "]" }.flat_map { |s| s.split "(" }.flat_map { |s| s.split ")" } dot = dots[-1] end @@ -42190,6 +45733,10 @@ module GTK ignores ||= [] ignores = [ignores].flatten keys = keys.map { |k| k.to_s } + keys = keys.reject { |k| k.include? '"' } + .reject { |k| k.start_with? "'" } + .reject { |k| k.include? "," } + .reject { |k| k.start_with? "#" } others = ["def", "end"] + [ :entity_keys_by_ref, :entity_name, @@ -42247,6 +45794,10 @@ module GTK return autocomplete_filter_methods lookup_result.call if lookup_result + if dot[0].upcase == dot[0] && (Object.const_defined? dot.to_sym) + return (Object.const_get dot.to_sym).autocomplete_methods + end + start_collecting = false dots_after_state = dots.find_all do |s| if s == "state" @@ -42262,10 +45813,16 @@ module GTK target = target.as_hash[k.to_sym] if target.respond_to? :as_hash end - return autocomplete_filter_methods target.as_hash.keys + if target.respond_to? :as_hash + return autocomplete_filter_methods target.as_hash.keys + else + return autocomplete_filter_methods target.autocomplete_methods + end end + text = text.each_line.reject { |l| l.strip.start_with? "#" }.join "\n" + text = text.each_line.map { |l| l.split("#").first }.join "\n" text.gsub!("[", " ") text.gsub!("]", " ") text.gsub!("(", " ") @@ -42281,153 +45838,156 @@ module GTK end # end GTK </code></pre> -<h3 id='----runtime/draw.rb'>runtime/draw.rb</h3> -<pre><code class="language-ruby"># ./dragon/runtime/draw.rb +<h3 id='----runtime/benchmark-rb'>runtime/benchmark.rb</h3> +<pre><code class="language-ruby"># ./dragon/runtime/benchmark.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License -# draw.rb has been released under MIT (*only this file*). +# benchmark.rb has been released under MIT (*only this file*). module GTK class Runtime - module Draw - def primitives pass - if $top_level.respond_to? :primitives_override - return $top_level.tick_render @args, pass - end - - # Don't change this draw order unless you understand - # the implications. - - # pass.solids.each { |s| draw_solid s } - # while loops are faster than each with block + module Benchmark + def benchmark_single iterations, name, proc + log <<-S +** Invoking :#{name}... +S idx = 0 - length = pass.solids.length - while idx < pass.solids.length - draw_solid (pass.solids.at idx) # accessing an array using .value instead of [] is faster + r = nil + time_start = Time.now + while idx < iterations + r = proc.call idx += 1 end + result = (Time.now - time_start).round 3 - # pass.static_solids.each { |s| draw_solid s } - idx = 0 - length = pass.static_solids.length - while idx < length - draw_solid (pass.static_solids.at idx) - idx += 1 - end + { name: name, + time: result, + time_ms: (result * 1000).to_i } + end - # pass.sprites.each { |s| draw_sprite s } - idx = 0 - length = pass.sprites.length - while idx < length - draw_sprite (pass.sprites.at idx) - idx += 1 - end + def benchmark opts = {} + iterations = opts.iterations - # pass.static_sprites.each { |s| draw_sprite s } - idx = 0 - length = pass.static_sprites.length - while idx < length - draw_sprite (pass.static_sprites.at idx) - idx += 1 - end + log <<-S +* BENCHMARK: Started +** Caller: #{(caller || []).first} +** Iterations: #{iterations} +S + procs = opts.find_all { |k, v| v.respond_to? :call } + + times = procs.map do |(name, proc)| + benchmark_single iterations, name, proc + end.sort_by { |r| r.time } + + first_place = times.first + second_place = times.second || first_place + + times = times.map do |candidate| + average_time = first_place.time + .add(candidate.time) + .abs + .fdiv(2) + + difference_percentage = 0 + if average_time == 0 + difference_percentage = 0 + else + difference_percentage = first_place.time + .subtract(candidate.time) + .abs + .fdiv(average_time) + .imult(100) + end - # pass.primitives.each { |p| draw_primitive p } - idx = 0 - length = pass.primitives.length - while idx < length - draw_primitive (pass.primitives.at idx) - idx += 1 + difference_time = ((first_place.time - candidate.time) * 1000).round + candidate.merge(difference_percentage: difference_percentage, + difference_time: difference_time) end - # pass.static_primitives.each { |p| draw_primitive p } - idx = 0 - length = pass.static_primitives.length - while idx < length - draw_primitive (pass.static_primitives.at idx) - idx += 1 + too_small_to_measure = false + if (first_place.time + second_place.time) == 0 + too_small_to_measure = true + difference_percentage = 0 + log <<-S +* BENCHMARK: Average time for experiments were too small. Increase the number of iterations. +S + else + difference_percentage = (((first_place.time - second_place.time).abs.fdiv((first_place.time + second_place.time).abs.fdiv(2))) * 100).round end - # pass.labels.each { |l| draw_label l } - idx = 0 - length = pass.labels.length - while idx < length - draw_label (pass.labels.at idx) - idx += 1 - end + difference_time = first_place.time.-(second_place.time).*(1000).abs.round - # pass.static_labels.each { |l| draw_label l } - idx = 0 - length = pass.static_labels.length - while idx < length - draw_label (pass.static_labels.at idx) - idx += 1 - end - - # pass.lines.each { |l| draw_line l } - idx = 0 - length = pass.lines.length - while idx < length - draw_line (pass.lines.at idx) - idx += 1 - end + r = { + iterations: iterations, + first_place: first_place, + second_place: second_place, + difference_time: difference_time, + difference_percentage: difference_percentage, + times: times, + too_small_to_measure: too_small_to_measure + } - # pass.static_lines.each { |l| draw_line l } - idx = 0 - length = pass.static_lines.length - while idx < pass.static_lines.length - draw_line (pass.static_lines.at idx) - idx += 1 - end + log_message = [] + only_one_result = first_place.name == second_place.name - # pass.borders.each { |b| draw_border b } - idx = 0 - length = pass.borders.length - while idx < length - draw_border (pass.borders.at idx) - idx += 1 + if only_one_result + log <<-S +* BENCHMARK: #{r.first_place.name} completed in #{r.first_place.time_ms}ms." +S + else + log <<-S +* BENCHMARK: #{r.message} +** Fastest: #{r.first_place.name.inspect} +** Second: #{r.second_place.name.inspect} +** Margin: #{r.difference_percentage}% (#{r.difference_time.abs}ms) #{r.first_place.time_ms}ms vs #{r.second_place.time_ms}ms. +** Times: +#{r.times.map { |t| "*** #{t.name}: #{t.time_ms}ms (#{t.difference_percentage}% #{t.difference_time.abs}ms)." }.join("\n")} +S end - # pass.static_borders.each { |b| draw_border b } - idx = 0 - length = pass.static_borders.length - while idx < length - draw_border (pass.static_borders.at idx) - idx += 1 - end + r + end + end + end +end - if !$gtk.production - # pass.debug.each { |r| draw_primitive r } - idx = 0 - length = pass.debug.length - while idx < length - draw_primitive (pass.debug.at idx) - idx += 1 - end +</code></pre> +<h3 id='----runtime/draw-rb'>runtime/draw.rb</h3> +<pre><code class="language-ruby"># ./dragon/runtime/draw.rb +# coding: utf-8 +# Copyright 2019 DragonRuby LLC +# MIT License +# draw.rb has been released under MIT (*only this file*). - # pass.static_debug.each { |r| draw_primitive r } - idx = 0 - length = pass.static_debug.length - while idx < length - draw_primitive (pass.static_debug.at idx) - idx += 1 - end +module GTK + class Runtime + module Draw + def primitives pass + if $top_level.respond_to? :primitives_override + return $top_level.tick_render @args, pass end - # pass.reserved.each { |r| draw_primitive r } - idx = 0 - length = pass.reserved.length - while idx < length - draw_primitive (pass.reserved.at idx) - idx += 1 + fn.each_send pass.solids, self, :draw_solid + fn.each_send pass.static_solids, self, :draw_solid + fn.each_send pass.sprites, self, :draw_sprite + fn.each_send pass.static_sprites, self, :draw_sprite + fn.each_send pass.primitives, self, :draw_primitive + fn.each_send pass.static_primitives, self, :draw_primitive + fn.each_send pass.labels, self, :draw_label + fn.each_send pass.static_labels, self, :draw_label + fn.each_send pass.lines, self, :draw_line + fn.each_send pass.static_lines, self, :draw_line + fn.each_send pass.borders, self, :draw_border + fn.each_send pass.static_borders, self, :draw_border + + if !self.production + fn.each_send pass.debug, self, :draw_primitive + fn.each_send pass.static_debug, self, :draw_primitive end - # pass.static_reserved.each { |r| draw_primitive r } - idx = 0 - length = pass.static_reserved.length - while idx < length - draw_primitive (pass.static_reserved.at idx) - idx += 1 - end + fn.each_send pass.reserved, self, :draw_primitive + fn.each_send pass.static_reserved, self, :draw_primitive rescue Exception => e pause! pretty_print_exception_and_export! e @@ -42438,7 +45998,10 @@ module GTK if s.respond_to? :draw_override s.draw_override @ffi_draw else - @ffi_draw.draw_solid s.x, s.y, s.w, s.h, s.r, s.g, s.b, s.a + s = s.as_hash if s.is_a? OpenEntity + @ffi_draw.draw_solid_2 s.x, s.y, s.w, s.h, + s.r, s.g, s.b, s.a, + (s.blendmode_enum || 1) end rescue Exception => e raise_conversion_for_rendering_failed s, e, :solid @@ -42449,14 +46012,16 @@ module GTK if s.respond_to? :draw_override s.draw_override @ffi_draw else - @ffi_draw.draw_sprite_3 s.x, s.y, s.w, s.h, - s.path.s_or_default, + s = s.as_hash if s.is_a? OpenEntity + @ffi_draw.draw_sprite_4 s.x, s.y, s.w, s.h, + (s.path || '').to_s, s.angle, s.a, s.r, s.g, s.b, s.tile_x, s.tile_y, s.tile_w, s.tile_h, !!s.flip_horizontally, !!s.flip_vertically, s.angle_anchor_x, s.angle_anchor_y, - s.source_x, s.source_y, s.source_w, s.source_h + s.source_x, s.source_y, s.source_w, s.source_h, + (s.blendmode_enum || 1) end rescue Exception => e raise_conversion_for_rendering_failed s, e, :sprite @@ -42467,7 +46032,8 @@ module GTK if s.respond_to? :draw_override s.draw_override @ffi_draw else - @ffi_draw.draw_screenshot s.path.s_or_default, + s = s.as_hash if s.is_a? OpenEntity + @ffi_draw.draw_screenshot (s.path || '').to_s, s.x, s.y, s.w, s.h, s.angle, s.a, s.r, s.g, s.b, @@ -42485,10 +46051,14 @@ module GTK if l.respond_to? :draw_override l.draw_override @ffi_draw else - @ffi_draw.draw_label l.x, l.y, l.text.s_or_default, - l.size_enum, l.alignment_enum, - l.r, l.g, l.b, l.a, - l.font.s_or_default(nil) + l = l.as_hash if l.is_a? OpenEntity + @ffi_draw.draw_label_3 l.x, l.y, + (l.text || '').to_s, + l.size_enum, l.alignment_enum, + l.r, l.g, l.b, l.a, + l.font, + (l.vertical_alignment_enum || 2), + (l.blendmode_enum || 1) end rescue Exception => e raise_conversion_for_rendering_failed l, e, :label @@ -42499,7 +46069,22 @@ module GTK if l.respond_to? :draw_override l.draw_override @ffi_draw else - @ffi_draw.draw_line l.x, l.y, l.x2, l.y2, l.r, l.g, l.b, l.a + l = l.as_hash if l.is_a? OpenEntity + if l.x2 + @ffi_draw.draw_line_2 l.x, l.y, l.x2, l.y2, + l.r, l.g, l.b, l.a, + (l.blendmode_enum || 1) + else + w = l.w || 0 + w = 1 if w == 0 + h = l.h || 0 + h = 1 if h == 0 + @ffi_draw.draw_line_2 l.x, l.y, + l.x + w - 1, + l.y + h - 1, + l.r, l.g, l.b, l.a, + (l.blendmode_enum || 1) + end end rescue Exception => e raise_conversion_for_rendering_failed l, e, :line @@ -42510,7 +46095,10 @@ module GTK if s.respond_to? :draw_override s.draw_override @ffi_draw else - @ffi_draw.draw_border s.x, s.y, s.w, s.h, s.r, s.g, s.b, s.a + s = s.as_hash if s.is_a? OpenEntity + @ffi_draw.draw_border_2 s.x, s.y, s.w, s.h, + s.r, s.g, s.b, s.a, + (s.blendmode_enum || 1) end rescue Exception => e raise_conversion_for_rendering_failed s, e, :border @@ -42530,14 +46118,14 @@ module GTK pause! pretty_print_exception_and_export! e end - end end end </code></pre> -<h3 id='----runtime/framerate.rb'>runtime/framerate.rb</h3> +<h3 id='----runtime/framerate-rb'>runtime/framerate.rb</h3> <pre><code class="language-ruby"># ./dragon/runtime/framerate.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # framerate.rb has been released under MIT (*only this file*). @@ -42573,13 +46161,8 @@ module GTK if @tick_speed_count > 60 * 2 if framerate_below_threshold? @last_framerate = current_framerate - if [email protected]? - if !@framerate_important_notification_happened - log_important framerate_warning_message - else - log framerate_warning_message - end - @framerate_important_notification_happened = true + if [email protected]? && [email protected]_replaying? + log framerate_warning_message end end @@ -42623,8 +46206,9 @@ module GTK end # end module GTK </code></pre> -<h3 id='----runtime/framerate_diagnostics.rb'>runtime/framerate_diagnostics.rb</h3> +<h3 id='----runtime/framerate_diagnostics-rb'>runtime/framerate_diagnostics.rb</h3> <pre><code class="language-ruby"># ./dragon/runtime/framerate_diagnostics.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # framerate_diagnostics.rb has been released under MIT (*only this file*). @@ -42742,7 +46326,7 @@ If this warning is getting annoying put the following in your tick method: def framerate_diagnostics_primitives [ - { x: 0, y: 93.from_top, w: 500, h: 93, a: 128 }.solid, + { x: 0, y: 93.from_top, w: 500, h: 93, a: 128 }.solid!, { x: 5, y: 5.from_top, @@ -42751,7 +46335,7 @@ If this warning is getting annoying put the following in your tick method: g: 255, b: 255, size_enum: -2 - }.label, + }.label!, { x: 5, y: 20.from_top, @@ -42760,7 +46344,7 @@ If this warning is getting annoying put the following in your tick method: g: 255, b: 255, size_enum: -2 - }.label, + }.label!, { x: 5, y: 35.from_top, @@ -42769,7 +46353,7 @@ If this warning is getting annoying put the following in your tick method: g: 255, b: 255, size_enum: -2 - }.label, + }.label!, { x: 5, y: 50.from_top, @@ -42778,7 +46362,7 @@ If this warning is getting annoying put the following in your tick method: g: 255, b: 255, size_enum: -2 - }.label, + }.label!, { x: 5, y: 65.from_top, @@ -42787,7 +46371,7 @@ If this warning is getting annoying put the following in your tick method: g: 255, b: 255, size_enum: -2 - }.label, + }.label!, ] end @@ -42796,7 +46380,7 @@ If this warning is getting annoying put the following in your tick method: end </code></pre> -<h3 id='----runtime/hotload.rb'>runtime/hotload.rb</h3> +<h3 id='----runtime/hotload-rb'>runtime/hotload.rb</h3> <pre><code class="language-ruby"># ./dragon/runtime/hotload.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -42810,7 +46394,14 @@ module GTK def hotload_init @hotload_if_needed = 0 @mailbox_if_needed = 0 + + # schema for file_mtimes + # { FILE_PATH: { current: (Time as Fixnum), + # last: (Time as Fixnum) }, + # FILE_PATH: { current: (Time as Fixnum), + # last: (Time as Fixnum) } } @file_mtimes = { } + @suppress_mailbox = true files_to_reload.each { |f| init_mtimes f } init_mtimes 'app/mailbox.rb' @@ -42848,6 +46439,7 @@ module GTK 'dragon/symbol.rb', 'dragon/numeric_deprecated.rb', 'dragon/numeric.rb', + 'dragon/hash_deprecated.rb', 'dragon/hash.rb', 'dragon/outputs_deprecated.rb', 'dragon/array_docs.rb', @@ -42880,6 +46472,7 @@ module GTK 'dragon/trace.rb', 'dragon/readme_docs.rb', 'dragon/hotload_client.rb', + 'dragon/wizards.rb', 'dragon/ios_wizard.rb', 'dragon/itch_wizard.rb', ] + core_files_to_reload + @required_files @@ -42898,10 +46491,8 @@ module GTK end def init_mtimes file - current_key = "current_#{file}".to_sym - last_key = "last_#{file}".to_sym - @file_mtimes[current_key] ||= @ffi_file.mtime(file) - @file_mtimes[last_key] ||= @ffi_file.mtime(file) + @file_mtimes[file] ||= { current: @ffi_file.mtime(file), + last: @ffi_file.mtime(file) } end def hotload_source_files @@ -42933,32 +46524,43 @@ module GTK end def hotload_if_needed + return if Kernel.tick_count < 0 hotload_source_files check_mailbox end def on_load_succeeded file - @rcb_sender.files_reloaded << file - @rcb_sender.reloaded_files << file + self.files_reloaded << file + self.reloaded_files << file Trace.untrace_classes! end + def reset_all_mtimes + @file_mtimes.each do |file, _| + @file_mtimes[file].current = @ffi_file.mtime(file) + @file_mtimes[file].last = @file_mtimes[file].current + end + + files_to_reload.each do |file, _| + @file_mtimes[file] ||= {} + @file_mtimes[file].current = @ffi_file.mtime(file) + @file_mtimes[file].last = @file_mtimes[file].current + end + end + def reload_if_needed file, force = false - current_key = "current_#{file}".to_sym - last_key = "last_#{file}".to_sym - @file_mtimes[current_key] ||= nil - @file_mtimes[last_key] ||= nil - @file_mtimes[current_key] = @ffi_file.mtime(file) - return if !force && @file_mtimes[last_key] == @file_mtimes[current_key] + @file_mtimes[file] ||= { current: @ffi_file.mtime(file), last: @ffi_file.mtime(file) } + @file_mtimes[file].current = @ffi_file.mtime(file) + return if !force && @file_mtimes[file].current == @file_mtimes[file].last on_load_succeeded file if reload_ruby_file file - @file_mtimes[last_key] = @file_mtimes[current_key] + @file_mtimes[file].last = @file_mtimes[file].current end end end end </code></pre> -<h3 id='----string.rb'>string.rb</h3> +<h3 id='----string-rb'>string.rb</h3> <pre><code class="language-ruby"># ./dragon/string.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -42991,6 +46593,30 @@ S end end + def char_byte + return nil if self.length == 0 + c = self.each_char.first.bytes + c = c.first if c.is_a? Enumerable + c + end + + def insert_character_at index, char + t = each_char.to_a + t = (t.insert index, char) + t.join + end + + def excluding_character_at index + t = each_char.to_a + t.delete_at index + t.join + end + + def excluding_last_character + return "" if self.length <= 1 + return excluding_character_at(self.length - 1) + end + def end_with_bang? self[-1] == "!" end @@ -43070,7 +46696,7 @@ S end </code></pre> -<h3 id='----tests.rb'>tests.rb</h3> +<h3 id='----tests-rb'>tests.rb</h3> <pre><code class="language-ruby"># ./dragon/tests.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -43206,14 +46832,14 @@ S log "#{self.failed.length} test(s) failed." self.failed.each do |h| log "**** Test name: :#{h[:m]}" - log "#{h[:e].to_s.gsub("* ERROR:", "").strip}" + log "#{h[:e].to_s.gsub("* ERROR:", "").strip}\n#{h[:e].__backtrace_to_org__}" end end end end </code></pre> -<h3 id='----trace.rb'>trace.rb</h3> +<h3 id='----trace-rb'>trace.rb</h3> <pre><code class="language-ruby"># ./dragon/trace.rb # coding: utf-8 # Copyright 2019 DragonRuby LLC @@ -43401,12 +47027,153 @@ module GTK end </code></pre> -<h3 id='----wizards.rb'>wizards.rb</h3> +<h3 id='----tweetcart-rb'>tweetcart.rb</h3> +<pre><code class="language-ruby"># ./dragon/tweetcart.rb +# coding: utf-8 +# Copyright 2019 DragonRuby LLC +# MIT License +# tweetcart.rb has been released under MIT (*only this file*). + +def $top_level.TICK &block + $top_level.define_method(:tick) do |args| + args.outputs[:scene].w = 160 + args.outputs[:scene].h = 90 + args.outputs[:scene].background_color = [0, 0, 0, 0] + block.call args + args.outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: :scene } + end + + def $top_level.bg! *rgb + r,g,b = rgb + r ||= 255 + g ||= r + b ||= g + $args.outputs.background_color = [r, g, b] + end + + def $top_level.slds + $args.outputs[:scene].sprites + end + + def $top_level.slds! *os + if (os.first.is_a? Numeric) + sld!(*os) + else + os.each { |o| sld!(*o) } + end + end + + def $top_level.sld! *params + x, y, w, h, r, g, b, a = nil + if params.length == 2 + x, y = params + elsif params.length == 3 && (params.last.is_a? Array) + x = params[0] + y = params[1] + r, g, b, a = params[2] + r ||= 255 + g ||= r + b ||= g + a ||= 255 + elsif params.length == 4 + x, y, w, h = params + elsif params.length == 5 && (params.last.is_a? Array) + x = params[0] + y = params[1] + w = params[2] + h = params[3] + r,g,b,a = params[4] + r ||= 255 + g ||= r + b ||= g + a ||= 255 + elsif params.length >= 7 + x, y, w, h, r, g, b = params + else + raise "I don't know how to render #{params} with reasonable defaults." + end + + w ||= 1 + h ||= 1 + r ||= 255 + g ||= 255 + b ||= 255 + a ||= 255 + + slds << { x: x, y: y, + w: w, h: h, + r: r, g: g, b: b, a: a, + path: :pixel } + end +end + +=begin +wht = [255] * 3 +red = [255, 0, 0] +blu = [0, 130, 255] +purp = [150, 80, 255] + +TICK { + bg! 0 + + slds << [0, 0, 3, 3, 0, 255, 0, 255] + + sld! 10, 10 + sld! 20, 20, 3, 2 + sld! 30, 30, 2, 2, red + sld! 35, 35, blu + + slds! 40, 40 + + slds! [50, 50], + [60, 60, purp], + [70, 70, 10, 10, wht], + [80, 80, 4, 4, 255, 0, 255] +} +=end + +</code></pre> +<h3 id='----wizards-rb'>wizards.rb</h3> <pre><code class="language-ruby"># ./dragon/wizards.rb +# coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # wizards.rb has been released under MIT (*only this file*). +class Wizard + def metadata_file_path + "metadata/game_metadata.txt" + end + + def get_metadata + metadata = $gtk.read_file metadata_file_path + + if !metadata + write_blank_metadata + metadata = $gtk.read_file metadata_file_path + end + + dev_id, dev_title, game_id, game_title, version, icon = *metadata.each_line.to_a + + { + dev_id: dev_id.strip.gsub("#", "").gsub("devid=", ""), + dev_title: dev_title.strip.gsub("#", "").gsub("devtitle=", ""), + game_id: game_id.strip.gsub("#", "").gsub("gameid=", ""), + game_title: game_title.strip.gsub("#", "").gsub("gametitle=", ""), + version: version.strip.gsub("#", "").gsub("version=", ""), + icon: icon.strip.gsub("#", "").gsub("icon=", "") + } + end +end + +class WizardException < Exception + attr_accessor :console_primitives + + def initialize *console_primitives + @console_primitives = console_primitives + end +end + module GTK class Wizards attr_accessor :ios, :itch |
