select a point in the scene to place a node (SpriteKit, Swift) - ios

I'd like to do so that when the player presses on a label, he then has to press once again on a point on the screen to pick a place where to build a house. Until now, here's what I've got: when the player presses on the label "build", a boolean ("wantsToBuild") is set to true, but for now obviously the house is built right on top of the label. I don't know either how to check if that place is already busy or if the player can build on there. I thought about having some placeholders but I wouldn't know how to set them up correctly. Could you help me solve this problem? thank you. EDIT: I've changed the code in the question following the suggestions in the answers but now i have a couple of problems: the joystick (which i didn't mention before) moves around even if i'm not touching the joystick itself. Plus even though I've checked with a print statement the value of wantsToBuild, and it seems set to false, whenever, i press on the screen, a house is alway built. Can you help me further?
class GameScene: SKScene {
var ship = LCDShip()
var shipSpeed : CGFloat = 0.08
var base = SKSpriteNode(imageNamed: "base")
var joystick = SKSpriteNode(imageNamed: "joystick")
var joystickActive = false
var length:CGFloat! = nil
var xDist:CGFloat! = nil
var yDist:CGFloat! = nil
var deltaVector:CGVector! = nil
let build = SKLabelNode()
var wantsToBuild = false
override func didMoveToView(view: SKView) {
build.name = "build"
build.fontName = "Chalkduster"
build.fontSize = 25
build.text = "Build a house"
build.zPosition = 2
build.position = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
addChild(build)
backgroundColor = SKColor.blackColor()
ship.position = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
addChild(ship)
base.position = CGPoint(x: 150, y: 200)
base.setScale(2)
base.alpha = 0.3
addChild(base)
joystick.position = base.position
joystick.setScale(2)
joystick.alpha = 0.4
joystick.name = "base"
addChild(joystick)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let house = SKSpriteNode(imageNamed: "house")
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
let label = self.childNodeWithName("build")!
if node.name == "build"{
print("where should i build the hosue?")
wantsToBuild = true
} else if wantsToBuild == true && node.name != "house" && location.x <= label.position.x - 15 || location.x >= label.position.x + 15 || location.y >= label.position.y + 15 || location.y <= label.position.y - 15 {
house.position = location
house.name = "house"
addChild(house)
wantsToBuild = false
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
deltaVector = CGVector(dx: location.x - base.position.x, dy: location.y - base.position.y)
let angle = atan2(deltaVector.dy, deltaVector.dx)
length = base.frame.size.height/2
xDist = sin(angle - 1.57079633) * length
yDist = cos(angle - 1.57079633) * length
if(CGRectContainsPoint(base.frame, location)){
joystick.position = location
} else {
joystick.position = CGPointMake(base.position.x - xDist, base.position.y + yDist)
}
ship.zRotation = angle - 1.57079633
}
}
override func update(currentTime: NSTimeInterval) {
if deltaVector != nil {
ship.position.x += deltaVector.dx * shipSpeed
ship.position.y += deltaVector.dy * shipSpeed
}
}
}

Bogy's method will work, but you probably want it to be && node.name!=
"build", unless you are building multiple houses, in which case you would probably want bothnode.name != "build" && node.name != "house"
Anyway...
This approach will work, unless your house sprite is big enough such then when you touch right next to the label, but not on the actual label, the house could overlap the label, and would basically be right on top of the label still. Again, IDK the sprite sizes and everything. To handle that scenario, you could create a range around the label that you are not allowed to place in, by doing this:
let label = self.childNodeWithName("build")!
if wantsToBuild == true && (location.x <= label.position.x - 15 || location.x >= label.position.x + 15 || location.y <= label.position.y - 15 || location.y >= label.position.y + 15) {
let sprite = SKSpriteNode(imageNamed: "house")
sprite.position = location
addChild(sprite)
wantsToBuild = false
}
This will kind of create a larger box around the label, and won't allow you to place the house in that box, so if you make the box big enough (you will probably have to tinker with the + 15 and - 15's), even if you put it as close as you can to the label, you can't place it close enough to overlap.
If your label is a sprite, and not an SKLabelNode, you could do it how Bogy is saying, and just add a border of invisible space to the actual sprite image, so the closest point to the sprite that isn't in the sprite will be far enough away to not overlap.

