Rubymotion a lancé en septembre dernier sa plateforme de création de jeux 2D pour smartphone (iOS et Android) nommée Motiongame.
Jusqu’à présent la création de jeux se faisait à l’aide de la gem Joybox. Aujourd’hui Rubymotion inclut une version bêta de Motiongame qui sera maintenue à jour, facile à mettre en place et cross-plateform !
Dans cet article nous allons dégrossir Motiongame pour, je l’espère, vous donner envie de développer des jeux smartphone !
Avant tout, il vous faudra installer Motiongame, pour cela rien de compliqué.
gem install motion-game
Nous pouvons maintenant créer notre premier jeu qui sera un simple hello world.
motion create --template=motion-game hello
cd hello
Votre projet est créé avec peu de fichiers par défaut ce qui le rend facile à aborder.
Pour cette introduction nous allons nous intérésser au dossier app
dans lequel on va développer un mini jeu.
app/application.rb
class Application < MG::Application
def start
MG::Director.shared.run(MainScene.new)
end
end
app/main_scene.rb
class MainScene < MG::Scene
def initialize
label = MG::Text.new("Hello World", "Arial", 96)
label.anchor_point = [0, 0]
add label
end
end
Par défaut tous les fichiers dans app
sont chargés par Motiongame. Le point d’entrée de l’application se nomme application.rb
et la scène où se déroule le jeu main_scene.rb
.
Le fichier application lance notre scène qui est un simple “Hello World” auquel on précise le style de police de caractère avant d’appliquer un point d’ancrage aux coordonnées d’origine de notre application.
Il est temps d’émuler notre jeu sur iOS et Android !
rake ios:simulator
rake android:emulator
Une fois lancé vous devriez voir “Hello World” apparaître.
Comme vous le voyez, il est très facile de créer un jeu iOS et Android avec un seul et même code !
Notre scène se contente pour le moment d’afficher un label prédéfini auquel vous pouvez appliquer plusieurs méthodes.
app/main_scene.rb
def initialize
label = MG::Text.new("Hello World", "Arial", 96)
label.position = [400, 400]
label.color = [0, 0.6, 0.8]
label.rotation = -10.0
add label
end
La position va nous permettre de déplacer notre label par rapport à son centre. On modifie aussi la couleur du texte et on applique une légère rotation sur notre élément.
Continuons avec un mini jeu qui est un prétexte à la découverte de fonctionnalités. Il existe déjà un exemple de Flappy Bird proposé par l’équipe de RubyMotion que je vous invite à télécharger. Notre mini jeu bien que moins élaboré nous permettra de découvrir d’autres méthodes qui pourront compléter votre apprentissage.
Le but du jeu “Synbioz vs Zombie” est simple, vous incarnez un survivant de l’équipe qui doit fuir le zombie le plus longtemps possible.
Avant de commencer il vous faudra récupérer les ressources images et audio sur github.
Pour réaliser notre jeu nous aurons besoin des trois scènes suivantes :
survivor_scene.rb
)main_scene.rb
)game_over_scene.rb
)C’est parti pour la création de notre jeu.
motion create --template=motion-game synbiozvszombie
cd synbiozvszombie
Dans le dossier app
, on ajoute un sous-dossier scenes
qui regroupera nos trois scènes sans oublier de supprimer main_scene.rb
à la racine de app
pour éviter les conflits.
Notre première scène va nous permettre de sélectionner le survivant de l’équipe synbioz que l’on souhaite incarner.
app/application.rb
class Application < MG::Application
def start
MG::Director.shared.run(SurvivorScene.new)
end
end
Notre jeu se lance maintenant sur la scène souhaitée.
app/scenes/survivor_scene.rb
class SurvivorScene < MG::Scene
def initialize
add_label
end
def add_label
label = MG::Text.new("Choose your survivor", "Arial", 80)
label.color = [0.7, 0.7, 0.7]
label.position = [MG::Director.shared.size.width / 2, MG::Director.shared.size.height - 100]
add label
end
end
Dans un premier temps nous ajoutons un simple texte au-dessous duquel nous pourrons choisir notre survivant.
MG::Director.shared.size.width
nous permet de récupérer la largeur de l’écran. Une fois divisée par deux, notre texte sera centré sur x
.
On laisse ensuite une marge de 100px en haut de l’écran.
Il est maintenant temps d’ajouter la liste de nos survivants :
app/scenes/survivor_scene.rb
def add_survivors
team_synbioz = ["Martin", "Nico", "Victor", "Jon", "Numa", "Clement", "Theo", "Cedric"]
team_synbioz.each_with_index do |name, index|
button = MG::Button.new("#{name}")
button.font_size = 35
button.position = [MG::Director.shared.size.width / 2, (MG::Director.shared.size.height - 200) - (index * 50)]
button.on_touch { MG::Director.shared.replace(MainScene.new(name)) }
add button
end
end
Pour chacun des membres de Synbioz, on ajoute un bouton. Une fois cliqué, nous remplaçons la scène courante par la scène principale avec MG::Director.shared.replace()
en spécifiant le nom du survivant en paramètre.
Vous devriez voir ceci à l’écran :
Notre scène principale comportera deux éléments, un survivant et un zombie. Le zombie se déplacera aléatoirement sur l’écran et vous devrez indiquer au survivant de se déplacer pour l’éviter.
Pour cela nous allons avoir besoin de ressources. Par défaut, Motiongame récupère ses ressources dans un dossier resources
à la racine de notre application.
Il suffit donc de créer ce dossier et d’y ajouter vos ressources.
app/scenes/main_scene.rb
def initialize(name)
add_zombie
end
def add_zombie
@zombie = MG::Sprite.new("zombie.png")
@zombie.position = [400, MG::Director.shared.size.height / 2]
add @zombie
end
Ici MG::Sprite.new("image")
récupère notre image sur laquelle on appliquera les méthodes souhaitées.
Il est tout aussi simple de lancer une musique extraite de ce dossier avec MG::Audio.play("song")
.
app/scenes/main_scene.rb
def initialize(name)
MG::Audio.play("night.wav", true, 0.5)
@name = name.downcase
add_zombie
end
Ce son d’ambiance est joué au lancement de notre scène, true
précisant qu’on souhaite qu’elle tourne en boucle. Enfin, 0.5
indique le volume sonore.
Nous allons maintenant importer l’image du survivant sélectionné qui est stockée dans le dossier resources/survivors
.
Nous récupérons le nom dans la variable d’instance @name
qui nous sert à afficher dynamiquement l’image de notre survivant.
app/scenes/main_scene.rb
def initialize(name)
MG::Audio.play("night.wav", true, 0.5)
@name = name.downcase
add_zombie
add_survivor
end
def add_survivor
@survivor = MG::Sprite.new("survivors/#{@name}.jpg")
@survivor.position = [100, MG::Director.shared.size.height / 2]
add @survivor
end
Une fois votre choix effectué vous arrivez sur la scène principale.
Nous souhaitons que lorsque nos personnages se touchent, la scène de game over se déclenche. Pour cela nous allons dans un premier temps déplacer notre survivant vers notre zombie pour observer le comportement par défaut.
app/scenes/main_scene.rb
def initialize(name)
MG::Audio.play("night.wav", true, 0.5)
@name = name.downcase
add_zombie
add_survivor
end
def add_survivor
@survivor = MG::Sprite.new("survivors/#{@name}.jpg")
@survivor.position = [100, MG::Director.shared.size.height / 2]
@survivor.move_to([450, @survivor.position.y], 1)
add @survivor
end
Pour le moment notre survivant traverse notre zombie, les deux étant de simples images. Il nous faut rendre ces images physiques pour qu’un contact soit possible.
app/scenes/main_scene.rb
def add_zombie
@zombie = MG::Sprite.new("zombie.png")
@zombie.attach_physics_box
@zombie.position = [400, MG::Director.shared.size.height / 2]
add @zombie
end
def add_survivor
y_position = MG::Director.shared.size.height / 2
@survivor = MG::Sprite.new("survivors/#{@name}.jpg")
@survivor.attach_physics_box
@survivor.position = [100, y_position]
@survivor.move_to([450, y_position], 1)
add @survivor
end
Cliquez sur l’image ci-dessous pour voir l’animation.
On a maintenant une collision entre notre survivant et le zombie ! On remarque aussi qu’ils sont maintenant soumis aux lois de la gravité ce qui est très intéressant dans un Mario 2D par exemple.
Notre mini jeu étant plus proche d’un Pacman, on va donc retirer cette gravité.
app/scenes/main_scene.rb
def initialize(name)
self.gravity = [0, 0]
MG::Audio.play("night.wav", true, 0.5)
@name = name.downcase
add_zombie
add_survivor
end
Maintenant que nos personnages se comportent comme on le souhaite, il ne reste qu’à déclencher notre scène de game over lors du contact, pour cela on utilise le contact_mask
.
app/scenes/main_scene.rb
def initialize(name)
self.gravity = [0, 0]
MG::Audio.play("night.wav", true, 0.5)
@name = name.downcase
add_zombie
add_survivor
on_contact_begin do
MG::Director.shared.replace(GameOverScene.new)
end
end
def add_zombie
@zombie = MG::Sprite.new("zombie.png")
@zombie.attach_physics_box
@zombie.position = [400, MG::Director.shared.size.height / 2]
@zombie.contact_mask = 1
add @zombie
end
def add_survivor
y_position = MG::Director.shared.size.height / 2
@survivor = MG::Sprite.new("survivors/#{@name}.jpg")
@survivor.attach_physics_box
@survivor.position = [100, y_position]
@survivor.contact_mask = 1
@survivor.move_to([450, y_position], 1)
add @survivor
end
app/scenes/game_over_scene.rb
class GameOverScene < MG::Scene
def initialize
add_label
end
def add_label
label = MG::Text.new("Game over...", "Arial", 96)
label.position = [MG::Director.shared.size.width / 2, MG::Director.shared.size.height / 2]
add label
end
end
Désormais, lorsque notre survivant touche le zombie, le bloc on_contact_begin
est appelé et remplace notre scène par l’écran de game over !
Nous souhaitons maintenant déplacer nos personnages. Le survivant devra se déplacer vers notre doigt dès qu’on touche l’écran. Le zombie se déplacera aléatoirement en respectant les limites de l’écran.
Nous allons d’abord déplacer notre survivant, pour cela nous avons besoin de récupérer les coordonnées de notre évènement que l’on utilisera dans @survivant.move_to([x, y], 1)
.
app/scenes/main_scene.rb
def initialize(name)
self.gravity = [0, 0]
@name = name.downcase
add_survivor
add_zombie
on_touch_begin do |touch|
@survivor.move_to([touch.location.x, touch.location.y], 1)
end
on_contact_begin do
MG::Director.shared.replace(GameOverScene.new(@score))
end
end
Le bloc on_touch_begin
nous permet de récupérer les coordonnées de contact de notre doigt. Il ne reste plus qu’à retirer le déplacement précédent.
Avec la méthode update
, Motiongame inclut un système de boucle facile à mettre en place. Il suffit de définir une méthode update(delta)
que l’on switch facilement grâce aux méthodes start_update
et stop_update
.
app/scenes/main_scene.rb
def initialize(name)
self.gravity = [0, 0]
@name = name.downcase
@zombie_update_position = 0
# CODE ...
start_update
end
def update(delta)
@zombie_update_position += delta
if @zombie_update_position >= 2.0
@zombie.move_to([random_position[:x], random_position[:y]], 1)
@zombie_update_position = 0
end
end
private
def random_position
{
x: Random.new.rand(0..MG::Director.shared.size.width),
y: Random.new.rand(0..MG::Director.shared.size.height)
}
end
Dans un premier temps on initialise @zombie_update_position
à zéro, puis on lance notre boucle avec le start_update
.
La méthode update
a comme paramètre delta
qui retourne le temps qu’a mis la boucle à s’exécuter. Ainsi, on peut récupérer le temps courant en incrémentant avec delta
.
On se sert ici de ce principe pour déplacer notre zombie toutes les deux secondes. On précise ensuite des coordonnées aléatoires qui respectent les dimensions de l’écran pour gérer le déplacement du zombie.
Afin de pimenter un peu notre mini jeu, on va modifier l’échelle de notre zombie toutes les deux secondes, puis on affichera le temps de survie sur l’écran de game over.
app/scenes/main_scene.rb
def initialize(name)
self.gravity = [0, 0]
@name = name.downcase
@time = 0
@zombie_update_position = 0
add_survivor
add_zombie
on_touch_begin do |touch|
@survivor.move_to([touch.location.x, touch.location.y], 2)
end
on_contact_begin do
MG::Director.shared.replace(GameOverScene.new(@time))
end
start_update
end
def add_zombie
# CODE ...
end
def add_survivor
# CODE ...
end
def update(delta)
@time += delta
@zombie_update_position += delta
if @zombie_update_position >= 2.0
@zombie.move_to([random_position[:x], random_position[:y]], 1)
@zombie.scale += 0.1
@zombie_update_position = 0
end
end
private
def random_position
{
x: Random.new.rand(0..MG::Director.shared.size.width),
y: Random.new.rand(0..MG::Director.shared.size.height)
}
end
On utilise la variable @time
que l’on transmet à la scène de game over.
app/scenes/game_over_scene.rb
class GameOverScene < MG::Scene
def initialize(score)
add_label
add_score(score)
end
def add_label
label = MG::Text.new("Game over...", "Arial", 96)
label.position = [MG::Director.shared.size.width / 2, MG::Director.shared.size.height / 2]
add label
end
def add_score(score)
time = score.round(2)
score = MG::Text.new("You survived #{time}s", "Arial", 40)
score.position = [MG::Director.shared.size.width / 2, (MG::Director.shared.size.height / 2) - 80]
add score
end
end
Dans cette introduction nous avons survolé beaucoup de fonctionnalités de Motiongame. Si le sujet vous intéresse, je ne saurais trop vous conseiller de faire un tour sur la documentation en attendant un prochain article où on entrera dans le détail !
L’équipe Synbioz. Libres d’être ensemble.