Skip to content

Instantly share code, notes, and snippets.

@pmark
Last active March 6, 2023 00:14
Show Gist options
  • Select an option

  • Save pmark/15da413c72aa000a2680bf43f06731a7 to your computer and use it in GitHub Desktop.

Select an option

Save pmark/15da413c72aa000a2680bf43f06731a7 to your computer and use it in GitHub Desktop.

Revisions

  1. pmark revised this gist Mar 6, 2023. 1 changed file with 72 additions and 0 deletions.
    72 changes: 72 additions & 0 deletions MonsterAIComponent.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@
    import OctopusKit

    // Define the available AI behaviors
    enum AIBehavior {
    case none
    case seek(target: Entity)
    case flee(target: Entity)
    case wander
    case followPath(path: GKPath)
    }

    // Define the monster AI component
    class MonsterAIComponent: OKComponent {

    var behaviorType: AIBehavior = .none {
    didSet {
    // Remove any existing behavior components
    entity?.removeComponents(ofType: BehaviorComponent.self)

    // Add the appropriate behavior component based on the behavior type
    switch behaviorType {
    case .none:
    break
    case .seek(let target):
    entity?.addComponent(BehaviorComponent(behavior: GKSeekBehavior(target: target.node)))
    case .flee(let target):
    entity?.addComponent(BehaviorComponent(behavior: GKFleeBehavior(target: target.node)))
    case .wander:
    entity?.addComponent(BehaviorComponent(behavior: GKRandomWalkBehavior()))
    case .followPath(let path):
    entity?.addComponent(BehaviorComponent(behavior: GKFollowPathBehavior(path: path, maxPredictionTime: 1.0, forward: true)))
    }
    }
    }

    override func update(deltaTime seconds: TimeInterval) {
    // Update any existing behavior components
    entity?.updateBehaviorComponents(deltaTime: seconds)
    }
    }



    extension MonsterAIComponent {
    enum AIBehavior {
    case randomMovement
    case followPlayer
    case patrol
    case custom(GKBehavior)
    }

    func setBehavior(_ behaviorType: AIBehavior) {
    if let oldBehavior = entity.component(ofType: MonsterAIComponent.self) {
    entity.removeComponent(oldBehavior)
    }

    switch behaviorType {
    case .randomMovement:
    let behavior = RandomMovementBehaviorComponent()
    entity.addComponent(behavior)
    case .followPlayer:
    let behavior = FollowPlayerBehaviorComponent()
    entity.addComponent(behavior)
    case .patrol:
    let behavior = PatrolBehaviorComponent()
    entity.addComponent(behavior)
    case .custom(let behavior):
    let behaviorComponent = CustomBehaviorComponent(behavior: behavior)
    entity.addComponent(behaviorComponent)
    }
    }
    }
  2. pmark revised this gist Mar 5, 2023. 2 changed files with 65 additions and 0 deletions.
    28 changes: 28 additions & 0 deletions PlayerControlComponent.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,28 @@

    class PlayerControlComponent: OKComponent {

    override var requiredComponents: [AnyClass] {
    return [OKPhysicsBodyComponent.self]
    }

    override func didAddToEntity() {

    guard let entity = entity else { return }

    // Set up a pan gesture recognizer to control the player
    let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:)))
    entity.scene?.view?.addGestureRecognizer(panRecognizer)
    }

    @objc func panGesture(_ recognizer: UIPanGestureRecognizer) {

    guard let physicsBodyComponent = entity?.component(ofType: OKPhysicsBodyComponent.self) else { return }

    // Calculate the velocity based on the pan gesture
    let translation = recognizer.translation(in: recognizer.view)
    let velocity = CGVector(dx: translation.x, dy: translation.y)

    // Apply the velocity to the physics body
    physicsBodyComponent.physicsBody?.velocity = velocity
    }
    }
    37 changes: 37 additions & 0 deletions PlayerMonsterEntities.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,37 @@
    import OctopusKit

    class PlayerEntity: OKEntity {

    override func awake(from system: OKComponentSystem) {

    // Add a physics body to the player
    let physicsBodyComponent = OKPhysicsBodyComponent(circleOfRadius: 32)
    addComponent(physicsBodyComponent)

    // Add a player control system to handle user input
    let playerControlComponent = PlayerControlComponent()
    addComponent(playerControlComponent)

    // Add a sprite component to display the player
    let spriteComponent = OKSpriteComponent(texture: SKTextureAtlas.spriteNode(named: "player", in: "game").texture)
    addComponent(spriteComponent)
    }
    }

    class MonsterEntity: OKEntity {

    override func awake(from system: OKComponentSystem) {

    // Add a physics body to the monster
    let physicsBodyComponent = OKPhysicsBodyComponent(circleOfRadius: 32)
    addComponent(physicsBodyComponent)

    // Add a monster AI system to handle behavior
    let monsterAIComponent = MonsterAIComponent()
    addComponent(monsterAIComponent)

    // Add a sprite component to display the monster
    let spriteComponent = OKSpriteComponent(texture: SKTextureAtlas.spriteNode(named: "monster", in: "game").texture)
    addComponent(spriteComponent)
    }
    }
  3. pmark created this gist Mar 5, 2023.
    46 changes: 46 additions & 0 deletions Level.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    import Foundation
    import SpriteKit

    class Level {
    let tileSet: SKTileSet
    let mapSize: CGSize
    let tileSize: CGSize
    let tileData: [[Int]]

    init(jsonData: Data) throws {
    let json = try JSONSerialization.jsonObject(with: jsonData, options: [])

    guard let jsonDict = json as? [String: Any],
    let tileSetName = jsonDict["tileset"] as? String,
    let tileSet = SKTileSet(named: tileSetName),
    let mapSizeDict = jsonDict["mapSize"] as? [String: CGFloat],
    let mapWidth = mapSizeDict["width"],
    let mapHeight = mapSizeDict["height"],
    let tileSizeDict = jsonDict["tileSize"] as? [String: CGFloat],
    let tileWidth = tileSizeDict["width"],
    let tileHeight = tileSizeDict["height"],
    let tileDataArray = jsonDict["tileData"] as? [[Int]]
    else {
    throw NSError(domain: "com.example.TileMapDemo", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON data"])
    }

    self.tileSet = tileSet
    self.mapSize = CGSize(width: mapWidth, height: mapHeight)
    self.tileSize = CGSize(width: tileWidth, height: tileHeight)
    self.tileData = tileDataArray
    }

    func createTileMapNode() -> SKTileMapNode {
    let tileMapNode = SKTileMapNode(tileSet: tileSet, columns: Int(mapSize.width), rows: Int(mapSize.height), tileSize: tileSize)

    for row in 0..<Int(mapSize.height) {
    for col in 0..<Int(mapSize.width) {
    let tileGID = tileData[row][col]
    let tileDefinition = tileSet.tileDefinition(forGID: UInt32(tileGID))
    tileMapNode.setTileGroup(tileDefinition?.tileGroup, forColumn: col, row: row)
    }
    }

    return tileMapNode
    }
    }
    9 changes: 9 additions & 0 deletions SKTextureAtlas+extensions.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    extension SKTextureAtlas {
    static func spriteNode(named name: String, in atlasName: String) -> SKSpriteNode {
    let atlas = SKTextureAtlas(named: atlasName)
    let texture = atlas.textureNamed(name)
    return SKSpriteNode(texture: texture)
    }
    }

    let sprite = SKTextureAtlas.spriteNode(named: "MySpriteImage", in: "MySprites")
    38 changes: 38 additions & 0 deletions TileMapGameScene.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,38 @@
    import OctopusKit
    import SpriteKit

    class GameScene: OKScene {

    override func didMove(to view: SKView) {
    // Load tile map
    let tileMap = SKTileMapNode()
    addChild(tileMap)

    // Create character entity
    let character = OKEntity()
    character.addComponent(OKSpriteNodeComponent(node: SKSpriteNode(imageNamed: "character")))
    character.addComponent(OKPhysicsBodyComponent(physicsBody: SKPhysicsBody(rectangleOf: character.spriteNode.size)))
    character.addComponent(OKControlledPathComponent())
    entityManager.add(character)

    // Create camera entity
    let camera = OKEntity()
    camera.addComponent(OKCameraComponent(focusEntity: character))
    camera.addComponent(OKTopDownCameraComponent())
    entityManager.add(camera)

    // Add virtual joystick
    let joystick = OKJoystickControlComponent()
    joystick.position = CGPoint(x: size.width - joystick.frame.width / 2 - 10, y: joystick.frame.height / 2 + 10)
    joystick.style = .circular
    joystick.movement = .full360
    joystick.velocityMultiplier = 5.0
    joystick.addHandler(for: .inputEvent) { inputEvent in
    if let joystickEvent = inputEvent as? OKJoystickEvent {
    character.component(ofType: OKControlledPathComponent.self)?.velocity = joystickEvent.velocity
    }
    }
    addChild(joystick.node)
    }

    }
    41 changes: 41 additions & 0 deletions TileSetLoader.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    import Foundation
    import SpriteKit

    class TileSetLoader {
    static func loadTileSet(from jsonData: Data) throws -> SKTileSet {
    let json = try JSONSerialization.jsonObject(with: jsonData, options: [])

    guard let jsonDict = json as? [String: Any],
    let tileDataArray = jsonDict["tiles"] as? [[String: Any]]
    else {
    throw NSError(domain: "com.example.TileMapDemo", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON data"])
    }

    let tileDefinitions = try tileDataArray.map { tileData in
    try createTileDefinition(from: tileData)
    }

    let tileSet = SKTileSet(tileGroups: [], tileSetType: .grid)

    for definition in tileDefinitions {
    tileSet.add(definition)
    }

    return tileSet
    }

    private static func createTileDefinition(from tileData: [String: Any]) throws -> SKTileDefinition {
    guard let id = tileData["id"] as? Int,
    let textureName = tileData["texture"] as? String,
    let texture = SKTexture(imageNamed: textureName)
    else {
    throw NSError(domain: "com.example.TileMapDemo", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid tile data"])
    }

    let tileDefinition = SKTileDefinition(texture: texture, size: texture.size())
    tileDefinition.userData = NSMutableDictionary()
    tileDefinition.userData?.setValue(id, forKey: "id")

    return tileDefinition
    }
    }