I think your condition is not good, I have nothing to try out but I would do something like this:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node.name == "build"{
print("where should i build the house?")
wantsToBuild = true
}
else if wantsToBuild == true && node.name != "house" {
let sprite = SKSpriteNode(imageNamed: "house")
sprite.position = location
sprite.name = "house"
addChild(sprite)
wantsToBuild = false
}
}
}

So I actually managed to solve the problem by cleaning up my code and using your suggestions better. Not sure it is fantastic code, but it works. I added a cost and a couple more if-statements. Here's what I have now, in case somebody could find it useful :)
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if CGRectContainsPoint(base.frame, location){
joystickActive = true
}else {
joystickActive = false
if node.name == "build" {
wantsToBuild = true
} else if node.name != "house" {
checkPlace(location)
wantsToBuild = false
}
}
}
}
func checkPlace(location : CGPoint) {
let label = self.childNodeWithName("build")!
let deltaX = location.x - label.position.x
let deltaY = location.y - label.position.y
let houseRadius : CGFloat = 60
let distance = sqrt(deltaX * deltaX + deltaY * deltaY)
if distance <= houseRadius{
print("YOU CANNOT BUILD HERE")
} else if distance > houseRadius && wantsToBuild == true{
if money > 0 {
buildConstruction(10, location: location)
}
}
}
func buildConstruction(cost: Int, location: CGPoint) {
let house = SKSpriteNode(imageNamed: "house")
house.position = location
house.name = "house"
addChild(house)
money -= cost
print(money)
}
once again, thank you to #Arkidillo and #Bogy who put me on the right track :)

Related

SpriteKit: calculate angle of joystick and change sprite based on that

I am making a RPG Birds-eye style game with SpriteKit. I made a joystick because a D-Pad does not give the player enough control over his character.
I cannot wrap my brain around how I would calculate the neccessary data needed to change the Sprite of my character based on the angle of the joystick thumb Node.
Here is my code I am using
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if isTracking == false && base.contains(location) {
isTracking = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location: CGPoint = touch.location(in: self)
if isTracking == true {
let v = CGVector(dx: location.x - base.position.x, dy: location.y - DPad.position.y)
let angle = atan2(v.dy, v.dx)
let deg = angle * CGFloat(180 / Double.pi)
let Length:CGFloat = base.frame.size.height / 2
let xDist: CGFloat = sin(angle - 1.57079633) * Length
let yDist: CGFloat = cos(angle - 1.57079633) * Length
print(xDist,yDist)
xJoystickDelta = location.x * base.position.x / CGFloat(Double.pi)
yJoystickDelta = location.y * base.position.y / CGFloat(Double.pi)
if base.contains(location) {
thumbNode.position = location
} else {
thumbNode.position = CGPoint(x: base.position.x - xDist, y: base.position.y + yDist)
}
}
}
}
Update method
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if xJoystickDelta > 0 && yJoystickDelta < 0 {
print("forward")
}
}
The way I have set up right now tests the positive or negative state of the Joystick position in a cross method based on where the thumb Node is inside of the four marked sections below
I dont want it to do that
How can I set it up so that it changes the sprite based on where the thumb node is actually pointing inside my joysticks base like so.
I have been struggling with this for 3 days now so any help would be appreciated.
That looks far too complicated. Just compare the x and y components
of the difference vector v. Something like this:
if v.dx > abs(v.dy) {
// right
} else if v.dx < -abs(v.dy) {
// left
} else if v.dy < 0 {
// up
} else if v.dy > 0 {
// down
}

Touch contact with circle sprite oversize

