Auteur : Fime

Projets

Comment a été créé Pong

Dans cet article, nous allons nous interresser à la programmation et à l’architecture de la version 2 de Pong. Le jeu est écrit en Python pour numworks sous Oméga, car cette version offre plus de tas (100ko contre 32ko dans la version officielle).

Pourquoi une seconde version ?

La première version avait une architecture assez peu soignée car je ne l’avais pas pensé de manière globale. De plus, elle avais des manques de compression assez flagrants car elle fesait plus de 12ko. Pour le petite anecdote, j’avais du la compresser énormément pour pouvoir la faire tourner car elle souffrait d’un dépassement de mémoire. Paradoxalement, c’est un de mes jeux qui a le mieux marché, sans doute car il était très complet. Mais je ne voulais pas laisser à un programmeur qui tombe sur le jeu l’image que c’est un travail que je trouve a posteriori presque bâclé. J’ai donc entrepris de le réécrire.

Première étape : programmer les menus.

Moteur de menu

Pour une fois, j’ai voulu commencer par programmer le menu (j’ai appelé le moteur menulib). Je pourrais alors adapter l’architecture du moteur de jeu à l’architecture globale. Pour programmer ce moteur de menu, je suis parti d’une syntaxe de création de menu un peu alambiquée, qui rassemble touts les éléments iterractifs (le moteur ne s’occupe que des éléments interractifs, les titres ou autre doivent être dessinés à part): l’unique fonction prend en parametre un liste de tupple qui contiennent eux même le type, le titre et si besoin les valeurs de l’élément :

menu ( position x, position y, elements list , [color] , [background color] )

Les éléments on chacun un identifiant :

TypeIDSyntaxeInformation
Button« btn »["btn","name"]Simple button. Not intended to be used as a text
List« lst »["lst","name",("element1","element2"),current element index]Element that allow the user making a choice beetwen the different elements purposed
Slider« sld »["sld","name",(min val,max val),current value]Element that allow the user chosing a value bettwen min val and max val
(Extrait de la documentation, que j’avais écrite en anglais)

La fonction retourne alors le nom du bouton sélectionné et l’indexe de chaque liste ou slider.

De cette manière, j’ai pu créer facilement des menus et récupérer le résultat de l’interaction avec l’utilisateur. Exemple ci dessous avec le menu principal

def mainMenu(conf=base_conf):
  transition(conf) #petite animation

  drawSprite(LOGO,10,10,12,conf[Col2]) #dessiner le logo
  drawRect(10,80,300,142,conf[Col3]) #dessiner le fond
  
  act_el=[["btn","Play"],["lst","Mode",("Solo","2 Player","Vs Comp."),conf[Mode]],["btn","Game Options"],["btn","Graphics Options"]]
  #défini les éléments
  get_act=menu(20,100,act_el,conf[Col1],conf[Col3]) # appelle la fonction et affecte le résulat de l'action à get_act
  
  conf[Mode]=get_act[1]["Mode"] #défini la configuration du mode de jeu à la sélection.
  if get_act[0]=="Game Options":gameMenu(conf) #analyse quel bouton a été selectionné
  elif get_act[0]=="Graphics Options":graphMenu(conf)
  else:gameEngine(conf)

La configuration et la persistance des options

Vous avez peut-être remarqué que les fonctions mainMenu, graphMenu, gameMenu et transition prennent toutes en paramètre la variable conf. C’est une liste, en quelque sorte un fichier de configuration, qui contient toutes le informations et paramètres du programme. Cette liste est enregistrée dans un fichier sous forme de chaine grâce à la fonction str() elle est décompressé à partir de ce fichier de manière totalement transparente grâce à la fonction eval() :

def saveConf(conf):
  try : #fonction try utilisée pour éviter des arrêts suite à une potentielle erreur.
    with open("pong.conf","w") as f:
      f.truncate(0) #on vide le fichier ...
      f.write(str(conf)) #... et on réécrit la chaine
  except: print("Saving configuration failed.") #message d'erreur en cas d'échec, mais cela ne stop pas le programme

