I'm getting values for zRotation less/greater than -/+pi with Swift3 (e.g. 7.41137313842773, in radians at about 0,5Pi). It happens when rotating a node using the approach described below.
Code is
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
newLocation = touch.location(in: self)
turnAngle = atan2(newLocation.y - lastLocation.y - mySprite.position.y, newLocation.x - lastLocation.x - mySprite.position.x)
mySprite.run(SKAction.rotate(byAngle: turnAngle, duration: 0.1))
lastLocation = newLocation
print("mySprite zRotation: \(mySprite.zRotation)")
}
}
I also notice a jittery jump in the sprite at the point when the sprite is rotated to pi/2 (absolute). If this is normal behaviour, is there a way to recalculate zRotation between -/+Pi or 0-2Pi?
I believe in your code there are two point to analyze:
SKAction and zRotation.
The first (SKAction) should be considered in general (not about this issue). If you want to launch an SKAction like 'rotate' inside a cycle, because it have a duration you should check if this action is finished before to launch another. An example:
extension SKNode
{
func actionForKeyIsRunning(key: String) -> Bool {
return self.action(forKey: key) != nil ? true : false
}
}
if !self.mySprite.actionForKeyIsRunning(key: "rotation") {
self.mySprite.run(SKAction.rotate(byAngle: turnAngle, duration: 0.1),withKey:"rotation")
}
Speaking about zRotation and your issue the reason you have a wrong zRotation value is because you are using an action. zRotation is a node property: properties of nodes undergoing animation are not updated at each step in the simulation. You can set zRotation to rotate your node:
self.mySprite.zRotation = turnAngle
or create a physics body and set the angular velocity.
For more details about SKAction and node properties look the official Apple doc where there is this note:
When You Shouldn’t Use Actions
Although actions are efficient, there is a cost to creating and
executing them. If you are making changes to a node’s properties in
every frame of animation and those changes need to be recomputed in
each frame, you are better off making the changes to the node directly
and not using actions to do so. For more information on where you
might do this in your game, see Advanced Scene Processing.
As #PietroPepe has pointed out, you are stacking actions one on top of the other. Let's break down your code:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
This tells us that your touch calls will be done multiple times throughout the life cycle of your touch. Even with a quick touch down and up action, it is very possible to go through at least 2 calls on this method.
newLocation = touch.location(in: self)
turnAngle = atan2(newLocation.y - lastLocation.y - mySprite.position.y, newLocation.x - lastLocation.x - mySprite.position.x)
The point of this code is to have the sprite move at the change of the touch from last position to current position
mySprite.run(SKAction.rotate(byAngle: turnAngle, duration: 0.1))
lastLocation = newLocation
print("mySprite zRotation: \(mySprite.zRotation)")
}
}
Finally this point of the code says do it in 1/10th of a second.
Now let's run through a scenario. We touch the screen, move it, and let go.
The amount of time it took to perform it was 4 frames, this means 1 touchBegan, 2 touchMoved, and 1 touchEnded.
in our toucheMoved events, we calculate a change of 60degrees, each time.
Now a frame is 1/60th of a second keep in mind.
Our rotation event will take 1/10th of a second to go from 0 to 60, so 6/60th seconds, 6 frames.
This means every frame we are moving 10 degrees.
Now we come to the second frame of the rotateBy.
The same logic as the first frame above will apply, so we will now have another series of rotations that will be moving by 10 degrees.
This mean we now have 2 events rotating our sprite at 6 degrees each.
So the timeline is as followed:
Frames: 0 1 2 3 4 5 6 7
+10 +10 +10 +10 +10 +10
+10 +10 +10 +10 +10 +10
Notice the inconsistent behaviors between the end and the beginning of your rotation? This is why things look all screwy.
Now, I am not sure of the desired effect you are looking for, but if I were to be doing this, I would be setting zRotation instead of using actions.
If you are looking for a delayed effect, then use the withKey parameter on the run function so that you only have 1 action running, and use SKAction.rotateTo to have the sprite always rotating to the last possible touch location. This of course means you need to keep track of when touch first began, not when last touched.
This also gets a little tricky depending on desired effect, because you may need to keep track of the time that has elapsed between the number of touchesMoved events called and apply the time difference to your duration, so basically duration:.01 - timeSinceTouchBegan,
Related
I'm making a little game using swift, a Snake Game. I've already tried to do this game using python and it worked! To run the game I used a while loop. In this way, I could always change the player position and check if he had hit the margins. Now I have to do this again, but I don't know how to tell to my program to check all the time if the snake has hit the margins or himself.
I should do something like that
if player.position.x <= 0 || player.position.x >= self.size.width || player.position.y <= 0 || player.position >= self.size.height{
death()
}
So, if the player position on x or y axis is major than the screen size or minor than 0, call the function "death()".
If I had to do this with python, I'd have just put all this code inside the "main while loop". But with swift there's not a big while I can use... any suggestions?
Use SpriteKit. You can select this when creating a new project, choose Game, and then select SpriteKit. It's Apple's framework to make games. It has what you will need, and is also faster - don't make games in UIKit.
Within SpriteKit, you have the update() method, as part of SKScene. Do NOT use a while loop. These are highly unreliable, and the speed of the snake will vary between devices.
Here is a small piece of code to help you get started on the SKScene:
import SpriteKit
class Scene: SKScene {
let timeDifference: TimeInterval = 0.5 // The snake will move 2 times per second (1 / timeDifference)
var lastTime: TimeInterval = 0
let snakeNode = SKSpriteNode(color: .green, size: CGSize(width: 20, height: 20))
override func update(_ currentTime: TimeInterval) {
if currentTime > lastTime + timeDifference {
lastTime = currentTime
// Move snake node (use an SKSpriteNode or SKShapeNode)
}
}
}
After doing all this, you can finally check if the snake is over some margins or whatever within this update() loop. Try to reduce the code within the update() loop as much as possible, as it gets run usually at 60 times per second.
If I understood you correctly: You are looking for a loop in Swift that does all the calculation and drawing for your game. Here you have some tutorials about loops in Swift. In fact there are while loops that you could use in order to answer your question. I hope I could help.
I'm creating a Snake Game using swift and the library SpriteKit.
To control the direction of the snake, the user has to swipe on the screen. To check which was the direction of the swipe, I use UISwipeGestureRecognizer .
Now, I have to pass the information (direction of the swipe) from the GameViewController.swift file to the GameScene.swift file. To to that, I declare a 4 items array called movesConoller:
movesController[false, false, false, false]
If the user swipes up, the first element of the array turns true, if he scrolls down, the second element turns true and the first one false... etc. etc.
Now, in the GameScene.swift file I have to say what the Snake has to do if the player moves up.
For this reason, I use 2 more variables:
var playerX:CGFloat = 0
var playerY:CGFloat = 0
and then I created this code
if movesController[0] == true {
playerY += 56
}
if movesController[1] == true {
playerY -= 56
}
if movesController[2] == true {
playerX += 56
}
if movesController[3] == true {
playerX -= 56
}
Now I create the movement
let startPoint = CGPoint(x: player.position.x, y: player.position.y )
let endPoint = CGPoint(x: playerX + player.position.x, y: playerY + player.position.y)
let moveIt = SKAction.move(to: endPoint, duration:0.1)
player.run(moveIt)
So, every time the program is executed, the playerX and playerYvariables become equal to 0. Then, based on the direction of the swipe, they are added or subtracted 56.
To move the snake I just say to the head to move from his startPointto his endPoint in 0.1 seconds.
Another thing I added is the tail. To create it I use two arrays, snakeX and snakeY.
To these two empty arrays (of type CGFloat) I add the previous position of the snake and then, if the score remains the same, the last element of each array is deleted. Else, the last element isn't deleted and remains in his array.
Whit this method, I make the tail growing when the snake eats an apple.
BUT the head moves 56 in 0.1seconds. This means that he makes a movement of 8 pixels at each execution. Because of that, I have to add to snakeX and snakeY the X and Y values every 7 execution of the program.
This is the problem. If the player swipes on the screen just after 7 execution of the program, the movement of the tail will be perfect. But if the player swipes before the 7 execution, there will be a problem. Let's suppose the snake is moving right and the player swipes up when the program is at its 4th execution.
//snakeX and snakeY values before the swipe
snakeX[168, 112, 56, 0] //all this numbers are multiple of 56
snakeY[224, 224, 224, 224] //the snake is moving right, the Y value doesn't change.
//snakeX and snakeY values after the swipe
snakeX[168 ,112 ,56 , 0]
snakeY[248, 224, 224, 224] //look at the first element
248 is not a multiple of 56. The snake moved Up at the 4th execution, after 3 execution his position will be added to the arrays. But in 3 execution he had moved of 24pixel.
Because of that, I'll get this bug
as you can see, the tail doesn't make a perfect corner.
The snake head doesn't complete his movement of 56 pixels. When I swipe, it leaves his movement and starts another one. Is there a way I can tell the head to always complete his movement before doing another one?
You're sort of trying to use a pixel-based approach to what's basically a grid-based game, but maybe something like this will work...
Make a variable controlling whether movement is allowed:
var moveAllowed = true
Guard your movement code with that variable:
if moveAllowed {
if movesController[0] == true {
playerY += 56
}
...
}
When you schedule the movement action, toggle moveAllowed, and add a completion handler to toggle it back after the action finishes:
...
moveAllowed = false
let moveIt = SKAction.move(to: endPoint, duration:0.1)
player.run(moveIt) { self.moveAllowed = true }
(The self.moveAllowed might be just moveAllowed depending on how you have things structured, but I can't tell from your fragments.)
Edit: I just realized that maybe you're setting snakeX and snakeY based on player.position. It's not really clear. In any case, then you'd use the same idea but copying from player.position only when moveAllowed is true. Basically that variable is telling you if the action is still running or not.
I have an SKEmitterNode on my scene, when I increase the particles on update, the amount of particles being shown are not increasing. I am not sure what step I am missing.
The code is really simple:
Create any kind of default particle from Xcode.
Set birthrate to 1, max to 0
Everything else doesn't really matter.
On GameScene update:
func update(currentTime: NSTimeInterval) {
//whatever it takes to get the particle from the scene
let particles = childNodeWithName("particles") as? SKEmitter
particles!.particleBirthRate = particles!.particleBirthRate + 1;
}
Note: When doing a print, the birth rate does increase, so I know it is not using a copy
So first off, what I'm trying to do but clearly failing to is prevent a sprite "the player" from moving off the edge of the map "400x400px img".
The controls on the game are buttons for moving right, left, up and down.
I have it so when the buttons are pressed it runs a function that checks if the player's destination is off the map's area.
****Note That the Map is a 5x5 grid map, each square of the grid is 80x80 inside map image****
Here's the function that checks if the player's destination is off the map:
func checkBordersDownPlayer() {
if player.position.y - 80 == map.position.y - 80 {
print("Node cannot move. Map borders are stopping the Node from moving past it's boundaries.")
}
else {
player.runAction(moveDown)
print("Player was able to move!")
}
}
Now what troubles me is that the player automatically ignores the print() functions and the if function but still runs the SKAction to move it.
What I could asses from your post is that perhaps you are not clear about the position concept of sprite node.
Unlike UIKit in which position of a frame is at the top left corner of the Rectangle, in Spritekit a node's position is at its center by default unless you will play with its anchor point.
Again taking your situation into consideration, you should use the tools provided by Spritekit for efficiently handling such tasks, You should use Physics body around the map image and make your player another physics body assigning them different physics categories.
In your case you don't need to assign any contact test bit mask or collision bit mask untill you are not interested in getting informed when the player touches the boundary set by you. You can refer to this tutorial for more https://www.raywenderlich.com/123393/how-to-create-a-breakout-game-with-sprite-kit-and-swift
Hope this helps.
Without knowing more of how you calculate movement the best I can do is guess this is your problem.
player.position.y - 80 == map.position.y - 80
Instead you probably want something like this...
player.position.y - 80 <= map.position.y - 80
Hopefully that helps
edit
One step further would be to reset the players position too...
func checkBordersDownPlayer() {
if player.position.y - 80 <= map.position.y - 80 {
player.position.y = map.position.y - 80
print("Node cannot move. Map borders are stopping the Node from moving past it's boundaries.")
}
else {
player.runAction(moveDown)
print("Player was able to move!")
}
}
Alright I'm offically stumped. I've been able to figure out my problems myself in the past but I'm lost on this one.
I'm trying to flip a child sprite's xscale. I want the flip to occur when the child's x position is negative. With the code below, the sprite does flip, every time the sprite reaches negative position, it loops back and forth flipping until reaching positive again.
I've tried numerous variations, but this is the code in it's simplest form. Any help or alternatives would be appreciative.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let pin = childNodeWithName(Pin1) as SKSpriteNode!
let guyPoint = guy.convertPoint(guy.position, fromNode: pin)
if guyPoint.x <= 0 {
guy.xScale = -1
}
else {
guy.xScale = 1
}
}
So if it is constantly flipping it's probably an issue of coordinate space. So check to make sure your convertPoint method is accurately returning the point you want. I would use println() to see what's going on there.
Probably what's happening is you want to flip the xScale when the child node is negative in the scene space or something (i.e. when the child is off the screen) but instead guyPoint might be the position of the child in its parent's coordinate space (so relative to the parent).
Also try with and without the else{} part to see if that changes anything.