I've got a simple spritekit project in which I'm noticing that the point at which contact is registered with my sprite is occurring well outside the circle physics body.
Demonstrated here:
https://gfycat.com/DentalBriskAfricangroundhornbill
With showPhysics on: http://i.imgur.com/F4BcXhb.png
Code for the object spawning the circles:
func spawnObject() {
object = SKSpriteNode(imageNamed: "Oval")
//object.fillColor = SKColor.white
object.name = "ball"
object.position = CGPoint(x: 280, y: 520)
object.physicsBody = SKPhysicsBody(circleOfRadius: object.size.width / 2)
object.physicsBody!.isDynamic = true
object.physicsBody!.contactTestBitMask = object.physicsBody!.collisionBitMask
object.physicsBody?.friction = 0.2
object.physicsBody?.restitution = 0.2
object.physicsBody?.mass = 5
addChild(object)
}
The circles themselves interact perfectly with one another - it appears touch is the outlier.
Would appreciate any assistance :)
Edit:
Example touch code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let tappedNodes = nodes(at: location)
for node in tappedNodes {
if node.name == "ball" {
node.alpha = 0.5
}
}
}
}

How to shoot a moving sprite in Swift?

I am trying to get my stationary monster to shoot at my moving player. Both the player position and the monster position are obtaining the right values and this code is written in a function that is called every second. Right now the projectile shows up but doesn't move away from the monster. Is there something else I should be using besides .applyAngularImpulse?
let deltaX = player.position.x - monster.position.x
let deltaY = player.position.y - monster.position.y
let angle = atan2(deltaY, deltaX)
monProjectile.physicsBody?.applyAngularImpulse(angle)
UPDATE
Having looked at the code you provided, I suspect two things are at fault:
1) You are specifying a "projectile" image that I can't see in your project.
2) You are trying to apply angular impulse (i.e. spin) rather than a regular impulse (i.e., direction plus speed).
To fix the first problem add an image for your projectile. To fix the second, consider using applyImpulse() with a CGVector.
Original answer
Off the top of my head, there are a few things that might cause this:
1) How much of an impulse are you applying? Print out the value and see what kind of number you're working with.
2) Does your projectile overlap the monster when it's created? If so, it might be colliding and getting stuck.
3) Is it possible the projectile is colliding with some other node entirely, e.g. a background picture?
You should consider setting showsPhysics to be true for your SKView so you can see what's happening more clearly.
func makeShoot() {
let Shoot:ShootClass = ShootClass.init()
Shoot.physicsBody = SKPhysicsBody(texture: Shoot.texture!,
size: Shoot.texture!.size())
Shoot.position = (self.Shoot?.position)!
Shoot.currentPosition = (self.Shoot?.position)!
Shoot.physicsBody?.isDynamic = true
Shoot.physicsBody?.allowsRotation = false
Shoot.physicsBody?.affectedByGravity = false
Shoot.physicsBody?.friction = 0
Shoot.physicsBody?.restitution = 1
Shoot.physicsBody?.mass = 1
Shoot.physicsBody?.linearDamping = 0.0
Shoot.physicsBody?.angularDamping = 0.0
Shoot.physicsBody?.categoryBitMask = ShootCategory
Shoot.physicsBody?.collisionBitMask = BorderCategory
Shoot.physicsBody?.contactTestBitMask = BorderCategory
PlayingView.addChild(Shoot);
Shoot.physicsBody?.applyImpulse(CGVector(dx: 100, dy: 100))
self.moveNodeToLocation(Shoot: Shoot)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let curTouch = touches.first!
let curPoint = curTouch.location(in: self)
if ((curPoint.x > 103.5 && curPoint.y > 50.0) || (curPoint.x < 840.0 && curPoint.y > 50.0)) {
StartingPoint = touches.first?.location(in: self)
direction?.isHidden = false
direction?.setScale(0.50)
FirstTouchLocater = SKSpriteNode(imageNamed: "ic_Shootz");
FirstTouchLocater.position = curPoint
FirstTouchLocater.alpha = 0.5
self.addChild(FirstTouchLocater);
}
else{
self.direction?.isHidden = true
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let point:CGPoint = (touches.first?.location(in: self))!
if point.y < 40 {
return
}
if !(isTouch!) {
isTouch = true
}
let dy:CGFloat = StartingPoint!.y - point.y
let size:CGFloat = dy*10/self.frame.height
if size < 2 && size > 0.50 {
direction?.setScale(size)
}
print("size ======> \(size)")
let curTouch = touches.first!
let curPoint = curTouch.location(in: self)
if (curPoint.x <= ((StartingPoint?.x)! + 20.0)) && ((curPoint.x + 20.0) >= (StartingPoint?.x)!) && (curPoint.y <= ((StartingPoint?.y)! + 20.0)) && ((curPoint.y + 20.0) >= (StartingPoint?.y)!){
self.direction?.isHidden = true
FirstTouchLocater?.isHidden = true
}
else if ((curPoint.x > 103.5 && curPoint.y > 50.0) || (curPoint.x < 840.0 && curPoint.y > 50.0)) {
let deltaX = (self.direction?.position.x)! - curPoint.x
let deltaY = (self.direction?.position.y)! - curPoint.y
let angle = atan2(deltaY, deltaX)
let DegreesToRadians = CGFloat.pi / 180
self.direction?.zRotation = angle + 90 * DegreesToRadians
self.direction?.isHidden = false
FirstTouchLocater?.isHidden = false
}
else{
self.direction?.isHidden = true
FirstTouchLocater?.isHidden = true
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if direction?.isHidden == false {
FirstTouchLocater.removeFromParent()
direction?.isHidden = true
direction?.setScale(0.1)
if timeThrow == nil && isTouch!
{
isTouch = false
counterY = 0;
let touch = touches.first
let touchLocation = touch?.location(in: self)
lastTouch = touchLocation
lastTouch1 = touchLocation
ShootThrow = 2
timeThrow = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.loadScreen), userInfo: nil, repeats: true)
}
}
}
func moveNodeToLocation(Shoot:SKSpriteNode) {
let dx = (lastTouch?.x)! - Shoot.position.x
let dy = (lastTouch?.y)! - Shoot.position.y
let speed1:CGFloat = 424
let hypo = hypot(dx, dy)
let newX = (speed1 * dx) / hypo
let newY = (speed1 * dy) / hypo
Shoot.physicsBody?.velocity = CGVector(dx:newX, dy: newY)
}
}

