Delay in Loop occurs only once - swift

In my code I am trying to have it so the entity moves a certain number of times, and delays between each movement.
These movements are essential small teleports to allow for the movement distance to be slowed or speed up (that is what the RGBA part of the code does by using the green color at a pixel of another image that is not displayed).
The problem I am getting is that when a entity is moved.
It moves the correct distance, but all at once. Thus if I think I am telling it to move 10 units over 10 seconds, it waits 10 seconds then moves 10 units.
This is a problem as it looks bad and makes it so the entity "overshoots" where it is selected to go (that is what the "if" statement in the loop does, it stops the entity when it gets close).
Note: This function is within a class that is a SKNode.
Here is the code (note: "moveTo" is defined outside of the function):
func moveGo(){
var dX: CGFloat
var dY: CGFloat
var magnitudeOfVector: Double
var originalX: CGFloat = self.position.x
var originalY: CGFloat = self.position.y
dX = ((moveTo?.x)! - (self.position.x))
dY = ((moveTo?.y)! - (self.position.y))
let wantedX: CGFloat = (moveTo?.x)!
let wantedY: CGFloat = (moveTo?.y)!
magnitudeOfVector = sqrt(((Double)(dX) * (Double)(dX) + (Double)(dY) * (Double)(dY)))
dY = dY/(CGFloat)(magnitudeOfVector)
dX = dX/(CGFloat)(magnitudeOfVector)
var currentX = self.position.x
var currentY = self.position.y
for _ in 1...100{
SKAction.wait(forDuration: 0.1)
if let (r,g,b,a) = pixel(in: image, at: self.position) {
print ("Red \(r), Green: \(g), Blue :\(b), Alpha: \(a)")
stepDebuff = 2*(CGFloat)(r)/255
}
dX = ((moveTo?.x)! - (originalX))
dY = ((moveTo?.y)! - (originalY))
dY = dY/(CGFloat)(magnitudeOfVector)
dX = dX/(CGFloat)(magnitudeOfVector)
dY = dY * defaultStepSize * stepDebuff
dX = dX * defaultStepSize * stepDebuff
currentX += dX
currentY += dY
self.position = CGPoint(x: currentX, y: currentY)
if abs(wantedX - currentX) < 2 && abs(wantedY - currentY) < 2{
if abs(wantedX - currentX) < 0.01 && abs(wantedY - currentY) < 0.01{
dY = 0
dX = 0
} else{
dX = ((moveTo?.x)! - (self.position.x))
dY = ((moveTo?.y)! - (self.position.y))
}
}
print("\(magnitudeOfVector)")
print("\(dX)")
print("\(currentX)")
}
}
The number of loops is arbitrary. defaultStepSize = 1.

Related

Normalising a CGFloat Array to UIView frame height

