I am trying to make a maze app. The goal is to move a ball (drag the ball with your finger) through a "maze" like level which has traps and other dangers. However, when I made it so the ball/player moves when its dragged, it started to now phase though the other sprite nodes instead of staying inside of the level. How can I make it so it doesn't go outside of the level?
Code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKSpriteNode()
var danger1 = SKSpriteNode()
var danger2 = SKSpriteNode()
var goal = SKSpriteNode()
override func didMove(to view: SKView) {
ball = self.childNode(withName: "ball") as! SKSpriteNode
danger1 = self.childNode(withName: "danger1") as! SKSpriteNode
danger2 = self.childNode(withName: "danger2") as! SKSpriteNode
goal = self.childNode(withName: "goal") as! SKSpriteNode
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 0
print("x: \(ball.position.x), y: \(ball.position.y)")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
ball.position.x = location.x
ball.position.y = location.y
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
Screenshot of game:
Maze Level
You need to set up a collisionBitMask for your nodes and set up your desired collision rules accordingly (i.e. prevent ball from clipping through the maze walls or dangers):
I'd recommend looking at the corresponding apple developer docs: https://developer.apple.com/documentation/spritekit/skphysicsbody/1520003-collisionbitmask
This other question's answer may also prove helpful: What are Sprite Kit's "Category Mask" and "Collision Mask"?
This was also a video that I found helpful when learning about bitmasks for detecting collisions & contact between SKSpriteNodes: https://youtu.be/467Doas5J6I?t=1114
*Edit*
You will also need to set up contact detection via contactTestBitMask so that you know when the ball has contacted the wall, at which point you can implement logic to ‘drop’ it from the dragging gesture. As #Steve Ives correctly noted, the dragging will override the collision physics and you would still get clipping without this.
Your major problem is you are moving your ball directly, ala hand of god.
What you need to do is play by the systems rules. Instead of applying the position directly, set the velocity of your ball to push your ball to where your finger is. This requires some math:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
//set the location's coordinate system to match the ball
let location = touch.location(in: ball.parent)
let angle = atan2(location.x,location.y)
let velocity = 1.0f //(change this to whatever speed you want your ball to move at)
ball.velocity = CGVector(velocity * cos(angle),velocity * sin(angle)) // you may have to swap around the sin/-sin/cos/-cos to get it the way you like it, I am assuming at angle 0 your ball is facing right, and it rotates in a counter clockwise direction)
}
}
Now your ball will forever be pushed towards the touching point.
To avoid bouncing, make sure you set the restitution to 0 on the physics body so that it loses all of its energy when it hits a wall.
Also check categoryBitmask is of some power of 2 on all objects that require physics (1 works in your example) so that it can collide with whatever is in its way
Related
I want to create a game in which the player draws a line with physics, similar to the game Happy Glass.
What I have tried:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
touchPosition = location
}
}
func draw(pos: CGPoint){
let node = SKSpriteNode(imageNamed: "circle")
node.position = CGPoint(x: pos.x, y: pos.y)
node.size = CGSize(width: 10, height: 10)
node.physicsBody = SKPhysicsBody(circleOfRadius: node.frame.width / 2)
//set up the physics
self.addChild(node)
}
override func didSimulatePhysics() {
if touching{
draw(pos: touchPosition)
}
}
So I basically change the touchPosition variable every time a touch is moved(when the user drags their finger across the screen) then I have didSimulatePhysics as repeating timer to add small circles at the touchPosition with physics on them.
The Problem:
This all works. The only problem is that they are too spread out. I want it to be a continuous line but the touchesMoved method is not recognizing enough touches so it looks like this.
Is there a way to recognize touches more frequently so I can fill an entire line or some way to possibly do this with a CGPath? I just can't have these gaps in between these points basically because I can't have other nodes with physicBodies falling between the cracks.
The other thing is that the points are constantly moving off the screen with a SKAction, so I can just draw a CGPath line from the current touch to the last touch because the point from the last touch will have moved slightly so i'm kind of confused in what to do and any suggestions would be helpful.
Thanks
I'm new with sprite kit. I have tried simple ball bouncing game with 2 player, another is tracking the ball slowly. But I have discovered a problem. When I move the line to ball (with edge) ball disappearing from the screen. Another times not a problem, ball bouncing. What is the problem?
I have one GameScene, sks and ViewController. My sprite nodes coming from sks. If someone explain this case. It would be better. I have attached what I did below.
My GameScene:
class GameScene: SKScene {
var ball = SKSpriteNode()
var enemy = SKSpriteNode()
var main = SKSpriteNode()
override func didMove(to view: SKView) {
ball = self.childNode(withName: "ball") as! SKSpriteNode
enemy = self.childNode(withName: "enemy") as! SKSpriteNode
main = self.childNode(withName: "main") as! SKSpriteNode
ball.physicsBody?.applyImpulse(CGVector(dx: -20, dy: -20))
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.angularDamping = 0
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
self.physicsBody = border
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 0.5))
}
View controller:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
}
}
override var prefersStatusBarHidden: Bool {
return true
}
Pad settings:
Ball settings:
Some updates
I have tried some messages in update function, then encountered with same case ball goes outside from left side of the device (using iPhone 6S)
2016-12-08 14:27:54.436485 Pong[14261:3102941] fatal error: ball out of left bounds: file
You're pinching the ball against the wall, with the enemy. This means that the force is eventually enough to create enough speed of ball movement/force to overcome the physics system, so it pops through the wall. If you make your enemy stop before it pinces the ball against the wall, you should be fine.
This 'pincing' is occurring because of this line of code:
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 0.5))
This is making the enemy chase the ball, which is a good idea for a ball game, but for the way it's being moved is wrong. Using an Action means the enemy has infinite force applied to it, and is aiming for the middle of the ball.
So when the ball gets to the wall, it's stopped against a physics object with infinite static force, then this enemy comes along and applies infinite force from the other side... and the ball either pops inside the bounds of the enemy, or over the other side of the wall, because it's being crushed by infinite forces.
So you either need to take very good care of how you control the enemy with Actions, or use forces to control the enemy, as these won't be infinite, and the physics system will be able to push back on the enemy.
How easy is it to reproduce the problem? In update(), print the ball's position to see where it is when it has 'disappeared'. (this will produce a lot of output, so be warned).
From what you've posted, it doesn't look like the ball is set to collide with the border, meaning the ball will not react (i.e. bounce off) the border and the border itself is immobile (as it's an edge-based physics body). This, combined with a high ball velocity (from a hard hit) might make it possible that you have hit the ball so hard with the 'main' sprite that it's gone through the border - using preciseCollisionDetection=true might resolve this but give the border a category first and add this to the ball's collisionBitMask.
here is an example of what Steve is saying (in your .update())
if ball.position.x > frame.maxX { fatalError(" ball out of right bounds") }
if ball.position.x < frame.minX { fatalError(" ball out of left bounds") }
if ball.position.y > frame.maxY { fatalError(" ball out of top bounds") }
if ball.position.y < frame.minY { fatalError(" ball out of bottom bounds) }
you could also just spam your debug window:
print(ball.position)
This will help you to find out what is going on--if your ball is flying through the boundary, or if it's getting destroyed somewhere, or some other possible bug.
As a workaround (for now) I would just replace the above "fatalError" with "ball.position = CGPoint(x: 0, y: 0)" or some other position to "reset" the ball in case of it getting lost.
You could even store it's last position in a variable, then restore it to that should the above if-statements trigger.
var lastBallLocation = CGPoint(x: 0, y: 0) // Just to initialize
override func update( prams ) {
if ball.position.x > frame.maxX { ball.position = lastBallLocation }
// .. copy the other three cases
lastBallLocation = ball.position // update only on successful position
Or, you could try making the walls thicker (use a shape node or spritenode and lay them on the outside of the frame such as the walls of a house, and your view on screen is the "room")
each wall also has a physics body for bouncing:
*I searched most places and could not find a "basic solution" to my question
Hi there, I am trying to create a simple SKSpriteNode that will move along with my finger anywhere on the screen (x & y coords). So far what I have done is the code below, and I can't seem to figure out much else because my Sprite won't even move when the game is ran. I am also getting an error at:
simpleS = self.childNode(withName: "simpleS") as! SKSpriteNode
This line of code causes my program to not run since I've added it. Please and thanks for your help.
In this case Sprite "simpleS" is the Sprite I am trying to move when touches begin.
CODE STARTS HERE:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var simpleS = SKSpriteNode()
override func didMove(to view: SKView) {
simpleS = self.childNode(withName: "simpleS") as! SKSpriteNode
simpleS.physicsBody?.affectedByGravity = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
simpleS.run(SKAction.moveTo(x: location.x, duration: 0.0))
simpleS.run(SKAction.moveTo(y: location.y, duration: 0.0))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
simpleS.run(SKAction.moveTo(x: location.x, duration: 0.0))
simpleS.run(SKAction.moveTo(y: location.y, duration: 0.0))
}
}
}
this code
simpleS = self.childNode(withName: "simpleS") as! SKSpriteNode
connects the sprites in your GameScene.sks with GameScene.swift
so in the first step you have to make sure that you set the right name (exactly "simpleS") in your sks file.
if you did it right, so please send the error for us.
i have built and ran your code and everything works in a right way.
another point is that if you want your sprites only moves by your finger movement you have to delete the codes in touchesBegan function, because touchesBegan moves your sprite wherever you touch.
Im just messing around in sprite-kit working on my game design skills and I came across something that I dont know how to do. That is make the physicsworld gravity more like moon gravity. I have a SKSpriteNode which is a red ball and I can drag that ball and click wherever I want it to go. What I wanted to create is somewhat of a throwing mechanism. Is this possible with one line of physics world gravity code? Or is it way more in depth this is code that I have.
import SpriteKit
class GameScene: SKScene {
let redBall = SKSpriteNode(imageNamed: "redball")
override func didMoveToView(view: SKView) {
let worldBorder = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody = worldBorder
self.physicsBody?.friction = 1
self.physicsWorld.gravity = CGVectorMake(0, -1)
redBall.position = CGPoint(x:250,y:50)
addChild(redBall)
redBall.physicsBody = SKPhysicsBody(circleOfRadius: redBall.size.width/2)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let location = touch.locationInNode(self)
redBall.position = location
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let location = touch.locationInNode(self)
redBall.position = location
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
My game state right now is when I drag a ball to where I want and release it just drops. What I want to accomplish is for example if I grab the ball and drag up and to the right I want the ball to follow my the direction it was going when I released it. How would I create that in my code thanks!
In my code all I do is self.physicsworld.gravity = CGVectorMake(0, 0) (this would turn off gravity completely). Then just mess around with that vector and find something that feels like moon gravity.
You might have to make your game-scene be a SKPhysicsContactDelegate
I'm working with Swift, Sprite Kit and Xcode 6,
I would like to create a particle effect in SpriteKit a little bit like the particles effects of the balls in the iOS game named "Duet", but I don't know how to proceed, I managed to create a particle effect, but not a particle like in this game who follows a node and draw the node's path...
Here is my code :
let firstCircle = SKSpriteNode(imageNamed: "Circle")
let particle = SKEmitterNode(fileNamed: "FirstParticle.sks")
override func didMoveToView(view: SKView)
{
firstCircle.physicsBody = SKPhysicsBody(circleOfRadius: 7)
firstCircle.physicsBody?.affectedByGravity = false
particle.targetNode = firstCircle
addChild(firstCircle)
addChild(particle)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
{
for touch: AnyObject in touches
{
firstCircle.position = touch.locationInNode(self)
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent)
{
for touch: AnyObject in touches
{
firstCircle.position = touch.locationInNode(self)
}
}
To achieve a Duet like effect you need particle to be a child of the trailed node and targetNode set to the parent scene. targetNode controls which node the particles are rendered as a child of.
When particle is a child of the trailed node, it will emit particles with the trailed node as the origin. Changing targetNode to the parent scene leaves already emitted particles behind as the trailed node moves.
This code should work but you may need to fine tune FirstParticle.sks.
let firstCircle = SKSpriteNode(imageNamed: "Circle")
let particle = SKEmitterNode(fileNamed: "FirstParticle.sks")
override func didMoveToView(view: SKView)
{
firstCircle.physicsBody = SKPhysicsBody(circleOfRadius: 7)
firstCircle.physicsBody?.affectedByGravity = false
particle.targetNode = self
addChild(firstCircle)
firstCircle.addChild(particle)
}
I was able to get a similar effect and ended up creating a Playground to demonstrate it. Check it out here.