Touch Sprite, Make It Jump Up Then Fall Down Again(Repeat as Many Times as Spritenode Is Tapped.)

Touch Sprite, make it jump up then fall down again(repeat as many times as spritenode is tapped.)

I tested your code and it seems that using firstBall.userInteractionEnabled = true is the cause. Without it, it should work. I did some research (here for example), but can't figure out what's the reason of this behavior with userInteractionEnabled. Or for what reason do you use userInteractionEnabled?

Update due to update of question

First ball.physicsBody?.restitution = 3 defines the bounciness of the physics body. The default value is 0.2 and the property must be between 0.0 ans 1.0. So if you set it to 3.0 it will cause some unexpected effects. I just deleted it to use the default value of 0.2.

Second, to make the ball jump after tap and increase the score I put

physicsBody?.velocity = CGVectorMake(0, 600)
physicsBody?.applyImpulse(CGVectorMake(0, 1100))

in the touchesBegan method of the Ball class

Result

Sample Image

How to add 1 point every time SKNode is touched

Step 1

First of all let's create a class for the Ball

class Ball: SKSpriteNode {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
}

Step 2

Then inside createPlayer() let's replace this

let sprite = SKSpriteNode(imageNamed: "Ball")

with this

let sprite = Ball(imageNamed: "Ball")
sprite.userInteractionEnabled = true

Step 3

Let's remove the touchesBegan from GameScene.

Update

This code is working as expected for me

class GameScene: SKScene {

var scoreLabel: SKLabelNode!
var ball: Ball!
private var score = 0 {
didSet {
scoreLabel.text = "\(score)"
print(score)

}
}

override func didMoveToView(view: SKView) {
let ball = Ball()
ball.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(ball)
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// this is called when you tap outside of the Ball
// use self.ball to make the ball to jump
}
}

