Créer un jeu Space Defender - Logique du jeu

Dans cette série de didacticiels, nous allons apprendre à créer un jeu de tir spatial comme le jeu classique Space Defender. Continuer à lire!


Aperçu de la série

Dans cette version de Space Defender, le joueur devra défendre son espace en tirant sur des ennemis. Chaque fois que le joueur réussit à détruire un ennemi, il gagne des points et lorsque le joueur a atteint 20 ou 40 points, son arme à feu reçoit une mise à niveau. Pour mélanger les choses, ce jeu enverra des packages bonus qui valent 5 points. Pour voir le jeu en action, regardez la courte vidéo ci-dessus.


Où nous nous sommes laissés…

Dans la première partie de cette série, nous avons appris à configurer notre application, à utiliser des polices personnalisées, à utiliser un scénarimage et à configurer notre menu principal. Dans la deuxième partie de cette série, nous allons apprendre à créer le gameplay de notre application. Alors, commençons!

Notre première étape consiste à créer un nouveau fichier appelé game.lua. Une fois créé, ouvrez le fichier dans votre éditeur préféré..


1. Ajouter des bibliothèques

Puisque nous commençons une nouvelle scène, nous devons avoir besoin de bibliothèques. Nous utiliserons le moteur physique intégré à Corona SDK pour la détection de collision.

 storyboard local = require ("storyboard") scène locale = storyboard.newScene () physique locale = require ("physique")

2. Configuration de la physique

