xcode 8 swift 3 slow down function's "firing" time - timer

I'm having trouble creating a gun that shoots at variable speeds (i.e. revolver vs machine gun). Right now I have this code for the actual firing of the bullet:
class GameScene: SKScene, SKPhysicsContactDelegate {
func spawnBullets(_ location: CGPoint){
let Bullet = SKSpriteNode(imageNamed: "circle")
Bullet.zPosition = -1
Bullet.position = CGPoint(x: ship.position.x,y: ship.position.y)
Bullet.size = CGSize(width: 30, height: 30)
Bullet.physicsBody = SKPhysicsBody(circleOfRadius: 15)
Bullet.physicsBody?.categoryBitMask = PhysicsCategory.Bullet
Bullet.physicsBody?.collisionBitMask = 0
Bullet.name = "Bullet"
Bullet.physicsBody?.isDynamic = true
Bullet.physicsBody?.affectedByGravity = false
self.addChild(Bullet)
var dx = CGFloat(location.x - base2.position.x)
var dy = CGFloat(location.y - base2.position.y)
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
let vector = CGVector(dx: 30.0 * dx, dy: 30.0 * dy)
Bullet.physicsBody?.applyImpulse(vector)
and then I have this in a later section
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ){
let location = touch.location(in: self)
if ball3.frame.contains(location) && base2.frame.contains(location) == false{
spawnBullets(location)}
I've tried using a timer function yet it seems just to make things worse. Right now the bullets are firing at a rate of 60 per seconds (or just the same rate as the fps). Thanks for any help!

A simple approach would be to add a cool down to reduce your bullet spawn rate. Add a cooldown property to your scene:
var cooldown = 0
When spawnBullets is called you can set the cooldown delay you want between shots based on the gun e.g. cooldown = 30 // Basing this on a constant 60fps the bullet will have a cooldown of 0.5 seconds
You will need to add a check for the cooldown, in touchesMoved you could add && cooldown == 0 to your conditional.
In your update method, decrement the cooldown:
if cooldown > 0 { cooldown -= 1 }

Related

find max among 3 (user populated) arrays (which may be empty)

I am populating 3 arrays and I've no idea which one is being populated (depends on user). My goal is 2 fold.
find which array is being populated
find the Max value Among the 3 arrays (or the arrays which are populated)
eg:
hrGenY = [100,101,102]
cadGenY = [80, 81, 82]
powerGenY = [100,104,106]
This is what I'm doing right now. (This code here is just to determine which array is being populated and NOT nil)
var whichTelemery: [Int] = []
if !powerGenY.isEmpty {
whichTelemery = powerGenY
} else if !hrGenY.isEmpty {
whichTelemery = hrGenY
} else if !cadGenY.isEmpty {
whichTelemery = cadGenY
}
after which I utilise this to determine the X & Y scale for my chart. I use whichever is the Max to normalise the scale.
var yMax: CGFloat = 0.0
var xMax: CGFloat = 0.0
var yMaxTemp: [CGFloat] = [0,0]
var xMaxTemp: [CGFloat] = [0,0]
// Determine yMax & xMax for Scale Resolution
if !dataPointsY.isEmpty {
xMaxTemp[0] = dataPointsX.max()!
yMaxTemp[0] = CGFloat((dataPointsY.max()! >= ftpPlus) ? (1.2*dataPointsY.max()!) : ftpPlus)
xMax = xMaxTemp.max()!
yMax = yMaxTemp.max()!
}
if !whichTelemery.isEmpty && dataPointsY.isEmpty {
xMaxTemp[1] = CGFloat(whichTelemery.count < 900 ? 900 : whichTelemery.count + 1)
yMaxTemp[1] = CGFloat((CGFloat(whichTelemery.max()!) >= ftpPlus) ? (1.2*CGFloat(whichTelemery.max()!)) : ftpPlus)
xMax = xMaxTemp.max()!
yMax = yMaxTemp.max()!
}
The problem with this method is I am making the assumption that the user's power (if populated) will always be higher than the HR(heart rate) which may not be true. When this happens, the HR line will not get drawn as it's above the yMax limit.
The way I'm thinking of doing it is very crude which would involve yet another group of checks to see which of the 3 arrays has the highest number. Would appreciate some better more elegant way.
Thanks
What about getting the max value for your normalisation by doing
let max = [hrGenY.max() ?? 0.0, cadGenY.max() ?? 0.0, powerGenY.max() ?? 0.0].max()

How to place SCNFloor on real floor?

I'd like to add an scnfloor where the real floor is, but it appears above and when adding a childNode, the object doesn't lay on the floor, but looks like flying.
When logging the position of the added node or plane anchor I always get y = 0, no matter if I'm scanning the floor or a table.
What am I missing? Or is it impossible to change the y of a scnfloor? But in the docs is written
"...of its local coordinate space"
, so I thought it was possible.
Thanks
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return}
let anchorX = planeAnchor.center.x
let anchorY = planeAnchor.center.y
let anchorZ = planeAnchor.center.z
print("planeAnchor.center %#", planeAnchor.center) //y always 0
print("node.position %#", node.position) //y always 0
print("convert %#", node.convertPosition(SCNVector3(anchorX, anchorY, anchorZ), to: nil)) //y always 0
let floor = SCNFloor()
let floorNode = SCNNode(geometry: floor)
floorNode.position = SCNVector3(anchorX, anchorY, anchorZ)
let floorShape = SCNPhysicsShape(geometry: floor, options: nil)
floorNode.physicsBody = SCNPhysicsBody(type: .static, shape: floorShape)
node.addChildNode(floorNode)
let childNode = createChildNode()
floorNode.addChildNode(childNode)
}
The center.y value of the anchor will always be zero, there is some more information here: https://developer.apple.com/documentation/arkit/arplaneanchor/2882056-center
Basically the anchors transform will give the correct position of the detected plane instead. Because of this you can just add your SCNFloor instance as a child of the passed in node and it will render in the correct position, there is no need to set the position explicitly as you are doing above.
You will need to update your geometry size as the ARPlaneAnchor's extent changes as ARKit refines the bounds of the plane. Some more information is here: https://developer.apple.com/documentation/arkit/tracking_and_visualizing_planes
For example, the code below works for me, rendering a SCNFloor at the initially detected size of the plane and renders a box that will sit exactly on top of it. The plane is accurately placed on the floor (or whatever horizontal surface you detect):
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {
return
}
let floor = SCNFloor()
let floorNode = SCNNode(geometry: floor)
floor.length = CGFloat(planeAnchor.extent.z)
floor.width = CGFloat(planeAnchor.extent.x)
node.addChildNode(floorNode)
let box = SCNBox(width: 0.05, height: 0.05, length: 0.05, chamferRadius: 0)
box.materials[0].diffuse.contents = UIColor.red
let boxNode = SCNNode(geometry: box)
boxNode.position = SCNVector3(0, 0.025, 0)
node.addChildNode(boxNode)
}

