The other day, I learned about the following charity projects on Twitter.
[A campaign to play 1000 indie games with a donation of at least $ 5 has started. As part of the anti-racism movement --Netorabo](https://nlab.itmedia.co.jp/nl/articles/2006/10/news135 .html)
I don't play PC games very much, but it contains materials, and when I was looking at it, I thought I should buy it if I donated it ... The letters ** Dragon Ruby GTK ** were there.
It seems that you can make cross-platform games with Ruby. (RubyMotion, which was predominant all at once, is now in this family)
This seems to be interesting, so I immediately popped it and played a little on the weekend.
Below is a record of trying the macOS version.
Hello World
Let's move it first.
$ unzip dragonruby-gtk-macos.zip
$ cd dragonruby-macos/
$ ./dragonruby
# ./Same for dragonruby mygame
It worked.
mygame / app / main.rb
is running.
In my environment, it feels good that the CPU fan doesn't groan even if I run it for a while. (The body gets hot)
I worked in the directory where I unzipped the zip earlier, but I will try working in another location. (To make git management easier)
$ mkdir -p hello-dragonruby-gtk/mygame/app
$ cd hello-dragonruby-gtk/
$ cp ~/Downloads/dragonruby-macos/dragonruby .
$ cp ~/Downloads/dragonruby-macos/font.ttf .
I felt that the dragonruby
command was not supposed to be called from another location by setting the PATH, so I copied it.
I also needed font.ttf
.
(Approximately 5.9MB in total)
When you execute it, various directories and files will be created, so gitignore them together.
.gitignore
# DragonRuby
/dragonruby
/font.ttf
/logs
/tmp
/exceptions
console_history.txt
Now that we're ready, let's write the source code to draw the rectangle.
mygame/app/main.rb
def tick args
args.outputs.solids << [args.grid.center_x - 32, args.grid.h * 0.1, 64, 64]
end
I will do it.
./dragonruby
I was able to draw.
Add it to ʻargs.outputs.solids to draw the shape. Also, since ʻargs.grid
contains information such as screen size, I am using that.
It's a little unique, but I don't think it's particularly difficult.
The basic specifications seem to be as follows.
--Screen is 1280x720 pixels
--When you change the screen size, it is automatically resized while maintaining the aspect ratio.
--The lower left is the origin
--FPS is fixed at 60
--The tick
I wrote earlier is called frame by frame.
Officially, I feel like I'm pushing this notation, but to be honest, it feels painful to kill me for the first time ...
Other writing styles were also prepared properly.
If this is the case, it seems okay to read it for the first time.
mygame/app/main.rb
args.outputs.solids << { x: args.grid.center_x - 32, y: args.grid.h * 0.1, w: 64, h: 64 }
It seems OK to define the following class.
--Return types such as : solid
with the primitive_marker
method
--Has the same attributes as the hash notation key
I'm using this method for now.
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
If you do not define serialize
, ʻinspect,
to_s, the console will be filled with the warning when an error occurs, so it is defined. (I have
@@ attr_keys` for that ...)
Then load this from 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
As an example other than Solid
, let's use Line
to draw a crosshair in the middle.
It is drawn by adding it to ʻargs.outputs.lines`.
I put ʻoutputs and
grid in variables because it is troublesome to hit ʻargs
every time.
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
It was confirmed that the square could be drawn in the center of the left and right.
Before the tick
method gets complicated, classify the game itself.
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
There is a helper called ʻattr_gtk, but I used ʻattr_accessor
here because I wanted to access state
etc. directly.
Use the left and right cursor keys to move the square (player
).
The information you want to keep across frames is stored in the state
.
Keyboard input can be detected by ʻinputs.keyboard.key name. (It seems that you can pick up the hold by ʻinputs.keyboard.key_held.key name
, but I don't use it)
The following changes have been made to 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
Now you can move the rectangle.
Add a gray Solid
that moves from side to side.
I haven't used any new elements.
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
You can add an entity with state.new_entity
.
It is a way to give information to an entity and use that information to display it on the screen with Solid
etc.
(I think it can be done without an entity, but since it is prepared, I will use it.)
I use key_up
to prevent it from being shot by holding it down.
Also, when the bullet reaches the edge of the screen, it is flagged as dead
and deleted.
(If there is a dead
flag, it will be reject
)
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
You can now shoot bullets.
You can use ʻintersect_rect` for collision detection.
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
It's a little confusing, but when you hit an enemy, the bullet disappears.
If the bullet hits the enemy, it will be +2 points, if it does not hit, it will be -1 point, and the total will be displayed in the upper right.
Use Label
to display the text.
I used Press Start 2P --Google Fonts to make a dot-like font. (Download and unpack it and place it in 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
Now that I have a score, I can play (for the time being).
I'm lonely if it stays square, so I'll use an image. (The image is the one I drew before, so the quality is that ...)
Place player.png
, ʻenemy.png, and
bullet.pngin
mygame / sprites`.
After that, change the code to use Sprite
instead of Solid
.
I don't need the crosshairs anymore, so I'll erase them.
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
Compared to the beginning, it looks a lot like that!
Prepare the metadata.
For icon, copy 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
I copied dragonruby-publish` but it didn't work for some reason, so Do it in the directory where you downloaded and unzipped DragonRuby. (Refer to the postscript below for how to copy and move)
$ 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
This will output the HTML5 version along with the files for other platforms.
(builds/hello-dragonruby-gtk-html5-0.1
)
If you publish this, you can play it in your browser. It's a big deal, so I put it on GitHub Pages.
https://tnantoka.github.io/hello-dragonruby-gtk/
It worked when I copied various things other than 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
```
There are more things to do `.gitignore`.
#### **`.gitignore`**
```python
# DragonRuby
/dragonruby*
/font.ttf
/logs
/tmp
/exceptions
/console_history.txt
/.dragonruby
/builds
/console-logo.png
/open-source-licenses.txt
```
# Source code
It is published below.
(DragonRuby is required separately)
https://github.com/tnantoka/hello-dragonruby-gtk/
# Impressions
I was able to play without any big addiction.
Can it be used in Production? I don't know what it is, but
It seemed to be usable enough to make a little thing.
I wanted to do creative coding with Ruby, which I'm used to writing, so can I use it as a tool? I think that.
(For that purpose, I would like to see the physics engine installed and the drawing enhanced.)
I will try to find time and touch it again.
# References
There is no API reference (?), So I searched around while looking around.
- README.md
――Read here first
- CHEATSHEET.md
――Read this too
- [DragonRuby Game Toolkit by DragonRuby](https://dragonruby.itch.io/dragonruby-gtk)
--There is a description such as Entity that is not in the README
- mygame/documantion
--Solid, Sprite, etc. will explain each element as it is
- samples
--There are 67 samples (MIT license!)
Recommended Posts