How can I make my frame smaller when I call CGRectIntersectsRect - swift

I have two images: ball and obstacle. If the ball hits the obstacle it's game over.
But since my obstacle and ball are not shaped as a rectangle, it looks like it's gameover before they hit each other. I understand that their frame actually does hit each other. But in the game, my gameover function is called before it looks like my ball and obstacle intersect.
Hope to find some help so I can have the result that my two images really looks like they hit each other before my gameover function is called.
if CGRectIntersectsRect(ball.frame, obstacle.frame) {
gameOver()
}

extension CGPoint {
func distance(to p: CGPoint) -> CGFloat {
return sqrt((p.x - self.x) * (p.x - self.x) + (p.y - self.y) * (p.y - self.y))
}
}
extension UIView {
func collided(with otherView: UIView) -> Bool {
let myRadius = bounds.width/2
let hisRadius = otherView.bounds.width/2
return self.center.distance(to: otherView.center) <= myRadius + hisRadius
}
}
Now it reads nicely too:
if ball.collided(with: obstacle) {
gameOver()
}

Related

Stop loop and start again from the beginning

I am trying to include enumerateChildNode with a For Loop. My code is below.
func movement() {
scene?.enumerateChildNodes(withName: "//enemy") {
(node, stop) in
enemyArray.append(node as! SKSpriteNode)
}
for i in enemyArray {
if enemy.position = player.position + 1 {
player.position = player.position + 1
}
continue
}
What the code above does is check for every enemy SKSpriteNode on the screen (lets say it finds 6), and then checks their positions relative to the player. If the players position is near the enemy position, it moves the players position, and then continues the for loop from where it was and checks the players position from the remaining enemies.
However, if the player moves near an enemy position that the for loop already checked (which was false at the time), it will miss that the player is near that enemy. EG: The for loop checks enemies 1,2,3 (finds 3 is near player and moves the player near 2) then checks 4,5,6. It will miss 2...
What I want the code to do
If enemy.position = player.position + 1 {
//move player
*stop the loop, and check the players position from the beginning of the for loop*
}
Call the method again and break out of the loop. But, this causes an infinite loop as you know while posting your question. Not sure how you're planning on changing the value of "i" since it's scope is within the method. Anyways here's how you achieve this:
var array = [1,2,3,4,5,6]
movement()
func movement() {
for i in array {
if i == 3 {
print("hi")
movement()
break
}
}
}

Implementing AI to Air Hockey in SpriteKIt?

I am working on a game similar to air hockey in SpriteKit for fun and to learn Swift/Xcode. I anticipate the AI to be quite a challenge as there is other elements to the game which will need to be accounted for. I know I'll have to keep tackling each issue one by one. I have created the 2 player mode for the game, and I'm working on AI now. Here is some code I have used for calculating and delegating the impulse from mallet to puck (in the 2 player mode):
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
bottomTouchIsActive = true
var releventTouch:UITouch!
//convert set to known type
let touchSet = touches
//get array of touches so we can loop through them
let orderedTouches = Array(touchSet)
for touch in orderedTouches
{
//if we've not yet found a relevent touch
if releventTouch == nil
{
//look for a touch that is in the activeArea (Avoid touches by opponent)
if activeArea.contains(CGPoint(x: touch.location(in: parent!).x, y: touch.location(in: parent!).y + frame.height * 0.24))
{
isUserInteractionEnabled = true
releventTouch = touch
}
else
{
releventTouch = nil
}
}
}
if (releventTouch != nil)
{
//get touch position and relocate player
let location = CGPoint(x: releventTouch!.location(in: parent!).x, y: releventTouch!.location(in: parent!).y + frame.height * 0.24)
position = location
//find old location and use pythagoras to determine length between both points
let oldLocation = CGPoint(x: releventTouch!.previousLocation(in: parent!).x, y: releventTouch!.previousLocation(in: parent!).y + frame.height * 0.24)
let xOffset = location.x - oldLocation.x
let yOffset = location.y - oldLocation.y
let vectorLength = sqrt(xOffset * xOffset + yOffset * yOffset)
//get eleapsed and use to calculate speed6A
if lastTouchTimeStamp != nil
{
let seconds = releventTouch.timestamp - lastTouchTimeStamp!
let velocity = 0.01 * Double(vectorLength) / seconds
//to calculate the vector, the velcity needs to be converted to a CGFloat
let velocityCGFloat = CGFloat(velocity)
//calculate the impulse
let directionVector = CGVector(dx: velocityCGFloat * xOffset / vectorLength, dy: velocityCGFloat * yOffset / vectorLength)
//pass the vector to the scene (so it can apply an impulse to the puck)
delegate?.bottomForce(directionVector, fromBottomPlayer: self)
delegate?.bottomTouchIsActive(bottomTouchIsActive, fromBottomPlayer: self)
}
//update latest touch time for next calculation
lastTouchTimeStamp = releventTouch.timestamp
}
}
I am wondering how I can convert this code for the AI. I have been adding some AI logic to the update function which I believe could also use time stamps and calculate distance traveled between frames to calculate the impulse. I just don't know exactly how to implement that thought. Any help is greatly appreciated :)
Here is some bare bones code I have so far for testing purposes mostly for the AI mode in the update function:
if (ball?.position.y)! < frame.height / 2
{
if (botPlayer?.position.y)! < frame.height * 0.75
{
botPlayer?.position.y += 1
}
}
else
{
if (botPlayer?.position.y)! > (ball?.position.y)!
{
if (botPlayer?.position.y)! - (ball?.position.y)! > frame.height * 0.1
{
botPlayer?.position.y -= 1
}
else
{
botPlayer?.position.y -= 3
}
}
else
{
botPlayer?.position.y += 1
}
}
if ((botPlayer?.position.x)! - (ball?.position.x)!) < 2
{
botPlayer?.position.x = (ball?.position.x)!
}
if (botPlayer?.position.x)! > (ball?.position.x)!
{
botPlayer?.position.x -= 2
}
else if (botPlayer?.position.x)! < (ball?.position.x)!
{
botPlayer?.position.x += 2
}
For AI to make a decision it must have information about the game state. To do this you can write a function that reads all available game data (placement of pucks, player scores, previous moves, etc) and returns the state as a dictionary. Then write a function to take in this dictionary, and output a decision. Consider this workflow in Python.
# we want the AI to make a decision
# start by grabbing game state
gameState = getGameState()
# pass this to the decision function
decision = getDecision( gameState )
# implement the decision
if decision == "move left":
moveLeft()
else:
moveRight()
Is this what you're looking for?