Après avoir configuré nos bibliothèques, nous allons configurer le moteur physique. Les paramètres ci-dessous définiront la gravité sur 0 (comme dans l'espace) et les itérations sur 16. La méthode setPositionIterations signifie que le moteur passera par 16 positions par image. Tout ce qui dépasse 16 peut nuire aux performances du jeu.

 physics.start () physics.setGravity (0, 0) physics.setPositionIterations (16)

3. Utilisation d'un générateur aléatoire

Bien que cette étape ne soit pas nécessaire pour ce didacticiel, il est judicieux de "créer" le générateur de nombres aléatoires. J'aime utiliser l'heure actuelle pour ensemencer le générateur.

 math.randomseed (os.time ())

4. Configurer les variables du jeu

Nous allons maintenant définir certaines variables pour notre jeu. Chaque variable a un commentaire à côté d'elle expliquant le but de la variable.

 local screenW, screenH, halfW, halfY = display.contentWidth, display.contentHeight, display.contentWidth * 0.5, display.contentHeight * 0.5 local gameover_returntomenu - en avant notre bouton de jeu - Configurer les paramètres de jeu motionx = 0; - Variable utilisée pour déplacer le caractère le long de l’axe des y = 10; - Contrôle la vitesse du navire playerScore = 0; - Définit le score du joueur playerLives = 20; - Définit le nombre de vies pour le lecteur slowEnemySpeed ​​= 2375; - Définit la vitesse à laquelle les navires blancs se déplacent à travers l'écran slowEnemySpawn = 2400; - Définit le taux de réapparition des navires blancs fastEnemySpeed ​​= 1875; - Définit la vitesse à laquelle les navires verts se déplacent à travers l'écran fastEnemySpawn = 1800; - Définit le taux de réapparition des navires verts bulletSpeed ​​= 325; - Définit la vitesse à laquelle la balle parcourt l'écran bulletSpawn = 250; - Définit le taux de réapparition des balles

5. Créer la scène de jeu principale

Après avoir créé les variables, nous allons configurer la scène dans la fonction scène: createScene. Si vous vous rappelez de la partie 1, cette fonction est utilisée pour créer des éléments visuels et une logique de jeu. Dans une fonction ultérieure, nous ferons appel à ces fonctions pour lancer le jeu..

Dans le code suivant, nous créons le scène: createScene fonction et en ajoutant les murs de fond et haut / bas. Les deux murs sont configurés en tant qu’objets physiques statiques pour empêcher le lecteur de sortir de l’écran..

 scène de fonction: createScene (événement) groupe local = self.view - Configure les éléments visuels et les murs locaux bg = display.newImageRect ("images / BKG.png", 480, 320) bg.x = halfW bg.y = halfY groupe: insert (bg) topwall local = display.newRect (0,0, screenW, 20) topwall.y = -5 topwall: setFillColor (0,0,0) topwall.alpha = 0.01 physics.addBody (topwall, "statique ") groupe: insérer (topwall) local bottomwall = display.newRect (0,0, screenW, 20) bottomwall.y = 325 bottomwall: setFillColor (0,0,0) bottomwall.alpha = 0.01 physics.addBody (bottomwall," static ") group: insert (bottomwall) end

6. Ajout d'éléments HUD

À l'intérieur de la même scène: createScene fonction, mais après la fond objet d'affichage, nous allons ajouter quatre autres objets d'affichage. Voici une explication du but de chaque objet.

  • btn_up, btn_down: Ces objets d'affichage agiront comme des boutons sur le côté gauche de l'écran et chaque objet déplacera le navire vers le haut ou le bas, respectivement. Cependant, ils ne sont utilisables que lorsque nous avons configuré la fonction de déplacement.
  • ennemiBar: Cet objet d'affichage est configuré en tant que capteur et réagit uniquement aux collisions physiques. Quand il réagit aux collisions, il enlève l'objet ennemi et soustrait un de la vie du joueur.
 local btn_up = display.newRect (0,0,75,160) btn_up: setReferencePoint (display.TopLeftReferencePoint) btn_up.x, btn_up.y = 0,0; btn_up.alpha = 0.01 groupe: insert (btn_up) local btn_down = display.newRect (0,0,75,160) btn_down: setReferencePoint (display.BottomLeftReferencePoint) btn_down.x, btn_down.y = 0, screenH; btn_down.alpha = 0.01 groupe: insérer (btn_down) ennemi localHitBar = display.newRect (-20,0,20,320) ennemisHitBar: setFillColor (0,0,0) ennemisHitBar.name = "ennemisHitBar" physics.addBody (ennemisHitBar,, isSensor = true) group: insert (ennemiHitBar)

Juste après l'objet d'affichage ennemisHitBar, nous allons ajouter des éléments d'interface graphique pour afficher le score du joueur et ses vies. Nous allons également afficher un texte à l'écran indiquant "Monter" et "Descendre" pour indiquer au joueur où il doit toucher pour déplacer le navire vers le haut ou le bas..

 local gui_score = display.newText ("Score:"… playerScore, 0,0, "Kemco Pixel", 16) gui_score: setReferencePoint (display.TopRightReferencePoint) gui_score.x = screenW groupe: insert (gui_score) local gui_lives = display.newText ("Lives:"… playerLives, 0,0, "Kemco Pixel", 16) gui_lives: setReferencePoint (display.BottomRightereferPoint) gui_lives.x = screenW gui_lives.y = screenH groupe: insérer (gui_lives) local gui_moveup = displayWne.Text ( "Move Up", 0,050,100, "Kemco Pixel", 16) groupe: insérer (gui_moveup) local gui_movedown = display.newText ("Move Down", 0,0,50,23, "Kemco Pixel", 16 ) gui_movedown: setReferencePoint (display.BottomLeftReferencePoint) gui_movedown.y = groupe screenH: insert (gui_movedown)

7. Ajouter le vaisseau du joueur

Ensuite, nous ajouterons le vaisseau du joueur à l'écran. Le vaisseau sera ajouté en tant qu’objet de physique dynamique afin de pouvoir réagir aux collisions avec d’autres objets de physique. Nous approfondirons les collisions plus loin dans ce tutoriel..

 local ship = display.newImageRect ("images / spaceShip.png", 29, 19) ship.x, ship.y = 75, 35 ship.name = "ship" physics.addBody (ship, "dynamique", friction = 0.5, bounce = 0) group: insert (ship)

8. Déplacement du vaisseau du joueur

Vous souvenez-vous du btn_up et btn_down afficher les objets que nous avons ajoutés? Nous allons maintenant ajouter des écouteurs d'événements à ces objets pour faciliter le déplacement du joueur. Quand btn_up est touché, nous allons rendre notre vitesse variable négative et quand btn_down est touché nous allons rendre notre vitesse positive. En rendant cette variable positive et négative, nous disons à notre prochaine fonction de faire monter ou descendre le navire.

 -- Lorsque vous appuyez sur le bouton haut, définissez notre mouvement pour déplacer la fonction d'expédition vers le haut. Btn_up: touch () motionx = -speed; end btn_up: addEventListener ("touch", btn_up) - Lorsque le bouton Bas est touché, définissez notre mouvement pour déplacer la fonction de navire vers le bas btn_down: touch () motionx = speed; end btn_down: addEventListener ("touch", btn_down)

9. Permettre le mouvement

Après avoir ajouté des auditeurs d'événements à notre btn_up et btn_down afficher des objets, nous allons créer deux écouteurs d’événements d’exécution avec leurs fonctions respectives. Ces fonctions exécuteront chaque image et la seule capture avec les fonctions d'exécution est que vous devez spécifier quand les arrêter. Nous couvrirons cela plus tard. Pour l'instant, la fonction stop va définir la variable motionx à 0 (car aucun bouton n'est touché) et le moveguy la fonction ajoutera la variable motionx à notre navire y position.

 fonction locale stop (event) si event.phase == "terminé" alors motionx = 0; end end Runtime: addEventListener ("touch", stop) - Cette fonction déplacera le navire en fonction de la fonction de mouvement locale moveguy (événement) ship.y = ship.y + motionx; end Runtime: addEventListener ("enterFrame", moveguy)

