From 6129a2bf00e77afd4c2c70b26e957e0c05e8a308 Mon Sep 17 00:00:00 2001 From: tobias <thinkdifferent055@gmail.com> Date: Sat, 15 Jul 2017 22:05:06 +0200 Subject: [PATCH] Add minimap --- Pacman3D-iOS.xcodeproj/project.pbxproj | 20 +++++-- Pacman3D-iOS/GameViewController.swift | 69 +++++++++++++++++++++++-- Pacman3D-iOS/Monster.swift | 18 ++++--- Pacman3D-iOS/MotionInput.swift | 2 +- Pacman3D-iOS/pause.png | Bin 0 -> 7188 bytes 5 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 Pacman3D-iOS/pause.png diff --git a/Pacman3D-iOS.xcodeproj/project.pbxproj b/Pacman3D-iOS.xcodeproj/project.pbxproj index eac038b..41a9bbe 100644 --- a/Pacman3D-iOS.xcodeproj/project.pbxproj +++ b/Pacman3D-iOS.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + F6090F531F1AA9270003F688 /* pause.png in Resources */ = {isa = PBXBuildFile; fileRef = F6090F521F1AA9270003F688 /* pause.png */; }; F6621DF41F12B09800F9E486 /* TouchInputHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6621DF31F12B09800F9E486 /* TouchInputHandler.swift */; }; F6621DF61F12B82200F9E486 /* MotionInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6621DF51F12B82200F9E486 /* MotionInput.swift */; }; F6BEB8071EF7F99B00EDAA66 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6BEB8061EF7F99B00EDAA66 /* AppDelegate.swift */; }; @@ -47,6 +48,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + F6090F521F1AA9270003F688 /* pause.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pause.png; sourceTree = "<group>"; }; F6621DF31F12B09800F9E486 /* TouchInputHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchInputHandler.swift; sourceTree = "<group>"; }; F6621DF51F12B82200F9E486 /* MotionInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionInput.swift; sourceTree = "<group>"; }; F6BEB8031EF7F99B00EDAA66 /* Pacman3D-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Pacman3D-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -100,6 +102,18 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + F6090F541F1AA92D0003F688 /* Resources */ = { + isa = PBXGroup; + children = ( + F6FEE71A1EF8135200C1DE35 /* level.plv */, + F6E39CE11F1A56E500A3E7D1 /* wall.jpg */, + F6E39CE31F1A583B00A3E7D1 /* floor.jpg */, + F6090F521F1AA9270003F688 /* pause.png */, + F6E39CE91F1A7C2C00A3E7D1 /* background.jpg */, + ); + name = Resources; + sourceTree = "<group>"; + }; F6BEB7FA1EF7F99B00EDAA66 = { isa = PBXGroup; children = ( @@ -135,10 +149,7 @@ F6BEB80C1EF7F99B00EDAA66 /* Main.storyboard */, F6BEB8111EF7F99B00EDAA66 /* LaunchScreen.storyboard */, F6BEB8141EF7F99B00EDAA66 /* Info.plist */, - F6FEE71A1EF8135200C1DE35 /* level.plv */, - F6E39CE11F1A56E500A3E7D1 /* wall.jpg */, - F6E39CE31F1A583B00A3E7D1 /* floor.jpg */, - F6E39CE91F1A7C2C00A3E7D1 /* background.jpg */, + F6090F541F1AA92D0003F688 /* Resources */, ); path = "Pacman3D-iOS"; sourceTree = "<group>"; @@ -295,6 +306,7 @@ F6BEB8131EF7F99B00EDAA66 /* LaunchScreen.storyboard in Resources */, F6BEB8101EF7F99B00EDAA66 /* Assets.xcassets in Resources */, F6BEB80E1EF7F99B00EDAA66 /* Main.storyboard in Resources */, + F6090F531F1AA9270003F688 /* pause.png in Resources */, F6FEE71B1EF8135200C1DE35 /* level.plv in Resources */, F6E39CEA1F1A7C2C00A3E7D1 /* background.jpg in Resources */, F6E39CE21F1A56E500A3E7D1 /* wall.jpg in Resources */, diff --git a/Pacman3D-iOS/GameViewController.swift b/Pacman3D-iOS/GameViewController.swift index 502f403..dc91c2a 100644 --- a/Pacman3D-iOS/GameViewController.swift +++ b/Pacman3D-iOS/GameViewController.swift @@ -28,14 +28,17 @@ class GameViewController: UIViewController, SKSceneDelegate, SCNPhysicsContactDe var lifeLabel: SKLabelNode! var backButton: SKSpriteNode! + var minimap: [[SKSpriteNode]] = [] + var restartButton: SKLabelNode! // MARK: - Game Objects let monsterCount = 6 + var level: Level! var player: Player! - var monsters: [Monster] = [] + var monsters: [Monster]! // MARK: - Input Handler private var touchInputHandler: TouchInputHandler! @@ -44,7 +47,9 @@ class GameViewController: UIViewController, SKSceneDelegate, SCNPhysicsContactDe override func viewDidLoad() { super.viewDidLoad() - let level = Level(named: "level") + monsters = [] + + level = Level(named: "level") // create a new scene scene = SCNScene(named: "art.scnassets/ship.scn")! @@ -88,6 +93,10 @@ class GameViewController: UIViewController, SKSceneDelegate, SCNPhysicsContactDe } // add a tap gesture recognizer + if touchInputHandler != nil { + scnView.removeGestureRecognizer(touchInputHandler) + } + touchInputHandler = TouchInputHandler(target: self, action: #selector(handleTap(_:))) touchInputHandler.scene = (self.view as? SCNView)?.overlaySKScene touchInputHandler.gameController = self @@ -138,7 +147,6 @@ class GameViewController: UIViewController, SKSceneDelegate, SCNPhysicsContactDe if contact.nodeA.name ?? "" == "Monster" && contact.nodeB.name ?? "" == "Monster" { return } - let pacman = self.scene.rootNode.childNode(withName: "Pacman", recursively: true)! if contact.nodeA.name ?? "" == "Monster" && contact.nodeB.name ?? "" == "Pacman" || contact.nodeB.name ?? "" == "Monster" && contact.nodeA.name ?? "" == "Pacman" { @@ -150,6 +158,15 @@ class GameViewController: UIViewController, SKSceneDelegate, SCNPhysicsContactDe let monster = contact.nodeA.name ?? "" == "Monster" ? contact.nodeA : contact.nodeB monster.removeFromParentNode() + for m in monsters { + if let n = m.node { + if n == monster { + monsters.remove(object: m) + break + } + } + } + if player.life <= 0 { self.scene.isPaused = true restartButton.isHidden = false @@ -181,6 +198,8 @@ class GameViewController: UIViewController, SKSceneDelegate, SCNPhysicsContactDe } lastTime = time } + + updateMinimap() } } @@ -199,7 +218,8 @@ class GameViewController: UIViewController, SKSceneDelegate, SCNPhysicsContactDe lifeLabel.position = CGPoint(x: self.view.frame.width - 50, y: 5) scene.addChild(lifeLabel) - backButton = SKSpriteNode(color: UIColor.purple, size: CGSize(width: 40, height: 40)) + backButton = SKSpriteNode(imageNamed: "pause.png") + backButton.size = CGSize(width: 60, height: 60) backButton.position = CGPoint(x: backButton.size.width / 2, y: backButton.size.height / 2) backButton.name = "Back" scene.addChild(backButton) @@ -213,9 +233,50 @@ class GameViewController: UIViewController, SKSceneDelegate, SCNPhysicsContactDe restartButton.name = "Restart" scene.addChild(restartButton) + let frame = self.view.frame + + minimap = [] + for (x, _) in level.data.enumerated() { + var nodes: [SKSpriteNode] = [] + for (z, _) in level.data[x].enumerated() { + let node = SKSpriteNode(color: UIColor.magenta, size: CGSize(width: 5, height: 5)) + let posX = frame.width - CGFloat(level.data[x].count * 5) + CGFloat(z * 5) + 2.5 + let posZ = frame.height - CGFloat(level.data.count * 5) + CGFloat(x * 5) + 2.5 + node.position = CGPoint(x: posX, y: posZ) + nodes.append(node) + scene.addChild(node) + } + minimap.append(nodes) + } + return scene } + func updateMinimap() { + for (x, _) in level.data.enumerated() { + for (z, data) in level.data[x].enumerated() { + let node = minimap[x][z] + switch data { + case .blank: + node.color = UIColor.black.withAlphaComponent(0.6) + case .point: + node.color = UIColor.blue.withAlphaComponent(0.6) + case .wall: + node.color = UIColor.gray.withAlphaComponent(0.6) + } + } + } + + for monster in monsters { + let x: Int = Int(monster.position.x / 5) + let z: Int = Int(monster.position.z / 5) + minimap[x][z].color = UIColor.red.withAlphaComponent(0.6) + } + + let x: Int = Int(player.position.x / 5) + let z: Int = Int(player.position.z / 5) + minimap[x][z].color = UIColor.yellow.withAlphaComponent(0.6) + } func restart() { viewDidLoad() diff --git a/Pacman3D-iOS/Monster.swift b/Pacman3D-iOS/Monster.swift index 30a1113..039c011 100644 --- a/Pacman3D-iOS/Monster.swift +++ b/Pacman3D-iOS/Monster.swift @@ -9,14 +9,14 @@ import Foundation import SceneKit -class Monster { +class Monster: Equatable { - private var position: SCNVector3 + private (set) var position: SCNVector3 private var direction: Direction = .north private let level: Level private let scene: SCNScene - private var node: SCNNode? + private (set) var node: SCNNode? init(position: SCNVector3, level: Level, scene: SCNScene) { self.position = position @@ -67,13 +67,13 @@ class Monster { } if direction == .north { - position = SCNVector3(x: node.position.x + 0.15, y: node.position.y, z: Float(z * 5)) + position = SCNVector3(x: node.position.x + 0.1, y: node.position.y, z: Float(z * 5)) } else if direction == .east { - position = SCNVector3(x: Float(x * 5), y: node.position.y, z: node.position.z + 0.15) + position = SCNVector3(x: Float(x * 5), y: node.position.y, z: node.position.z + 0.1) } else if direction == .south { - position = SCNVector3(x: node.position.x - 0.15, y: node.position.y, z: Float(z * 5)) + position = SCNVector3(x: node.position.x - 0.1, y: node.position.y, z: Float(z * 5)) } else if direction == .west { - position = SCNVector3(x: Float(x * 5), y: node.position.y, z: node.position.z - 0.15) + position = SCNVector3(x: Float(x * 5), y: node.position.y, z: node.position.z - 0.1) } node.position = position } @@ -102,4 +102,8 @@ class Monster { return directions } + + public static func ==(lhs: Monster, rhs: Monster) -> Bool { + return lhs.node == rhs.node + } } diff --git a/Pacman3D-iOS/MotionInput.swift b/Pacman3D-iOS/MotionInput.swift index 822bdaf..bd56d8f 100644 --- a/Pacman3D-iOS/MotionInput.swift +++ b/Pacman3D-iOS/MotionInput.swift @@ -26,7 +26,7 @@ class MotionInput { let direction: Float = rotate < 0 ? 1.0 : -1.0 if abs(rotate) > 0.3 { if !self.isRotating { - let action = SCNAction.rotateBy(x: 0, y: CGFloat(direction * Float.pi * 0.5), z: 0, duration: 0.25) + let action = SCNAction.rotateBy(x: 0, y: CGFloat(direction * Float.pi * 0.5), z: 0, duration: 0.15) pacman.runAction(action, completionHandler: nil) self.isRotating = true diff --git a/Pacman3D-iOS/pause.png b/Pacman3D-iOS/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..28b37804f7fefa1fdd7688fd35580a5a6967471f GIT binary patch literal 7188 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 literal 0 HcmV?d00001 -- GitLab