Last active
March 6, 2023 00:14
-
-
Save pmark/15da413c72aa000a2680bf43f06731a7 to your computer and use it in GitHub Desktop.
OctopusKit 2D game with data driven tile maps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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