DragonRuby Game Toolkit Contributions

repository·main·Indexed 42 days ago

https://github.com/dragonruby/dragonruby-game-toolkit-contrib

A collection of community contributions and extensions for the DragonRuby Game Toolkit, featuring C extensions, specialized libraries like 'rure', and guides on using the dragonruby-bind utility to create native bindings for high-performance tasks and third-party C libraries.

Tokens
148.9K
Snippets
530
Records
625
Agent score
54%

What's inside DragonRuby Game Toolkit Contrib

  1. What is the Layout API and how to use it

    main

    The Layout API provides tools for placing primitives on a virtual grid that stays within the "safe area" across all platforms. This is ideal for rendering static UI elements like buttons, menu items, and configuration screens.

    All functions are available globally via Layout.*. You can use Layout.rect to define positions based on grid coordinates rather than raw pixel values.

    def tick args
       puts Layout.function(...)
    end
  2. What is rure and its regex capabilities?

    main

    rure is a C API for Rust's regex library. It provides a regex engine that guarantees linear time searching using finite automata.

    Key Features:

    • Capturing groups
    • Lazy matching
    • Unicode support
    • Word boundary assertions
    • Matching semantics follow Perl's "leftmost first" approach.

    Limitations:

    • Does not support backreferences.
    • Does not support arbitrary lookaround.
    • Does not support splitting strings by regex or replacing regex matches in a string (these are currently missing from the C API).
  3. Understand Logical vs Pixel Grid properties

    main

    Grid properties fall into two categories:

    1. Logical: Values are represented at the logical scale of 720p (1280x720 for landscape, 720x1280 for portrait). You should almost always use these.
    2. Pixels: Values represent actual pixels for the current display. These are primarily used for C Extensions, Shaders, or sanity checking Texture Atlases.

    Note: For Standard License holders, Pixel category properties will return the same values as the Logical category. Pixel-accurate values require a Pro License with hd=true.

    def tick args
      # Logical width
      Grid.w
    
      # Width in pixels
      Grid.w_px
    end
  4. Avoid using bare iVars for global state

    main
    Do not use bare instance variables (e.g., @player ||= { x: 0, y: 0 }) at the top level of your script to manage game state. Doing so pollutes the global object space and prevents the state from being cleared automatically via DR.reset. Instead, use args.state for prototyping or classes with iVars for structured development.
  5. Synthesize sound procedurally

    main

    Instead of a file path, you can provide an array to the input key to generate sound: [channels, sample_rate, sound_source].

    • channels: 1 for mono, 2 for stereo.
    • sample_rate: Number of values per second.
    • sound_source:
      • A Proc object: Called on demand to generate samples. Must generate enough samples for 0.1 to 0.5 seconds to avoid skips. These loop endlessly.
      • An Array of float values (-1.0 to 1.0): Played back once. Supports looping.

    Note for Stereo: If channels is 2, the array must be interleaved: [L, R, L, R, ...]. Note on Gain: Changing gain while a synthesized sound is playing may cause clicking/popping. Bake attack/release into the waveform instead.

    def generate_sine_wave frequency:, duration:, fade_out: true
      samples_per_period = (48000 / frequency).ceil
      count = (samples_per_period * duration.fdiv(60)).floor * frequency
      count.map_with_index do |i|
        attack_perc = (i / samples_per_period).clamp(0, 1)
        release_perc = if fade_out
                          1 - (i / count)
                       elsif i > count - samples_per_period
                         (count - i) / samples_per_period
                       else
                         1
                       end
        Math.sin((2 * Math::PI) * (i / samples_per_period)) * attack_perc * release_perc
      end
    end
    
    def tick args
      if Kernel.tick_count == 0
        wave_data = generate_sine_wave frequency: 440.0,
                                       duration: 60 * 1.5,
                                       fade_out: true
        args.audio[:my_audio] = {
          input: [1, 48000, wave_data],
        }
      end
    end
  6. Understand the DragonRuby Game Loop and the `tick` method

    main

    DragonRuby operates on a fixed game loop that executes the tick method exactly 60 times per second (60Hz). All game logic and rendering instructions must reside within this method.

    To implement your game, create a file at mygame/app/main.rb and define a Main module containing the tick method. The tick method receives a single argument, args, which is a structure containing all the information needed to manage game state, handle inputs, and send drawing commands to the engine.

    DragonRuby clears args.outputs every frame, so you must re-submit all drawing instructions (labels, sprites, etc.) every single time tick runs.

    module Main
      def tick args
        # Your game logic and rendering commands go here
      end
    end
  7. Understand DragonRuby's approach to API design and complexity

    main

    DragonRuby follows a philosophy of providing multiple options across a spectrum of "make it fast" vs "make it right". Instead of forcing a single "correct" way (the "Pit of Success" pattern), the engine provides incremental paths that allow developers to transition from rapid prototyping to highly structured code as their needs grow.

    For example, when working with render primitives, you can choose from the following spectrum:

    1. Tuples/Arrays: Simplest and fastest for prototyping.
    2. Hashes: Requires slightly more work but allows for additional properties.
    3. Open and Strict Entities: More complex, but yields cleaner APIs.
    4. Classes: Provides maximum power and flexibility, but requires the most code and programming knowledge.
  8. Implement custom Sprite classes via Duck Typing

    main

    You can create custom Ruby classes to represent sprites. To be rendered as a primitive by args.outputs.sprites, the class must:

    1. Implement all sprite properties (x, y, w, h, path, etc.).
    2. Define a method primitive_marker that returns the symbol :sprite.
    class Sprite
      attr_accessor :x, :y, :w, :h, :path, :angle, :a, :r, :g, :b, :source_x,
                    :source_y, :source_w, :source_h, :flip_horizontally,
                    :flip_vertically, :angle_anchor_x, :angle_anchor_y
    
      def primitive_marker
        :sprite
      end
    end
    
    class PlayerSprite < Sprite
      def initialize x, y, w, h
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.path = 'sprites/player.png'
      end
    end
    
    # Render the custom object
    args.outputs.sprites << PlayerSprite.new(10, 10, 32, 64)
  9. Use C extensions for high-performance pixel array manipulation

    main

    When working with pixel arrays in DragonRuby, moving the creation and manipulation logic from Ruby to C can improve performance. This is particularly beneficial for computationally demanding tasks because C can access memory and process numbers faster.

    Crucially, using C allows you to hand a pixel array directly to the renderer. This avoids the overhead of converting a C-based pixel array into a Ruby array, which the engine would otherwise have to convert back to a native format for rendering.

  10. Understand the DragonRuby sandboxed filesystem

    main

    DragonRuby uses a sandboxed filesystem that automatically handles platform-specific storage locations. When using relative paths, the engine resolves them based on the game's directory or the platform's appropriate data directory.

    Production Data Directories:

    • Windows: C:\Users\YourWindowsUsername\AppData\Roaming\[devtitle]\gametitle
    • MacOS: $HOME/Library/Application Support/[gametitle]
    • Linux: $HOME/.local/share/[gametitle]
    • HTML5: Browser's IndexedDB

    Note: [devtitle] and [gametitle] are values from your app/metadata/game_metadata.txt file.

    Reading Priority: When reading files, the engine checks the data directory first, then the game directory. If a file exists in both, the version in the data directory takes precedence. This allows for persistent user data to override shipped game assets.

  11. How to use Geometry module methods

    main

    The Geometry module provides common game development calculations. You can use these methods in two ways:

    1. Mixin Variant: The methods are mixed into Hash, Array, and DragonRuby's Entity class, allowing you to call them directly on instances.
    2. Module Variant: You can call the methods directly on the Geometry module by passing the objects as arguments.

    Commonly mixed-in methods include:

    • intersect_rect?
    • inside_rect?
    • scale_rect
    • angle_to
    • angle_from
    • point_inside_circle?
    • center_inside_rect
    • center_inside_rect_x
    • center_inside_rect_y
    • anchor_rect
    • rect_center_point
    # define to rectangles
    rect_1 = { x: 0, y: 0, w: 100, h: 100 }
    rect_2 = { x: 50, y: 50, w: 100, h: 100 }
    
    # mixin variant (call from instance)
    puts rect_1.intersect_rect?(rect_2)
    
    # module variant (call from Geometry)
    puts Geometry.intersect_rect?(rect_1, rect_2)