Skip to content

Instantly share code, notes, and snippets.

@pmark
Last active March 6, 2023 00:14
Show Gist options
  • Save pmark/15da413c72aa000a2680bf43f06731a7 to your computer and use it in GitHub Desktop.
Save pmark/15da413c72aa000a2680bf43f06731a7 to your computer and use it in GitHub Desktop.
OctopusKit 2D game with data driven tile maps
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
}
}
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)
}
}
}
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
}
}
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)
}
}
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")
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)
}
}
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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment