Why this function stop working as soon as I add hit boxes? - swift

I'm trying to crate a car game where you move the car from left to right by touching the screen but as soon as I set "physics definition -> body type" and the car reach the far left or the far right of the screen, this movement function stop working.
I'm using
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let touchLocation = touch.location(in: self)
if touchLocation.x > centrePoint {
if playerCar.position.x == playerCarAtMaxLeft {
playerCar.position.x = playerCarAtLeft
playerCarMoveRight = true
playerCarMoveLeft = false
} else if playerCar.position.x == playerCarAtLeft {
playerCar.position.x = playerCarAtRight
playerCarMoveRight = true
playerCarMoveLeft = false
} else if playerCar.position.x == playerCarAtRight {
playerCar.position.x = playerCarAtMaxRight
playerCarMoveRight = true
playerCarMoveLeft = false
} else {
playerCarMoveRight = false
playerCarMoveLeft = true
}
} else {
if playerCar.position.x == playerCarAtMaxRight {
playerCar.position.x = playerCarAtRight
playerCarMoveRight = false
playerCarMoveLeft = true
} else if playerCar.position.x == playerCarAtRight {
playerCar.position.x = playerCarAtLeft
playerCarMoveRight = false
playerCarMoveLeft = true
} else if playerCar.position.x == playerCarAtLeft {
playerCar.position.x = playerCarAtMaxLeft
playerCarMoveRight = false
playerCarMoveLeft = true
} else{
playerCarMoveRight = true
playerCarMoveLeft = false
}
}
canMove = true
}
}
playerCar is a SKSpriteNode
playerCarAt... are CGFloat
playerCarMove... are Boolean

I'd suggest you just let the car slide on the x-axis as much as it wants and then use the update method to keep track of the cars position.
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
In that way you can set up let maxXPosition and as soon as the car-node hits that point, you stop the movement.
A way of doing so is to make the car-node movement an SKAction.moveTo.
A simple version would be:
class GameScene: SKScene {
var car : SKSpriteNode! // Your car sprite
var maxXPosition : CGFloat! // The max point on the x-axis the car is allowed to move to
override func didMove(to view: SKView) {
// Initiate car
car = self.childNode(withName: "//car") as? SKSpriteNode
// Setup the max x position
maxXPosition = 200
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
moveCar(toPosition: location)
}
}
override func update(_ currentTime: TimeInterval) {
// Check the cars position
if car.position.x >= 200 {
// Stop the car is the point has been reached
car.removeAction(forKey: "carDrive")
}
}
func moveCar(toPosition position: CGPoint) {
let moveCarToXPoint = SKAction.moveTo(x: position.x, duration: 1)
car.run(moveCarToXPoint, withKey: "carDrive")
}
}

Related

How to add a 30 second timer to end a game round?

I am currently experimenting with some code that I found on the internet about a game where you have to click on one set of items and avoid clicking on the other. I am currently trying to add a timer to the game so that it lasts of a total of 30 seconds but I am really struggling to do so as I am quite inexperienced with this programming language.
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController, SCNSceneRendererDelegate {
var gameView:SCNView!
var SceneGame:SCNScene!
var NodeCamera:SCNNode!
var targetCreationTime:TimeInterval = 0
override func viewDidLoad() {
super.viewDidLoad()
View_in()
initScene()
initCamera()
}
func View_in(){
gameView = self.view as! SCNView
gameView.allowsCameraControl = true
gameView.autoenablesDefaultLighting = true
gameView.delegate = self
}
func initScene (){
SceneGame = SCNScene()
gameView.scene = SceneGame
gameView.isPlaying = true
}
func initCamera(){
NodeCamera = SCNNode()
NodeCamera.camera = SCNCamera()
NodeCamera.position = SCNVector3(x:0, y:5, z:10)
SceneGame.rootNode.addChildNode(NodeCamera)
}
func createTarget(){
let geometry:SCNGeometry = SCNPyramid( width: 1, height: 1, length: 1)
let randomColor = arc4random_uniform(2
) == 0 ? UIColor.green : UIColor.red
geometry.materials.first?.diffuse.contents = randomColor
let geometryNode = SCNNode(geometry: geometry)
geometryNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
if randomColor == UIColor.red {
geometryNode.name = "enemy"
}else{
geometryNode.name = "friend"
}
SceneGame.rootNode.addChildNode(geometryNode)
let randomDirection:Float = arc4random_uniform(2) == 0 ? -1.0 : 1.0
let force = SCNVector3(x: randomDirection, y: 15, z: 0)
geometryNode.physicsBody?.applyForce(force, at: SCNVector3(x: 0.05, y: 0.05, z: 0.05), asImpulse: true)
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
if time > targetCreationTime{
createTarget()
targetCreationTime = time + 0.6
}
cleanUp()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: gameView)
let hitList = gameView.hitTest(location, options: nil)
if let hitObject = hitList.first{
let node = hitObject.node
if node.name == "friend" {
node.removeFromParentNode()
self.gameView.backgroundColor = UIColor.black
}else {
node.removeFromParentNode()
self.gameView.backgroundColor = UIColor.red
}
}
}
func cleanUp() {
for node in SceneGame.rootNode.childNodes {
if node.presentation.position.y < -2 {
node.removeFromParentNode()
}
}
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
You could use a Timer object, documented here. Just set up the timer when you want the game to start, probably once you've finished all your initializations. When you set up the timer, just wait for it to call back to your code when it finishes and run whatever logic you want to use to terminate your game.
EDIT
Create a variable representing the time you want your game will end:
var time: CGFloat = 60
Then, add an SCNAction to your scene so that each second it will decrease this variable value, for example in the viewDidLoad:
//One second before decrease the time
let wait = SCNAction.wait(forDuration: 1)
//This is the heart of this answer
// An action that reduce the time and when it is less than 1 (it reached zero) do whatever you want
let reduceTime = SCNAction.run{ _ in
self.time -= 1
if self.time < 1 {
// Do whatever you want
// for example show a game over scene or something else
}
}
}
SceneGame.rootNode.run(SCNAction.repeatForever(SCNAction.sequence([wait,reduceTime])))
If you want, you can show the remaining time by using SKLabel on an HUD, which is an SKScene used as overlay.
You can check this tutorial for how to create an HUD
As well, you can use an SCNText, this is the documentation about it

Change child parent in GameKit

