So Ive been working on a game and have a little sprite that is currently running in place. I need to make him jump up whenever the user taps the screen to dodge obstacles. I also need him to return to his starting point on the ground after he jumps.
You can try something like this!
import SpriteKit
class GameScene: SKScene {
var player: SKSpriteNode?
override func didMove(to view: SKView) {
player = self.childNode(withName: "player") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
func touchDown(atPoint pos: CGPoint) {
jump()
}
func jump() {
player?.texture = SKTexture(imageNamed: "player_jumping")
player?.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 500))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
func touchUp(atPoint pos: CGPoint) {
player?.texture = SKTexture(imageNamed: "player_standing")
}
}
On the player's attribute inspector window make sure you select 'Dynamic' and 'Affected By Gravity' options only, otherwise it will not fall back to the ground after the jump.. :)
I don't think physics would be overkill for a simple game as someone said in the previous answer... It's not a big deal for an iPhone or iPad hardware, they have a crazy computing power.
Using physics is overkill for a game like this. Not even the 2D Mario games use physics so I doubt you will need it.
As a very simple implementation you can use a sequence of SKActions.
// move up 20
let jumpUpAction = SKAction.moveBy(x: 0, y: 20, duration: 0.2)
// move down 20
let jumpDownAction = SKAction.moveBy(x: 0, y: -20, duration: 0.2)
// sequence of move yup then down
let jumpSequence = SKAction.sequence([jumpUpAction, jumpDownAction])
// make player run sequence
player.run(jumpSequence)
That should work. I may have mistyped something in the browser though.
You can tweak this to make the jump look more natural. Maybe add a few more steps to the jump process to change the speed of the rise/fall over time to make it look like he's accelerating. etc...
Related
I'm writing a Pong-like game using SpriteKit and Swift 5 for learning purpose. And I'm trying to made the player's paddle (an SKNode rectangle) move across the screen in the x coordinate but couldn't.
I've tried this simple solution:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
playerPaddle.position.x = location.x
}
It works but seems like the paddle only moves on clicks (touches), but not drags.
Also I've tried using the UIPanGestureRecognizer, but I don't really understand how to call it, here is the code which I copied from the official Apple Developer's doc and changed a little to match my situation:
#IBAction func panPiece(_ gestureRecognizer : UIPanGestureRecognizer) {
guard gestureRecognizer.view != nil else {return}
let piece = gestureRecognizer.view!
let translation = gestureRecognizer.translation(in: piece.superview)
if gestureRecognizer.state == .began {
self.initialPos = piece.center
}
else if gestureRecognizer.state != .changed {
let newPos = CGPoint(x: initialPos.x + translation.x, y: initialPos.y)
piece.center = newPos
}
}
Your touchesMoved solution works for me, so I'd assume that something else is amiss. Regardless, I can offer an alternative:
I also built Pong to learn Swift and SpriteKit, and I implemented the paddle movement using SKActions. Any time a touch begins, cancel the paddle's current actions and begin moving to the new x location.
If you want to keep it simple to start, then you can have a constant move time, like 0.5 seconds. Otherwise, you can calculate the time to move by getting the absolute distance between the paddle and touch location and dividing by some constant. Changing the constant will change the speed at which the paddle moves.
Here's an example with the constant move time:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if playerPaddle.hasActions() { playerPaddle.removeAllActions() }
for touch in touches {
let newX = touch.location(in: self).x
let oldX = playerPaddle.position.x
playerPaddle.run(.moveBy(x: newX - oldX, y: 0, duration: 0.5))
}
}
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
I'm subclassing nodes to use for touch detection. I have a box parent which has a line child directly next to it, with the gray space being just empty space:
The problem is when I click the gray space, it registers as a touch on the box, which is quite far away.
Here's the code where I show the problem, and my crappy workaround... I make two sets of boxes, the first being the one showing the problem, and the second being the one with the workaround:
import SpriteKit
class GameScene: SKScene {
enum sizez {
static let
box = CGSize(width: 50, height: 35),
line = CGSize(width: 200, height: 10)
}
class Box: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched box")
}
}
class Line: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched line")
}
}
override func didMove(to view: SKView) {
// The problem sprites that register touch despite empty space:
let box = Box(color: .red, size: sizez.box)
box.isUserInteractionEnabled = true
addChild(box)
let line = Line(color: .purple, size: sizez.line)
line.isUserInteractionEnabled = true
line.anchorPoint = CGPoint.zero
line.position = CGPoint(x: box.frame.maxX + 10, y: box.frame.minY)
///////////////////
box.addChild(line)
///////////////////
// These sprites detect touches properly (no detection in empty space)
let box2 = Box(color: .red, size: sizez.box)
box2.isUserInteractionEnabled = true
box2.position.y -= 100
addChild(box2)
let line2 = Line(color: .purple, size: sizez.line)
line2.isUserInteractionEnabled = true
line2.anchorPoint = CGPoint.zero
line2.position = CGPoint(x: box2.frame.maxX + 10, y: box2.frame.minY)
////////////////
addChild(line2)
////////////////
}
}
When you click directly above (or even farther out from) the top line, you get this:
When you do the same for the bottom line, you get this:
It will be a huge hassle to forgo SK's built in parent / child system, and then for me to keep track of them on my own manually... as well as it being a big performance hit for my app.
Any reasonable workaround or solution that lets me click in the gray space while using code similar to the first box would be greatly appreciated.
UPDATE:
By making an invisible background node and setting its zPositon 1 less, I can now click in the gray space and it registers as the background node, not the box.
let bkgSize = CGSize(width: 1000, height: 1000)
let bkg = Bkg(color: .gray, size: bkgSize)
bkg.isUserInteractionEnabled = true
bkg.zPosition -= 1
addChild(bkg)
But still, why is this empty space touch being registered as a box touch in the absence of a background node?
You're absolutely correct with a background node it works as expected, without a background sprite it gives the results as described by #Confused. I was able to get it to work as expected without a background by just fine tuning the TouchesBegan function like so...
class Box: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: parent!)
if frame.contains(touchLocation) {
print("touched box")
}
}
}
}
class Line: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: parent!)
if frame.contains(touchLocation) {
print("touched line")
}
}
}
}
From my understanding of how bounds work for parented objects, I think this is what's going on...
Addendum
This is slightly related, and may help in understanding why this is happening, and why Apple considers this to be how a parent and its children should use an accumulated (combined) rectangle/quad for touch response:
*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.
I'm new to coding. I have created a scene in swift sprite kit with a ball and floor. When executed, the ball drops and bounces on the floor. I am looking for a bit advise on how to move and drop the ball with touches.
To detect drag ball you have to create UIPanGestureRecognizer and add it to the ball's view
let dragBall = UIPanGestureRecognizer(target: self, action:"dragBall:")
ball.addGestureRecognizer(dragBall)
Next step is implementing "dragBall" function which will handle drag event, example:
#IBAction func dragBall(recognizer: UIPanGestureRecognizer) {
let point = recognizer.locationInView(self.view);
ball.center.x=point.x
ball.center.y=point.y
}
You should also disable gravity for this object for time of dragging, you can check if Pan gesture ended by checking state of gesture recognizer, if state is UIGestureRecognizerStateEnded you should add gravity again
import SpriteKit
class GameScene: SKScene {
let ball = SKSpriteNode(imageNamed: "ball")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
ball.position = CGPoint(x:500,y:500)
ball.anchorPoint = CGPoint(x:0.0,y:0.0)
ball.size = CGSize(width: 60, height: 60)
// physics
ball.physicsBody = SKPhysicsBody(circleOfRadius: 1)
// this defines the mass, roughness and bounciness
ball.physicsBody.friction = 0.2
ball.physicsBody.restitution = 0.8
ball.physicsBody.mass = 0.1
// this will allow the balls to rotate when bouncing off each other
ball.physicsBody.allowsRotation = true
self.addChild(ball)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent)
{ ball.position = touches.anyObject().locationInNode(self); }
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
by the end of the day i worked out this bit of code but it seems very hard to keep hold of ball