def loadConf():
  try: #fonction try utilisée pour éviter des arrêts suite à une potentielle erreur ou si la configuration n'a pas encore été enregistrée
    with open("pong.conf","r") as f:return eval(f.readline()) #comme la chaine correspond à une liste, elle est évaluée comme une liste par eval()
  except: #si le fichier n'existe pas
    print("Loading configuration failed.")
    return base_conf #on retourne la configuration par défaut

Pour plus de lisibilité du code, chaque élément de la configuration (qui rappelons-le, est une liste) n’est non pas atteint grace à un index mais grace à une constante qui a pour valeur l’index correspondant :

Mode,Diff,MaxPts,BallSpd,BallDetails,PadDetails,Col1,Col2,Col3,BgCol,Theme,Best=0,1,2,3,4,5,6,7,8,9,10,11

Gestion des thème

Pour gérer les theme, j’ai simplement utilisé la configuration. Les couleurs et le numéro du thème y sont contenus et mis à jour quand cela est nécessaire. Pour mettre à jour ces couleurs, une fonction récupère le numéro du thème dans la configuration et renvoie cette configuration avec les bonnes couleurs :

def setTheme(conf,nb):
  conf[Theme]=nb #définit le numéro du thème dans la configuration
  a,b,c,d=(255,255,255),(255,200,0),(100,100,100),(60,60,60) #défini les 4 couleurs du thème de base
  if nb==1:a,b,c,d=d,(200,150,60),(200,200,200),a #thème light : réarrange et modifie les couleurs
  elif nb==2:b=(220,50,50) #theme omega
  elif nb==3:b=(200,100,200) #theme nsiOs
  conf[Col1:BgCol+1]=a,b,c,d #on defini les couleurs dans la config
  return conf #et on renvoie la config

Le moteur de jeu

Architecture et déplacement

J’ai longtemps hésité sur le choix de l’architecture. J’ai bien évidemment directement pensé à une classe en orienté objet qui pourrait s’occuper à la fois des pads et de la balle. Mais cette option est assez très gourmande en mémoire et j’ai aussi pensé à une fonction « constructrice » qui retournerais une liste avec toutes les information de l’objet. Finallement, j’ai tout de même opté pour une classe Entity() qui contient différentes fonctions très utiles :

