Site de support du club Création de jeux vidéo.
2024-2025
import SwiftUI
struct GameView : View {
@ObservedObject var viewModel: GameViewModel
@State private var grid = Array(repeating: Array(repeating: false, count: 3), count: 3)
@State private var score = 0
@State private var timer:Timer? = nil
@State private var time = 30
@State private var gameTimer:Timer? = nil
@State private var gameActive = false
@State private var activeTile: (row: Int, col: Int)? = nil
var body: some View {
VStack {
HStack {
Text("Score : \(score)")
.font(.title)
.padding()
Text("\(time)⏱️")
.font(.title)
.padding()
}
gridView
.padding()
Button(gameActive ? "STOP" : "START") {
if(gameActive) {
stopGame()
} else {
startGame()
}
}
.padding()
.background(gameActive ? .red : Color.blue)
.foregroundColor(Color.white)
.cornerRadius(10)
.font(.title)
}
}
var gridView : some View {
VStack() {
ForEach(0..<3) { row in
HStack(){
ForEach(0..<3) { col in
tileView(row:row, col:col)
}
}
}
}
}
func tileView(row: Int, col: Int) -> some View {
RoundedRectangle(cornerSize: CGSize(width: 20, height: 20))
.foregroundColor(grid[row][col] ? Color.yellow : Color.blue)
.frame(width: 80, height: 80)
.onTapGesture {
tapTile(row:row, col:col)
}
}
func tapTile(row:Int, col:Int) {
guard gameActive else { return }
if grid[row][col] {
score += 1
grid[row][col] = false
activeTile = nil
}
}
func startGame() {
gameActive = true
gameTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
time = max(0, time-1)
if time == 0 {
stopGame()
}
}
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
lightRandomTile()
}
reset()
}
func lightRandomTile() {
// desactiver l'ancienne tuile
if let active = activeTile {
grid[active.row][active.col] = false
score = max(0, score - 1)
activeTile = nil
}
// Choisi une tile aleatoirement
let col = Int.random(in: 0..<3)
let row = Int.random(in: 0..<3)
grid[row][col] = true
activeTile = (row:row, col:col)
}
func stopGame() {
gameActive = false
timer?.invalidate()
timer = nil
gameTimer?.invalidate()
gameTimer = nil
}
func reset() {
score = 0
}
func swichOffAllTiles() {
// Eteint les cases
for row in 0..<3 {
for col in 0..<3 {
grid[row][col] = false
}
}
}
}
Langage du code : Swift (swift)
2023-2025
Copier le code source (iOS 17.0+)
Créer une nouvelle App sur Swift Playgrounds. Remplacez le code existant en copiant/collant les codes suivants.
Modèle vide
Voici un modèle (template) vide pour vous lancer dans votre propre jeu. Il permet de lancer une vue SpriteKit avec SwiftUI.
Commencez par remplacer le contenu des fichiers MyApp.swift et ContentView.swift par ceux disponible ci-dessous
MyApp.swift
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Langage du code : Swift (swift)ContentView.swift
import SwiftUI
import SpriteKit
struct ContentView: View {
var body: some View {
SpriteView(scene: GameScene(), transition: SKTransition.crossFade(withDuration: 0.5), isPaused: false, preferredFramesPerSecond: 60)
}
}
Langage du code : Swift (swift)Enfin ajoutez un fichier Swift GameScene contenant cette base de jeu prête à gérer une caméra et la physique de base.
GameScene.swift
import SwiftUI
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var mainCamera = SKCameraNode()
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
scene?.size = CGSize(width: 600, height: 800)
scene?.scaleMode = SKSceneScaleMode.aspectFill
scene?.camera = mainCamera
}
}
Langage du code : Swift (swift)Suika Game
GameScene.swift
import SwiftUI
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var mainCamera = SKCameraNode()
var nextFruitType: FruitType!
var nextFruit: SKNode!
var currentFruitType: FruitType!
var currentFruit: SKNode!
var lastPos:CGPoint!
var score = 0
var scoreLbl: SKLabelNode!
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
scene?.size = CGSize(width: 600, height: 800)
scene?.scaleMode = SKSceneScaleMode.aspectFill
scene?.camera = mainCamera
addChild(generateTank())
lastPos = CGPoint(x: 0, y: tankHeight / 2 + 20)
nextFruitType = FruitType.availableFruits.randomElement()
setCurrentFruit()
addChild(createUI())
}
func createUI() -> SKLabelNode {
scoreLbl = SKLabelNode()
scoreLbl.position = CGPoint(x: 0, y: 350)
scoreLbl.fontName = ""
scoreLbl.color = UIColor.white
scoreLbl.text = String(score)
return scoreLbl
}
func setCurrentFruit() {
currentFruitType = nextFruitType
currentFruit = makeFruit(fruitType: currentFruitType, hasGravity: false)
currentFruit.position = lastPos
addChild(currentFruit)
createNextFruit()
}
func createNextFruit() {
nextFruitType = FruitType.availableFruits.randomElement()
nextFruit?.removeFromParent()
nextFruit = makeFruit(fruitType: nextFruitType, hasGravity: false)
nextFruit.position = CGPoint(x: 125, y: 350)
addChild(nextFruit)
}
var isReleased = false
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isReleased) {
return
}
let touch = touches.first
if var pos = touch?.location(in: self) {
pos.y = lastPos.y
if(pos.x > CGFloat(tankWidth) / 2.5) {
pos.x = CGFloat(tankWidth) / 2.5
}
else if(pos.x < -CGFloat(tankWidth) / 2.5) {
pos.x = -CGFloat(tankWidth) / 2.5
}
currentFruit.position = pos
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isReleased) {
return
}
isReleased = true
let touch = touches.first
if var pos = touch?.location(in: self) {
pos.y = lastPos.y
if(pos.x > CGFloat(tankWidth) / 2.5) {
pos.x = CGFloat(tankWidth) / 2.5
}
else if(pos.x < -CGFloat(tankWidth) / 2.5) {
pos.x = -CGFloat(tankWidth) / 2.5
}
lastPos.x = pos.x
currentFruit.position = pos
currentFruit.physicsBody?.affectedByGravity = true
}
}
//physics
func didBegin(_ contact: SKPhysicsContact) {
let nodeA = contact.bodyA.node
let nodeB = contact.bodyB.node
if(nodeA == currentFruit || nodeB == currentFruit) {
setCurrentFruit()
isReleased = false
}
if(nodeA?.name == "fruit" && nodeB?.name == "fruit") {
let fruitA: FruitType = nodeA?.userData!["fruitType"] as! FruitType
let fruitB: FruitType = nodeB?.userData!["fruitType"] as! FruitType
if( fruitA == fruitB) {
score += 2 * (fruitA.rawValue + 1)
scoreLbl.text = String(score)
nodeA?.removeFromParent()
nodeB?.removeFromParent()
if let nextFruitType = FruitType(rawValue: fruitA.rawValue+1) {
let fruit = makeFruit(fruitType: nextFruitType)
fruit.position = contact.contactPoint
addChild(fruit)
}
}
}
}
}
Langage du code : Swift (swift)FruitGenerator.swift
Cette fonction s’occupe de générer des fruits à la bonne taille et de la bonne couleur
import SwiftUI
import SpriteKit
enum FruitType: Int, CaseIterable {
case cerise,
fraise,
grenade,
orange,
pomme,
citron,
peche,
ananas,
melon,
pasteque
static let availableFruits = [cerise, fraise, grenade, orange]
}
func makeFruit(fruitType: FruitType, hasGravity: Bool = true)->SKNode {
var radius = 10.0
var imageName: String = "cerise"
switch fruitType {
case .cerise:
radius = 10.0
imageName = "cerise"
case .fraise:
radius = 15.0
imageName = "cerise"
case .grenade:
radius = 20.0
imageName = "cerise"
case .orange:
radius = 25.0
imageName = "cerise"
case .pomme:
radius = 30.0
imageName = "cerise"
case .citron:
radius = 35.0
imageName = "cerise"
case .peche:
radius = 40.0
imageName = "cerise"
case .ananas:
radius = 60.0
imageName = "cerise"
case .melon:
radius = 70.0
imageName = "cerise"
case .pasteque:
radius = 80.0
imageName = "cerise"
}
let node = SKSpriteNode(imageNamed: imageName)
node.name = "fruit"
node.size = CGSize(width: radius*2, height: radius*2)
node.userData = NSMutableDictionary()
node.userData?.setValue(fruitType, forKey: "fruitType")
node.physicsBody = SKPhysicsBody(circleOfRadius: radius)
node.physicsBody?.affectedByGravity = hasGravity
node.physicsBody?.contactTestBitMask = 0x1
return node
}
Langage du code : Swift (swift)TankGenerator.swift
Cette fonction génère le réservoir à fruit avec la physique qui va bien.
import SwiftUI
import SpriteKit
let tankWidth = 300
let tankHeight = 400
func generateTank()->SKNode {
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: -tankHeight))
path.addLine(to: CGPoint(x: tankWidth, y: -tankHeight))
path.addLine(to: CGPoint(x: tankWidth,y: 0))
let node = SKShapeNode(path: path, centered: true)
node.lineWidth = 10.0
node.name = "tank"
node.physicsBody = SKPhysicsBody(edgeChainFrom: node.path!)
return node
}
Langage du code : Swift (swift)MyApp.swift
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Langage du code : Swift (swift)ContentView.swift
import SwiftUI
import SpriteKit
struct ContentView: View {
var body: some View {
SpriteView(scene: GameScene(), transition: SKTransition.crossFade(withDuration: 0.5), isPaused: false, preferredFramesPerSecond: 60)
}
}
Langage du code : Swift (swift)Flappy Bird
Télécharger les images
Télécharger les images de Flappy Birds
Remplacez le code existant par celui-ci
import SwiftUI
@main // Indique que c'est le fichier principal qui sera lancé en premier
struct MyApp: App { // Définition du contenu de l'App
var body: some Scene { // Corps de l'application
WindowGroup {
ContentView() // Charge la vue définie dans le fichier ContentView
}
}
}Langage du code : JavaScript (javascript)ContentView
Remplacez le code existant par celui-ci
import SwiftUI // Paquet gérant l'interface
import SpriteKit // Paquet gérant le moteur de jeu 2D
struct ContentView: View { // Vue chargéé dans l'application
var scene: SKScene { // Scene contenant le jeu
let scene = GameScene() // Scene définie dans le fichier GameScene
scene.size = CGSize(width: 600, height: 600) // On fixe la taille de la scène en 600px de large par 600px de long
return scene
}
var body: some View {
Text("Mon jeu") // Élément d'interface pour afficher du text
.font(.largeTitle) // On utilise le style largeTitle pour agrandir le texte.
SpriteView(scene: scene) // Élément d'interface permettant d'afficher une scène SpriteKit
.scaledToFit() // Agrandi la vue pour qu'elle touche un bord sans déformer le contenu
}
}Langage du code : JavaScript (javascript)GameScene
Le fichier GameScene n’existe pas. Il faut le créer en appuyant sur l’icône
> Fichier Swift
import SwiftUI
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate { // Scene SpriteKit
var score = 0
var scoreLabel: SKLabelNode!
var highScore = 0
var highScoreLabel: SKLabelNode!
struct PhysicsCategory {
static let none : UInt32 = 0
static let all : UInt32 = UInt32.max
static let player : UInt32 = 0b0001
static let pipe : UInt32 = 0b0010
static let ground : UInt32 = 0b0100
static let gate : UInt32 = 0b1000
}
// Gére le début d'un contact
func didBegin(_ contact: SKPhysicsContact) {
if(contact.bodyA.node?.name == "player" && contact.bodyB.node?.name == "pipe") {
// Nous allons faire recommancer le jeu ici
startGame()
}
}
// Gére la fin d'un contact
func didEnd(_ contact: SKPhysicsContact) {
if(contact.bodyA.node?.name == "player" && contact.bodyB.node?.name == "gate") {
// Nous allons faire recommancer le jeu ici
score = score + 1
scoreLabel?.text = "\(score)"
if(score > highScore) {
highScore = score
UserDefaults.standard.setValue(highScore, forKey: "highScore")
UserDefaults.standard.synchronize()
highScoreLabel?.text = "\(highScore)"
}
}
}
// Redémarre le jeu
func startGame() {
let scene = GameScene()
scene.size = CGSize(width: 600, height: 600)
view?.presentScene(scene, transition: SKTransition.fade(withDuration: 0.5))
}
// Fonction qui sert à créer le joueur
func createPlayer()->SKSpriteNode { // Elle renvoie un Noeud qu'on peut ajouter par la suite dans la scène
let flappyFlyTexture = [SKTexture(imageNamed: "flappy_red01"),
SKTexture(imageNamed: "flappy_red02"),
SKTexture(imageNamed: "flappy_red03")]
for texture in flappyFlyTexture {
texture.filteringMode = SKTextureFilteringMode.nearest
}
let player = SKSpriteNode(imageNamed: "flappy_red01")
let flappyAnim = SKAction.animate(with: flappyFlyTexture, timePerFrame: 0.1)
player.run(SKAction.repeatForever(flappyAnim))
player.texture?.filteringMode = SKTextureFilteringMode.nearest
player.size = CGSize(width: 60, height: 50)
player.position = CGPoint(x: 100, y: 300) // position permet de déplacer le noeud sur la scène. Ici nous le centrons
player.name = "player" // name permet de récupérer le noeud grace à son nom avec la méthode childNode(withName: "player") par exemple
player.physicsBody = SKPhysicsBody(rectangleOf: player.size) // PhysicBody est la représentation du noeud dans le moteur physique. Ici c'est un rectangle de la taille du player
player.physicsBody?.categoryBitMask = PhysicsCategory.player
player.physicsBody?.collisionBitMask = PhysicsCategory.ground
return player;
}
func createBG()->SKSpriteNode {
let bg = SKSpriteNode(imageNamed: "background_night")
bg.size = CGSize(width: 600, height: 600)
bg.position = CGPoint(x: 300, y: 300)
return bg
}
// Fonction qui permet de créer des ronds de rayon radius
func createRond(radius:Double)->SKShapeNode {
let rond = SKShapeNode(circleOfRadius: radius) // Constante qui contient un noeud de forme. Ici un cercle.
rond.strokeColor = .white // Couleur du contour
rond.fillColor = .red // Couleur du remplissage
rond.physicsBody = SKPhysicsBody(circleOfRadius: radius) // Corps physique en forme de cercle cette fois-ci
rond.physicsBody?.isDynamic = false // Si c'est faux, le corps n'est pas déplacé par le moteur physique
rond.position = CGPoint(x: 600, y: 400)
return rond
}
func createUI() {
scoreLabel = SKLabelNode(text: "0")
scoreLabel.name = "score"
scoreLabel.position = CGPoint(x: 300, y: 550)
scoreLabel.fontSize = 50
scoreLabel.fontName = ""
addChild(scoreLabel)
highScoreLabel = SKLabelNode(text: "0")
highScoreLabel.name = "hightscore"
highScoreLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.right
highScoreLabel.position = CGPoint(x: 590, y: 570)
highScoreLabel.fontSize = 30
highScoreLabel.fontName = ""
highScoreLabel.zPosition = 200
addChild(highScoreLabel)
highScoreLabel?.text = "\(highScore)"
}
func createGround()->SKSpriteNode {
// Création d'un sol pour que le joueur repose dessus
let ground = SKSpriteNode(imageNamed: "ground")
ground.texture?.filteringMode = SKTextureFilteringMode.nearest
ground.size = CGSize(width: 600, height: 100)
ground.physicsBody = SKPhysicsBody(rectangleOf: ground.size)
ground.physicsBody?.isDynamic = false;
ground.position = CGPoint(x: 300, y: 0)
return ground
}
func moveGround() {
let parent = SKNode()
let ground = createGround()
ground.position = CGPoint(x: 300, y: 0)
let ground2 = createGround()
ground2.position = CGPoint(x: 900, y: 0)
parent.addChild(ground)
parent.addChild(ground2)
let sequence = SKAction.sequence([SKAction.moveTo(x: -600, duration: 3),
SKAction.removeFromParent()])
parent.run(sequence)
addChild(parent)
}
override func update(_ currentTime: TimeInterval) {
scoreLabel?.zPosition = 100
}
// Fonction spéciale de SKScene appelée une seule fois indiquant que la vue a fini de s'afficher pour la premier fois
override func didMove(to view: SKView) {
highScore = UserDefaults.standard.integer(forKey: "highScore")
physicsWorld.contactDelegate = self
// une scène est également un noeud qui contient lui aussi un physicsBody
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame) // Nous créons un edgeLoop qui permet de créer une sorte de mur sur le contour de la scène empêchant les objets d'en sortir
addChild(createBG())
let player = createPlayer() // Nous créons le joueur
addChild(player) // que nous ajoutons à la scène pour qu'il s'affiche et intéragisse avec le moteur physique
player.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 50))
let rond = createRond(radius:50)// Nous créons un cercle de diamètre 50
//addChild(rond)
// Création des actions pour animer le cercle de gauche à droite
let action = SKAction.moveTo(x: 0, duration: 2) // Cette action envoie un noeud jusq'à sa position x = 0
let action2 = SKAction.moveTo(x:600, duration: 2) // Cette action envoie un noeud jusqu'à sa position x = 600
// Une séquence permet d'appeler des actions les unes après les autres
// Notez l'utilisation des crochets [a,b,c] qui permettent de définir un tableau. Ici le tableau contient les éléments a, b et c. Les éléments sont séparés par des virgules.
let sequence = SKAction.sequence([action, action2])
// Pour lancer les actions on utilise la méthode run() d'un noeud.
rond.run(SKAction.repeatForever(sequence)) // la méthode repeatForever, comme son nom l'indique permet de réperter des actions à l'infini.
// Fonction qui appelle notre créateur d'obstacles à l'infini.
run(SKAction.repeatForever(SKAction.sequence([
SKAction.run(createObstacle),
SKAction.wait(forDuration: 2)
])))
run(SKAction.repeatForever(SKAction.sequence([
SKAction.run(moveGround),
SKAction.wait(forDuration: 3)
])))
createUI()
}
// Fonction spécial de UIKit qui permet de récupérer les intéractions entre les doigts et l'écran de l'iPad ou iPhone
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//let touch = touches.first // Voici comment récupérer le premier touché sur l'écran
if let player = self.childNode(withName: "player") { // Nous cherchons le noeud contenant le nom player
player.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 100)) // Nous appliquons une impulsion vers le haut au joueur pour le faire sauter.
}
//addChild(CreatePlayer())
}
// Fonction qui crée des obstable
func createObstacle() {
let ecart = 250
let obstacleBottom = SKSpriteNode(imageNamed: "pipe_top")
obstacleBottom.size = CGSize(width: 100, height: 300)
obstacleBottom.position = CGPoint(x: 0, y: ecart)
obstacleBottom.name = "pipe"
obstacleBottom.physicsBody = SKPhysicsBody(rectangleOf: obstacleBottom.size)
obstacleBottom.physicsBody?.isDynamic = false
obstacleBottom.physicsBody?.categoryBitMask = PhysicsCategory.pipe
obstacleBottom.physicsBody?.contactTestBitMask = PhysicsCategory.player
let obstacleTop = SKSpriteNode(imageNamed: "pipe_bottom")
obstacleTop.size = CGSize(width: 100, height: 300)
obstacleTop.position = CGPoint(x: 0, y: -ecart)
obstacleTop.name = "pipe"
obstacleTop.physicsBody = SKPhysicsBody(rectangleOf: obstacleBottom.size)
obstacleTop.physicsBody?.isDynamic = false
obstacleTop.physicsBody?.categoryBitMask = PhysicsCategory.pipe
obstacleTop.physicsBody?.contactTestBitMask = PhysicsCategory.player
let gate = SKSpriteNode(color: .white, size: CGSize(width: 50, height: (ecart - 300 / 2) * 2)) // Taille aléatoire en hauteur entre 20 et 200 unités
gate.name = "gate"
gate.physicsBody = SKPhysicsBody(rectangleOf: gate.size)
gate.physicsBody?.isDynamic = false
gate.physicsBody?.collisionBitMask = PhysicsCategory.none
gate.physicsBody?.categoryBitMask = PhysicsCategory.gate
gate.physicsBody?.contactTestBitMask = PhysicsCategory.player
gate.alpha = 0
// Créer l'animation de droite vers la gauche grâce aux actions
let bougeVersLaGauche = SKAction.moveTo(x: -50, duration: 3)
let supprimer = SKAction.removeFromParent()
let sequence = SKAction.sequence([bougeVersLaGauche, supprimer])
let parent = SKNode()
parent.position = CGPoint(x: 650, y: Double.random(in: 200...400))
parent.addChild(obstacleBottom)
parent.addChild(obstacleTop)
parent.addChild(gate)
parent.run(sequence)
addChild(parent)
}
}
Langage du code : HTML, XML (xml)Installer le playground (iOS 17.1)
Votre iPad doit être sur la version iOS 17.1 minimum. Sinon ça ne fonctionnera pas.
- Scannez le QR avec l’iPad pour afficher cette page

3. Regardez la vidéo pour ajouter le Playground dans Swift Playgrounds


