How to shoot a moving sprite in Swift? - ios

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)
}
}

Related

Swift SpriteKit touchesBegan detect right/left

So in my application I am using the code below to bounce the ball, but the problem is that the ball bounces left if I press right, and left if I press left. If I press in the middle it bouncec straight up as normal. This is the code I am using:
var PlayingFlag:Bool = false
let jumpAmount:Double = 310.0
var Ballx1:CGFloat = 0.0
var Ballx2:CGFloat = 0.0
var Ballx3:CGFloat = 0.0
var Ballx4:CGFloat = 0.0
var BallMoveAmount = (10.0, 100.0, 150.0, 200.0)
func CreateBall(){
let ballTexture = SKTexture(imageNamed: "ball")
ballTexture.filteringMode = .Nearest
self.ball = SKSpriteNode(texture: ballTexture)
self.ball!.physicsBody = SKPhysicsBody(texture: ballTexture, size: ball!.size)
self.ball!.physicsBody?.dynamic = true
self.ball!.physicsBody?.allowsRotation = true
self.ball!.physicsBody?.restitution = 0.6
self.ball!.physicsBody?.mass = 0.430 // m = 430g
self.ball!.position = CGPoint(x: self.frame.size.width / 2 , y: self.frame.size.height)
self.ball!.physicsBody?.collisionBitMask = 0x1 << 1
self.ball!.zPosition = 10
self.addChild(self.ball!)
let Ballx0 = ball!.size.width
self.Ballx1 = Ballx0 * 0.2
self.Ballx2 = Ballx0 * 0.4
self.Ballx3 = Ballx0 * 0.6
self.Ballx4 = Ballx0 * 0.8
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let ballHeight = self.ball!.size.height
for touch : UITouch in touches{
let tpoint = touch.locationInNode(self)
if tpoint.y < (self.ball!.position.y + (ballHeight / 2.0)) {
if tpoint.y > (self.ball!.position.y - (ballHeight / 2.0))
{
var xpower:Double = 0.0
let xpo = abs(tpoint.x - self.ball!.position.x)
if(xpo < self.Ballx1){
xpower = BallMoveAmount.0
}else if (xpo < self.Ballx2) {
xpower = BallMoveAmount.1
}else if (xpo < self.Ballx3) {
xpower = BallMoveAmount.2
}else if (xpo < self.Ballx4) {
xpower = BallMoveAmount.3
}else{
return
}
if xpo > 0 {
xpower = xpower * -1
}
self.ball!.physicsBody?.velocity = CGVector.zero
self.ball!.physicsBody?.applyImpulse(CGVector(dx: xpower, dy: self.jumpAmount))
self.ball!.runAction(se_ball)
if !self.PlayingFlag { // initial touch
self.gonode?.removeFromParent()
self.PlayingFlag = true
}else{
//something else
}
}
}
}
}
func didBeginContact(contact: SKPhysicsContact) {
if !self.PlayingFlag { return }
if contact.bodyA.node == self.floorSprite || contact.bodyB.node == self.floorSprite {
self.PlayingFlag = false
// Game over...
self.ball?.removeFromParent()
}
}
Try adding this function:
func vectorFrom(point pointA: CGPoint, to pointB: CGPoint) -> CGVector {
let vector = CGVector(dx: pointB.x - pointA.x, dy: 310.0)
return vector
}
And then change this line:
self.ball!.physicsBody?.applyImpulse(CGVector(dx: xpower, dy: self.jumpAmount))
to this:
let dirVector = vectorFrom(point: tpoint, to: ball!.position)
ball!.physicsBody?.applyImpulse(dirVector)
Let me know if this didn't work out for you!
Try changing
if xpo > 0 {
xpower = xpower * -1
}
To
if xpo > 0 {
xpower = xpower * 1.5
}

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

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 :)

how to run a SKAction in the direction of a joystick's degree

i am creating a game and i am having trouble making my bullets spawn in the direction of my joystick. here is the method for my bullets
func spawnBullets(){
let bullet = SKSpriteNode(imageNamed: "bullet.png")
bullet.zPosition = -5
bullet.position = CGPointMake(hero.position.x, hero.position.y)
bullet.xScale = 0.25
bullet.yScale = 0.25
bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.size)
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.enemy
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.dynamic = false
let action = SKAction.moveToY(self.size.height + 30, duration: 1.0)
let actionDone = SKAction.removeFromParent()
bullet.runAction(SKAction.sequence([action, actionDone]))
self.addChild(bullet)
}
here is how i am calling the method in my
override func didMoveToView(view: SKView) {
_ = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self,
selector: Selector ("spawnBullets"), userInfo: nil, repeats: true)
}
and here is how i coded my touches moved function (i believe that is the method that i need to edit)
override func touchesMoved(touches: Set<UITouch>, withEvent event:
UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let v = CGVector(dx: location.x - joystickBase.position.x, dy:
location.y - joystickBase.position.y)
let angle = atan2(v.dy, v.dx)
let deg = angle * CGFloat(180 / M_PI)
let length: CGFloat = joystickBase.frame.size.height / 2
let xDist:CGFloat = sin(angle - 1.5709633) * length
let yDist:CGFloat = cos(angle - 1.5709633) * length
print(deg + 180)
if (stickActive == true) {
if (CGRectContainsPoint(joystickBase.frame, location)) {
joystick.position = location
}else{
joystick.position = CGPointMake(joystickBase.position.x -
xDist, joystickBase.position.y + yDist)
}
}
}
}
You are only moving the bullets in a vertical direction:
let action = SKAction.moveToY(self.size.height + 30, duration: 1.0)
Not sure if this is a twin stick game or not.
To get sprites to go based on the joystick, just do the following
let accel = 10 //accel is the # of pixels the sprite moves per frame, do not use moveTo, because moveTo will change the speeds of your sprite based on distance
let action = SKAction.moveByX(joystick.x * accel,y:joystick.y * accel,duration:1)
This of course is based on the understanding that your joystick coordinates go from -1.0 to 1.0 on each axis, and that point up or right gets you a 1.0 result
I would not even do this approach though, SKActions are not a good choice for things that are not automated. You should be updating the position of the sprite at some point in your update phase
sprite.position.x += joystick.x * accel
sprite.position.y += joystick.y * accel

