L'autre jour, j'ai découvert les projets caritatifs suivants sur Twitter.
[La campagne pour jouer à 1000 jeux indépendants avec un don d'au moins 5 $ a commencé. Dans le cadre du mouvement anti-discrimination raciale --Netorabo](https://nlab.itmedia.co.jp/nl/articles/2006/10/news135 .html)
Je ne joue pas beaucoup aux jeux PC, mais il contient du matériel, et quand je le regardais, j'ai trouvé les mots ** DragonRuby GTK **.
Il semble que vous puissiez créer des jeux multiplateformes avec Ruby. (RubyMotion, qui était prédominante à la fois, fait maintenant partie de cette famille)
Cela semble intéressant, alors je l'ai immédiatement sauté et j'ai joué un peu le week-end.
Vous trouverez ci-dessous un enregistrement de l'essai de la version macOS.
Hello World
Commençons par le déplacer.
$ unzip dragonruby-gtk-macos.zip 
$ cd dragonruby-macos/
$ ./dragonruby
# ./Idem pour dragonruby mygame
 
Ça a marché.
mygame / app / main.rb est en cours d'exécution.
Dans mon environnement, le ventilateur du processeur ne gémit pas même si je le fais fonctionner pendant un moment. (Le corps devient chaud)
J'ai travaillé dans le répertoire où j'ai décompressé le zip plus tôt, mais j'essaierai de travailler dans un autre emplacement. (Pour faciliter la gestion de git)
$ mkdir -p hello-dragonruby-gtk/mygame/app
$ cd hello-dragonruby-gtk/
$ cp ~/Downloads/dragonruby-macos/dragonruby .
$ cp ~/Downloads/dragonruby-macos/font.ttf .
Je pense que la commande dragonruby n'est pas censée être appelée à partir d'un autre emplacement en définissant le PATH, donc je l'ai copiée.
J'avais aussi besoin de font.ttf.
(Environ 5,9 Mo au total)
Lorsque vous l'exécutez, divers répertoires et fichiers seront créés, donc ignorez-les ensemble.
.gitignore
# DragonRuby
/dragonruby
/font.ttf
/logs
/tmp
/exceptions
console_history.txt
Maintenant que nous sommes prêts, écrivons le code source pour dessiner le carré.
mygame/app/main.rb
def tick args
  args.outputs.solids << [args.grid.center_x - 32, args.grid.h * 0.1, 64, 64]
end
Je le ferai.
./dragonruby
 
J'ai pu dessiner.
Ajoutez-le à ʻargs.outputs.solids pour dessiner la figure.  De plus, puisque ʻargs.grid contient des informations telles que la taille de l'écran, je l'utilise.
C'est un peu unique, mais je ne pense pas que ce soit particulièrement difficile.
Les spécifications de base semblent être les suivantes.
Officiellement, j'ai l'impression de pousser cette notation, mais pour être honnête, ça fait mal de me tuer pour la première fois ...
D'autres styles d'écriture ont également été préparés correctement.
Si tel est le cas, il semble normal de le lire pour la première fois.
mygame/app/main.rb
  args.outputs.solids << { x: args.grid.center_x - 32, y: args.grid.h * 0.1, w: 64, h: 64 }
Il semble correct de définir la classe suivante.
primitive_marker renvoie un type tel que: solid
--A les mêmes attributs que la clé de notation de hachageJ'utilise cette méthode pour le moment.
mygame/app/primitives.rb
class Primitive
  def initialize(attributes)
    attr_keys.each { |key| send("#{key}=", attributes[key]) }
  end
  def primitive_marker
    self.class.name.downcase
  end
  def attr_keys
    self.class.class_variable_get(:@@attr_keys)
  end
  def serialize
    attr_keys.map { |key| [key, send(key)] }.to_h
  end
  def inspect
    serialize.to_s
  end
  def to_s
    serialize.to_s
  end
end
class Solid < Primitive
  @@attr_keys = %i[x y w h r g b a]
  attr_accessor(*@@attr_keys)
end
Si vous ne définissez pas serialize, ʻinspect, to_s, la console sera remplie avec l'avertissement lorsqu'une erreur se produit, elle est donc définie.  (J'ai @@ attr_keys` pour ça ...)
Ensuite, chargez-le depuis main.rb.
mygame/app/main.rb
require 'app/primitives.rb'
def tick args
  args.outputs.solids << Solid.new(x: args.grid.center_x - 32, y: args.grid.h * 0.1, w: 64, h: 64)
end
À titre d'exemple autre que "Solide", dessinons une ligne transversale au milieu en utilisant "Ligne". Il est dessiné en l'ajoutant à ʻargs.outputs.lines`.
J'ai mis ʻoutputs et grid dans les variables car il est difficile de frapper ʻargs à chaque fois.
mygame/app/primitives.rb
class Line < Primitive
  @@attr_keys = %i[x y x2 y2 r g b a]
  attr_accessor(*@@attr_keys)
end
mygame/app/main.rb
require 'app/primitives.rb'
def tick args
  outputs, grid = args.outputs, args.grid
  outputs.solids << Solid.new(x: grid.center_x - 32, y: grid.h * 0.1, w: 64, h: 64)
  outputs.lines << Line.new(x: 0, y: grid.center_y, x2: grid.w, y2: grid.center_y)
  outputs.lines << Line.new(x: grid.center_x, y: 0, x2: grid.center_x, y2: grid.h)
end
 
Il a été confirmé que le carré pouvait être dessiné au centre de la gauche et de la droite.
Avant que la méthode tick ne devienne compliquée, classez le jeu lui-même.
mygame/app/main.rb
require 'app/primitives.rb'
class Game
  attr_accessor :state, :outputs, :grid
  def tick
    output
  end
  def output
    outputs.solids << Solid.new(x: grid.center_x - 32, y: grid.h * 0.1, w: 64, h: 64)
    outputs.lines << Line.new(x: 0, y: grid.center_y, x2: grid.w, y2: grid.center_y)
    outputs.lines << Line.new(x: grid.center_x, y: 0, x2: grid.center_x, y2: grid.h)
  end
  def serialize
    {}
  end
  def inspect
    serialize.to_s
  end
  def to_s
    serialize.to_s
  end
end
 
$game = Game.new
def tick args
  $game.state = args.state
  $game.outputs = args.outputs
  $game.grid = args.grid
  $game.tick
end
Il y a un assistant appelé ʻattr_gtk, mais j'utilise ʻattr_accessor ici parce que je voulais accéder directement à state etc.
Déplacez le carré (player) avec les touches fléchées gauche et droite.
Les informations que vous souhaitez conserver dans les cadres sont stockées dans state.
L'entrée au clavier peut être détectée par ʻinputs.keyboard.key name. (Il semble que vous puissiez récupérer la cale par ʻinputs.keyboard.key_held.key name, mais je ne l'utilise pas)
Les modifications suivantes ont été apportées à main.rb.
mygame/app/main.rb
 require 'app/primitives.rb'
 
 class Game
-  attr_accessor :state, :outputs, :grid
+  attr_accessor :state, :outputs, :grid, :inputs
 
   def tick
+    set_defaults
+    handle_inputs
+    update_state
     output
   end
 
+  def set_defaults
+    state.player_x ||= grid.center_x - 32
+    state.player_dx ||= 0
+  end
+
+  def handle_inputs
+    if inputs.keyboard.right
+      state.player_dx = 5
+    elsif inputs.keyboard.left
+      state.player_dx = -5
+    else
+      state.player_dx = 0
+    end
+  end
+
+  def update_state
+    state.player_x += state.player_dx
+  end
+
   def output
-    outputs.solids << Solid.new(x: grid.center_x - 32, y: grid.h * 0.1, w: 64, h: 64)
+    outputs.solids << Solid.new(x: state.player_x, y: grid.h * 0.1, w: 64, h: 64)
 
     outputs.lines << Line.new(x: 0, y: grid.center_y, x2: grid.w, y2: grid.center_y)
     outputs.lines << Line.new(x: grid.center_x, y: 0, x2: grid.center_x, y2: grid.h)
@@ -26,12 +50,13 @@ class Game
     serialize.to_s
   end
 end
 $game = Game.new
 
 def tick args
   $game.state = args.state
   $game.outputs = args.outputs
   $game.grid = args.grid
+  $game.inputs = args.inputs
   $game.tick
 end
Vous pouvez maintenant déplacer le quadrilatère.

Ajoutez un "solide" gris qui se déplace d'un côté à l'autre. Je n'ai utilisé aucun nouvel élément.
mygame/app/main.rb
   def set_defaults
     state.player_x ||= grid.center_x - 32
     state.player_dx ||= 0
+
+    state.enemy_x ||= grid.center_x - 32
+    state.enemy_dx ||= 5
   end
 
   def update_state
     state.player_x += state.player_dx
+
+    state.enemy_x += state.enemy_dx
+    if state.enemy_x < 0 || state.enemy_x > grid.w - 64
+      state.enemy_dx *= -1
+    end
   end
 
   def output
     outputs.solids << Solid.new(x: state.player_x, y: grid.h * 0.1, w: 64, h: 64)
+ 
+    outputs.solids << Solid.new(x: state.enemy_x, y: grid.h * 0.7, w: 64, h: 64, r: 150, g: 150, b: 150)
     outputs.lines << Line.new(x: 0, y: grid.center_y, x2: grid.w, y2: grid.center_y)
     outputs.lines << Line.new(x: grid.center_x, y: 0, x2: grid.center_x, y2: grid.h)
   end

Vous pouvez ajouter une entité avec state.new_entity.
Vous pouvez donner des informations à l'entité et utiliser ces informations pour l'afficher à l'écran avec Solid etc.
(Je pense que cela peut être fait sans entité, mais je vais l'utiliser parce qu'il est préparé.)
J'utilise key_up pour l'empêcher d'être abattu en le maintenant enfoncé.
De plus, lorsque la balle atteint le bord de l'écran, elle est supprimée en définissant le drapeau «mort». (S'il y a un drapeau «mort», ce sera «rejet»)
mygame/app/main.rb
   def set_defaults
     state.player_x ||= grid.center_x - 32
     state.player_dx ||= 0
 
     state.enemy_x ||= grid.center_x - 32
     state.enemy_dx ||= 5
+
+    state.bullets ||= []
   end
 
   def handle_inputs
     if inputs.keyboard.right
       state.player_dx = 5
     elsif inputs.keyboard.left
       state.player_dx = -5
     else
       state.player_dx = 0
     end
+
+    if inputs.keyboard.key_up.space
+      state.bullets << state.new_entity(:bullet) do |bullet|
+        bullet.y = player_rect[:y]
+        bullet.x = player_rect[:x] + 16
+        bullet.size = 32
+        bullet.dy = 10
+        bullet.solid = { x: bullet.x, y: bullet.y, w: bullet.size, h: bullet.size, r: 255, g: 100, b: 100 }
+      end
+    end
   end
   def update_state
     state.player_x += state.player_dx
 
     state.enemy_x += state.enemy_dx
     if state.enemy_x < 0 || state.enemy_x > grid.w - 64
       state.enemy_dx *= -1
     end
+
+    state.bullets.each do |bullet|
+      bullet.y += bullet.dy
+      bullet.solid[:y] = bullet.y
+
+      if bullet.y > grid.h
+        bullet.dead = true
+      end
+    end
+    state.bullets = state.bullets.reject(&:dead)
   end
   def output
-    outputs.solids << Solid.new(x: state.player_x, y: grid.h * 0.1, w: 64, h: 64)
+    outputs.solids << Solid.new(player_rect)
 
     outputs.solids << Solid.new(x: state.enemy_x, y: grid.h * 0.7, w: 64, h: 64, r: 150, g: 150, b: 150)
+ 
+    outputs.solids << state.bullets.map(&:solid)
     outputs.lines << Line.new(x: 0, y: grid.center_y, x2: grid.w, y2: grid.center_y)
     outputs.lines << Line.new(x: grid.center_x, y: 0, x2: grid.center_x, y2: grid.h)
   end
+
+  private
+
+  def player_rect
+    { x: state.player_x, y: grid.h * 0.1, w: 64, h: 64 }
+  end

Vous pouvez maintenant tirer des balles.
Vous pouvez utiliser ʻintersect_rect` pour le jugement de hit.
mygame/app/main.rb
   def update_state
     state.player_x += state.player_dx
 
     state.enemy_x += state.enemy_dx
     if state.enemy_x < 0 || state.enemy_x > grid.w - 64
       state.enemy_dx *= -1
     end
 
     state.bullets.each do |bullet|
       bullet.y += bullet.dy
       bullet.solid[:y] = bullet.y
 
       if bullet.y > grid.h
         bullet.dead = true
       end
+      if bullet.solid.intersect_rect?(enemy_rect)
+        bullet.dead = true
+      end
     end
     state.bullets = state.bullets.reject(&:dead)
   end
 
   def output
     outputs.solids << Solid.new(player_rect)
 
-    outputs.solids << Solid.new(x: state.enemy_x, y: grid.h * 0.7, w: 64, h: 64, r: 150, g: 150, b: 150)
+    outputs.solids << Solid.new(enemy_rect.merge(r: 150, g: 150, b: 150))
 
     outputs.solids << state.bullets.map(&:solid)
 
     outputs.lines << Line.new(x: 0, y: grid.center_y, x2: grid.w, y2: grid.center_y)
     outputs.lines << Line.new(x: grid.center_x, y: 0, x2: grid.center_x, y2: grid.h)
   end
+
+  def enemy_rect
+    { x: state.enemy_x, y: grid.h * 0.7, w: 64, h: 64 }
+  end

C'est un peu déroutant, mais quand il frappe un ennemi, la balle disparaît.
Si la balle touche l'ennemi, ce sera +2 points, si elle ne touche pas, ce sera -1 point, et le total sera affiché en haut à droite.
Utilisez Label pour afficher le texte.
J'ai utilisé Press Start 2P --Google Fonts pour le faire ressembler à un point. (Mettez celui téléchargé et développé dans mygame / fonts
mygame/app/primitives.rb
class Label < Primitive
  @@attr_keys = %i[x y text size_enum alignment_enum font r g b a]
  attr_accessor(*@@attr_keys)
end
mygame/app/main.rb
   def set_defaults
     state.player_x ||= grid.center_x - 32
     state.player_dx ||= 0
 
     state.enemy_x ||= grid.center_x - 32
     state.enemy_dx ||= 5
 
     state.bullets ||= []
+
+    state.score ||= 0
   end
  
   def update_state
     state.player_x += state.player_dx
 
     state.enemy_x += state.enemy_dx
     if state.enemy_x < 0 || state.enemy_x > grid.w - 64
       state.enemy_dx *= -1
     end
 
     state.bullets.each do |bullet|
       bullet.y += bullet.dy
       bullet.solid[:y] = bullet.y
 
       if bullet.y > grid.h
         bullet.dead = true
+        state.score -= 1
       end
       if bullet.solid.intersect_rect?(enemy_rect)
         bullet.dead = true
+        state.score += 2
       end
     end
     state.bullets = state.bullets.reject(&:dead)
   end
 
   def output
     outputs.solids << Solid.new(player_rect)
 
     outputs.solids << Solid.new(enemy_rect.merge(r: 150, g: 150, b: 150))
 
     outputs.solids << state.bullets.map(&:solid)
+
+    outputs.labels << Label.new(
+      x: grid.w * 0.99,
+      y: grid.h * 0.98,
+      text: state.score,
+      alignment_enum: 2,
+      font: 'fonts/Press_Start_2P/PressStart2P-Regular.ttf'
+    )
 
     outputs.lines << Line.new(x: 0, y: grid.center_y, x2: grid.w, y2: grid.center_y)
     outputs.lines << Line.new(x: grid.center_x, y: 0, x2: grid.center_x, y2: grid.h)
   end

Maintenant que j'ai un score, je peux jouer (pour le moment).
Je suis seul si ça reste carré, alors je vais utiliser une image. (L'image est celle que j'ai dessinée avant, donc la qualité est que ...)
Placez player.png, ʻenemy.png et bullet.png dans mygame / sprites`.
Après cela, changez le code pour utiliser Sprite au lieu de Solide.
Je n'ai plus besoin de la ligne croisée, alors je vais l'effacer.
mygame/app/primitives.rb
class Sprite < Primitive
  @@attr_keys = %i[
    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
    tile_x tile_y tile_w tile_h
  ]
  attr_accessor(*@@attr_keys)
end
mygame/app/main.rb
   def handle_inputs
     if inputs.keyboard.right
       state.player_dx = 5
     elsif inputs.keyboard.left
       state.player_dx = -5
     else
       state.player_dx = 0
     end
 
     if inputs.keyboard.key_up.space
       state.bullets << state.new_entity(:bullet) do |bullet|
         bullet.y = player_rect[:y]
         bullet.x = player_rect[:x] + 16
         bullet.size = 32
         bullet.dy = 10
-        bullet.solid = { x: bullet.x, y: bullet.y, w: bullet.size, h: bullet.size, r: 255, g: 100, b: 100 }
+        bullet.sprite = { x: bullet.x, y: bullet.y, w: bullet.size, h: bullet.size, r: 255, g: 100, b: 100, path: 'sprites/bullet.png' }
       end
     end
   end
 
   def update_state
     state.player_x += state.player_dx
 
     state.enemy_x += state.enemy_dx
     if state.enemy_x < 0 || state.enemy_x > grid.w - 64
       state.enemy_dx *= -1
     end
 
     state.bullets.each do |bullet|
       bullet.y += bullet.dy
-      bullet.solid[:y] = bullet.y
+      bullet.sprite[:y] = bullet.y
 
       if bullet.y > grid.h
         bullet.dead = true
         state.score -= 1
       end
-      if bullet.solid.intersect_rect?(enemy_rect)
+      if bullet.sprite.intersect_rect?(enemy_rect)
         bullet.dead = true
         state.score += 2
       end
     end
     state.bullets = state.bullets.reject(&:dead)
   end
 
   def output
-    outputs.solids << Solid.new(player_rect)
+    outputs.sprites << Sprite.new(player_rect.merge(path: 'sprites/player.png'))
 
-    outputs.solids << Solid.new(enemy_rect.merge(r: 150, g: 150, b: 150))
+    outputs.sprites << Sprite.new(enemy_rect.merge(r: 150, g: 150, b: 150, path: 'sprites/enemy.png'))
 
-    outputs.solids << state.bullets.map(&:solid)
+    outputs.sprites << state.bullets.map(&:sprite)
 
     outputs.labels << Label.new(
       x: grid.w * 0.99,
       y: grid.h * 0.98,
       text: state.score,
       alignment_enum: 2,
       font: 'fonts/Press_Start_2P/PressStart2P-Regular.ttf'
     )
-    outputs.lines << Line.new(x: 0, y: grid.center_y, x2: grid.w, y2: grid.center_y)
-    outputs.lines << Line.new(x: grid.center_x, y: 0, x2: grid.center_x, y2: grid.h)     
   end

Par rapport au début, ça ressemble beaucoup à ça!
Préparez les métadonnées.
Pour l'icône, copiez player.png.
mygame/metadata/game_metadata.txt
devid=hello-dragonruby-gtk
devtitle=Hello DragonRuby GTK
gameid=hello-dragonruby-gtk
gametitle=Hello DragonRuby GTK
version=0.1
icon=metadata/icon.png
J'ai copié dragonruby-publish` mais cela n'a pas fonctionné pour une raison quelconque, donc Faites-le dans le répertoire où vous avez téléchargé et décompressé DragonRuby. (Reportez-vous au post-scriptum ci-dessous pour savoir comment copier et déplacer)
$ cp mygame/sprites/player.png mygame/metadata/icon.png
$ cp -r mygame/ ~/Downloads/dragonruby-macos/hello-dragonruby-gtk
$ cd ~/Downloads/dragonruby-macos
$ ./dragonruby-publish --only-package hello-dragonruby-gtk
Cela affichera la version HTML5 avec les fichiers pour d'autres plates-formes.
(builds/hello-dragonruby-gtk-html5-0.1)
Si vous publiez ceci, vous pouvez le lire dans votre navigateur. Comme c'est un gros problème, je l'ai mis sur les pages GitHub.
https://tnantoka.github.io/hello-dragonruby-gtk/
Cela fonctionnait quand je copiais diverses choses autres que dragonruby-publish.
$ cp ~/Downloads/dragonruby-macos/dragonruby-publish .
$ cp ~/Downloads/dragonruby-macos/.dragonruby .
$ cp ~/Downloads/dragonruby-macos/*.png .
$ cp ~/Downloads/dragonruby-macos/open-source-licenses.txt .
$ ./dragonruby-publish --only-package
```
 Il y a plus de choses à faire `.gitignore`.
#### **`.gitignore`**
```python
# DragonRuby
/dragonruby*
/font.ttf
/logs
/tmp
/exceptions
/console_history.txt
/.dragonruby
/builds
/console-logo.png
/open-source-licenses.txt
```
# Code source
 Il est publié ci-dessous.
 (DragonRuby est requis séparément)
https://github.com/tnantoka/hello-dragonruby-gtk/
# Impressions
 J'ai pu jouer sans grande dépendance.
 Peut-il être utilisé en production? Je ne sais pas ce que c'est, mais
 Cela semblait assez utilisable pour faire un petit truc.
 Je voulais faire du codage créatif avec Ruby, que j'ai l'habitude d'écrire, puis-je l'utiliser comme un outil? Je pense que.
 (Pour cela, j'aimerais que vous installiez un moteur physique et amélioriez le dessin.)
 J'essaierai de trouver le temps et de le toucher à nouveau.
# Les références
 Il n'y a pas de référence API (?), J'ai donc cherché tout en regardant autour de moi.
- README.md
 ――Lisez d'abord ici
- CHEATSHEET.md
 ――Lisez ceci aussi
- [DragonRuby Game Toolkit by DragonRuby](https://dragonruby.itch.io/dragonruby-gtk)
 --Il existe une description telle que Entity qui n'est pas dans README
- mygame/documantion
 --Solid, Sprite, etc. expliquera chaque élément tel quel
- samples
 --Il y a 67 échantillons (licence MIT!)
Recommended Posts