Change image name of global variable - swift

I'm creating a Snake Game using swift and the library SpriteKit.
At the top of my program I declare a variable
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player = SKSpriteNode(imageNamed: "head")
[...]
}
In this way I can access to this variable in all of my functions. But I'd like to change the snake's image when the it moves.
For example, if the snake moves Up, I'd like to display the image of the snake looking up.
So I have this code to control the movements of the snake
if movesController[0]==true{
playerY += 56.0000000
}
if movesController[1]==true{
playerY -= 56.0000000
}
if movesController[2]==true{
playerX += 56.0000000
}
if movesController[3]==true{
playerX -= 56.0000000
}
and to change the image of the snake I thought I could just do this
if movesController[0]==true{
playerY += 56.0000000
player = SKSpriteNode(imageNamed: "head_up")
}
if movesController[1]==true{
playerY -= 56.0000000
player = SKSpriteNode(imageNamed: "head_down")
}
if movesController[2]==true{
playerX += 56.0000000
player = SKSpriteNode(imageNamed: "head_right")
}
if movesController[3]==true{
playerX -= 56.0000000
player = SKSpriteNode(imageNamed: "head_left")
}
But adding this 4 lines, the Snake is unable to move...
So I tried changing the declaration of the player variable
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var imageName = "head"
var player = SKSpriteNode(imageNamed: "\("imageName)")
[...]
}
and then change the 4 lines I added before with imageName = "move_up" (or down - left - right)
But at the start of the program it gave me an error
Cannot use instance member 'imageName' within property initializer; property initializers run before 'self' is available
What should I do? My last option is to create another playerVariable and put it above the real snake image, but it would mean adding more nodes for nothing.
You must change player's texture, but not create new player at each step. Try add this function:
func changeHead(imageNamed name: String){
player.texture = SKTexture(imageNamed: name)
}
And the program text will be changed in this way:
if movesController[0]==true{
playerY += 56.0000000
changeHead(imageNamed: "head_up")
}
if movesController[1]==true{
playerY -= 56.0000000
changeHead(imageNamed: "head_down")
}
if movesController[2]==true{
playerX += 56.0000000
changeHead(imageNamed: "head_right")
}
if movesController[3]==true{
playerX -= 56.0000000
changeHead(imageNamed: "head_left")
}
Actually, your way to solve this problem is true.Put self before the imageName variable. It would be work.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var imageName = "head"
var player = SKSpriteNode(imageNamed: "\("self.imageName)")
[...]
}