class Ball: SKSpriteNode {

init() {
let texture = SKTexture(imageNamed: "ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
userInteractionEnabled = true
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Sample Image

Make an SKSpriteNode fall when the screen is not being tapped

trying setting your gravity in the game to
self.physicsWorld.gravity = CGVectorMake(0,-9.8);
and setting your SKSpriteNode to dynamic.

test.physicsbody.dynamic = YES;

SKAction scale physicsBody

If you set the size of the physicsbody to the size of the circle, you may achieve what you seek.

_circle = [SKSpriteNode spriteNodeWithImageNamed:@"cirlce"];
_circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_circle.size.width/2];

Sprite Kit set Min. and Max. for Jump

Here is an working example on how to make something like:

  • long pressed jump based on duration of press
  • short (one tap jump)
  • restrict character to jump while in the air
  • keep character jumping while finger is on screen

Code (Swift 4.x)

import SpriteKit

struct Constants {
static let minimumJumpForce:CGFloat = 15.0
static let maximumJumpForce:CGFloat = 30.0
static let characterSideSpeed:CGFloat = 18.0
}

class GameScene: SKScene,SKPhysicsContactDelegate
{
let CharacterCategory : UInt32 = 0x1 << 1
let PlatformCategory : UInt32 = 0x1 << 2
let WallCategory : UInt32 = 0x1 << 3

var force: CGFloat = 16.0 //Initial force
var pressed = false
var isCharacterOnGround = false // Use this to prevent jumping while in the air
let character = SKSpriteNode(color: .green, size: CGSize(width: 30, height:30))
let debugLabel = SKLabelNode(fontNamed: "Geneva")

override func didMove(to view: SKView)
{
//Setup contact delegate so we can use didBeginContact and didEndContact methods
physicsWorld.contactDelegate = self
physicsWorld.speed = 0.5
//Setup borders so character can't escape from us :-)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
self.physicsBody?.categoryBitMask = WallCategory
self.physicsBody?.collisionBitMask = CharacterCategory

//Setup character
character.position = CGPoint(x: 150, y: 150)
character.physicsBody = SKPhysicsBody(rectangleOf: character.size)
character.physicsBody?.categoryBitMask = CharacterCategory
character.physicsBody?.contactTestBitMask = PlatformCategory
character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
character.physicsBody?.allowsRotation = false
character.physicsBody?.isDynamic = true
character.physicsBody?.restitution = 0.1

self.addChild(character)

generatePlatforms()

debugLabel.text = " DEBUG: "
debugLabel.fontColor = .white
debugLabel.fontSize = 12.0
debugLabel.position = CGPoint(x: frame.midX, y: frame.midY+100)
self.addChild(debugLabel)
}

func generatePlatforms(){
for i in 1...4
{
let position = CGPoint(x: frame.midX, y: CGFloat(i)*140.0 - 100)
let platform = createPlatformAtPosition(position: position)
self.addChild(platform)
}
}

func createPlatformAtPosition(position : CGPoint)->SKSpriteNode{

let platform = SKSpriteNode(color: .green, size: CGSize(width: frame.size.width, height:20))

platform.position = position

platform.physicsBody = SKPhysicsBody(
edgeFrom: CGPoint(x: -platform.size.width/2.0, y:platform.size.height/2.0),
to:CGPoint(x: platform.size.width/2.0, y: platform.size.height/2.0))

platform.physicsBody?.categoryBitMask = PlatformCategory
platform.physicsBody?.contactTestBitMask = CharacterCategory
platform.physicsBody?.collisionBitMask = CharacterCategory
platform.physicsBody?.allowsRotation = false
platform.name = "platform"
platform.physicsBody?.isDynamic = false
platform.physicsBody?.restitution = 0.0

return platform
}

func jump(force : CGFloat){
if(self.isCharacterOnGround){
self.character.physicsBody?.applyImpulse(CGVector(dx: 0, dy: force))
self.character.physicsBody?.collisionBitMask = WallCategory
self.isCharacterOnGround = false
}
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.pressed = true

let timerAction = SKAction.wait(forDuration: 0.05)

let update = SKAction.run({
if(self.force < Constants.maximumJumpForce){
self.force += 2.0
}else{
self.jump(force: Constants.maximumJumpForce)
self.force = Constants.maximumJumpForce
}
})

let sequence = SKAction.sequence([timerAction, update])
let repeat_seq = SKAction.repeatForever(sequence)
self.run(repeat_seq, withKey:"repeatAction")
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

self.removeAction(forKey: "repeatAction")
self.jump(force: self.force)
self.force = Constants.minimumJumpForce
self.pressed = false

}

override func update(_ currentTime: TimeInterval) {

debugLabel.text = "DEBUG: onTheGround : \(isCharacterOnGround), force \(force)"

if(character.position.x <= character.size.width/2.0 + 5.0 && character.physicsBody!.velocity.dx < 0.0 ){
character.physicsBody?.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
}else if((character.position.x >= self.frame.size.width - character.size.width/2.0 - 5.0) && character.physicsBody!.velocity.dx >= 0.0){
character.physicsBody?.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
}else if(character.physicsBody!.velocity.dx > 0.0){
character.physicsBody!.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
}else{
character.physicsBody!.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
}
}

func didBegin(_ contact: SKPhysicsContact) {

var firstBody, secondBody: SKPhysicsBody

if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}

if ((firstBody.categoryBitMask & CharacterCategory) != 0 &&
(secondBody.categoryBitMask & PlatformCategory != 0)) {

let platform = secondBody.node! as! SKSpriteNode
// platform.color = UIColor.redColor()
let platformSurfaceYPos = platform.position.y + platform.size.height/2.0

let player = contact.bodyB.node! as! SKSpriteNode
let playerLegsYPos = player.position.y - player.size.height/2.0

if((platformSurfaceYPos <= playerLegsYPos)){
character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
self.isCharacterOnGround = true

if(self.pressed){
let characterDx = character.physicsBody?.velocity.dx
character.physicsBody?.velocity = CGVector(dx: characterDx!, dy: 0.0)
self.jump(force: Constants.maximumJumpForce)
}
}
}
}

func didEnd(_ contact: SKPhysicsContact) {

var firstBody, secondBody: SKPhysicsBody

if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}

if ((firstBody.categoryBitMask & CharacterCategory) != 0 &&
(secondBody.categoryBitMask & PlatformCategory != 0)) {

let platform = secondBody.node as! SKSpriteNode
let platformSurfaceYPos = platform.position.y + platform.size.height/2.0

let player = contact.bodyB.node as! SKSpriteNode
let playerLegsYPos = player.position.y - player.size.height/2.0

if((platformSurfaceYPos <= playerLegsYPos) && ((character.physicsBody?.velocity.dy)! > CGFloat(0.0))){
character.physicsBody?.collisionBitMask = WallCategory
self.isCharacterOnGround = false
}
}
}
}