10. balles de tir

Nous avons maintenant notre navire en mouvement, mais ce n'est pas le tir! Pour que le navire soit prêt à tirer des balles, nous devons créer le fireShip () une fonction. Cette fonction créera de nouveaux objets d'affichage réagissant aux collisions physiques et déplacera également l'objet sur l'écran de gauche à droite..

Pour rendre le jeu plus intéressant, nous allons permettre au joueur de tirer plus de balles quand il atteint un certain score. Lorsque le joueur atteint 20, le navire tire deux balles et lorsque le joueur atteint 40, le navire tire une troisième balle qui tire vers le bas en diagonale.

 fonction fireShip () bullet = display.newImageRect ("images / bullet.png", 13, 8) bullet.x = ship.x + 9 bullet.y = ship.y + 6 bullet: toFront () bullet.name = " bullet "physics.addBody (bullet, isSensor = true) transition.to (bullet, time = bulletSpeed, x = 500, onComplete = fonction (self) self.parent: remove (self); self = nil; end; ) if (playerScore> = 20) alors secondBullet = display.newImageRect ("images / bullet.png", 13, 8) secondBullet.x = ship.x + 9 secondBullet.y = ship.y + 12 secondBullet: toFront ( ) secondBullet.name = "bullet" physics.addBody (secondBullet, isSensor = true) transition.to (secondBullet, time = bulletSpeed, x = 500, onComplete = fonction (self) self.parent: remove (auto); self = nil; end;) end if (playerScore> = 40), alors thirdBullet = display.newImageRect ("images / bullet.png", 13, 8) thirdBullet.x = ship.x + 9 thirdBullet.y = ship. y + 12 thirdBullet: toFront () thirdBullet.name = "bullet" physics.addBody (thirdBullet, isSensor = true) transition.to (thirdBullet, time = bulletSpeed, x = 500, y = ship.y + 100, onComplete = fonction (self) self.parent: remove (self); soi = nul; fin; )

11. Créer des ennemis