Swift scene kit - cant apply velocity WHILE doing rotation? Direction is off?

Ok, so Im going straight off Apple's tutorial using a joystick moving an SCNNode for SceneKithere.
I've copied the code and gotten the joystick to both move and rotate the character - but not simultaneously, and not in the right direction relative to the node.
All the correct code is in that download, but what I've done is here is where I get the angle offset of the joystick handle and the float2 from the joystick UI-
characterDirection = float2(Float(padNode.stickPosition.x), -Float(padNode.stickPosition.y))
let direction = theDude.characterDirection(withPointOfView: renderer.pointOfView)
directionAngle = CGFloat(atan2f(direction.x, direction.z))
public func characterDirection(withPointOfView pointOfView: SCNNode?) -> float3 {
let controllerDir = theDude.direction //THIS ISNT BEING UPDATED
if controllerDir.allZero() {
return float3.zero
}
var directionWorld = float3.zero
if let pov = pointOfView {
let p1 = pov.presentation.simdConvertPosition(float3(controllerDir.x, 0.0, controllerDir.y), to: nil)
let p0 = pov.presentation.simdConvertPosition(float3.zero, to: nil)
directionWorld = p1 - p0
directionWorld.y = 0
if simd_any(directionWorld != float3.zero) {
let minControllerSpeedFactor = Float(0.2)
let maxControllerSpeedFactor = Float(1.0)
let speed = simd_length(controllerDir) * (maxControllerSpeedFactor - minControllerSpeedFactor) + minControllerSpeedFactor
directionWorld = speed * simd_normalize(directionWorld)
}
}
return directionWorld
}
I didn't write the last part and still trying to understand it. But what is relevant is I have a float3 and an angle, and they are conflicting when I try to run them both as SCNActions in my renderer update func:
Here is what Apple basically had in update:
// move
if !direction.allZero() {
theDude.characterVelocity = direction * Float(characterSpeed)
var runModifier = Float(1.0)
theDude.walkSpeed = CGFloat(runModifier * simd_length(direction))
// move character - IMPORTANT
theDude.directionAngle = CGFloat(atan2f(direction.x, direction.z))
theDude.node.runAction(SCNAction.move(by: SCNVector3(theDude.characterDirection(withPointOfView: theDude.node)), duration: TimeInterval(40))) //HERE - random time
theDude.isWalking = true
} else {
theDude.isWalking = false
theDude.node.removeAllActions()
}
}
Where on the commented line I applied the move and here Apple had the rotation applied:
var directionAngle: CGFloat = 0.0 {
didSet {
theDude.node.runAction(
SCNAction.rotateTo(x: 0.0, y: directionAngle, z: 0.0, duration: 0.1, usesShortestUnitArc:true))
}
}
They are both happening, problem is I don't know really what to put as my time and my node moves say, left when I have the joystick pointed right, etc because I am not doing the move correctly.
I tried to copy the demo but they have a moving floor, so it is different. What am I doing wrong here?

SpriteKit - touchesMoved, aim crosshair

I have a spritenode and a crosshair node, i want when the player touch the spritenode and move the crosshair also moves.
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
var body = self.nodeAtPoint(location)
if var name: String = body.name {
if body.name == "aim-button" {
crossHair.position = CGPointMake(crossHair.position.x + 10, crossHair.position.y + 10)
}
}
}
}
The crosshair does get move but only in one direction, and i have no idea how to make it accurate depending on the space the touch moved to-from (which is obviously way smaller than what the crosshair should actually move on the screen) and the direction.
for touch: AnyObject in touches {
//Get the current position in scene of the touch.
let location = touch.locationInNode(self)
//Get the previous position in scene of the touch.
let previousLocation = touch.previousLocationInNode(self)
//Calculate the translation.
let translation = CGPointMake(location.x - previousLocation.x, location.y - previousLocation.y)
//Get the current position in scene of the crossHair.
let position = crossHair.position
// Get the bode touched
var body = self.nodeAtPoint(location)
if var name: String = body.name {
if body.name == "aim-button" {
//Set the position of the crosshair to its current position plus the translation.
crossHair.position = CGPointMake(position.x + translation.x * 2, position.y + translation.y * 2)
//Set the position of the body
body.position = location
}
}
}
If the crosshair should move more distance than the touch, just multiply the translation by a factor (i'm using 2 in the example above).