Note that this is simple example, and in real application you will probably have to handle states like isOnTheGround in a different way. Right now, to determine if character is on the ground you just set isOnTheGround = true when character make a contact with platform, and set it to false in didEndContact...But there are situations when character can be in contact with platform while in the air (eg. side contact)...

EDIT:

I changed the code to let the player jump while pressed. Here is the result:

Sample Image

Important:

Actual platform implementation and contact handling is up to you and this is not tested. The only purpose of this example is to show you how to jump while pressed. Currently, physicsWorld.speed is set to 0.5 to make animation slower because its easier to debug like that, but you can change this to default value (1.0).

So, as you can see from the image, while player is on the first platform some small jumps are presented (by simple tapping, or short pressing). Then (player is still on first platform) long press has been made, and player has jumped on second platform. After that, another long press is done, but this time without releasing, and player starts jumping from one platform to another using maximum force.

This needs a lot of tweaking and proper platform & contact detection, but it can give you an idea about how to implement jumping you asked about.

SpriteKitNode not moving

First thoughts I have to check the PhysicsBody has been initialised - perhaps your optional ? unwrap is failing. So add in the following line below your var ball declaration:

ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.frame.size)

It may not be the ideal physics body shape, but this should allow your ball to move as per the impulse.

How would I move my node up when there is a touch and hold on the screen in Swift Spritekit?

Firstly, you need to keep track of when the user is touching and holding the screen. You can do this like so, in your SKScene:

var touchingScreen = false

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
touchingScreen = true
}

override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
touchingScreen = false
}

override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
touchingScreen = false
}

Secondly, you now need to make the object move upwards whilst the user is pressing the screen, again in your SKScene:

override func update(currentTime: CFTimeInterval) {
if touchingScreen {
// Adjust the CGVector to suit your needs.
sprite.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 50))
}
}

Alternatively, if you want your object to move upwards at a constant velocity, replace:

sprite.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 50))

with

sprite.physicsBody!.velocity = CGVector(dx: yourDx, dy: yourDy)

Hope that helps!

Simulate water/ make sprite float on water in spritekit

There are three possible options I believe you have with regards to simulating water.

1) As mentioned in the comments you could try to use SKFieldNode (iOS 8+). But from personal experience the field node didn't really do it for me because you don't get much control over your simulation with it unless you heavily customize it, in which case you might as well just do your own calculations from scratch and reduce complexity.

2) You could adjust the linear and rotational damping of your sprite when inside the water. In fact, even apple mentions this in the quote from their documentation. However this won't give you buoyancy.

The linearDamping and angularDamping properties are used to calculate
friction on the body as it moves through the world. For example, this
might be used to simulate air or water friction.

3) Perform the calculations yourself. In the update method, check when the body enters you "water" and when it does you can calculate viscosity and/or buoyancy and adjust the velocity of your node accordingly. This in my opinion is the best option but also the more difficult.

Edit: I just wrote a quick example of option 3 in Swift. I think it's what you are looking for. I added factor constants on the top so you can adjust it to get exactly what you want. The motion is applied dynamically so it won't interfere with you current velocity (i.e. you can control your character while in the water). Below is the code for the scene and a gif as well. Keep in mind that the delta time is assumed to be 60 frames a second (1/60) and there is no velocity clamping. These are features you may or may not want depending on your game.

Sample Image

Swift