I am plotting an array of CGFloat values within a UIBezierPath. Now I want to Normalise the values so the max value of the array will occupy the whole height.
So far
let scaleFactor = (waveView.frame.height) * readFile.maxValue
for point in readFile.points {
let nextPoint = CGPoint(x: soundPath.currentPoint.x + sampleSpace, y: middleY - (point * scaleFactor) - 1.0)
soundPath.addLine(to: nextPoint)
soundPath.move(to: nextPoint)
}
But it does not seem to work...
EDIT
ReadFile:
class ReadFile {
public enum ReadFileError:Error{
case errorReading(String)
}
/* Player Interval Measured in Miliseconds (By now..) */
var beginPosition:Int32 = 0
var endPosition:Int32 = 0
/* Sample Rate -> Default 48KHz */
var sampleRate:Double = 48000
var samplesSeconds:CGFloat = 5
var maxValue:CGFloat = 0
var points:[CGFloat] = []
}
And
sampleSpace = 0.2
Thank you Andy for your answer but I have finally figured it out.
I am drawing a sound wave so it has positives and negatives values.
heightMax = waveView.frame.height/2
Applying a rule of three (Spanish translation) I end up with this:
func drawSoundWave(windowLength:Int32){
// Drawing code
print("\(logClassName): Drawing!!!")
print("\(logClassName): points COUNT = \(readFile.points.count)")
let soundPath = UIBezierPath()
soundPath.lineWidth = lineWidth
soundPath.move(to: CGPoint(x:0.0 , y: middleY))
print("\(logClassName) max ")
for point in readFile.points{
let normalizedHeight:CGFloat = (waveView.frame.height * point) / (2 * readFile.maxValue)
let nextPoint = CGPoint(x: soundPath.currentPoint.x + sampleSpace, y: middleY - (normalizedHeight))
soundPath.addLine(to: nextPoint)
soundPath.move(to: nextPoint)
}
let trackLayer = CAShapeLayer()
trackLayer.path = soundPath.cgPath
waveView.layer.addSublayer(trackLayer)
trackLayer.strokeColor = UIColor.red.cgColor
trackLayer.lineWidth = lineWidth
trackLayer.fillColor = UIColor.green.cgColor
trackLayer.lineCap = kCALineCapRound
}
where
let normalizedHeight:CGFloat = (waveView.frame.height * point) / (2 * readFile.maxValue)
is the normalize value given a readFile.maxValue and waveView.frame.height

Spritekit Add nodes without overlapping

I am trying to add 20 nodes at random locations on the screen without any of the nodes overlapping. Ive got the adding of the nodes at random locations part but I sill get some that overlap. What I have done so far is: I would be greatful for a point in the right dirrection.
while i < 20 {
let bubbleSize = self.frame.width / 12
let bubble = SKShapeNode(circleOfRadius: bubbleSize)
let widthL = -self.frame.size.width / 2 + bubble.frame.size.width / 2
let widthH = self.frame.size.width / 2 - bubble.frame.size.width / 2
let heightL = -self.frame.size.height / 2 + bubble.frame.size.height/ 2
let heightH = self.frame.size.height / 2 - bubble.frame.size.height / 2
var randWidth = randomNumber(range: widthL..<widthH)
var randHeight = randomNumber(range: heightL..<heightH)
bubble.fillColor = SKColor.cyan
bubble.position = CGPoint(x: randWidth, y: randHeight)
self.addChild(bubble)
}
func randomNumber(range: Range<CGFloat>) -> CGFloat {
//function that gives a random number of a range of CGFloats entered
let min = range.lowerBound
let max = range.upperBound
return CGFloat(arc4random_uniform(UInt32(CGFloat(max - min)))) + min
}
This should get you started. It essentially divides the screen into a grid and places the bubbles in grid locations while ensuring no other bubble is at that position. there is lots more you could do with this, like add slight random placement to the location (random generate some small offsets -2 to +2 for x and y values so that they don't look so perfectly placed).
let bubbleSize = self.frame.width / 12
let minX = 0 - self.frame.size.width / 2
let maxX = self.frame.size.width / 2
let minY = 0 - self.frame.size.height/ 2
let maxY = self.frame.size.height/ 2
var usedIndexes: [Int]!
let xRange = maxX * 2
let yRange = maxY * 2
let cols = xRange / bubbleSize
let rows = yRange / bubbleSize
func createBubbles(count: Int) {
for _ in 0..<count {
createBubble()
}
}
func createBubble() {
let index = findSlot()
let posX = index % cols
let posY = index / cols
let bubble = SKShapeNode(circleOfRadius: bubbleSize)
bubble.fillColor = SKColor.cyan
bubble.position = CGPoint(x: posX, y: posY)
self.addChild(bubble)
}
func findSlot() -> Int {
//preventative measure to stop endless loop from happening
guard usedIndexes.count > rows * cols else { return 0 }
let randomX = randomNumber(range: 0..<cols)
let randomY = randomNumber(range: 0..<rows)
let index = randomX + randomY * cols
if usedIndexes.contains(index) {
return findSlot()
}
else {
usedIndexes.append(index)
}
return index
}

Swift 4: SKSpriteNode won't stop on a position

Here i have a tank with bullets, when the user taps the screen, the tank fires, it's fine
but when the bullet comes out of the tank, it goes above the screen and it won't stop on the touch position
here's my code:
for touch in touches {
let location = touch.location(in: self)
let bullet = SKSpriteNode(imageNamed: "bullet4")
bullet.position = CGPoint(x: self.MainTank.position.x, y: self.MainTank.position.y + 10)
bullet.size = CGSize(width: 12, height: 20)
bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width / 2)
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.isDynamic = true // false stops the bullet on the tank
bullet.physicsBody?.allowsRotation = false
self.addChild(bullet)
var dx = CGFloat(location.x)
var dy = CGFloat(location.y)
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
let vector = CGVector(dx: 5.0 * dx, dy: 5.0 * dy)
bullet.physicsBody?.applyImpulse(vector)
}
i tried to set isDynamic to true, however this makes the bullet stop at the tanks position
Thanks
It probably moves outside of the screen because you multiply the vector by 5.0. I also wouldn't create your vector like that, as the division part with the magnitude isn't necessary. Try this instead:
let dx = location.x - bullet.position.x
let dy = location.y - bullet.position.y
let vector = CGVector(dx: dx, dy: dy)
bullet.run(SKAction.move(by: vector, duration: 1.0)) //choose your own duration of course