Implementing collision detections

Basically the game consists of a basket that the player moves across the screen, the aim of the game is for the player to catch balls falling from the top of the screen. I am currently trying to add collision detection between the balls and the basket, but am facing difficulties namely, implementing this collision detection. I am new to swift, sprite kit and app development, so please help. Any help would be appreciated. Another problem I am facing is that all the balls are falling in the centre of the screen. A line of code is supposed to execute when, the ball hits the basket and following that the ball should disappear, please help as I am new to Spritekit.
import SpriteKit
class GameScene: SKScene {
var basket = SKSpriteNode()
let actionMoveRight = SKAction.moveByX(50, y: 0, duration: 0.2)
let actionMoveLeft = SKAction.moveByX(-50, y: 0, duration: 0.2)
//let physicsBody = SKPhysicsBody(texture: , size: 3500)
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.physicsWorld.gravity = CGVectorMake(0.0, -0.5)
self.backgroundColor = SKColor.whiteColor()
basket = SKSpriteNode(imageNamed: "basket")
basket.setScale(0.5)
basket.position = CGPointMake(self.size.width/2, self.size.height/8)
basket.size.height = 50
basket.size.width = 75
self.addChild(basket)
let updateAction = SKAction.runBlock {
var choice = arc4random_uniform(3)
switch choice {
case 1 :
var ball1 = SKSpriteNode(imageNamed: "redBall")
ball1.position = CGPointMake(self.size.width/3, self.size.height)
ball1.setScale(0.5)
ball1.size.height = 20
ball1.size.width = 30
ball1.physicsBody = SKPhysicsBody(circleOfRadius: ball1.size.height / 2.75)
ball1.physicsBody!.dynamic = true
self.addChild(ball1)
println("0")
case 0 :
var ball2 = SKSpriteNode(imageNamed: "redBall")
ball2.position = CGPointMake(self.size.width/5, self.size.height)
ball2.setScale(0.5)
ball2.size.height = 20
ball2.size.width = 30
ball2.physicsBody = SKPhysicsBody(circleOfRadius: ball2.size.height / 2.75)
ball2.physicsBody!.dynamic = true
self.addChild(ball2)
println("1")
case 2 :
var ball3 = SKSpriteNode(imageNamed: "redBall")
ball3.position = CGPointMake(self.size.width*4/5, self.size.height)
ball3.setScale(0.5)
ball3.size.height = 20
ball3.size.width = 30
ball3.physicsBody = SKPhysicsBody(circleOfRadius: ball3.size.height / 2.75)
ball3.physicsBody!.dynamic = true
self.addChild(ball3)
println("2")
default :
println("Problem")
}
}
let waitDuration : NSTimeInterval = 1.0
let updateAndWaitAction = SKAction.sequence([updateAction,SKAction.waitForDuration(waitDuration)])
let repeatForeverAction = SKAction.repeatActionForever(updateAndWaitAction)
self.runAction(repeatForeverAction)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if location.x > basket.position.x {
if basket.position.x < self.frame.maxX {
basket.runAction(actionMoveRight)
}
}
else {
if basket.position.x > self.frame.minX {
basket.runAction(actionMoveLeft)
}
}
}
}
override func update(currentTime: CFTimeInterval) {
}
}
For now you have a code that typically used in situations where user is taping something. You need to use BodyA & BodyB and assign a bitmasks to your nodes.
self.basket.physicsBody?.categoryBitMask = ColliderType.basket.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball1.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball1.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball2.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball2.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball3.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball3.rawValue
And do that for every ball too. And then in func didBeginContact you should say to Xcode what to do, if you have an animation or something:
if (contact.bodyA.categoryBitMask == ColliderType.ball1.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball1.rawValue) {
yourGameOverFunc()
}
if (contact.bodyA.categoryBitMask == ColliderType.ball2.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball2.rawValue) {
yourGameOverFunc()
}
if (contact.bodyA.categoryBitMask == ColliderType.ball3.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball3.rawValue) {
yourGameOverFunc()
}