class GameScene: SKScene {
//MARK: Factors
let VISCOSITY: CGFloat = 6 //Increase to make the water "thicker/stickier," creating more friction.
let BUOYANCY: CGFloat = 0.4 //Slightly increase to make the object "float up faster," more buoyant.
let OFFSET: CGFloat = 70 //Increase to make the object float to the surface higher.
//MARK: -
var object: SKSpriteNode!
var water: SKSpriteNode!
override func didMoveToView(view: SKView) {
object = SKSpriteNode(color: UIColor.whiteColor(), size: CGSize(width: 25, height: 50))
object.physicsBody = SKPhysicsBody(rectangleOfSize: object.size)
object.position = CGPoint(x: self.size.width/2.0, y: self.size.height-50)
self.addChild(object)
water = SKSpriteNode(color: UIColor.cyanColor(), size: CGSize(width: self.size.width, height: 300))
water.position = CGPoint(x: self.size.width/2.0, y: water.size.height/2.0)
water.alpha = 0.5
self.addChild(water)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
}
override func update(currentTime: CFTimeInterval) {
if water.frame.contains(CGPoint(x:object.position.x, y:object.position.y-object.size.height/2.0)) {
let rate: CGFloat = 0.01; //Controls rate of applied motion. You shouldn't really need to touch this.
let disp = (((water.position.y+OFFSET)+water.size.height/2.0)-((object.position.y)-object.size.height/2.0)) * BUOYANCY
let targetPos = CGPoint(x: object.position.x, y: object.position.y+disp)
let targetVel = CGPoint(x: (targetPos.x-object.position.x)/(1.0/60.0), y: (targetPos.y-object.position.y)/(1.0/60.0))
let relVel: CGVector = CGVector(dx:targetVel.x-object.physicsBody.velocity.dx*VISCOSITY, dy:targetVel.y-object.physicsBody.velocity.dy*VISCOSITY);
object.physicsBody.velocity=CGVector(dx:object.physicsBody.velocity.dx+relVel.dx*rate, dy:object.physicsBody.velocity.dy+relVel.dy*rate);
}
}
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {object.position = (touches.anyObject() as UITouch).locationInNode(self);object.physicsBody.velocity = CGVectorMake(0, 0)}
}

Objective-C

#import "GameScene.h"
#define VISCOSITY 6.0 //Increase to make the water "thicker/stickier," creating more friction.
#define BUOYANCY 0.4 //Slightly increase to make the object "float up faster," more buoyant.
#define OFFSET 70.0 //Increase to make the object float to the surface higher.

@interface GameScene ()
@property (nonatomic, strong) SKSpriteNode* object;
@property (nonatomic, strong) SKSpriteNode* water;
@end

@implementation GameScene
-(void)didMoveToView:(SKView *)view {
_object = [[SKSpriteNode alloc] initWithColor:[UIColor whiteColor] size:CGSizeMake(25, 50)];
self.object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.object.size];
self.object.position = CGPointMake(self.size.width/2.0, self.size.height-50);
[self addChild:self.object];
_water = [[SKSpriteNode alloc] initWithColor:[UIColor cyanColor] size:CGSizeMake(self.size.width, 300)];
self.water.position = CGPointMake(self.size.width/2.0, self.water.size.height/2.0);
self.water.alpha = 0.5;
[self addChild:self.water];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
}
-(void)update:(NSTimeInterval)currentTime {
if (CGRectContainsPoint(self.water.frame, CGPointMake(self.object.position.x,self.object.position.y-self.object.size.height/2.0))) {
const CGFloat rate = 0.01; //Controls rate of applied motion. You shouldn't really need to touch this.
const CGFloat disp = (((self.water.position.y+OFFSET)+self.water.size.height/2.0)-((self.object.position.y)-self.object.size.height/2.0)) * BUOYANCY;
const CGPoint targetPos = CGPointMake(self.object.position.x, self.object.position.y+disp);
const CGPoint targetVel = CGPointMake((targetPos.x-self.object.position.x)/(1.0/60.0), (targetPos.y-self.object.position.y)/(1.0/60.0));
const CGVector relVel = CGVectorMake(targetVel.x-self.object.physicsBody.velocity.dx*VISCOSITY, targetVel.y-self.object.physicsBody.velocity.dy*VISCOSITY);
self.object.physicsBody.velocity=CGVectorMake(self.object.physicsBody.velocity.dx+relVel.dx*rate, self.object.physicsBody.velocity.dy+relVel.dy*rate);
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.object.position = [(UITouch*)[touches anyObject] locationInNode:self];
self.object.physicsBody.velocity = CGVectorMake(0, 0);
}
@end


Related Topics



Leave a reply



Submit