J'ai joué avec DragonRuby GTK (Game Toolkit)

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
Screen Shot 2020-06-14 at 22.55.54.png

Ç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)

Dessinez un carré

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
Screen Shot 2020-06-14 at 23.07.44.png

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.

Le problème qu'il est difficile de se souvenir de l'ordre de l'arrangement

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.

hacher

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 }

classe

Il semble correct de définir la classe suivante.

J'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

Tracer une ligne

À 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
Screen Shot 2020-06-14 at 23.53.59.png

Il a été confirmé que le carré pouvait être dessiné au centre de la gauche et de la droite.

Les jeux sont également classés

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éplacer avec le clavier

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.

move.gif

Faire un ennemi

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

enemy.gif

Tirer des balles

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

bullet.gif

Vous pouvez maintenant tirer des balles.

Efface la balle (si elle touche l'ennemi)

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

collision.gif

C'est un peu déroutant, mais quand il frappe un ennemi, la balle disparaît.

But

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

score.gif

Maintenant que j'ai un score, je peux jouer (pour le moment).

Utiliser des images

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

sprite.gif

Par rapport au début, ça ressemble beaucoup à ça!

Emballage

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/

Copiez et déplacez dragonruby-publish (note supplémentaire)

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

J'ai joué avec DragonRuby GTK (Game Toolkit)
J'ai joué avec wordcloud!
Jeu de frappe simple avec DragonRuby
J'ai joué avec PyQt5 et Python3
J'ai joué avec Mecab (analyse morphologique)!
[Scikit-learn] J'ai joué avec la courbe ROC
[Introduction à Pytorch] J'ai joué avec sinGAN ♬
J'ai fait un jeu de vie avec Numpy
J'ai fait un jeu rogue-like avec Python
Je veux faire un jeu avec Python
[Python] J'ai joué avec le traitement du langage naturel ~ transformers ~
J'ai joué avec Floydhub pour le moment
J'ai joué avec Diamond, un outil de collecte de métriques
J'ai fait un jeu de cueillette avec Python
J'ai fait un jeu d'éclairage de sapin de Noël avec Python
J'ai fait un jeu mono tombé avec Sense HAT
〇✕ J'ai fait un jeu
[Python] J'ai installé le jeu depuis pip et j'ai essayé de jouer
J'ai fait un jeu de puzzle (comme) avec Tkinter of Python
J'ai essayé de simuler la probabilité d'un jeu de bingo avec Python