swift shooting objects from circle shape

I have a circle in the middle and I need to fire bullets in direction opposite to where I touch and come out from the player, I wrote the following code but nothing happen
private func fire(position: CGPoint) {
let bullet = SKShapeNode(circleOfRadius: 2)
bullet.strokeColor = SKColor.darkGray
bullet.fillColor = SKColor.darkGray
bullet.physicsBody = SKPhysicsBody(circleOfRadius: 2)
let posX = (position.x) * -1
let posY = (position.y) * -1
let pos = CGPoint(x: posX, y: posY)
bullet.position = pos
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.isDynamic = false
bullet.physicsBody?.pinned = false
numberOfBullets += 1
bullet.name = String(numberOfBullets)
bullet.physicsBody?.collisionBitMask = bodyType.enemy.rawValue
bullet.physicsBody?.categoryBitMask = bodyType.bullet.rawValue
bullet.physicsBody?.contactTestBitMask = bodyType.enemy.rawValue
bullet.physicsBody?.usesPreciseCollisionDetection = true
world.addChild(bullet)
bulletForce(bullet: bullet, position: position)
}
private func bulletForce(bullet: SKShapeNode, position: CGPoint) {
let ddx = Double((bullet.position.x - position.x))
let ddy = Double((bullet.position.y - position.y))
let degree = atan2(ddy, ddx) * (180.0 / M_PI)
let magnitude = 900.0
let angle = degree
let dx = magnitude * cos(angle)
let dy = magnitude * sin(angle)
let vector = CGVector(dx: dx,dy: dy)
bullet.physicsBody?.applyForce(vector)
//bullet.run(fire, withKey: "firing\(bullet.name)")
}
EDIT ::
This solved the problem thanks to the aid from the accepted answer
let posX = (position.x) * -1
let posY = (position.y) * -1
let pos = CGPoint(x: posX, y: posY)
let theta = atan2((pos.y - player.position.y), (pos.x - player.position.x))
let dx = cos(theta) / 4
let dy = sin(theta) / 4
world.addChild(bullet)
let vector = CGVector(dx: dx, dy: dy)
bullet.physicsBody!.applyImpulse(vector)
where theta is the angle i want the bullet to go to and here it is
Note::
The player is in the middle ( circle ).
The posX and posY are the x and y coordinates where the user touched and they are multiplied by -1 to get the opposite direction where my bullet will be placed.
The angle theta is then calculated using the atan2 function that instead of positioning my bullet away from the player ( circle ), it will position it at the edge of circle.
Have you tried switching isDynamic from "false" to "true"? Also, try adding GCVector(dx: 1.0 * dx, dy: 1.0 * dy)