Une fois que nous avons configuré notre vaisseau pour tirer, nous devons donner au joueur des ennemis sur lesquels tirer! Nous allons créer deux fonctions différentes - createSlowEnemy () et createFastEnemy (). Les deux fonctions créeront un objet d'affichage physique qui se déplace de droite à gauche avec la vitesse de l'ennemi et l'image étant la seule différence..

 function createSlowEnemy () ennemis = display.newImageRect ("images / ennemis.png", 32, 26) ennemis.rotation = 180 ennemis.x = 500 ennemis.y = math.random (10, screenH-10) ennemis.nom = "ennemis" physics.addBody (ennemi, isSensor = true) transition.to (ennemi, heure = slowEnemySpeed, x = -20) fin de la fonction createFastEnemy () ennemi = display.newImageRect ("images / fastEnemy.png" , 32, 26) ennemis.rotation = 180 ennemis.x = 500 ennemis.y = math.random (10, screenH-10) ennemis.nom = "ennemis" physics.addBody (ennemis, isSensor = true). to (ennemi, time = fastEnemySpeed, x = -20) end

12. Créer des packages bonus

Ensuite, nous allons créer des packages de bonus que notre joueur pourra saisir dans la fonction. createBonus (). le createBonus () la fonction créera des objets d'affichage physique qui se déplacent de droite à gauche et chaque package de bonus que le joueur récupère gagne 5 points.

 function createBonus () bonus = display.newImageRect ("images / bonus.png", 18, 18) bonus.rotation = 180 bonus.x = 500 bonus.y = math.random (10, screenH-10) bonus.name = "bonus" physics.addBody (bonus, isSensor = true) transition.to (bonus, time = 1475, x = -20, onComplete = fonction () display.remove (bonus) bonus = nil end;) fin

13. Mise à jour des vies de joueurs

Notre dernière fonction est la fonction updateLives (). Cette fonction sera appelée chaque fois qu'un ennemi dépasse le joueur pour lui donner le but de défendre son côté de l'espace. Si le nombre de vies est supérieur à 0, cette fonction soustraira une vie et mettra à jour le texte à l'écran. Sinon, il en résultera une scène de game over.

Dans la scène de la fin de la partie, nous annulons tous nos chronomètres et retirons tous les écouteurs de nos événements. Avec le SDK Corona, il est très important de vous rappeler que vous devez explicitement indiquer à votre application à quel moment il faut supprimer les écouteurs d'exécution et les minuteurs (uniquement lorsque le minuteur est en cours d'exécution). Une fois que ceux-ci ont été supprimés, nous afficherons un message de fin de partie pour permettre au joueur de revenir au menu..

 function updateLives () if (playerLives> = 0) alors playerLives = playerLives - 1 gui_lives.text = "Lives:"… playerLives gui_lives.x = screenW min timer.cancel (tmr_fireShip) timer.cancel (tmr_sendSlowSemennes). ) timer.cancel (tmr_sendFastEnemies) timer.cancel (tmr_sendBonus) Durée: removeEventListener ("collision", onCollision) Durée: removeEventListener ("enterFrame", moveguy) Durée: removeEventListener ("stop") - Affichage de récit de défense gameover_message = display.newText ("Game Over!", 0,0, "Kemco Pixel", 32) gameover_message.x = halfW gameover_message.y = halfY - 15 groupe: insertion (gameover_message) fonction returnToMenuTouch (événement) si (événement. phase == "a commencé") puis storyboard.gotoScene ("menu", "slideRight", "1000") fin end gameover_returntomenu = display.newText ("Retour au menu", 0,0, "Kemco Pixel", 28) gameover_returntomenu .x = halfW gameover_returntomenu.y = halfY + 35 gameover_returntomenu: addEventListener ("touch", returnToMenuTouch) groupe: dans sert (gameover_returntomenu) end end

14. Détection de collision

