Writing Multiple Scripts

    This tutorial builds off the Simple Runner game introduced in Your First Extension and will introduce you to working with multiple scripts and interacting with Godot’s existing signals to teleport the player, creating an infinite runner.

    Writing Multiple Scripts.tutorial
    A screenshot of the Simple Runner game. The player wears a bird costume and runs alongside boxes.

    Handle vertical movement

    The player currently only moves along the horizontal axis, but remains in place vertically. Let’s adjust the player code to include vertical motion.

    1. In PlayerController.swift, find the movementVector computed property and update its y value to include a constant speed of one.

      PlayerController.swift
      //
      //  PlayerController.swift
      //
      //
      //  Created by Marquis Kurt on 7/19/23.
      //
      
      import SwiftGodot
      
      @Godot
      class PlayerController: CharacterBody2D {
          var acceleration: Float = 100
          var friction: Double = 100
          var speed: Double = 200
      
          var movementVector: Vector2 {
              var movement = Vector2.zero
              movement.x = Float(
                  Input.getActionStrength(action: "move_right") - Input.getActionStrength(action: "move_left"))
              movement.y = 1.0
              return movement.normalized()
          }
      
          override func _physicsProcess(delta: Double) {
              if Engine.isEditorHint() { return }
              if movementVector != .zero {
                  let acceleratedVector = Vector2(x: acceleration, y: acceleration)
                  let acceleratedMovement = movementVector * acceleratedVector
                  self.velocity = acceleratedMovement.limitLength(speed)
              } else {
                  velocity = velocity.moveToward(to: .zero, delta: friction)
              }
              self.moveAndSlide()
              super._physicsProcess(delta: delta)
          }
      }
      PlayerController-multiscript-2.swift
    2. In the terminal, navigate to the SimpleRunnerDriver package and run swift build to build the package.

      A terminal window at the SwiftRunnerDriver package, about to run swift build.
    3. Open the .build/{arch}/debug directory in the Finder using the open command, replacing arch with your Mac’s architecture folder.

      A terminal window at the SwiftRunnerDriver package, about to run open.
    4. Copy the libSimpleRunnerDriver.dylib file into the bin directory of the SimpleRunner Godot project.

      Two Finder windows that show the dynamic library files being copied to the game's files.
    5. Open the project in the Godot editor and open the main.tscn scene file.

      The Godot editor open with the main SimpleRunnerProject.
    6. Run the project by pressing the Play button in the toolbar. The player should now be moving down the screen, past the edges of the level.

      The SimpleRunner project running, with the player moving down the screen.

    Teleport the player

    Now that the player can run down the screen, let’s allow the player to teleport to the top of the level when they reach the bottom.

    To do so, you will create a new node class that represents the main level logic and detect when the player has reached the bottom and teleport them to the top.

    1. Create a new Swift file called MainLevel in the SimpleRunnerDriver package and import the SwiftGodot library.

      MainLevel.swift
      //
      //  MainLevel.swift
      //  
      //
      //  Created by Marquis Kurt on 7/22/23.
      //
      
      import SwiftGodot
      MainLevel-multiscript-init.swift
    2. Write a new MainLevel class that inherits from Node2D and has the @Godot attribute

      MainLevel.swift
      //
      //  MainLevel.swift
      //  
      //
      //  Created by Marquis Kurt on 7/22/23.
      //
      
      import SwiftGodot
      
      @Godot
      class MainLevel: Node2D {
      }
      MainLevel-multiscript-2.swift
    3. Add the player, spawnpoint, and teleportArea properties to MainLevel, using the @SceneTree macro to list their names in the scene tree.

      Note that these properties are marked optional, as they may not exist in the scene tree.

      MainLevel.swift
      //
      //  MainLevel.swift
      //  
      //
      //  Created by Marquis Kurt on 7/22/23.
      //
      
      import SwiftGodot
      
      @Godot
      class MainLevel: Node2D {
          @SceneTree(path: "CharacterBody2D") var player: PlayerController?
          @SceneTree(path: "Spawnpoint") var spawnpoint: Node2D?
          @SceneTree(path: "Telepoint") var teleportArea: Area2D?
      
      }
      MainLevel-multiscript-3.swift
      A screenshot of the scene tree for the main level in Godot.
    4. Create a new teleportPlayerToTop() method and add a guard statement to check that the player and spawn point exist.

      GD.pushWarning(_:) allows us to send warning messages to Godot’s console.

      MainLevel.swift
      //
      //  MainLevel.swift
      //  
      //
      //  Created by Marquis Kurt on 7/22/23.
      //
      
      import SwiftGodot
      
      @Godot
      class MainLevel: Node2D {
          @SceneTree(path: "CharacterBody2D") var player: PlayerController?
          @SceneTree(path: "Spawnpoint") var spawnpoint: Node2D?
          @SceneTree(path: "Telepoint") var teleportArea: Area2D?
      
          private func teleportPlayerToTop() {
              guard let player, let spawnpoint else {
                  GD.pushWarning("Player or spawnpoint is missing.")
                  return
              }
              
          }
      }
      MainLevel-multiscript-4.swift
    5. Set the player’s position so that their horizontal coordinate is kept, but the vertical coordinate is that of the spawn point.

      This will effectively teleport the player to the top of the map, preserving where the player was on the horizontal axis.

      MainLevel.swift
      //
      //  MainLevel.swift
      //  
      //
      //  Created by Marquis Kurt on 7/22/23.
      //
      
      import SwiftGodot
      
      @Godot
      class MainLevel: Node2D {
          @SceneTree(path: "CharacterBody2D") var player: PlayerController?
          @SceneTree(path: "Spawnpoint") var spawnpoint: Node2D?
          @SceneTree(path: "Telepoint") var teleportArea: Area2D?
      
          private func teleportPlayerToTop() {
              guard let player, let spawnpoint else {
                  GD.pushWarning("Player or spawnpoint is missing.")
                  return
              }
      
              player.position = Vector2(x: player.position.x, y: spawnpoint.position.y)
          }
      }
      MainLevel-multiscript-5.swift
    6. In the _ready() method, connect the teleport area’s bodyEntered signal method to check if the player has entered the area and teleport them when necessary.

      MainLevel.swift
      //
      //  MainLevel.swift
      //  
      //
      //  Created by Marquis Kurt on 7/22/23.
      //
      
      import SwiftGodot
      
      @Godot
      class MainLevel: Node2D {
          @SceneTree(path: "CharacterBody2D") var player: PlayerController?
          @SceneTree(path: "Spawnpoint") var spawnpoint: Node2D?
          @SceneTree(path: "Telepoint") var teleportArea: Area2D?
      
          override func _ready() {
              teleportArea?.bodyEntered.connect { [self] enteredBody in
                  if enteredBody.isClass("\(PlayerController.self)") {
                      teleportPlayerToTop()
                  }
              }
      
              super._ready()
          }
      
          private func teleportPlayerToTop() {
              guard let player, let spawnpoint else {
                  GD.pushWarning("Player or spawnpoint is missing.")
                  return
              }
      
              player.position = Vector2(x: player.position.x, y: spawnpoint.position.y)
          }
      }
      MainLevel-multiscript-6.swift
    7. Register the class type in SimpleRunnerDriver.swift to expose it to Godot.

      SimpleRunnerDriver.swift
      // The Swift Programming Language
      // https://docs.swift.org/swift-book
      
      import SwiftGodot
      
      let allNodes: [Wrapped.Type] = [PlayerController.self, MainLevel.self]
      
      #initSwiftExtension(cdecl: "swift_entry_point", types: allNodes)
      SimpleRunnerDriver-multiscript-2.swift

    Using the new main level node

    Now that the main level code has been added, let’s replace the current main level node with its new instance provided by your extension.

    1. Rebuild the extension and copy over libSimpleRunnerDriver.dylib as before.

      A terminal window at the SwiftRunnerDriver package, about to run swift build.
    2. Open the project in the Godot editor and open the main.tscn scene file.

      The Godot editor open with the main SimpleRunnerProject.
    3. Right click on the Main Level node and select Change Type.

      The Godot editor with the context menu for Main Level open, highlighting Change Type.
    4. Search for “MainLevel” and set the node’s type to the new MainLevel node you created.

      If the node doesn’t appear in the search results, retry the steps for registering the class, rebuilding the extension, and re-opening Godot.

      The Godot editor with the context menu for Main Level open, highlighting Change Type.
    5. Run the project by pressing the Play button in the toolbar. The player should now be moving down the screen, teleporting to the top of the screen once the player reaches the bottom. 🎉

      The SimpleRunner project running, with the player moving down the screen and teleporting.