Trying to make an isometric grid programmatically using Swift

The only reason I'm attempting this programmatically is so I can access the locations of each tile, making it possible to manipulate the grid. I've looked at about 15 different tutorials using other programming languages, but even with that knowledge, I'm still having a very difficult time creating one in Swift.
I've tried creating a nested for loop but I'm not even sure if the code inside the loops make logical sense for creating an isometric grid, I just took it off of one of the tutorials I found:
func generateGrid() {
for (var i = 0; i < 5; i++) {
for (var j = 5; j >= 0; j--){
tile.position = CGPoint(x: (j * Int(tile.size.height) / 2) +
(i * Int(tile.size.width) / 2),
y: (i * Int(tile.size.height) / 2) -
(j * Int(tile.size.width) / 2))
addChild(tile)
}
}
}
Then I tried to call this in the "didMoveToView" function, but obviously you can't add the same node more than once.
Please give me any direction that you can.
Here's an example of how to create an isometric grid in SpriteKit:
Define the grid settings
let tileHeight:CGFloat = 16.0
let tileWidth:CGFloat = 32.0
let numRows = 5
let numCols = 6
Create grid elements and add them to the scene at the appropriate locations
let size = CGSizeMake(tileWidth, tileHeight)
for row in 1...numRows {
for col in 1...numCols {
// Create a new tile object
let tile = newTile(size)
// Convert (col,row) to (x,y)
tile.position = tilePosition(col, row: row)
self.addChild(tile)
}
}
This function converts a grid position to x and y scene coordinates. Use this to create the grid and to add buildings on the grid.
func tilePosition(col:Int, row:Int) -> CGPoint {
let x = (CGFloat(row) * tileWidth / 2.0) + (CGFloat(col) * tileWidth / 2.0)
let y = (CGFloat(col) * tileHeight / 2.0) - (CGFloat(row) * tileHeight / 2.0)
return CGPointMake(x+100.0, y+100.0)
}
This function creates a new diamond-shaped SKShapeNode
func newTile(size:CGSize) -> SKShapeNode {
let shape = SKShapeNode()
let path = UIBezierPath()
path.move(to: CGPoint(x:0, y:size.height / 2.0))
path.addLine(to: CGPoint(x:size.width / 2.0, y:0))
path.addLine(to: CGPoint(x:0, y:-size.height / 2.0))
path.addLine(to: CGPoint(x:-size.width / 2.0, y:0))
path.close()
shape.path = path.cgPath
shape.lineWidth = 1.0
shape.fillColor = SKColor.gray
return shape
}
This function determines the appropriate z position to ensure that buildings placed on the grid will be rendered in the correct order.
func buildingZPosition(col:Int, row:Int) -> CGFloat {
return CGFloat(row*numCols + numCols-col)
}
Figure 1. Isometric Grid Created with the Above Code
To create an isometric grid like so
one has to create a two loops. One of them iterates through rows and the other iterates through columns. Notice that the center of every other column is shifted up by half of the height of the tile and the same for every other row. To compensate for this we check if the row and column is even or odd and adjust it accordingly. A potential function would look something like this:
func generateGrid() {
for i in 0..<5 {
// Every other line needs to be shifted
var offsetX = 0
if i % 2 == 0 {
offsetX = 0
} else {
offsetX = tile.size.width / 2.0
}
for j in 0..<5 {
var tile = Tile()
// Every other line needs to be shifted
var offsetY = 0
if i % 2 == 0 {
offsetX = 0
} else {
offsetX = tile.size.height / 2.0
}
tile.position = CGPoint(x: (i * tile.size.width) - offsetX, y: (j * tile.size.height) - offsetY)
addChild(tile)
}
}
}