anima-core 0.2.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +19 -0
- data/README.md +213 -43
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +195 -45
- data/app/decorators/user_message_decorator.rb +16 -5
- data/app/jobs/agent_request_job.rb +55 -2
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +15 -4
- data/app/models/concerns/event/broadcasting.rb +81 -0
- data/app/models/event.rb +20 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +366 -21
- data/config/application.rb +2 -0
- data/config/initializers/event_subscribers.rb +0 -1
- data/config/routes.rb +0 -6
- data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
- data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -6
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +5 -40
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/events/subscribers/persister.rb +1 -0
- data/lib/events/user_message.rb +17 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +11 -20
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +226 -0
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +151 -0
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tools/write.rb +86 -0
- data/lib/tui/app.rb +985 -26
- data/lib/tui/cable_client.rb +69 -31
- data/lib/tui/message_store.rb +103 -8
- data/lib/tui/screens/chat.rb +293 -45
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +290 -3
- data/app/controllers/api/sessions_controller.rb +0 -25
- data/lib/events/subscribers/action_cable_bridge.rb +0 -59
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dragonruby
|
|
3
|
+
description: "DragonRuby 2D game development — game loops, sprites, input, collisions, scenes. Activate when building games with DragonRuby/DRGTK, working with args.outputs/state/inputs, or editing game files."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DragonRuby Game Toolkit
|
|
7
|
+
|
|
8
|
+
This skill provides comprehensive guidance for building 2D games with DragonRuby Game Toolkit (DRGTK). Use for game loop implementation, sprite rendering, input handling, collision detection, animation, and scene management.
|
|
9
|
+
|
|
10
|
+
## Quick Reference
|
|
11
|
+
|
|
12
|
+
### Basic Game Structure
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def boot args
|
|
16
|
+
args.state = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tick args
|
|
20
|
+
# Called 60 times per second
|
|
21
|
+
args.state.player ||= { x: 640, y: 360, w: 50, h: 50, path: 'player.png' }
|
|
22
|
+
|
|
23
|
+
# Handle input
|
|
24
|
+
args.state.player.x += 5 if args.inputs.right
|
|
25
|
+
args.state.player.x -= 5 if args.inputs.left
|
|
26
|
+
|
|
27
|
+
# Render
|
|
28
|
+
args.outputs.sprites << args.state.player
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Key Concepts
|
|
33
|
+
|
|
34
|
+
| Concept | Purpose |
|
|
35
|
+
|---------|---------|
|
|
36
|
+
| `def tick(args)` | Main game loop (60 FPS) |
|
|
37
|
+
| `args.outputs` | Render sprites, labels, primitives |
|
|
38
|
+
| `args.state` | Persistent game data storage |
|
|
39
|
+
| `args.inputs` | Keyboard, mouse, controller input |
|
|
40
|
+
| `args.grid` | Screen dimensions (1280x720) |
|
|
41
|
+
| `Geometry` | Collision detection helpers |
|
|
42
|
+
|
|
43
|
+
### Coordinate System
|
|
44
|
+
|
|
45
|
+
- **Screen**: 1280x720 pixels
|
|
46
|
+
- **Origin**: Bottom-left (0, 0)
|
|
47
|
+
- **Y-axis**: Increases upward
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
(0, 720) ─────────────── (1280, 720)
|
|
51
|
+
│ │
|
|
52
|
+
│ 1280 × 720 │
|
|
53
|
+
│ │
|
|
54
|
+
(0, 0) ─────────────── (1280, 0)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Rendering Primitives
|
|
58
|
+
|
|
59
|
+
Use `args.outputs.primitives` for FIFO (first-in, first-out) render order control.
|
|
60
|
+
|
|
61
|
+
### Sprites (Images)
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
args.outputs.primitives << {
|
|
65
|
+
x: 100, y: 100, w: 64, h: 64,
|
|
66
|
+
path: 'sprites/player.png',
|
|
67
|
+
angle: 45,
|
|
68
|
+
anchor_x: 0.5, anchor_y: 0.5,
|
|
69
|
+
r: 255, g: 255, b: 255, a: 255,
|
|
70
|
+
flip_horizontally: false
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Labels (Text)
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
args.outputs.primitives << {
|
|
78
|
+
x: 640, y: 360,
|
|
79
|
+
text: "Score: #{args.state.score}",
|
|
80
|
+
size_px: 22,
|
|
81
|
+
anchor_x: 0.5, anchor_y: 0.5,
|
|
82
|
+
r: 255, g: 255, b: 255
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Solids and Borders
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# Filled rectangle (use primitive_marker: :solid)
|
|
90
|
+
args.outputs.primitives << {
|
|
91
|
+
x: 0, y: 0, w: 100, h: 100,
|
|
92
|
+
r: 255, g: 0, b: 0,
|
|
93
|
+
primitive_marker: :solid
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# For many rectangles, use path: :solid for better performance
|
|
97
|
+
args.outputs.primitives << { x: 0, y: 0, w: 100, h: 100, path: :solid, r: 255, g: 0, b: 0 }
|
|
98
|
+
|
|
99
|
+
# Outline rectangle
|
|
100
|
+
args.outputs.primitives << { x: 0, y: 0, w: 100, h: 100, r: 0, g: 0, b: 0, primitive_marker: :border }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## State Management
|
|
104
|
+
|
|
105
|
+
Use `args.state` with `||=` for lazy initialization:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
def tick args
|
|
109
|
+
args.state.player ||= { x: 640, y: 360 }
|
|
110
|
+
args.state.enemies ||= []
|
|
111
|
+
args.state.score ||= 0
|
|
112
|
+
args.state.scene ||= :title
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Input Handling
|
|
117
|
+
|
|
118
|
+
### Unified Input (Keyboard + Controller)
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
# Directional (arrows, WASD, gamepad)
|
|
122
|
+
args.inputs.up / down / left / right
|
|
123
|
+
|
|
124
|
+
# Magnitude values
|
|
125
|
+
args.inputs.left_right # -1, 0, or 1
|
|
126
|
+
args.inputs.up_down # -1, 0, or 1
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Keyboard
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
args.inputs.keyboard.key_down.space # Pressed this frame
|
|
133
|
+
args.inputs.keyboard.key_held.space # Held down
|
|
134
|
+
args.inputs.keyboard.key_up.space # Released this frame
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Mouse
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
args.inputs.mouse.click # Any button clicked
|
|
141
|
+
args.inputs.mouse.x / .y # Position
|
|
142
|
+
args.inputs.mouse.inside_rect?(rect) # Collision check
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Collision Detection
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
if Geometry.intersect_rect?(player, enemy)
|
|
149
|
+
enemy.dead = true
|
|
150
|
+
args.state.score += 1
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Clean up dead entities
|
|
154
|
+
args.state.enemies.reject! { |e| e.dead }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Animation
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# Frame-based animation
|
|
161
|
+
sprite_index = 0.frame_index(count: 6, hold_for: 8, repeat: true)
|
|
162
|
+
args.state.player.path = "sprites/player-#{sprite_index}.png"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Scene Management
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
def tick args
|
|
169
|
+
args.state.scene ||= :title
|
|
170
|
+
send("#{args.state.scene}_tick", args)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def title_tick args
|
|
174
|
+
args.outputs.labels << { x: 640, y: 400, text: "Press SPACE", anchor_x: 0.5 }
|
|
175
|
+
args.state.scene = :gameplay if args.inputs.keyboard.key_down.space
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def gameplay_tick args
|
|
179
|
+
# Game logic here
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Best Practices
|
|
184
|
+
|
|
185
|
+
### Do
|
|
186
|
+
|
|
187
|
+
- Use hash syntax for sprites/labels (clearer than arrays)
|
|
188
|
+
- Use `||=` for state initialization
|
|
189
|
+
- Remove offscreen entities to prevent memory leaks
|
|
190
|
+
- Update logic before rendering
|
|
191
|
+
- Use `$gtk.reset` during development to reset state
|
|
192
|
+
- Use `args.outputs.primitives` for FIFO render order control
|
|
193
|
+
- Use `primitive_marker: :solid` or `:border` for rectangle types
|
|
194
|
+
|
|
195
|
+
### Don't
|
|
196
|
+
|
|
197
|
+
- Hardcode magic numbers (use constants like `FPS = 60`)
|
|
198
|
+
- Forget early returns in scene methods
|
|
199
|
+
- Render before updating state (causes 1-frame lag)
|
|
200
|
+
- Let collections grow infinitely (reject dead entities)
|
|
201
|
+
|
|
202
|
+
## Additional Resources
|
|
203
|
+
|
|
204
|
+
### Reference Files
|
|
205
|
+
|
|
206
|
+
For detailed API documentation and patterns:
|
|
207
|
+
|
|
208
|
+
- **`references/core.md`** - Game loop, args object, rendering, coordinates
|
|
209
|
+
- **`references/input.md`** - Keyboard, mouse, controller input patterns
|
|
210
|
+
- **`references/entities.md`** - Entity spawning, collision, lifecycle
|
|
211
|
+
- **`references/game-logic/state.md`** - Timers, scoring, scene transitions
|
|
212
|
+
- **`references/game-logic/persistence.md`** - Save/load, file I/O patterns
|
|
213
|
+
- **`references/audio.md`** - Sound effects, music playback, audio controls
|
|
214
|
+
- **`references/rendering/primitives.md`** - Sprites, labels, solids, borders, layering
|
|
215
|
+
- **`references/rendering/animation.md`** - frame_index, spritesheets, easing functions
|
|
216
|
+
|
|
217
|
+
### Example Files
|
|
218
|
+
|
|
219
|
+
Working code in `examples/`:
|
|
220
|
+
|
|
221
|
+
- **`examples/core/`** - Hello world, sprites, labels, state, coordinates
|
|
222
|
+
- **`examples/input/`** - Directional, keyboard, mouse, movement
|
|
223
|
+
- **`examples/entities/`** - Storage, factories, collision, lifecycle
|
|
224
|
+
- **`examples/game-logic/`** - Timers, scoring, save/load, state transitions
|
|
225
|
+
- **`examples/audio/`** - Sound effects, background music, pause/resume
|
|
226
|
+
- **`examples/rendering/`** - Sprites, labels, animation, layering
|
|
227
|
+
|
|
228
|
+
## Development Workflow
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
def tick args
|
|
232
|
+
# Game logic
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
$gtk.reset # Add at end during development
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Reset Methods
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
$gtk.reset # Immediate reset
|
|
242
|
+
$gtk.reset_next_tick # Reset before next tick (safer)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Debug Output
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
args.outputs.debug << "Frame: #{Kernel.tick_count}"
|
|
249
|
+
args.outputs.debug.watch args.state.player
|
|
250
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# audio_events.rb
|
|
2
|
+
# Sound effects triggered by game events
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
defaults(args)
|
|
6
|
+
input(args)
|
|
7
|
+
calc(args)
|
|
8
|
+
render(args)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def defaults(args)
|
|
12
|
+
args.state.player ||= { x: 640, y: 360, w: 50, h: 50, dy: 0 }
|
|
13
|
+
args.state.coins ||= [{ x: 700, y: 360, w: 30, h: 30 }]
|
|
14
|
+
args.state.timer ||= 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def input(args)
|
|
18
|
+
player = args.state.player
|
|
19
|
+
|
|
20
|
+
# Sound on input event (jump)
|
|
21
|
+
if args.inputs.keyboard.key_down.space && player.dy == 0
|
|
22
|
+
args.outputs.sounds << "sounds/jump.wav"
|
|
23
|
+
player.dy = 10
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def calc(args)
|
|
28
|
+
player = args.state.player
|
|
29
|
+
|
|
30
|
+
# Gravity
|
|
31
|
+
player.dy -= 0.5
|
|
32
|
+
player.y += player.dy
|
|
33
|
+
player.y = 360 if player.y <= 360
|
|
34
|
+
player.dy = 0 if player.y == 360
|
|
35
|
+
|
|
36
|
+
# Sound on collision event (coin pickup)
|
|
37
|
+
args.state.coins.reject! do |coin|
|
|
38
|
+
if player.intersect_rect?(coin)
|
|
39
|
+
args.outputs.sounds << { path: "sounds/coin.wav", gain: 0.8 }
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Sound on timer event (every 3 seconds)
|
|
45
|
+
args.state.timer += 1
|
|
46
|
+
if args.state.timer.zmod?(180)
|
|
47
|
+
args.outputs.sounds << { path: "sounds/ambient.wav", gain: 0.3 }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def render(args)
|
|
52
|
+
args.outputs.solids << args.state.player
|
|
53
|
+
args.outputs.solids << args.state.coins.map { |c| c.merge(r: 255, g: 200, b: 0) }
|
|
54
|
+
args.outputs.labels << { x: 10, y: 720, text: "SPACE to jump (input event)" }
|
|
55
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# background_music.rb
|
|
2
|
+
# Looping music using args.audio[:key]
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
# Initialize music once - loops automatically
|
|
6
|
+
if Kernel.tick_count == 0
|
|
7
|
+
args.audio[:music] = {
|
|
8
|
+
input: "sounds/music.ogg", # Path to music file
|
|
9
|
+
gain: 0.8, # Volume (0.0 to 1.0)
|
|
10
|
+
looping: true # Loop when finished
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Stop music with Q key
|
|
15
|
+
if args.inputs.keyboard.key_down.q
|
|
16
|
+
args.audio[:music] = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Start music with P key (if stopped)
|
|
20
|
+
if args.inputs.keyboard.key_down.p && !args.audio[:music]
|
|
21
|
+
args.audio[:music] = { input: "sounds/music.ogg", looping: true }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
status = args.audio[:music] ? "Playing" : "Stopped"
|
|
25
|
+
args.outputs.labels << [
|
|
26
|
+
{ x: 640, y: 400, text: "Music: #{status}", alignment_enum: 1 },
|
|
27
|
+
{ x: 640, y: 360, text: "P = Play | Q = Stop", alignment_enum: 1 }
|
|
28
|
+
]
|
|
29
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# crossfade.rb
|
|
2
|
+
# Smooth crossfade between two music tracks
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
defaults(args)
|
|
6
|
+
update_crossfade(args)
|
|
7
|
+
input(args)
|
|
8
|
+
render(args)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def defaults(args)
|
|
12
|
+
return if args.audio[:track_a]
|
|
13
|
+
|
|
14
|
+
# Initialize two tracks - one playing, one silent
|
|
15
|
+
args.audio[:track_a] = { input: "sounds/music.ogg", gain: 1.0, looping: true }
|
|
16
|
+
args.audio[:track_b] = { input: "sounds/music2.ogg", gain: 0.0, looping: true }
|
|
17
|
+
args.state.active_track = :track_a
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update_crossfade(args)
|
|
21
|
+
active = args.state.active_track
|
|
22
|
+
inactive = active == :track_a ? :track_b : :track_a
|
|
23
|
+
|
|
24
|
+
# Fade in active track
|
|
25
|
+
if args.audio[active]
|
|
26
|
+
args.audio[active].gain = (args.audio[active].gain + 0.01).clamp(0, 1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Fade out inactive track
|
|
30
|
+
if args.audio[inactive]
|
|
31
|
+
args.audio[inactive].gain = (args.audio[inactive].gain - 0.01).clamp(0, 1)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def input(args)
|
|
36
|
+
# Swap tracks on SPACE
|
|
37
|
+
if args.inputs.keyboard.key_down.space
|
|
38
|
+
args.state.active_track = args.state.active_track == :track_a ? :track_b : :track_a
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def render(args)
|
|
43
|
+
a_vol = (args.audio[:track_a]&.gain.to_f * 100).to_i
|
|
44
|
+
b_vol = (args.audio[:track_b]&.gain.to_f * 100).to_i
|
|
45
|
+
|
|
46
|
+
args.outputs.labels << [
|
|
47
|
+
{ x: 640, y: 400, text: "Press SPACE to crossfade", alignment_enum: 1 },
|
|
48
|
+
{ x: 640, y: 360, text: "Track A: #{a_vol}%", alignment_enum: 1 },
|
|
49
|
+
{ x: 640, y: 330, text: "Track B: #{b_vol}%", alignment_enum: 1 }
|
|
50
|
+
]
|
|
51
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# music_controls.rb
|
|
2
|
+
# Music controls: pause, volume, seeking
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
defaults(args)
|
|
6
|
+
input(args)
|
|
7
|
+
render(args)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def defaults(args)
|
|
11
|
+
return if args.audio[:music]
|
|
12
|
+
|
|
13
|
+
args.audio[:music] = {
|
|
14
|
+
input: "sounds/music.ogg",
|
|
15
|
+
looping: true,
|
|
16
|
+
gain: 0.5,
|
|
17
|
+
paused: false
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def input(args)
|
|
22
|
+
music = args.audio[:music]
|
|
23
|
+
return unless music
|
|
24
|
+
|
|
25
|
+
# Toggle pause
|
|
26
|
+
music.paused = !music.paused if args.inputs.keyboard.key_down.space
|
|
27
|
+
|
|
28
|
+
# Volume control
|
|
29
|
+
if args.inputs.keyboard.key_down.up
|
|
30
|
+
music.gain = (music.gain + 0.1).clamp(0, 1)
|
|
31
|
+
elsif args.inputs.keyboard.key_down.down
|
|
32
|
+
music.gain = (music.gain - 0.1).clamp(0, 1)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Seeking
|
|
36
|
+
if args.inputs.keyboard.key_down.right
|
|
37
|
+
music.playtime += 5
|
|
38
|
+
elsif args.inputs.keyboard.key_down.left
|
|
39
|
+
music.playtime = [music.playtime - 5, 0].max
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render(args)
|
|
44
|
+
music = args.audio[:music]
|
|
45
|
+
args.outputs.labels << [
|
|
46
|
+
{ x: 10, y: 700, text: "SPACE: #{music.paused ? 'Resume' : 'Pause'}" },
|
|
47
|
+
{ x: 10, y: 670, text: "UP/DOWN: Volume (#{(music.gain * 100).to_i}%)" },
|
|
48
|
+
{ x: 10, y: 640, text: "LEFT/RIGHT: Seek (+/- 5s)" },
|
|
49
|
+
{ x: 10, y: 610, text: "Position: #{music.playtime.to_i}s" }
|
|
50
|
+
]
|
|
51
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# sound_effects.rb
|
|
2
|
+
# One-shot sound effects using args.outputs.sounds
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
args.state.notes ||= [:c3, :d3, :e3, :f3, :g3, :a3, :b3, :c4]
|
|
6
|
+
|
|
7
|
+
# Basic sound playback - add path string
|
|
8
|
+
if args.inputs.mouse.click
|
|
9
|
+
args.outputs.sounds << "sounds/#{args.state.notes.sample}.wav"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Sound with volume control - use hash
|
|
13
|
+
if args.inputs.keyboard.key_down.space
|
|
14
|
+
args.outputs.sounds << {
|
|
15
|
+
path: "sounds/#{args.state.notes.sample}.wav",
|
|
16
|
+
gain: 0.5 # Volume: 0.0 to 1.0
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Multiple sounds at once
|
|
21
|
+
if args.inputs.keyboard.key_down.enter
|
|
22
|
+
args.outputs.sounds << "sounds/jump.wav"
|
|
23
|
+
args.outputs.sounds << { path: "sounds/coin.wav", gain: 0.3 }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
args.outputs.labels << [
|
|
27
|
+
{ x: 640, y: 400, text: "Click or SPACE for sounds", alignment_enum: 1 },
|
|
28
|
+
{ x: 640, y: 360, text: "ENTER for multiple sounds", alignment_enum: 1 }
|
|
29
|
+
]
|
|
30
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Demonstrates DragonRuby's coordinate system
|
|
2
|
+
# Origin (0, 0) is at BOTTOM-LEFT corner
|
|
3
|
+
# Screen size is 1280 x 720 by default
|
|
4
|
+
|
|
5
|
+
def tick args
|
|
6
|
+
# Show mouse position (updates in real-time)
|
|
7
|
+
pos = args.inputs.mouse.position
|
|
8
|
+
args.outputs.labels << {
|
|
9
|
+
x: pos.x + 10,
|
|
10
|
+
y: pos.y + 10,
|
|
11
|
+
text: "x: #{pos.x.to_i}, y: #{pos.y.to_i}"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Draw axis lines
|
|
15
|
+
args.outputs.lines << { x: 0, y: 360, x2: 1280, y2: 360, r: 100, g: 100, b: 100 }
|
|
16
|
+
args.outputs.lines << { x: 640, y: 0, x2: 640, y2: 720, r: 100, g: 100, b: 100 }
|
|
17
|
+
|
|
18
|
+
# Corner labels to show coordinate system
|
|
19
|
+
args.outputs.labels << { x: 10, y: 710, text: "Top-Left (0, 720)" }
|
|
20
|
+
args.outputs.labels << { x: 10, y: 30, text: "Bottom-Left (0, 0) - ORIGIN" }
|
|
21
|
+
args.outputs.labels << { x: 1000, y: 710, text: "Top-Right (1280, 720)" }
|
|
22
|
+
args.outputs.labels << { x: 1000, y: 30, text: "Bottom-Right (1280, 0)" }
|
|
23
|
+
|
|
24
|
+
# Center marker
|
|
25
|
+
args.outputs.solids << { x: 638, y: 358, w: 4, h: 4, r: 255, g: 0, b: 0 }
|
|
26
|
+
args.outputs.labels << { x: 640, y: 340, text: "Center (640, 360)", anchor_x: 0.5 }
|
|
27
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Minimal DragonRuby example - Shows a simple label
|
|
2
|
+
# The tick method is called 60 times per second
|
|
3
|
+
|
|
4
|
+
def tick args
|
|
5
|
+
# Display text at position (640, 360) - center of the 1280x720 screen
|
|
6
|
+
args.outputs.labels << {
|
|
7
|
+
x: 640,
|
|
8
|
+
y: 360,
|
|
9
|
+
text: "Hello, DragonRuby!",
|
|
10
|
+
size_px: 22,
|
|
11
|
+
anchor_x: 0.5,
|
|
12
|
+
anchor_y: 0.5
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Show the current frame count
|
|
16
|
+
args.outputs.labels << {
|
|
17
|
+
x: 640,
|
|
18
|
+
y: 320,
|
|
19
|
+
text: "Frame: #{Kernel.tick_count}",
|
|
20
|
+
size_px: 18,
|
|
21
|
+
anchor_x: 0.5,
|
|
22
|
+
anchor_y: 0.5
|
|
23
|
+
}
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Demonstrates text rendering with alignment, size, and color
|
|
2
|
+
|
|
3
|
+
def tick args
|
|
4
|
+
# Different text sizes
|
|
5
|
+
args.outputs.labels << { x: 100, y: 600, text: "Small text", size_px: 18 }
|
|
6
|
+
args.outputs.labels << { x: 100, y: 550, text: "Medium text", size_px: 22 }
|
|
7
|
+
args.outputs.labels << { x: 100, y: 500, text: "Large text", size_px: 26 }
|
|
8
|
+
|
|
9
|
+
# Text alignment (centered at x: 640)
|
|
10
|
+
args.outputs.labels << { x: 640, y: 400, text: "Left aligned", anchor_x: 0, anchor_y: 0.5 }
|
|
11
|
+
args.outputs.labels << { x: 640, y: 350, text: "Center aligned", anchor_x: 0.5, anchor_y: 0.5 }
|
|
12
|
+
args.outputs.labels << { x: 640, y: 300, text: "Right aligned", anchor_x: 1, anchor_y: 0.5 }
|
|
13
|
+
|
|
14
|
+
# Colored text (RGB values 0-255)
|
|
15
|
+
args.outputs.labels << { x: 100, y: 200, text: "Red text", r: 255, g: 0, b: 0 }
|
|
16
|
+
args.outputs.labels << { x: 100, y: 150, text: "Green text", r: 0, g: 255, b: 0 }
|
|
17
|
+
args.outputs.labels << { x: 100, y: 100, text: "Blue text", r: 0, g: 0, b: 255 }
|
|
18
|
+
args.outputs.labels << { x: 100, y: 50, text: "Faded text", r: 0, g: 0, b: 0, a: 128 }
|
|
19
|
+
|
|
20
|
+
# Reference line for alignment demonstration
|
|
21
|
+
args.outputs.lines << { x: 640, y: 0, x2: 640, y2: 720 }
|
|
22
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Demonstrates sprite rendering with hash properties
|
|
2
|
+
# Shows position, size, path, angle, and alpha (transparency)
|
|
3
|
+
|
|
4
|
+
def tick args
|
|
5
|
+
# Basic sprite rendering
|
|
6
|
+
args.outputs.sprites << {
|
|
7
|
+
x: 100,
|
|
8
|
+
y: 200,
|
|
9
|
+
w: 128,
|
|
10
|
+
h: 101,
|
|
11
|
+
path: 'dragonruby.png'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Sprite with animated alpha (fading effect)
|
|
15
|
+
args.outputs.sprites << {
|
|
16
|
+
x: 300,
|
|
17
|
+
y: 200,
|
|
18
|
+
w: 128,
|
|
19
|
+
h: 101,
|
|
20
|
+
path: 'dragonruby.png',
|
|
21
|
+
a: Kernel.tick_count % 255
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Rotating sprite with anchoring
|
|
25
|
+
args.outputs.sprites << {
|
|
26
|
+
x: 500,
|
|
27
|
+
y: 250,
|
|
28
|
+
w: 128,
|
|
29
|
+
h: 101,
|
|
30
|
+
path: 'dragonruby.png',
|
|
31
|
+
angle: Kernel.tick_count % 360,
|
|
32
|
+
anchor_x: 0.5,
|
|
33
|
+
anchor_y: 0.5
|
|
34
|
+
}
|
|
35
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Demonstrates state management using args.state with ||= pattern
|
|
2
|
+
# The ||= operator initializes values only on first tick
|
|
3
|
+
|
|
4
|
+
def tick args
|
|
5
|
+
# Initialize player only once (on first frame)
|
|
6
|
+
# ||= means "assign if nil" - persists across frames
|
|
7
|
+
args.state.player ||= {
|
|
8
|
+
x: 640,
|
|
9
|
+
y: 360,
|
|
10
|
+
w: 50,
|
|
11
|
+
h: 50,
|
|
12
|
+
path: 'sprites/square/green.png'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Initialize counter if not set
|
|
16
|
+
args.state.counter ||= 0
|
|
17
|
+
args.state.counter += 1
|
|
18
|
+
|
|
19
|
+
# Move player with arrow keys
|
|
20
|
+
args.state.player.x += 5 if args.inputs.right
|
|
21
|
+
args.state.player.x -= 5 if args.inputs.left
|
|
22
|
+
args.state.player.y += 5 if args.inputs.up
|
|
23
|
+
args.state.player.y -= 5 if args.inputs.down
|
|
24
|
+
|
|
25
|
+
# Render player and counter
|
|
26
|
+
args.outputs.sprites << args.state.player
|
|
27
|
+
args.outputs.labels << { x: 10, y: 710, text: "Counter: #{args.state.counter}" }
|
|
28
|
+
args.outputs.labels << { x: 10, y: 680, text: "Use arrow keys to move" }
|
|
29
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Handle background/unfocused state gracefully
|
|
2
|
+
# Important for web and mobile where users tab away
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
if game_paused?(args)
|
|
6
|
+
render_pause_screen(args)
|
|
7
|
+
return
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
tick_game(args)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def game_paused?(args)
|
|
14
|
+
# Only pause in production builds when window loses focus
|
|
15
|
+
!args.inputs.keyboard.has_focus &&
|
|
16
|
+
args.gtk.production &&
|
|
17
|
+
Kernel.tick_count > 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render_pause_screen(args)
|
|
21
|
+
args.outputs.background_color = [0, 0, 0]
|
|
22
|
+
args.outputs.labels << {
|
|
23
|
+
x: 640,
|
|
24
|
+
y: 360,
|
|
25
|
+
text: "Game Paused",
|
|
26
|
+
size_enum: 10,
|
|
27
|
+
alignment_enum: 1,
|
|
28
|
+
r: 255, g: 255, b: 255
|
|
29
|
+
}
|
|
30
|
+
args.outputs.labels << {
|
|
31
|
+
x: 640,
|
|
32
|
+
y: 300,
|
|
33
|
+
text: "Click to resume",
|
|
34
|
+
size_enum: 2,
|
|
35
|
+
alignment_enum: 1,
|
|
36
|
+
r: 180, g: 180, b: 180
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def tick_game(args)
|
|
41
|
+
# Normal game logic here
|
|
42
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# DragonRuby publishing workflow
|
|
3
|
+
|
|
4
|
+
# 1. Clean up player-specific data before packaging
|
|
5
|
+
rm -f mygame/high-score.txt
|
|
6
|
+
rm -f mygame/saves/*
|
|
7
|
+
rm -rf mygame/tmp/*
|
|
8
|
+
|
|
9
|
+
# 2. Package only (creates ./builds/ directory)
|
|
10
|
+
./dragonruby-publish --only-package
|
|
11
|
+
|
|
12
|
+
# 3. Test locally before uploading
|
|
13
|
+
# - Linux: ./builds/mygame-linux-amd64.bin
|
|
14
|
+
# - macOS: open ./builds/mygame-mac-*/My\ Game.app
|
|
15
|
+
# - Windows: ./builds/mygame-windows-amd64.exe
|
|
16
|
+
|
|
17
|
+
# 4. Publish to itch.io (after first manual setup)
|
|
18
|
+
./dragonruby-publish mygame
|
|
19
|
+
|
|
20
|
+
# Alternative: Manual upload mode
|
|
21
|
+
# ./dragonruby-publish --package mygame
|
|
22
|
+
# Then upload ./builds/*.zip to itch.io manually
|
|
23
|
+
|
|
24
|
+
# 5. Publish to Steam (requires steam_metadata.txt)
|
|
25
|
+
# ./dragonruby-publish
|
|
26
|
+
# Uses steamcmd for authentication
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Production-ready cvars.txt
|
|
2
|
+
# Location: mygame/metadata/cvars.txt
|
|
3
|
+
|
|
4
|
+
# Minimize logging noise
|
|
5
|
+
log.level=info
|
|
6
|
+
log.filter_subsystems=HTTPServer,HTTP
|
|
7
|
+
|
|
8
|
+
# Disable web server in production
|
|
9
|
+
# webserver.enabled=false
|
|
10
|
+
|
|
11
|
+
# Keep game running when backgrounded (0 = no sleep)
|
|
12
|
+
# Useful for music games or web where users tab away
|
|
13
|
+
# renderer.background_sleep=0
|
|
14
|
+
|
|
15
|
+
# Fullscreen by default (ensure quit option in game!)
|
|
16
|
+
# renderer.fullscreen=true
|