I am making a small SpriteKit game in Swift playgrounds and it is an asteroid defence type of game. Basically,the player is defending the spacecraft with a cannon and has to destroy approaching rocks. The image link below is my scene without the gun added yet, but basically I want the rocks to move slowly down from the top towards the spacecraft at the bottom (Sorry, it's too big to add in here).
https://www.dropbox.com/s/pisgazar4jaxra0/Screenshot%202017-03-18%2011.23.17.png?dl=0
The rocks will be SKSpriteNodes and I want to speed them up for each successive rock to increase difficulty over time.
How would I be able to implement this. I have seen some answers elsewhere about using UIBezierPaths but how do I make it random but with the constraint that the rocks only fall from above?
Thanks in advance!
If you only need them to go from top to bottom without any special curvy trajectory then you don't need to deal with splines.
Let's address the gradual acceleration first. You want them to fall slowly in the beginning and speed up during game progress. One approach would be to do track level time and do some duration math based on elapsed level time. An easier one would be to decrease the duration of time taken by the asteroid to reach the bottom. In your scene declaration section, declare those:
fileprivate let startingDuration: TimeInterval = 6.0 // Put here whatever value you feel fit for the level start falling speed
fileprivate let difficultyIncrease: TimeInterval = 0.05 // Put here whatever value you feel fit for how much should each next asteroid accelerate
fileprivate let collisionPosition = CGPoint(x: 300, y: 200) // Put here the onscreen location where the asteroids should be heading to
fileprivate var asteroidCounter = 0 // Increase this counter every time you spawn a new asteroid
Now, let's get down to the actual asteroid movement. There are different ways to approach it. The easiest one and recommended by Apple is to use an action for that. Add this code to your asteroid spawn method:
// Implement here your spawning code ...
// It's best to choose random starting points (X axis) a bit higher than the upper screen boundary
let fallingDuration: TimeInterval = startingDuration - difficultyIncrease * TimeInterval(asteroidCounter)
let fallingAction = SKAction.move(to: collisionPosition, duration: fallingDuration)
asteroid.runAction(fallingAction)
If you need a function for random integer generation, try this:
func RandomInt(min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max-(min-1)))) + min
}
Related
I'm developing a small game in Swift 3 with SpriteKit and I want to move the enemies to the direction of the character, but at all times the enemies stop when they arrive at the character's initial position.
I'm doing this:
enemigo.name = "enemigo"
enemigo.position = CGPoint(x: 280, y: 100)
enemigo.xScale = 1/1
enemigo.yScale = 1/15
addChild(enemigo)
let movement1 = CGVector(
dx: (enemigo.position.x - personaje.position.x)*10,
dy: (enemigo.position.y - personaje.position.y)*10
)
let actionTransaction = SKAction.move(by: movement1, duration: 20)
enemigo.run(actionTransaction)
How should I move the enemies to a specific direction without stopping at the initial position of the character?
You've done all the hard work figuring out the vector of direction.
Now you can repeat this moveBy action as often as you like, and the enemy will keep moving in the same direction, further and further, because move(by: CGVector) is relative, not absolute:
let actionTransaction = SKAction.move(by: movement1, duration: 20)
So all you need do is run this forever with a key, so you can then seek it out from anywhere, and stop it whenever you like.
run(SKAction.repeatForever(actionTransaction), withKey: "movingEnemy")
Alternatively, you could consider using GameplayKit together with SpriteKit. GameplayKit provides standard implementations of common algorithms for games and can be used together with SpriteKit.
Unfortunately I'm not too familiar with it, so I can't give you code. I would suggest having a look at Apple's GameplayKit programming guide. There are functions available for setting a goal of an agent (e.g. an enemy) to move toward a specific location, avoid obstacles, intercept another agent, or flee from an agent, etc.
So while your player moves, some enemies could be programmed to try and catch the player, while other enemies could try to run away from the player. Other enemies could be made to wander around randomly, etc.
So in a nutshell, GameplayKit can add quite powerful functionality to a SpriteKit game. This could mean spending a bit more time upfront thinking about the architecture of your game, but in the end it could be worth it if you will also use the other functionalities of GameplayKit.
Context
For the purpose of a MWE we will be using the following image of a stick figure:
with the goal of having a chain of these sprites move, hand-in-hand, across the screen:
It is worthwhile to note that the stick figure image itself is wider than the arm-span of this stick figure. The background is, however, transparent.
Depending on the application, one may make a class that either inherits from SKSpriteNode or encapsulates it, e.g. a class called Person, to store additional information, where there may be an array var people = [Person]().
Questions
1.) Suppose you had two instances of the aforementioned Person class with each sprite taking a stick figure image. How could one position them - programmatically - such that the sprites are touching ''hand in hand'' although the image has a transparent background? Of course one could spend some time fiddling about to get find a spacing parameter to ensure this is achieved, but that parameter would always have to be, via trial-and-error, re-calculated if the sprites were re-scaled.
2.) Given a chain of these sprites, hand in hand, how could one animate them to move across the screen at the same velocity? If one calculates the spacing parameter alluded to in 1.) then an SKAction could be given to each Person such that their end position is offset (but total distance traveled is the same), where the TimeInterval is maintained the same. Is there a way to tell all the sprites to move to the left until off the screen at a rate of $x$ pixels per second?
It looks like you've mostly answered your own questions already, but here are some additional ideas:
Make the spacing value proportional to the size of the sprite.
Yes, there is an SKAction that moves a sprite a given distance over a given period of time (effectively a velocity): let moveAction = SKAction.moveBy(x: 10, y: 0, duration: 2)
Ok I'm developing a game in which you land a rocket ship while avoiding asteroids... Pretty simple. I have two UI Text elements that are meant to display the current vertical and horizontal velocity. I searched around and came up empty in terms of unity having a built in function to get the current velocity of a 2D rigidbody so I came up with this:
void Update () {
shipVelocity = Mathf.Abs((lastPosition.y - spaceShip.transform.position.y) / Time.deltaTime);
shipAngularVelocity = Mathf.Abs((lastPosition.x - spaceShip.transform.position.x) / Time.deltaTime);
uiText = "Vertical Velocity: " + shipVelocity;
currentVelocityText.text = uiText;
uiText = "Horizontal Velocity: " + shipAngularVelocity;
currentAngularVelocityText.text = uiText;
lastPosition = spaceShip.transform.position;
}
Basically it just does your standard velocity = delta position / delta time but something is wrong. The numbers bounce all over the place and after the ship has landed and is completely stationary the numbers still bounce around from 0-1. While it's flying the numbers rapidly change from what I think is an accurate velocity to 0 and back - so fast it basically just looks like it's blinking.
Could it be it's just calling it too fast? I.e. frame rate too high? If so how can I slow it down?
Thnx!
If you are updating a Rigidbody, it is important to put your update logic in "FixedUpdate()". This version of update is called at a set rate (default 50x/sec) and therefore should be used for updating physics logic. Link: https://docs.unity3d.com/ScriptReference/MonoBehaviour.FixedUpdate.html
This is common in games as it separates the framerate (draws/sec) from the update logic (updates/sec), meaning that of the framerate drops, the games underlying physics are not altered.
What I'm trying to do is moving a sprite in a random direction or a random point and have it continue moving - with the affects of physics. I tried moving a sprite to a random point and although it worked, it just stopped if the random point was in the middle of the screen. I'm just hoping for the basics so any code/help from someone would be helpful. Also if you think theres a post already on this, can you list the hyperlink? I spent some time researching and I couldn't figure it out. Thanks in advance!
See SKPhysicsBody and methods like applyImpulse.
In short, you create and configure an SKPhysicsBody; assign it to the sprite; and then apply force vectors to the body. Procedural example:
let sprite = SKShapeNode(circleOfRadius: 10)
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 10)
sprite.physicsBody.friction = 0.2
sprite.physicsBody.restitution = 0.75
sprite.physicsBody.mass = 0.5
sprite.physicsBody.applyImpulse(CGVector(1, 1))
Much of this you can configure declaratively, rather than procedurally, with the SpriteKit scene editor in Xcode.
If I'm making a game in SpriteKit that has a large "world", and I need the user to have the option of zooming in and out of the SKScene, how would I go about this? Or, to make things simpler, in the didMoveToView function, how can I present more of the world to the user's device's screen (without using world.runAction(SKAction.scaleTo(0.5)) or something)?
There's a SKCameraNode that's built specifically for this. The SKCameraNode defines the viewport into your scene. You create a camera node and assign it to the camera property of your scene.
let cameraNode = SKCameraNode()
cameraNode.position = CGPoint(x: scene.size.width / 2, scene.size.height / 2)
scene.addChild(cameraNode)
scene.camera = cameraNode
You can then create actions and run those actions on the camera. So to zoom in on the scene, you'd do this.
let zoomInAction = SKAction.scale(to: 0.5, duration: 1)
cameraNode.run(zoomInAction)
The cameraNode basically is a square node in the scene, that I think takes the proportions of the view by default? Cuz there's no size initializer. So when you make it smaller, the scene looks like it gets zoomed. To zoom out you'd make an action that increases the scale. Basically imagine a rectangle on your entire scene, and whatever is in the cameraNode's rectangle directly shows on your iPhone screen. You can also add moveTo actions and sequence actions and set timingModes on the actions same as if it were your normal spriteNode.
Here's the WWDC where the apple guy shows what I've just said. CameraNode bit is around 3 mins before the end.
https://developer.apple.com/videos/play/wwdc2015-604/
So, the best solution I could could find goes something like this. In the didMoveToView function, create an SKSpriteNode called World and make it whatever size you want your world to be. Then, write world.setScale(0.5) if you want a 50% zoom-out. However, if you have a player node that needs to always be centered in the screen, you'll need to add the following to your update function.
override func update(currentTime: CFTimeInterval) {
world.position.x = -player.position.x * (0.5)
world.position.y = -player.position.y * (0.5)
}