Removing SKSpriteNode with touch detection

I want to remove a node generated by this function by touching it.
func cuadrado(){
var cuadradoRojo = SKSpriteNode(imageNamed: "cuadradoRojo")
cuadradoRojo.physicsBody = SKPhysicsBody(circleOfRadius: cuadradoRojo.size.width)
cuadradoRojo.physicsBody?.dynamic = true
cuadradoRojo.physicsBody?.categoryBitMask = BodyType.cuadrado.rawValue
cuadradoRojo.physicsBody?.contactTestBitMask = BodyType.colorAzul.rawValue | BodyType.colorRojo.rawValue
cuadradoRojo.physicsBody?.collisionBitMask = 0
var actionArray3:NSMutableArray = NSMutableArray()
if gameOver == false{
let minX = circuloAzul.size.width/2
let maxX = self.frame.size.width - circuloAzul.size.width/2
let rangeX = maxX - minX
let position:CGFloat = CGFloat(arc4random()) % CGFloat(rangeX) + CGFloat(minX)
cuadradoRojo.position = CGPointMake(position, self.frame.size.height + cuadradoRojo.size.height)
addChild(cuadradoRojo)
let minDuration = 3
let duration = Int(minDuration)
func touchesBegan(touches: NSSet, withEvent event: UIEvent){
for touch: AnyObject in touches {
let location = (touch as UITouch).locationInNode(self)
if self.nodeAtPoint(location) == self.cuadradoRojo {
cuadradoRojo.removeFromParent()
}
}
}
actionArray3.addObject(SKAction.moveTo(CGPointMake(position, -cuadradoRojo.size.height), duration: NSTimeInterval(duration)))
cuadradoRojo.runAction(SKAction.sequence(actionArray3))
}
It runs perfectly but it doesn't detect the touch, if I put the touches function outside it detects the touch, but the game crashes.
Thanks for your help!

Resources