Loop in the svg.js

rect
.animate(1000,"<>",0).dmove(50,0)
.animate(1000,"<>",0).dmove(-10,0)
.animate(1000,"<>",0).dmove(20,0)
.animate(1000,"<>",0).dmove(-60,0).loop(true, true);
Why (and should it?) does the loop not repeat the entire animation? He skips 2 and the 3 step.
Demonstration: https://codepen.io/Andreslav/pen/BxGygp
A very similar issue was solved here. The solution can be adapted for your problem:
let w = 100,
h = 100,
t = 1000,
draw = SVG('svg').size('100%', '100%').viewbox(0,0,w,h);
function animation() {
draw.rect(10,10)
.animate(t,">",0).dx(w/2).width(20).height(10)
.animate(t,">",0).dy(h/2).width(15).height(15)
.animate(t,">",0).dx(-w/2).width(10).height(15)
.animate(t,">",0).dy(-h/2).width(10).height(10).after(animation)
}
animation()
It appears the loop only works on the last action.
You can try a different approach to accomplish the same thing by using a path.
E.g: (you'll need to adjust to replicate the same points)
let width = 1000,
height = 100,
draw = SVG('svg').size('100%', '100%').viewbox(0,0,width,height);
// use path to replicate movement
const path = draw.path("M200 0 H 250 H 100 H 120 H 80")
const length = path.length()
path.fill('none').stroke({width:1, color: '#ccc'})
const rect = draw.rect(100, 100)
rect.animate(5000, '<>').during(function(pos, morph, eased){
var p = path.pointAt(eased * length)
rect.center(p.x, p.y)
}).loop(true, true)
(from tutorials)

SCNText, containerFrame, wrapping and ARKit

Has anyone managed to get an SCNText string wrapping correctly within a containerFrame in ARKit?
I've had a go, but the lines seem to be superimposed on top of each other, rather than being rendered vertically in sequence. If it was a problem with the size of the containerFrame being too small, I'd expect the string to just be truncated. It doesn't make a difference which truncation mode I use (...end / ...none / ..middle) etc.
This is code from my SCNNode subclass, creating the extruded text in the init method. The same code works fine (with different sizes obviously) to produce wrapped, extruded text in a standard SceneKit view.
let extrudedText = SCNText(string: definition.text, extrusionDepth: 0.1)
extrudedText.font = UIFont(name: definition.fontname, size: 0.2)!
extrudedText.containerFrame = CGRect(origin: .zero, size: CGSize(width: 1.8, height: 1.5))
extrudedText.truncationMode = kCATruncationMiddle
extrudedText.isWrapped = true
extrudedText.alignmentMode = kCAAlignmentLeft
let material = SCNMaterial.material(named: "rustediron-streaks")
extrudedText.materials = [material]
geometry = extrudedText
// Update pivot of object to its center
// https://stackoverflow.com/questions/44828764/arkit-placing-an-scntext-at-a-particular-point-in-front-of-the-camera
let (min, max) = boundingBox
let dx = min.x + 0.5 * (max.x - min.x)
let dy = min.y + 0.5 * (max.y - min.y)
let dz = min.z + 0.5 * (max.z - min.z)
pivot = SCNMatrix4MakeTranslation(dx, dy, dz)
Answer from Apple: my font size was too small. If I use a "normal" font size and containing frame on the SCNText object, and then set a scale on the node that contains it, everything wraps as expected. Something like:
extrudedText.font = UIFont(name: definition.fontname, size: 20)!
extrudedText.containerFrame = CGRect(origin: .zero, size: CGSize(width: 100.0, height: 500.0))
...
scale = SCNVector3Make(0.01, 0.01, 0.01)

Swift: Array map or reduce, enumerate with index

I have an array of values [CGFloat] and array of days [CGFloat] with which each value is associated (the time is also important so the days array is a decimal value).
Each day has between 0 and n values. (n typically less than 5 or 6)
I want to find the mean value for each day, so after the calculations I would like to have an array of means [CGFloat] and an array of days [CGFloat], or a dictionary of the two combined or an array of [CGPoint]. I am fairly certain this can be done with either a mapping or reducing or filter function, but I'm having trouble doing so.
For instance
the third day might look like [2.45, 2.75, 2.9]
with associated values [145.0, 150.0, 160.0]
And I would like to end with day[2] = 2.7
and value[2] = 151.7
or
[CGPoint(2.7, 151.7)] or [2.7 : 151.7]
Can anyone offer guidance?
Some code:
let xValues : [CGFloat] = dates.map{round((CGFloat($0.timeIntervalSinceDate(dateZero))/(60*60*24))*100)/100}
let yValues : [CGFloat] = valueDoubles.map{CGFloat($0)}
//xValues and yValues are the same length
var dailyMeans = [CGFloat]()
var xVals = [CGFloat]()
let index = Int(ceil(xValues.last!))
for i in 0..<index{
let thisDay = xValues.enumerate().filter{$0.element >= CGFloat(i) && $0.element < CGFloat(i+1)}
if thisDay.count > 0{
var sum : CGFloat = 0
var day : CGFloat = 0
for i in thisDay{
sum += yValues[i.index]
day += xValues[i.index]
}
dailyMeans.append(sum/CGFloat(thisDay.count))
xVals.append(day/CGFloat(thisDay.count))
}
}
The above code works, but also has to do that enumerate.filter function values.count * days.last times. So for 40 days and 160 readings.. like 6500 times. And I'm already using way too much processing power as it is. Is there a better way to do this?
edit: forgot a line of code defining index as the ceiling of xValues.last
This has been seen 1000 times so I thought I would update with my final solution:
var daySets = [Int: [CGPoint]]()
// points is the full array of (x: dayTimeInDecimal, y: value)
for i in points {
let day = Int(i.x)
daySets[day] = (daySets[day] ?? []) + [i]
}
let meanPointsEachDay = daySets.map{ (key, value) -> CGPoint in
let count = CGFloat(value.count)
let sumPoint = value.reduce(CGPoint.zero, {CGPoint(x: $0.x + $1.x, y: $0.y + $1.y)})
return CGPoint(x: sumPoint.x/count, y: sumPoint.y/count)
}
// must be sorted by 'day'!!!
let arrA0 = [2.45, 2.75, 2.9, 3.1, 3.2, 3.3]
// associated values
let arrA1 = [145.0, 150.0, 160.0, 245.0, 250.0, 260.0]
let arr = Array(zip(arrA0, arrA1))
// now i have an array of tuples, where tuple.0 is key and tuple.1 etc. is associated value
// you can expand the tuple for as much associated values, as you want
print(arr)
// [(2.45, 145.0), (2.75, 150.0), (2.9, 160.0), (3.1, 245.0), (3.2, 250.0), (3.3, 260.0)]
// now i can perform my 'calculations' the most effective way
var res:[Int:(Double,Double)] = [:]
// sorted set of Int 'day' values
let set = Set(arr.map {Int($0.0)}).sort()
// for two int values the sort is redundant, but
// don't be depend on that!
print(set)
// [2, 3]
var g = 0
var j = 0
set.forEach { (i) -> () in
var sum1 = 0.0
var sum2 = 0.0
var t = true
while t && g < arr.count {
let v1 = arr[g].0
let v2 = arr[g].1
t = i == Int(v1)
if t {
g++
j++
} else {
break
}
sum1 += v1
sum2 += v2
}
res[i] = (sum1 / Double(j), sum2 / Double(j))
j = 0
}
print(res)
// [2: (2.7, 151.666666666667), 3: (3.2, 251.666666666667)]
see, that every element of your data is process only once in the 'calculation', independent from size of 'key' set size
use Swift's Double instead of CGFloat! this increase the speed too :-)
and finally what you are looking for
if let (day, value) = res[2] {
print(day, value) // 2.7 151.666666666667
}

Resources