class Entity():
    def __init__(it,x,y,w,h,col,bg_col):
      #Fonction d'initialisation des attributs
      #Défini la position, la taille, la couleur, etc...
      it.x,it.y,it.w,it.h,it.col,it.bg_col=x,y,w,h,col,bg_col
      it.spd_x,it.spd_y=0,0
      it.last_draw=(int(it.x-it.w//2),int(it.y-it.h//2),int(it.w),int(it.h),it.bg_col)
    def hitBox(it,it2):
      #Fonction qui renvoie vrai si it (objet 1) touche it2 (objet 2)
      if it.x-it.w//2<it2.x+it2.w//2 and it.x+it.w//2>it2.x-it2.w//2 and it.y-it.h//2<it2.y+it2.h//2 and it.x+it.w>it2.x and it.y<it2.y+it2.h//2 and it.y+it.h//2>it2.y-it2.h//2:return True
      else: return False
    def applyVec(it):
      #Fonction qui applique la vitesse de l'objet à sa position
      it.x+=it.spd_x
      it.y+=it.spd_y
    
    def hideObj(it):
      #Fonction qui dessine un rectangle de la couleur du fond pour cacher le dernier affichage de l'objet
      drawRect(*it.last_draw)
    
    def drawObj(it,detail=0):
      #Fonction qui dessine un rectangle à la place de l'objet
      it.last_draw=[int(it.x-it.w//2),int(it.y-it.h//2),int(it.w),int(it.h),it.bg_col]
      if detail:
        for x2,y2 in zip((2,0),(0,2)):drawRect(int(it.x-it.w//2)+x2,int(it.y-it.h//2)+y2,int(it.w)-x2*2,int(it.h)-y2*2,it.col)
      else: drawRect(*it.last_draw[0:4]+[it.col])

Ensuite, j’ai amélioré le déplacement de la balle. Je lui ai ajouté un attribut ball.a qui représente son angle, et à chaque collision sa vitesse latérale et horizontale est redéfinie grace à une fonction :

def vec(s,a): #la fonction prend en parametre la vitesse s et l'angle a
      a=radians(a)#les fonctions trigo prennent l'angle en radians
      x=s*cos(a)
      y=s*sin(a)
      return x,y #retour du vecteur vitesse en 2D

Pour les collisions : c’est encore plus simple. J’utilise une fonction qui prend en paramètre l’angle de l’objet et de la surface, qui calcule l’angle d’incidence et qui retourne l’opposé :

def collide(a1,a2):
  return a2-simp(a1-a2)
#la fonctio simp() simplifie un angle pour qu'il soit compris entre 0° et 360°.

Apres, je « n’avais qu’a » associer des touches aux pad, gérer leur collision avec le haut et la bas et détecter quand la balle est en dehors.

Afficher le score

Il ne restait plus qu’a afficher le score (bon, en fait il restait pas mal de petits truc à faire mais je vais pas les détailler ici). Pour cela, j’ai utilisé la technique de Schraf : Math-infos que j’ai adapté. J’ai d’abord créé une fonction drawSprite() un peu compliquée, qui grosso modo lit un chiffre pour le décompresser en binaire; et lit chaque bit. Si le bit est de 1, un carré est dessiné. Si le bit est de 0, on laisse la case vide. La fonction drawNumber permet d’automatiser l’affichage d’un sprite pour chaque chiffre du nombre désiré.

NumApps

Pong en Python, Numworks

Pong est un jeu que l’on n’a plus besoin de présenter. Autrefois présent sur des bornes d’arcades lors de la genèse du jeu vidéo au début des années 70, le concept a évolué mais reste toujours aussi fun ! Découvrir la programmation d’un jeu en python sur ce jeu est passionnant, et si le code de 12ko de la première version offrait de grandes possibilités, la version 2 présentée ici est à découvrir sans tarder.

Captures d’écran du jeux

Une version 2 optimisée

La première version de ce jeu, conçu en deux semaines et quelques insomnies, a plutôt bien marché mais paradoxalement c’était mon premier jeu, et donc il n’était vraiment pas optimisé. Six mois plus tard, j’ai décidé de m’y remettre. J’ai donc pris mon courage à deux mains et je l’ai entièrement réécrit. De cette manière, la nouvelle version est plus petite mais bien meilleure.

Des menus, des graphisme et un gameplay revisité

Le jeu offre tout premièrement des menus interactifs, programmés grâce à une bibliothèque personnelle, menulib. Ce dernier vous permet de tout (ou presque) paramétrer, des points maximums à la vitesse de la balle, la difficulté, mais aussi les graphismes. En effet, grosse nouveauté, le jeu supporte désormais des thèmes et des particules ont étés ajoutées quand on active les détails de la balle.

Le jeu est jouable en plusieurs modes :

  • Solo : on contrôle les deux pads et on doit faire le plus grand nombre de rebonds
  • 2 players : on a la possibilité de jouer avec un ami en duel
  • Contre l’ordinateur : on joue contre la calculatrice. Attention ! La difficulté se règle dans les paramètres de jeu.

Contrôles

Flèches haut/basBouton supprimer (backspace)Boutons multiplication/addition
Pad 1 (gauche)PausePad 2 (droite) (en mode 2 joueurs)

Téléchargement

NumApps

JetPack Bird en Python, Numworks

Sortie en 2013, Flappy Bird fut un énorme succès sur les plateforme Android et iOs. Le jeu proposé ici est une fusion de deux jeux, Flappy Bird, jeu ou l’on fait voler un oiseau en tapotant l’écran, et JetPack joyride, dans lequel le personnage Barry utilise un jetpack pour se promener à sa guise entre des rayons lazers.

Capture d’écran du jeux

La jouabilité

Pour ce jeu, j’ai voulu donner plus de jouabilité au personnage, pour qu’il soit adapté au « grand publique ». Au départ pensé comme un simple clone de Flappy Bird, je me suis tourné vers un déplacement plus fluide tel que celui d’un JetPack, fonctionnant avec une accélération et non par à-coup. Une seule touche est utile, la touche [OK].

Optimisation et graphismes

Comme je l’ai déjà dit, je voulais un jeu adapté à tout le monde. Donc, par soucis de place, j’ai opté d’enlever tout menu. Pour rendre le jeu tout de même beau, j’ai ajouté une fonction qui trace un contour autour de chaque rectangle tracé. Malheureusement, pour avoir un jeu rapide, il est plus facile de tracer un grand rectangle noir qui sert de cadre puis de tracer à l’intérieur un rectangle de la couleur désirée, mais (combiné à l’écran lui même qui a quelques défauts) cela produit des effets de rafraichissement pas très agréables… Rassurez vous tout de même, cela reste largement jouable ! Notez que le meilleur score est enregistré avec Oméga (et Os dérivées). Bon jeu !

Téléchargement

Nous vous proposons 2 liens distincts, le premier est le lien vers la source du créateur de l’application, le deuxième est un lien alternatif en cas de problème. Seul le premier lien garanti de disposer de la dernière version de l’application ainsi que la documentation écrite directement dans le workshop.

NumApps

Doodle Man en python, NumWorks

Doodle Jump eu une énorme succès sur les smartphones modernes à la fin des années 2000. Il exploitait alors l’ accéléromètre pour offrir un gameplay enfantin et addictif. Cette version qui se contrôle avec la croix directionnelle va faire un carton !

Captures d’écran du jeux

Un peu de technique

Un gameplay simplifié pour tenir sur 8ko

Après avoir programmé un jeu pas très ambitieux avec Cyclope Snake, j’ai voulu recréer un classique des jeux mobile : Doodle Jump … avec les contraintes de la machine !

Les contraintes en question, et bien, ces sont principalement le manque de mémoire. Pour éviter de surcharger le stockage, j’ai été obligé de faire des concessions : le cube rouge aux yeux luisants (un mélange entre Super Meat Boy et Herobrine ?!) n’a pas d’ennemis à esquiver ou éliminer. Par conséquent, il ne tire pas de balles vers le haut.

Les plateformes sont de plusieurs types : mouvante, fantôme, éphémère, trampoline, etc …

Seul objectif : battre son meilleur score

Sans ennemis, la seul chose à faire est de grimper le plus haut possible ! Les plateformes et le personnage diminuent de taille au fur et à mesure. Par contre, les commande restent volontairement assez « amples » ! Le jeu est donc pensé pour être challengeant. Dernière chose : avec l’Os Omega, le jeu enregistré le meilleur score dans un fichier de sorte à le recharger d’une partie à l’autre !

Télécharger

Nous vous proposons 2 liens distincts, le premier est le lien vers la source du créateur de l’application, le deuxième est un lien alternatif en cas de problème. Seul le premier lien garanti de disposer de la dernière version de l’application ainsi que la documentation écrite directement dans le workshop.

NumApps

Cyclope Snake en python, NumWorks.

Parfois il suffit de rajouter un chrono dans un jeu et un comportement aléatoire pour tout changer. Ce Snake réinvente le gameplay, et reprend les codes graphiques des premiers jeux sur console. Un succès garanti !

Un Snake avec des graphismes évolués

Ce qui fait de cyclope Snake un jeu (un peu) différent, c’est que l’expérience de jeu a été améliorée avec des petits éléments de gameplay ou de graphisme comme par exemple l’interface style arcade, avec des contours et un tableau de score.

Commandes

Croix directionnelleOK
Se déplacerValider

Un peu de technique

Sur gameplay, le meilleur score est enregistré et rechargé à chaque lancement si vous utilisez Omega Os.
Par ailleurs, le serpent a une jauge de faim qui se réinitialise à chaque fois qu’il mange un fruit. Si cette jauge est remplie, il devient affamé et va aller dans le direction opposée de celle désirée, jusqu’à ce qu’il remange un fruit.

Les éléments du jeu possèdent une ombre (ça m’a été plutôt difficile à intégrer, je reviendrai là dessus dans un article futur, peut être), et le serpent a plusieurs détails.

Vous trouverez sur la page de téléchargement du jeu des instructions de personnalisation du jeu (couleurs, taille, difficulté, etc…)

Télécharger

Nous vous proposons 2 liens distincts, le premier est le lien vers la source du créateur de l’application, le deuxième est un lien alternatif en cas de problème. Seul le premier lien garanti de disposer de la dernière version de l’application, ainsi que la documentation écrite directement dans le workshop.