I am working on an arrow shooting game - The player need to shoot 3 arrows to a moving target (the target moves from left to right). When an arrow hits the target it should move with it (left to right). The most obvious thing to do would be to change the arrow parent to the target. From some reason its causing me some troubles -
I tried --- arrow.move(toParent:target) and I don't see the arrow on the screen even after I set a new location for it
If I simply --- target.addChild(arrow) I get a failure since I did not remove the arrow from its parent (which is the scene in this case)
When I --- arrow.removeFromParent() and then target.addChild(arrow) its causing other arrows to collide with each other and I still don't see the arrow on the screen.
This is my code -
class GameScene: SKScene, SKPhysicsContactDelegate {
var target:SKSpriteNode?
var arrows = [SKSpriteNode]()
var arrowContactPoint:CGPoint?
let noCategory:UInt32 = 0
let arrowCategory:UInt32 = 0b1
let targetCategory:UInt32 = 0b1 << 1
let obstacleCategory:UInt32 = 0b1 << 2
override func didMove(to view: SKView) {
allowCollisionDetection()
setTarget()
moveTargetFromSideToSide()
newArrow()
}
func didBegin(_ contact: SKPhysicsContact) {
let categoryBitMaskBodyA:UInt32 = contact.bodyA.categoryBitMask
let categoryBitMaskBodyB:UInt32 = contact.bodyB.categoryBitMask
if ((categoryBitMaskBodyA == targetCategory && categoryBitMaskBodyB == arrowCategory) || (categoryBitMaskBodyA == arrowCategory && categoryBitMaskBodyB == targetCategory)) {
arrowContactPoint = contact.contactPoint
arrowCollideWithTarget()
} else if (categoryBitMaskBodyA == obstacleCategory || categoryBitMaskBodyB == obstacleCategory) {
let obstacleNode:SKNode = ((categoryBitMaskBodyA == arrowCategory) ? contact.bodyA.node! : contact.bodyB.node)!
arrowCollideWithObstacle(obstacle:obstacleNode)
} else if (categoryBitMaskBodyA == arrowCategory && categoryBitMaskBodyB == arrowCategory) {
newGame()
} else {
print("Something went wrong")
}
newArrow()
}
func touchDown(atPoint pos : CGPoint) {
}
func touchMoved(toPoint pos : CGPoint) {
}
func touchUp(atPoint pos : CGPoint) {
shootArrow()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
func allowCollisionDetection() {
self.physicsWorld.contactDelegate = self
}
func setTarget() {
target = self.childNode(withName: "target") as? SKSpriteNode
//Set the target bit mask, it's tag
target?.physicsBody?.categoryBitMask = targetCategory
//Set with which objects the target collide
target?.physicsBody?.collisionBitMask = noCategory
//Set to which coliision we want to responde/handle - didBegin will get triggered
target?.physicsBody?.contactTestBitMask = arrowCategory
}
func moveTargetFromSideToSide() {
let moveRight = SKAction.moveBy(x: frame.size.width - (target?.size.width)!, y: 0, duration: 2)
let moveLeft = SKAction.moveBy(x: -(frame.size.width - (target?.size.width)!), y: 0, duration: 2)
let moveBackAndForth = SKAction.repeatForever(SKAction.sequence([moveRight, moveLeft]))
target?.run(moveBackAndForth)
}
func newArrow() {
let arrow = SKSpriteNode(imageNamed: "arrow1")
let arrowTexture = SKTexture(imageNamed: "arrow1")
arrow.position = CGPoint.zero
self.addChild(arrow)
arrow.physicsBody = SKPhysicsBody(texture: arrowTexture, size: arrowTexture.size())
arrow.physicsBody?.isDynamic = true
arrow.physicsBody?.allowsRotation = true
arrow.physicsBody?.affectedByGravity = false
arrow.physicsBody?.friction = 0.2
arrow.physicsBody?.restitution = 0.2
arrow.physicsBody?.linearDamping = 0.1
arrow.physicsBody?.angularDamping = 0.1
arrow.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
arrow.physicsBody?.categoryBitMask = arrowCategory
arrow.physicsBody?.collisionBitMask = noCategory
arrow.physicsBody?.contactTestBitMask = arrowCategory | obstacleCategory | targetCategory
arrows.append(arrow)
}
func shootArrow(){
print("shootArrow")
arrows.last!.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 80))
}
func arrowCollideWithTarget() {
print("arrowCollideWithTarget")
arrows.last!.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
arrows.last!.move(toParent: target!)
}
func arrowCollideWithObstacle(obstacle:SKNode) {
print("arrowCollideWithObstacle")
arrows.last!.removeFromParent()
arrows.removeLast()
}
func newGame() {
print("New Game")
for i in 0 ..< (arrows.count) {
arrows[i].removeFromParent()
}
arrows.removeAll()
}
}
What eventually solved it for me was using this method ---
move(toParent: )
This is my code -
func arrowCollideWithTarget() {
arrows.last!.move(toParent:target!)
}

Multiple circles drawn SpriteKit