Nous sommes prêts pour notre fonction finale à l'intérieur de notre scène: fonction createScene ()! Cette fonction gérera toute notre détection de collision en comparant la propriété mon nom de object1 à celui de l'objet 2. Chaque objet est passé en paramètre à cette fonction sous le nom de la variable un événement.

Pour vous faciliter la tâche, j'ai décomposé les cinq cas de collision..

  • Cas 1 - L'objet 1 est une balle et l'objet 2 est un ennemi
    Que ce passe-t-il: Supprimer l'ennemi et mettre à jour le score
  • Cas 2 - L'objet 1 est un ennemi et l'objet 2 est une balle
    Que ce passe-t-il: Supprimer l'ennemi et mettre à jour le score
  • Cas 3 - L'objet 1 est un navire et l'objet 2 est un bonus
    Que ce passe-t-il: Supprimer le bonus et mettre à jour le score
  • Cas 4 - L'objet 1 est un ennemi et l'objet 2 est un ennemiHitBar
    Que ce passe-t-il: Éliminer l'ennemi et mettre à jour des vies
  • Cas 5 - L'objet 1 est un ennemiHitBar et l'objet 2 est un ennemi
    Que ce passe-t-il: Éliminer l'ennemi et mettre à jour des vies
 function onCollision (event) if (event.object1.name == "bullet" et event.object2.name == "ennemi") puis display.remove (event.object2) playerScore = playerScore + 1 elseif (event.object1.name == "ennemi" et event.object2.name == "bullet") puis display.remove (event.object1) playerScore = playerScore + 1 elseif (event.object1.name == "ship" et event.object2.name = = "bonus") puis display.remove (event.object2) playerScore = playerScore + 5 elseif (event.object1.name == "ennemis" et event.object2.name == "ennemisHitBar") puis display.remove (événements. object1) updateLives () elseif (event.object1.name == "ennemisHitBar" et event.object2.name == "ennemis"), puis display.remove (event.object2) updateLives () et finit gui_score.text = "Score:" … PlayerScore gui_score.x = screenW end

15. Synchroniser les timers de mouvement

Puisque tout est prêt pour notre jeu, il suffit de tout faire bouger! À l'intérieur de la fonction scène: enterScene () - rappelez-vous que le enterScene la fonction est en dehors de la createScene fonction - nous allons créer 5 timers et un auditeur d’exécution. Les timers enverront les balles, les ennemis et les bonus tandis que l'auditeur d'exécution traitera la détection de collision.

 fonction scène: enterScene (événement) groupe local = self.view tmr_fireShip = timer.performWithDelD (bulletSpawn, fireShip, 0) tmr_sendSlowEnemies = timer.perform createSlowEnemy, 0) tmr_sendFastEnemies = timer.performWithDelay (fastEnemySpawn, createFastEnemy, 0) tmr_sendBonus = timer.performWithDelay (2500, createBonus, 0) Durée: addEventListener ("collision", fin)

16. Détruire la scène

Le dernier ajout (promis!) Est le scène: destroyScene () fonction et les auditeurs d'événements de la scène. La fonction de destruction de la scène s'assurera que la physique est supprimée une fois que le joueur quitte la scène. Les auditeurs de la scène appellent le createScene, enterScene, et destroyScene respectivement.

 scène de fonction: destroyScene (événement) groupe local = self.view package.loaded [physics] = nil physics = nil scène de fin: addEventListener ("createScene", scène) scène: addEventListener ("enterScene", scène) scène: addEventListener (" destroyScene ", scène) retour scène

Conclusion

Toutes nos félicitations! Vous avez appris beaucoup de choses telles que le storyboard, la physique, les collisions et bien plus encore de Corona! Ce sont des compétences précieuses qui peuvent être appliquées à presque tous les jeux. Si vous souhaitez créer ce jeu pour votre appareil, je vous recommande fortement les documents officiels de Corona sur la création d'un appareil..

Merci beaucoup d'avoir lu! Si vous avez des questions, s'il vous plaît laissez-les dans les commentaires ci-dessous.