I am trying to make the game, but I stuck on camera movement with player. I need to set max camera and player x position, but I get infinite movement to the right or left.
I was tying to use override func didFinishUpdate()
override func didFinishUpdate() {
cam.position.x = player.position.x
}
and here I tried to set world size
worldNode = SKSpriteNode()
worldNode?.size.width = backGroundImage.size.width
self.addChild(worldNode!)
Please help
func keepPlayerInBounds() {
if player.position.x < frame.minX + player.size.width/2 {
player.position.x = frame.minX + player.size.width/2
}
}
put this in update, then add the other 3 boundaries (the above is the left boundary)
this also assumes player.anchorPoint is 0.5
Related
I am creating a sprite-kit Swift Xcode game. The game has coins in the levels and I used a method learned from an online instructor of using red color sprite boxes to add coins in them. The method worked, and now I have coins replacing the place of what used to be the red color sprite.
In the game, I added SKPhysics to every sprite in it. As soon as I ran the project, a random transparent box sprite appeared, adjacent to the position of the coins (I know because my ball bumped into it and stopped and also that white lines formed around it because I let the game show the physics on the screen).
I've tried changing the anchor point of the red box because I thought it was a problem of positioning but that didn't work. Below is the code I used for adding the coins on the screen
Where I called it (the default of a switch statement of adding physics to every special sprite depending on their name in it) :
switch name {
case "Enemy":
PhysicsHelper.addPhysicsBody(to: sprite, with: name)
default:
let component = name.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
if let rows = Int(component[0]), let columns = Int(component[1]) {
calculateGridWith(rows: rows, columns: columns, parent: sprite)
}
}
}
The functions:
static func calculateGridWith(rows: Int, columns: Int, parent: SKSpriteNode) {
parent.color = UIColor.clear
for x in 0..<columns {
for y in 0..<rows {
let position = CGPoint(x: x, y: y)
addCoin(to: parent, at: position, columns: columns)
}
}
}
static func addCoin(to parent: SKSpriteNode, at position: CGPoint, columns: Int) {
let coin = SKSpriteNode(imageNamed: GameConstants.StringConstants.coinImageName)
coin.size = CGSize(width: parent.size.width/CGFloat(columns), height: parent.size.width/CGFloat(columns))
coin.name = GameConstants.StringConstants.coinName
coin.position = CGPoint(x: position.x * coin.size.width + coin.size.width/2, y: position.y * coin.size.height + coin.size.height/2)
let coinFrames = AnimationHelper.loadTextures(from: SKTextureAtlas(named: GameConstants.StringConstants.coinRotateAtlas), withName: GameConstants.StringConstants.coinPrefixKey)
coin.run(SKAction.repeatForever(SKAction.animate(with: coinFrames, timePerFrame: 0.1)))
PhysicsHelper.addPhysicsBody(to: coin, with: GameConstants.StringConstants.coinName)
parent.addChild(coin)
}
The functions for removing the coins after touching it:
func handleCollectible(sprite: SKSpriteNode) {
switch sprite.name! {
case GameConstants.StringConstants.coinName:
collectCoin(sprite: sprite)
default:
break
}
}
func collectCoin(sprite: SKSpriteNode) {
coins += 1
if let coinDust = ParticleHelper.addParticleEffect(name: GameConstants.StringConstants.coinDustEmitterKey, particlePositionRange: CGVector(dx: 5.0, dy: 5.0), position: CGPoint.zero) {
coinDust.zPosition = GameConstants.ZPositions.objectZ
sprite.addChild(coinDust)
sprite.run(SKAction.fadeOut(withDuration: 0.4), completion: {
coinDust.removeFromParent()
sprite.removeFromParent()
})
}
}
I've expected coins to replace the red box I drew in the SKS file. But the result is coins replacing the red box and a transparent SKPhysicsBody/sprite appearing on the screen adjacent to the coins.
Does anyone know how to fix this bug?
Thanks!
I have an SKSpriteNode that moves back and forth.
I need to get it's position as it moves (so it changes all the time) and get different positions as it moves across the screen.
When I try and receive the position it comes as the original position.
Object movement:
let right = SKAction.moveBy(x: self.frame.width, y: 0, duration: 3)
let left = SKAction.moveBy(x: -self.frame.width, y: 0, duration: 3)
And this is what I'm trying to do to get it's position printed as it goes.
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
pos = String(describing: blockposition.x)
print(pos)
}
Is it possible to get one that updates as it moves?
in your "class"
1_ type var sprite(the name of your sprite) = SKSpriteNode()
2_ then add your synod to the scene
3_ then in the update func call print(sprite(the name of your sprite).position.x or y) as you want hope it worked for you
You can't do it this way.
From https://developer.apple.com/documentation/spritekit/skaction?changes=_1
Observing Changes to Node Properties
Generally, actions do not call public methods on nodes. For example, if you wanted to subclass SKNode to respond to a move(to:duration:) action, you may consider overriding its position property to add a didSet observer (see Overriding Property Observers).
class MovingNode: SKSpriteNode {
override var position: CGPoint {
didSet {
// code to react to position change
}
}
}
However, because a move action running on an instance of MovingNode doesn't set its position, the observer isn't invoked and your code to react to a position change is never executed.
In this example, the solution is to use SKSceneDelegate. By comparing a node's position in the delegate's update(_:for:) method - which is called at the beginning of each frame - to its position in the delegate's didEvaluateActions(for:) method - which is called after any actions have been evaluated - you can check if it has moved and react accordingly.
Listing 7 shows an example of how you can implement this solution.
Listing 7
Responding to a position change during a running action
let node = SKNode()
var nodePosition = CGPoint()
func update(_ currentTime: TimeInterval, for scene: SKScene) {
nodePosition = node.position
}
func didEvaluateActions(for scene: SKScene) {
let distance = hypot(node.position.x - nodePosition.x,
node.position.y - nodePosition.y)
if distance > 0 {
// code to react to position change
}
}
I am new to Swift and SpriteKit and am learning to understand the control in the game "Fish & Trip". The sprite node is always at the center of the view and it will rotate according to moving your touch, no matter where you touch and move (hold) it will rotate correspondingly.
The difficulty here is that it is different from the Pan Gesture and simple touch location as I noted in the picture 1 and 2.
For the 1st pic, the touch location is processed by atan2f and then sent to SKAction.rotate and it is done, I can make this working.
For the 2nd pic, I can get this by setup a UIPanGestureRecognizer and it works, but you can only rotate the node when you move your finger around the initial point (touchesBegan).
My question is for the 3rd pic, which is the same as the Fish & Trip game, you can touch anywhere on the screen and then move (hold) to anywhere and the node still rotate as you move, you don't have to move your finger around the initial point to let the node rotate and the rotation is smooth and accurate.
My code is as follow, it doesn't work very well and it is with some jittering, my question is how can I implement this in a better way? and How can I make the rotation smooth?
Is there a way to filter the previousLocation in the touchesMoved function? I always encountered jittering when I use this property, I think it reports too fast. I haven't had any issue when I used UIPanGestureRecoginzer and it is very smooth, so I guess I must did something wrong with the previousLocation.
func mtoRad(x: CGFloat, y: CGFloat) -> CGFloat {
let Radian3 = atan2f(Float(y), Float(x))
return CGFloat(Radian3)
}
func moveplayer(radian: CGFloat){
let rotateaction = SKAction.rotate(toAngle: radian, duration: 0.1, shortestUnitArc: true)
thePlayer.run(rotateaction)
}
var touchpoint = CGPoint.zero
var R2 : CGFloat? = 0.0
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches{
let previousPointOfTouch = t.previousLocation(in: self)
touchpoint = t.location(in: self)
if touchpoint.x != previousPointOfTouch.x && touchpoint.y != previousPointOfTouch.y {
let delta_y = touchpoint.y - previousPointOfTouch.y
let delta_x = touchpoint.x - previousPointOfTouch.x
let R1 = mtoRad(x: delta_x, y: delta_y)
if R2! != R1 {
moveplayer(radiant: R1)
}
R2 = R1
}
}
}
This is not an answer (yet - hoping to post one/edit this into one later), but you can make your code a bit more 'Swifty' by changing the definition for movePlayer() from:
func moveplayer(radian: CGFloat)
to
rotatePlayerTo(angle targetAngle: CGFloat) {
let rotateaction = SKAction.rotate(toAngle: targetAngle, duration: 0.1, shortestUnitArc: true)
thePlayer.run(rotateaction)
}
then, to call it, instead of:
moveplayer(radiant: R1)
use
rotatePlayerTo(angle: R1)
which is more readable as it describes what you are doing better.
Also, your rotation to the new angle is constant at 0.1s - so if the player has to rotate further, it will rotate faster. it would be better to keep the rotational speed constant (in terms of radians per second). we can do this as follows:
Add the following property:
let playerRotationSpeed = CGFloat((2 *Double.pi) / 2.0) //Radian per second; 2 second for full rotation
change your moveShip to:
func rotatePlayerTo(angle targetAngle: CGFloat) {
let angleToRotateBy = abs(targetAngle - thePlayer.zRotation)
let rotationTime = TimeInterval(angleToRotateBy / shipRotationSpeed)
let rotateAction = SKAction.rotate(toAngle: targetAngle, duration: rotationTime , shortestUnitArc: true)
thePlayer.run(rotateAction)
}
this may help smooth the rotation too.
I am making a simple game in SpriteKit, and I have a scrolling background. What simply happens is that a few background images are placed adjacent to each other when the game scene is loaded, and then the image is moved horizontally when it scrolls out of the screen. Here is the code for that, from my game scene's didMoveToView method.
// self.gameSpeed is 1.0 and gradually increases during the game
let backgroundTexture = SKTexture(imageNamed: "Background")
var moveBackground = SKAction.moveByX(-self.frame.size.width, y: 0, duration: (20 / self.gameSpeed))
var replaceBackground = SKAction.moveByX(self.frame.size.width, y: 0, duration: 0)
var moveBackgroundForever = SKAction.repeatActionForever(SKAction.sequence([moveBackground, replaceBackground]))
for var i:CGFloat = 0; i < 2; i++ {
var background = SKSpriteNode(texture: backgroundTexture)
background.position = CGPoint(x: self.frame.size.width / 2 + self.frame.size.width * i, y: CGRectGetMidY(self.frame))
background.size = self.frame.size
background.zPosition = -100
background.runAction(moveBackgroundForever)
self.addChild(background)
}
Now I want to increase the speed of the scrolling background at certain points of the game. You can see that the duration of the background's horizontal scroll is set to (20 / self.gameSpeed). Obviously this does not work, because this code is only run once, and therefore the movement speed is never updated to account for a new value of the self.gameSpeed variable.
So, my question is simply: how do I increase the speed (reduce the duration) of my background images' movements according to the self.gameSpeed variable?
Thanks!
You could use the gameSpeed variable to set the velocity of the background. For this to work, firstly, you need to have a reference to your two background pieces (or more if you so wanted):
class GameScene: SKScene {
lazy var backgroundPieces: [SKSpriteNode] = [SKSpriteNode(imageNamed: "Background"),
SKSpriteNode(imageNamed: "Background")]
// ...
}
Now you need your gameSpeed variable:
var gameSpeed: CGFloat = 0.0 {
// Using a property observer means you can easily update the speed of the
// background just by setting gameSpeed.
didSet {
for background in backgroundPieces {
// Minus, because the background is moving from left to right.
background.physicsBody!.velocity.dx = -gameSpeed
}
}
}
Then position each piece correctly in didMoveToView. Also, for this method to work each background piece needs a physics body so you can easily change its velocity.
override func didMoveToView(view: SKView) {
for (index, background) in enumerate(backgroundPieces) {
// Setup the position, zPosition, size, etc...
background.physicsBody = SKPhysicsBody(rectangleOfSize: background.size)
background.physicsBody!.affectedByGravity = false
background.physicsBody!.linearDamping = 0
background.physicsBody!.friction = 0
self.addChild(background)
}
// If you wanted to give the background and initial speed,
// here's the place to do it.
gameSpeed = 1.0
}
You could update gameSpeed in update for example with gameSpeed += 0.5.
Finally, in update you need to check if a background piece has gone offscreen (to the left). If it has it needs to be moved to the end of the chain of background pieces:
override func update(currentTime: CFTimeInterval) {
for background in backgroundPieces {
if background.frame.maxX <= 0 {
let maxX = maxElement(backgroundPieces.map { $0.frame.maxX })
// I'm assuming the anchor of the background is (0.5, 0.5)
background.position.x = maxX + background.size.width / 2
}
}
}
You could make use of something like this
SKAction.waitforDuration(a certain amount of period to check for the updated values)
SKAction.repeatActionForever(the action above)
runAction(your action)
{ // this is the completion block, do whatever you want here, check the values and adjust them accordly
}
How can I add my starsSqArray to a for loop in my update function that grabs all the SKSpriteNodesfor _starsSq1 so that all of the stars move together and not separately?
Right now my Swift class keeps returning an error saying that _starsSqArray doesn't have a position (my code is bugged out). My goal is to grab the plotted stars and move them downward all at once.
import SpriteKit
class Stars:SKNode {
//Images
var _starsSq1:SKSpriteNode?
//Variables
var starSqArray = Array<SKSpriteNode>()
var _starSpeed1:Float = 5;
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init() {
super.init()
println("stars plotted")
createStarPlot()
}
/*-------------------------------------
## MARK: Update
-------------------------------------*/
func update() {
for (var i = 0; i < starSqArray.count; i++) {
_starsSq1!.position = CGPoint(x: self.position.x , y: self.position.y + CGFloat(_starSpeed1))
}
}
/*-------------------------------------
## MARK: Create Star Plot
-------------------------------------*/
func createStarPlot() {
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
for (var i = 0; i < 150 ; i++) {
_starsSq1 = SKSpriteNode(imageNamed: "starSq1")
addChild(_starsSq1!)
//starSqArray.addChild(_starsSq1)
var x = arc4random_uniform(UInt32(screenSize.width + 400))
var y = arc4random_uniform(UInt32(screenSize.height + 400))
_starsSq1!.position = CGPoint(x: CGFloat(x), y: CGFloat(y))
}
}
}
A couple of suggestions by a design point of view:
You already have all your stars (I guess so) grouped togheter inside a common parent node (that you correctly named Stars). Then you just need to move your node of type Stars and all its child node will move automatically.
Manually changing the coordinates of a node inside an update method does work but (imho) it is not the best way to move it. You should use SKAction instead.
So, if you want to move the stars forever with a common speed and direction you can add this method to Stars
func moveForever() {
let distance = 500 // change this value as you prefer
let seconds : NSTimeInterval = 5 // change this value as you prefer
let direction = CGVector(dx: 0, dy: distance)
let move = SKAction.moveBy(direction, duration: seconds)
let moveForever = SKAction.repeatActionForever(move)
self.runAction(moveForever)
}
Now you can remove the code inside the update method and call moveForever when you want the stars to start moving.
Finally, at some point the stars will leave the screen. I don't know the effect you want to achieve but you will need to deal with this.
for (SKSpriteNode *spriteNode in starSqArray) {
spriteNode.position = CGPoint(x: spriteNode.position.x , y: spriteNode.position.y + GFloat(_starSpeed1))
}
Use the above code in the update() function.
Hope this helps...