I'm working on simple game which will based on paiting background by player. In left and right corner there will be buttons which will move character to left or right. I've already implemented that (character is moving and lefts painted background behind), but with adding another circles fps's drops really fast. Is there any solution to that?
import SpriteKit
class GameScene: SKScene {
var playerDot:PlayerDot = PlayerDot(imageNamed:"player")
var isTurningLeft:Bool = false
var isTurningRight:Bool = false
var lastLocation:CGPoint = CGPoint()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myLabel = SKLabelNode(fontNamed:"Helvetica")
myLabel.name = "left"
myLabel.text = "Left"
myLabel.fontSize = 30
myLabel.horizontalAlignmentMode = .Left
myLabel.position = CGPoint(x:CGRectGetMinX(self.frame), y:CGRectGetMinY(self.frame))
self.addChild(myLabel)
let myLabel2 = SKLabelNode(fontNamed:"Helvetica")
myLabel2.name = "right"
myLabel2.text = "Right"
myLabel2.fontSize = 30
myLabel2.horizontalAlignmentMode = .Right
myLabel2.position = CGPoint(x:CGRectGetMaxX(self.frame), y:CGRectGetMinY(self.frame))
self.addChild(myLabel2)
playerDot.position = CGPoint(x:CGRectGetMaxX(self.frame)/2, y:CGRectGetMinY(self.frame))
self.addChild(playerDot)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = true
isTurningRight = false
}
else if theName == "right" {
isTurningRight = true
isTurningLeft = false
}
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = false
}
else if theName == "right" {
isTurningRight = false
}
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = false
}
else if theName == "right" {
isTurningRight = false
}
}
}
}
override func update(currentTime: CFTimeInterval) {
if(isTurningLeft){
playerDot.increaseAngle()
} else if (isTurningRight){
playerDot.decreaseAngle()
}
//calculates new character position based on angle of movement changed
playerDot.updatePosition()
drawCircle()
}
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let circle = SKShapeNode(circleOfRadius: 10 )
circle.position = playerDot.position
circle.fillColor = SKColor.orangeColor()
circle.strokeColor = SKColor.orangeColor()
self.addChild(circle)
lastLocation = playerDot.position
}
}
func distanceFromCGPoints(a:CGPoint,b:CGPoint)->CGFloat{
return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
}
EDIT:
drawCircle with SKShapeNode replaced with SKSpriteNote
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let playerDot2 = SKSpriteNode(imageNamed:"player")
playerDot2.position = playerDot.position
self.addChild(playerDot2)
lastLocation = playerDot.position
}
}
If you are having frame rate drops then some parts of your code are slowing down execution too much. You need to optimise your code. Use Instruments (Time Profiler) to find out which lines/functions are causing problems speed wise. If you have never used it before read up on it and use it a couple times to get the gist, then I recommend watching the WWDC video Profiling In-Depth
Based on your edit (switching to SKSpriteNode), this is how you could do it:
Declare a texture as a property of your scene. This way, you load it once and reuse it later:
let yourTexture = SKTextureAtlas(named: "yourAtlas").textureNamed("yourTexture")
Then in your drawCircle method:
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let playerDot2 = SKSpriteNode(texture: yourTexture)
playerDot2.position = playerDot.position
self.addChild(playerDot2)
lastLocation = playerDot.position
}
}
This way (by using SKSpriteNode instead of SKShapeNode) you have reduced number of draw calls required to render all those circles. Also because you are reusing the same texture, instead of allocating it every time using imageNamed you have reduced greatly an amount of memory that app consumes. Now, SpriteKit can render hundreds of nodes #60 fps if those nodes are rendered in batches. Still there is a limit before fps start dropping. And that depends on a device.

How to hide controls in YTPlayerView?