Moving objects to the center of the screen

I'm having a coding issue right now. I have a two walls/blocks moving to the center of the screen, but when they reach the center they don't stop once they reached the center. How do I make them stop after they reach/touch each other/the center.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
if (playButton.containsPoint(location))
{
playButton.removeFromParent()
title.removeFromParent()
//Wall Timer
wallTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: ("walls"), userInfo: nil, repeats: false)
//Physics World
self.physicsWorld.gravity = CGVectorMake(0.0, -5.0)
}
else
{}
}
}
func didBeginContact(contact: SKPhysicsContact)
{
if contact.bodyA.node != nil && contact.bodyB.node != nil {
let firstBody = contact.bodyA.node as! SKSpriteNode
let secondBody = contact.bodyB.node as! SKSpriteNode
if ((firstBody.name == "leftWall") && (secondBody.name == "rightWall")) {
collisionWalls(firstBody, rightWall: secondBody)
}
else if ((firstBody.name == "rightWall") && (secondBody.name == "leftWall")) {
collisionWalls(secondBody, rightWall: firstBody)
}
}
}
func collisionWalls(leftWall : SKSpriteNode, rightWall : SKSpriteNode)
{
leftWall.removeAllActions()
rightWall.removeAllActions()
}
func walls() {
let leftWall = SKSpriteNode(imageNamed: "blue background1")
let rightWall = SKSpriteNode(imageNamed: "blue background1")
//Left Wall Code
leftWall.size = CGSizeMake(300, 90)
leftWall.position = CGPoint(x: scene!.frame.width / 6, y: scene!.frame.height / 6)
leftWall.zPosition = 1.0
leftWall.physicsBody = SKPhysicsBody(rectangleOfSize: leftWall.size)
leftWall.physicsBody?.affectedByGravity = false
leftWall.physicsBody?.dynamic = false
leftWall.name = "leftWall"
leftWall.physicsBody?.categoryBitMask = PhysicsCatagory.leftWall
leftWall.physicsBody?.collisionBitMask = PhysicsCatagory.rightWall
leftWall.physicsBody?.contactTestBitMask = PhysicsCatagory.rightWall
leftWall.removeFromParent()
self.addChild(leftWall)
//Right Wall Code
rightWall.size = CGSizeMake(300, 90)
rightWall.position = CGPointMake(self.size.width * 0.87, scene!.frame.height / 6)
rightWall.zPosition = 1.0
rightWall.physicsBody = SKPhysicsBody(rectangleOfSize: rightWall.size)
rightWall.physicsBody?.affectedByGravity = false
rightWall.physicsBody?.dynamic = false
rightWall.name = "rightWall"
rightWall.physicsBody?.categoryBitMask = PhysicsCatagory.rightWall
rightWall.physicsBody?.collisionBitMask = PhysicsCatagory.leftWall
rightWall.physicsBody?.contactTestBitMask = PhysicsCatagory.leftWall
rightWall.removeFromParent()
self.addChild(rightWall)
//Right and Left Wall actions
let moveLeft = SKAction.moveToX(scene!.frame.width * 1.35, duration: 5.0)
let moveRight = SKAction.moveToX(self.size.width * -0.59, duration: 5.0)
leftWall.runAction(SKAction.sequence([moveLeft]))
rightWall.runAction(SKAction.sequence([moveRight]))
}
The SKActions are not stopping at the middle of the screen. Try this:
let moveLeft = SKAction.moveToX(scene!.frame.width / 2, duration: 5.0)
let moveRight = SKAction.moveToX(scene!.frame.width / 2, duration: 5.0)
Have you tested to see if func didBeginContact(contact: SKPhysicsContact) gets fired?If it does not, than this is a problem I once had, Until I realized that this class (the one that contains func didBeginContact(contact: SKPhysicsContact)) must be set as the SKScene's contactdelegateafter that it should work.(if not please say so)
Or in your case: you should make sure self.contactdelegate = self.

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