I am using youtube-ios-player-helper library to play youtube videos in my app.
There is a small time delay between you press play button and video starts playing in the full screen. So I want to hide controls in this between time.
How can I do this?
Here what I have right now in Swift:
protocol YouTubePlayerViewDelegate {
func playVideo(id: String)
}
class DeferredYouTubePlayerView : UIView, EventActionable, YTPlayerViewDelegate {
var youtubeId: String!
var origin: String!
var playerWidth: Int!
var imageView: ImageLoadingBackgroundView?
var playImageView: UIImageView?
var player: YTPlayerView?
var backDropView: UIView?
var eventCategory:String!
var touchCancelled = false
let playerVars:[NSObject:AnyObject] = [
"playsinline":0,
"autoplay" : 0,
"modestbranding" : 1,
"origin":MetroManager.sharedInstance.selectedMetro!.baseURL
]
var delegate : YouTubePlayerViewDelegate?
func loadVideo() {
backgroundColor = UIColor.blackColor()
if imageView == nil {
imageView = ImageLoadingBackgroundView()
imageView!.setTranslatesAutoresizingMaskIntoConstraints(false)
imageView?.constrain(.FillParent(self))
}
if playImageView == nil {
playImageView = UIImageView(image: UIImage(named: "play_button"))
playImageView?.constrain(.Height(42))
playImageView?.constrain(.Height(42))
playImageView?.constrain(.CenterInParent(imageView!))
}
if let url = buildImageURL() {
imageView?.alpha = 1
imageView!.imageURL = url
} else {
imageView?.alpha = 0
}
if backDropView == nil {
backDropView = UIView()
backDropView?.backgroundColor = UIColor.blackColor()
backDropView?.constrain(.CenterInParent(self))
backDropView?.constrain(.FillParent(self))
addSubview(backDropView!)
}
if player == nil {
player = YTPlayerView()
player?.constrain(.CenterInParent(self))
player?.constrain(.FillParent(self))
player?.delegate = self
player!.loadWithVideoId(youtubeId, playerVars: playerVars)
addSubview(player!)
bringSubviewToFront(imageView!)
}
}
func buildImageURL() -> NSURL? {
return NSURL(string: "http://img.youtube.com/vi/\(youtubeId)/0.jpg")
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if let playImageView = playImageView {
playImageView.image = UIImage(named: "play_button_highlighted")
}
}
//If the user moves their finger, they probably want to slide to the next view, cancel touches
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
if let playImageView = playImageView {
playImageView.image = UIImage(named: "play_button")
}
touchCancelled = true
}
func playerView(playerView: YTPlayerView!, didChangeToState state: YTPlayerState) {
switch state {
case .Buffering:
fallthrough
case .Playing:
hidePoster()
case .Ended:
fallthrough
case .Paused:
showPoster()
default:
()
}
}
func hidePoster() {
UIView.animateWithDuration(DEFAULT_ANIMATION_TIME, animations: { () -> Void in
self.imageView?.alpha = 0
})
}
func showPoster() {
UIView.animateWithDuration(DEFAULT_ANIMATION_TIME, animations: { () -> Void in
self.imageView?.alpha = 1
})
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
println("ended")
if let playImageView = playImageView {
playImageView.image = UIImage(named: "play_button")
}
if(!touchCancelled){
loggerA(category: eventCategory, action: "Cover Scroll", label: "Video Play")
// delegate?.playVideo(youtubeId)
hidePoster()
player!.playVideo()
}
touchCancelled = false
}
deinit{
//delegate = nil
}
}
Oh, I have just found the solution: I need to set the "controls" to (0) false value in playerVars; So in my context it will look like this: let playerVars:[NSObject:AnyObject] = [ "controls":0, ... ]
And it just worked!

Pause and Resume SKScene

I was testing my game on my iPhone and I realized that I have a problem with the home button. When I pause the game and load another app the game is not paused and continues when the app is not on screen.
Here's the code:
class GameScene: SKScene, SKPhysicsContactDelegate{
var PauseButton = SKSpriteNode(imageNamed: "pause")
var Resume = SKSpriteNode(imageNamed: "resume1")
}
init(size: CGSize, won: Bool) {
super.init(size: size)
//pause
PauseButton.position = CGPointMake(330, 700)
PauseButton.zPosition = 3;
PauseButton.size = CGSizeMake(40, 40);
PauseButton.name = "PauseButton"
self.addChild(PauseButton)
//resume
Resume = SKSpriteNode(imageNamed: "resume1")
Resume.position = CGPointMake(520, 450)
Resume.zPosition = 100
Resume.setScale(1.3)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
show = self.scene as SKScene!
for touch in touches {
let Location:CGPoint = touch.locationInNode(self)
let Node:SKNode = self.nodeAtPoint(Location)
if(Node == PauseButton){
self.show.paused = true
PauseButton.removeFromParent()
self.addChild(Resume)
}
if(Node == Resume){
Resume.removeFromParent()
self.show.paused = false
self.addChild(PauseButton)
}
How do I get the game to pause when I press the home button?
Pause the view not the scene.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let view = view else {
return
}
view.paused = !view.paused
}
SpriteKit will auto Resume when you comeback to your app. so you need :
Step 1 : create a variable :
var wasPaused : Bool = false
Step 2 : change it in your pause/resume function :
wasPaused = true // pause func
wasPaused = false // resume fun
Step 3 : in update fun of your scene:
override func update(currentTime: CFTimeInterval) {
if isPausing{
self.paused = true
return
}
}