drawing line using vertex shader & fragment shader in spritkit in swift - ios

I need to draw a line between two spritnodes while hand is pointing from one node to another with rendering or animation.
The line drawing is working for me as I follow this link Bobjt's answer, Animate Path Drawing in SpriteKit . I have used concept of fragment shader and vertex shader here..
I have added animatestroke.fsh file and apply the following code .. But the handplayer animation and line drawing speed is mismatching some times.
my line rendering i have given increment by
strokeLengthFloat += 0.01
And handplayer duration is 1.5:
let moveTosecondObject = SKAction.move(to: handmovepoint, duration: 1.5 )
Here is my implementation code:
variable declaration
var handplayer = SKSpriteNode()
var firstObject = SKSpriteNode()
var secondObject = SKSpriteNode()
var startTrigger: Bool = false
let strokeSizeFactor = CGFloat( 2.0 )
var strokeShader: SKShader!
var strokeLengthUniform: SKUniform!
var _strokeLengthFloat: Float = 0.0
var strokeLengthKey: String!
var strokeLengthFloat: Float {
get {
return _strokeLengthFloat
}
set( newStrokeLengthFloat ) {
_strokeLengthFloat = newStrokeLengthFloat
strokeLengthUniform.floatValue = newStrokeLengthFloat
}
}
Loading method
override func didMove(to view: SKView) {
handplayer = SKSpriteNode(imageNamed: "HandImage.png")
handplayer.size.width = handplayer.size.width * 0.5
handplayer.size.height = handplayer.size.height * 0.5
handplayer.position.x = firstObject.position
self.addChild(handplayer)
UpdatePosition()
}
func UpdatePosition(){
//Line drawing part
strokeLengthKey = "u_current_percentage"
strokeLengthUniform = SKUniform( name: strokeLengthKey, float: 0.0 )
let uniforms: [SKUniform] = [strokeLengthUniform]
strokeShader = shaderWithFilename( "animateStroke", fileExtension: "fsh", uniforms: uniforms )
strokeLengthFloat = 0.0
let cameraNode = SKCameraNode()
self.camera = cameraNode
let lineStartPosition = CGPoint(x:firstObject.frame.origin.x+firstObject.size.width/2,y:firstObject.frame.origin.y-firstObject.size.height*0.2)
let lineEndPosition = CGPoint(x:secondObject.frame.origin.x+secondObject.size.width/2,y:secondObject.frame.origin.y+secondObject.size.height*1.2)
let path = CGMutablePath()
path.move(to: CGPoint(x: lineStartPosition.x, y: lineStartPosition.y))
path.addLine(to: CGPoint(x: : lineEndPosition.x, y: lineEndPosition.y), transform: CGAffineTransform.identity)
let strokeWidth = 1.0 * strokeSizeFactor
let shapeNode = SKShapeNode( path: path )
shapeNode.lineWidth = strokeWidth
shapeNode.lineCap = .square
shapeNode.addChild( cameraNode )
shapeNode.strokeShader = strokeShader
shapeNode.calculateAccumulatedFrame()
self.addChild( shapeNode )
}
// center the camera
cameraNode.position = CGPoint( x: self.frame.size.width/2.0, y: self.frame.size.height/2.0 )
**Handplayer Rendering part**
let handmovepoint = CGPoint(x: secondObject.position.x , y: secondObject.position.y + handplayer.size.height*0.1 )
let moveTosecondObject = SKAction.move(to: handmovepoint, duration: 1.5 )
let delay = SKAction.wait(forDuration: 1)
handplayer.run(SKAction.sequence([ delay ]), completion: {() -> Void in
self.startTrigger = true
self.handplayer.run(SKAction.sequence([ moveTosecondObject ]), completion: {() -> Void in
//self.handplayer.alpha = 0.0
})
}
//line rendering part
override func update(_ currentTime: TimeInterval) {
if(startTrigger){
strokeLengthFloat += 0.01
}
if startTrigger == true{
if strokeLengthFloat > 1.0 {
//once line drawing completed
self.startTrigger = false
print("Line drawing ended")
self.handplayer.alpha = 0.0
self.handplayer.removeFromParent()
}
}
}
animatestroke.fsh class
void main()
{
if ( u_path_length == 0.0 ) {
gl_FragColor = vec4( 0.0, 0.0, 1.0, 1.0 ); // draw blue // this is an error
} else if ( v_path_distance / u_path_length <= u_current_percentage ) {
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 ); // draw red
} else {
gl_FragColor = vec4( 0.0, 0.0, 0.0, 0.0 ); // draw nothing
}
}
How could I fix this? Or If anyone have other idea for implementhing this scenario share your Idea.

Related

How to make Circular audio visualizer in swift?

I want to make a visualizer like this Circular visualizer, click the green flag to see the animation.
In my project first I draw a circle, I calculate the points on the circle to draw the visualizer bars, I rotate the view to make the bars feels like circle. I use StreamingKit to stream live radio. StreamingKit provides the live audio power in decibels. Then I animate the visualizer bars. But when I rotate the view the height and width changes according to the angle I rotate. But the bounds value not change (I know the frame depends on superViews).
audioSpectrom Class
class audioSpectrom: UIView {
let animateDuration = 0.15
let visualizerColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
var barsNumber = 0
let barWidth = 4 // width of bar
let radius: CGFloat = 40
var radians = [CGFloat]()
var barPoints = [CGPoint]()
private var rectArray = [CustomView]()
private var waveFormArray = [Int]()
private var initialBarHeight: CGFloat = 0.0
private let mainLayer: CALayer = CALayer()
// draw circle
var midViewX: CGFloat!
var midViewY: CGFloat!
var circlePath = UIBezierPath()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
convenience init() {
self.init(frame: CGRect.zero)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
self.layer.addSublayer(mainLayer)
barsNumber = 10
}
override func layoutSubviews() {
mainLayer.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
drawVisualizer()
}
//-----------------------------------------------------------------
// MARK: - Drawing Section
//-----------------------------------------------------------------
func drawVisualizer() {
midViewX = self.mainLayer.frame.midX
midViewY = self.mainLayer.frame.midY
// Draw Circle
let arcCenter = CGPoint(x: midViewX, y: midViewY)
let circlePath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
let circleShapeLayer = CAShapeLayer()
circleShapeLayer.path = circlePath.cgPath
circleShapeLayer.fillColor = UIColor.blue.cgColor
circleShapeLayer.strokeColor = UIColor.clear.cgColor
circleShapeLayer.lineWidth = 1.0
mainLayer.addSublayer(circleShapeLayer)
// Draw Bars
rectArray = [CustomView]()
for i in 0..<barsNumber {
let angle = ((360 / barsNumber) * i) - 90
let point = calculatePoints(angle: angle, radius: radius)
let radian = angle.degreesToRadians
radians.append(radian)
barPoints.append(point)
let rectangle = CustomView(frame: CGRect(x: barPoints[i].x, y: barPoints[i].y, width: CGFloat(barWidth), height: CGFloat(barWidth)))
initialBarHeight = CGFloat(self.barWidth)
rectangle.setAnchorPoint(anchorPoint: CGPoint.zero)
let rotationAngle = (CGFloat(( 360/barsNumber) * i)).degreesToRadians + 180.degreesToRadians
rectangle.transform = CGAffineTransform(rotationAngle: rotationAngle)
rectangle.backgroundColor = visualizerColor
rectangle.layer.cornerRadius = CGFloat(rectangle.bounds.width / 2)
rectangle.tag = i
self.addSubview(rectangle)
rectArray.append(rectangle)
var values = [5, 10, 15, 10, 5, 1]
waveFormArray = [Int]()
var j: Int = 0
for _ in 0..<barsNumber {
waveFormArray.append(values[j])
j += 1
if j == values.count {
j = 0
}
}
}
}
//-----------------------------------------------------------------
// MARK: - Animation Section
//-----------------------------------------------------------------
func animateAudioVisualizerWithChannel(level0: Float, level1: Float ) {
DispatchQueue.main.async {
UIView.animateKeyframes(withDuration: self.animateDuration, delay: 0, options: .beginFromCurrentState, animations: {
for i in 0..<self.barsNumber {
let channelValue: Int = Int(arc4random_uniform(2))
let wavePeak: Int = Int(arc4random_uniform(UInt32(self.waveFormArray[i])))
let barView = self.rectArray[i] as? CustomView
guard var barFrame = barView?.frame else { return }
// calculate the bar height
let barH = (self.frame.height / 2 ) - self.radius
// scale the value to 40, input value of this func range from 0-60, 60 is low and 0 is high. Then calculate the height by minimise the scaled height from bar height.
let scaled0 = (CGFloat(level0) * barH) / 60
let scaled1 = (CGFloat(level1) * barH) / 60
let calc0 = barH - scaled0
let calc1 = barH - scaled1
if channelValue == 0 {
barFrame.size.height = calc0
} else {
barFrame.size.height = calc1
}
if barFrame.size.height < 4 || barFrame.size.height > ((self.frame.size.height / 2) - self.radius) {
barFrame.size.height = self.initialBarHeight + CGFloat(wavePeak)
}
barView?.frame = barFrame
}
}, completion: nil)
}
}
func calculatePoints(angle: Int, radius: CGFloat) -> CGPoint {
let barX = midViewX + cos((angle).degreesToRadians) * radius
let barY = midViewY + sin((angle).degreesToRadians) * radius
return CGPoint(x: barX, y: barY)
}
}
extension BinaryInteger {
var degreesToRadians: CGFloat { return CGFloat(Int(self)) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
extension UIView{
func setAnchorPoint(anchorPoint: CGPoint) {
var newPoint = CGPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x: self.bounds.size.width * self.layer.anchorPoint.x, y: self.bounds.size.height * self.layer.anchorPoint.y)
newPoint = newPoint.applying(self.transform)
oldPoint = oldPoint.applying(self.transform)
var position : CGPoint = self.layer.position
position.x -= oldPoint.x
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
self.layer.position = position;
self.layer.anchorPoint = anchorPoint;
}
}
I drag a empty view to storyBoard and give custom class as audioSpectrom.
ViewController
func startAudioVisualizer() {
visualizerTimer?.invalidate()
visualizerTimer = nil
visualizerTimer = Timer.scheduledTimer(timeInterval: visualizerAnimationDuration, target: self, selector: #selector(self.visualizerTimerFunc), userInfo: nil, repeats: true)
}
#objc func visualizerTimerFunc(_ timer: CADisplayLink) {
let lowResults = self.audioPlayer!.averagePowerInDecibels(forChannel: 0)
let lowResults1 = self.audioPlayer!.averagePowerInDecibels(forChannel: 1)
audioSpectrom.animateAudioVisualizerWithChannel(level0: -lowResults, level1: -lowResults1)
}
OUTPUT
Without animation
With animation
In my observation, the height value and width value of frame changed when rotates. Means when I give CGSize(width: 4, height: 4) to bar, then when I rotate using some angle it changes the size of frame like CGSize(width: 3.563456, height: 5.67849) (not sure for the value, it's an assumption).
How to resolve this problem?
Any suggestions or answers will be appreciated.
Edit
func animateAudioVisualizerWithChannel(level0: Float, level1: Float ) {
DispatchQueue.main.async {
UIView.animateKeyframes(withDuration: self.animateDuration, delay: 0, options: .beginFromCurrentState, animations: {
for i in 0..<self.barsNumber {
let channelValue: Int = Int(arc4random_uniform(2))
let wavePeak: Int = Int(arc4random_uniform(UInt32(self.waveFormArray[i])))
var barView = self.rectArray[i] as? CustomView
guard let barViewUn = barView else { return }
let barH = (self.frame.height / 2 ) - self.radius
let scaled0 = (CGFloat(level0) * barH) / 60
let scaled1 = (CGFloat(level1) * barH) / 60
let calc0 = barH - scaled0
let calc1 = barH - scaled1
let kSavedTransform = barViewUn.transform
barViewUn.transform = .identity
if channelValue == 0 {
barViewUn.frame.size.height = calc0
} else {
barViewUn.frame.size.height = calc1
}
if barViewUn.frame.height < CGFloat(4) || barViewUn.frame.height > ((self.frame.size.height / 2) - self.radius) {
barViewUn.frame.size.height = self.initialBarHeight + CGFloat(wavePeak)
}
barViewUn.transform = kSavedTransform
barView = barViewUn
}
}, completion: nil)
}
}
Output
Run the below code snippet show the output
<img src="https://i.imgflip.com/227xsa.gif" title="made at imgflip.com"/>
GOT IT!!
circular-visualizer
There are two (maybe three) issues in your code:
1. audioSpectrom.layoutSubviews()
You create new views in layoutSubviews and add them to the view hierarchy. This is not what you are intened to do, because layoutSubviews is called multiple times and you should use it only for layouting purposes.
As a dirty work-around, I modified the code in the func drawVisualizer to only add the bars once:
func drawVisualizer() {
// ... some code here
// ...
mainLayer.addSublayer(circleShapeLayer)
// This will ensure to only add the bars once:
guard rectArray.count == 0 else { return } // If we already have bars, just return
// Draw Bars
rectArray = [CustomView]()
// ... Rest of the func
}
Now, it almost looks good, but there are still some dirt effects with the topmost bar. So you'll have to change
2. audioSectrom.animateAudioVisualizerWithChannel(level0:level1:)
Here, you want to recalculate the frame of the bars. Since they are rotated, the frame also is rotated, and you'd have to apply some mathematical tricks. To avoid this adn make your life more easy, you save the rotated transform, set it to .identity, modify the frame, and then restore the original rotated transform. Unfortunately, this causes some dirt effects with rotations of 0 or 2pi, maybe caused by some rounding issues. Never mind, there is a much more simple solution:
Instead of modifiying the frame, you better modify the bounds.
frame is measured in the outer (in your case: rotated) coordinate system
bounds is measured in the inner (non-transformed) coordinate system
So I simply replaced all the frames with bounds in the function animateAudioVisualizerWithChannel and also removed the saving and restoring of the transformation matrix:
func animateAudioVisualizerWithChannel(level0: Float, level1: Float ) {
// some code before
guard let barViewUn = barView else { return }
let barH = (self.bounds.height / 2 ) - self.radius
let scaled0 = (CGFloat(level0) * barH) / 60
let scaled1 = (CGFloat(level1) * barH) / 60
let calc0 = barH - scaled0
let calc1 = barH - scaled1
if channelValue == 0 {
barViewUn.bounds.size.height = calc0
} else {
barViewUn.bounds.size.height = calc1
}
if barViewUn.bounds.height < CGFloat(4) || barViewUn.bounds.height > ((self.bounds.height / 2) - self.radius) {
barViewUn.bounds.size.height = self.initialBarHeight + CGFloat(wavePeak)
}
barView = barViewUn
// some code after
}
3. Warnings
By the way, you should get rid of all the warnings in your code. I didn't clean up my answer code to keep it comparable with the orginal code.
For example, in var barView = self.rectArray[i] as? CustomView you don't need the conditional cast, because the array already contains CustomView objects.
So, all the barViewUn stuff is unnecessary.
Much more to find and to clean up.

How to detect no collision in a sprite kit game

I'm doing a game where you have to capture candies using a spider hung by a thread, as I show in this link: Game screenshot (I'm new here so I can't post images yet). I already have the movement of the spider from left to right and also I'm able to catch the candies using SKAction moving through 'Y', my only issue is I didn't figure it out yet how to know if the spider don't capture any candy, during his movement, I was trying use the allContactedBodies function when the action finish but the count of the array returned is always zero. Any suggestions?
Here is the code :
class GameScene: SKScene, SKPhysicsContactDelegate {
private var rope = SKSpriteNode(imageNamed: "rope")
private var anchor = SKSpriteNode(imageNamed: "anchor")
private var currentCharacter: SKSpriteNode!
private var candy: SKSpriteNode!
var direction : String = "backward"
var lastCandyAdded: TimeInterval = 0
let candyVelocity: CGFloat = 4.0
let characterBitMask : UInt32 = 0x1 << 1
let candyBitMask: UInt32 = 0x1 << 2
let characterVelocity: CGFloat = 18.0
var direction : String = "backward"
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
self.addAnchor()
self.addRope()
self.addCharacter()
let jointOneFixed = SKPhysicsJointFixed.joint(withBodyA: anchor.physicsBody!, bodyB: rope.physicsBody!, anchor: anchor.position)
self.physicsWorld.add(jointOneFixed);
let jointTwoFixed = SKPhysicsJointFixed.joint(withBodyA: rope.physicsBody!, bodyB: currentCharacter.physicsBody!, anchor: currentCharacter.position)
self.physicsWorld.add(jointTwoFixed);
}
func addAnchor(){
anchor.position = CGPoint(x: self.size.width / 2, y: self.size.height + 1)
anchor.anchorPoint = CGPoint(x: 0.5, y: 0.5)
anchor.setScale(1)
anchor.zPosition = 2
anchor.name = "anchor"
anchor.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: anchor.size.width, height: anchor.size.height))
anchor.physicsBody?.affectedByGravity = false
anchor.physicsBody?.friction = 0;
anchor.physicsBody?.linearDamping = 0;
anchor.physicsBody?.mass = 10;
self.addChild(anchor)
}
func addCharacter() {
let characterName: String = UserDefaults.standard.string(forKey: "current_character")!
currentCharacter = SKSpriteNode(imageNamed: characterName);
currentCharacter.position = CGPoint(x: self.size.width / 2, y: self.size.height - 400)
currentCharacter.anchorPoint = CGPoint(x: 0.5, y: 0.5)
currentCharacter.setScale(0.43)
currentCharacter.zPosition = 2
currentCharacter.name = "character"
currentCharacter.physicsBody = SKPhysicsBody(rectangleOf: currentCharacter.size)
currentCharacter.physicsBody?.categoryBitMask = characterBitMask
currentCharacter.physicsBody?.contactTestBitMask = candyBitMask
currentCharacter.physicsBody?.collisionBitMask = candyBitMask;
currentCharacter.physicsBody?.affectedByGravity = false;
currentCharacter.physicsBody?.friction = 0;
currentCharacter.physicsBody?.linearDamping = 0;
currentCharacter.physicsBody?.mass = 20;
self.addChild(currentCharacter)
}
func addRope() {
rope.position = CGPoint(x: anchor.position.x, y: anchor.position.y - 70)
rope.setScale(0.65)
rope.zPosition = 1
rope.name = "rope"
rope.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: rope.size.width, height: rope.size.height))
rope.physicsBody?.affectedByGravity = false;
rope.physicsBody?.friction = 0;
rope.physicsBody?.linearDamping = 0;
rope.physicsBody?.mass = 5;
rope.physicsBody?.allowsRotation = false
self.addChild(rope)
}
func addCandy() {
let number = Int.random(min: 1, max: 24)
let candyWord = "candie"
let candyTexture = SKTexture(imageNamed: "\(candyWord)\(number)")
candy = SKSpriteNode(texture: candyTexture)
candy.zPosition = 3
candy.setScale(0.40)
candy.physicsBody = SKPhysicsBody(circleOfRadius: max(candy.size.width / 2, candy.size.height / 2))
candy.physicsBody?.isDynamic = true
candy.name = "candy"
candy.physicsBody?.categoryBitMask = candyBitMask
candy.physicsBody?.contactTestBitMask = characterBitMask
candy.physicsBody?.collisionBitMask = characterBitMask
candy.physicsBody?.affectedByGravity = false
candy.position = CGPoint(x: self.frame.size.width + 20, y: self.frame.size.height / 2 + 150)
self.addChild(candy)
}
func moveCandy() {
self.enumerateChildNodes(withName: "candy", using: {(node, stop) -> Void in
if let candy = node as? SKSpriteNode {
candy.position = CGPoint(x: candy.position.x - self.candyVelocity, y: candy.position.y)
if candy.position.x < 0 {
candy.removeFromParent()
}
}
})
}
override func update(_ currentTime: TimeInterval) {
self.moveCandy()
self.moveCharacter()
if currentTime - self.lastCandyAdded > 0.75 {
self.lastCandyAdded = currentTime + Double.random(min: 0.00, max: 0.60)
self.addCandy()
}
}
func collisionBetween(candy: SKNode, object: SKNode) {
let moveUp = SKAction.moveBy(x: 0, y: 100, duration:0.0)
let shrinkRope = SKAction.animate(with: [SKTexture(imageNamed: "rope")], timePerFrame: 0)
let moveUpBlock = SKAction.run({
self.anchor.run(moveUp)
})
let shrinkRopeBlock = SKAction.run({
self.rope.run(shrinkRope)
})
let sequence = SKAction.sequence([SKAction.wait(forDuration: 0.07), shrinkRopeBlock, moveUpBlock])
self.run(sequence)
candy.removeFromParent()
}
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeA.name == "candy" && nodeB.name == "character" {
collisionBetween(candy: nodeA, object: nodeB)
} else if nodeA.name == "character" && nodeB.name == "candy" {
collisionBetween(candy: nodeB, object: nodeA)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){
let moveDown = SKAction.moveBy(x: 0, y: -100, duration:0.0)
let stretchRope = SKAction.animate(with: [SKTexture(imageNamed: "rope_stretch")], timePerFrame: 0)
let moveDownBlock = SKAction.run({
self.anchor.run(moveDown, completion: {
var physicBodies = self.currentCharacter.physicsBody?.allContactedBodies();
// This count is always zero
print(physicBodies?.count)
})
})
let stretchRopeBlock = SKAction.run({
self.rope.run(stretchRope)
})
let sequence = SKAction.sequence([moveDownBlock, stretchRopeBlock])
self.run(sequence)
}
func moveCharacter(){
self.enumerateChildNodes(withName: "anchor", using: {(node, stop) -> Void in
if let anchorNode = node as? SKSpriteNode {
if anchorNode.position.x < 120 {
anchorNode.position = CGPoint(x: anchorNode.position.x + self.characterVelocity, y: anchorNode.position.y)
self.direction = "forward"
} else if anchorNode.position.x > self.size.width - 120 {
anchorNode.position = CGPoint(x: anchorNode.position.x - self.characterVelocity, y: anchorNode.position.y)
self.direction = "backward"
} else if self.direction == "forward" {
anchorNode.position = CGPoint(x: anchorNode.position.x + self.characterVelocity, y: anchorNode.position.y)
self.direction = "forward"
} else {
anchorNode.position = CGPoint(x: anchorNode.position.x - self.characterVelocity, y: anchorNode.position.y)
self.direction = "backward"
}
}
})
}
}
First of all you have a lot of code in this question...too much, it makes it hard to focus on what is really happening. You are also missing a block of code for moveCharacter().
You should strongly look at creating subclasses for your player, rope and (mostly)candies. I would also look into creating an array of candies initially so that you are not dynamically creating physics objects during run time.
As for your question wouldn't it be as simple as creating a couple of variables in your class
private var isMoving = false
private var didGetCandy = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//prevent the user from firing the move while in the middle of a move
guard !isMoving else { return }
isMoving = true
let moveDownBlock = SKAction.run({
self.anchor.run(moveDown) {
//this is an appreviated form of a 'completion' block
self.isMoving = false
print("did I get a candy \(didGetCandy)")
}
})
}
func collisionBetween(candy: SKNode, object: SKNode) {
didGetCandy = true
}

CGRect in Swift 3 rotating with correct origin

I'm trying to rotate a line (thin rectangle) around its endpoint. I have it working fine, but step 2 is to shorten the line and have it still rotate around the endpoint (center of rotation).
// Create and add a colored square
var rod = UIView()
var countsteps = 0
var Mass = 1
var Radius = 1.00
let rectWidth = screenWidth * 0.5
let rectVertical = screenHeight * 0.5
let rectLeft = screenWidth * 0.5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// set background color to blue
rod.backgroundColor = UIColor(red: 145/255, green: 170/255, blue: 157/255, alpha: 1)
//rod Line
rod.frame = CGRect(x: rectLeft, y: rectVertical, width: 3, height: rectWidth)
// finally, add the square to the screen
self.view.addSubview(rod)
//Create Timer
_ = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(Acceleration.update), userInfo: nil, repeats: true)
}
func update() {
UIView.animate(withDuration: 0.1, animations: {
//Slope Line
let rectSize = CGFloat(self.Radius) * self.rectWidth / 100
let amountRotation = CGFloat(CGFloat(self.countsteps)/57.3)
let originPoint = CGPoint(x: 0,y: 0)
self.rod.layer.anchorPoint = originPoint
self.rod.transform = CGAffineTransform(rotationAngle: amountRotation)
self.countsteps = self.countsteps + 1
if (self.countsteps > 359) {
self.countsteps = 0
}
})
}
I tried adding this line:
self.rod.frame.size = CGSize(width: 3.00, height: rectSize)
But, that makes the box rotate while changing proportions around a different axis.
Try adding this line:
self.rod.layer.position = originPoint
so, your update func looks like:
func update() {
UIView.animate(withDuration: 0.1, animations: {
//Slope Line
let rectSize = CGFloat(self.Radius) * self.rectWidth / 100
let amountRotation = CGFloat(CGFloat(self.countsteps)/57.3)
let originPoint = CGPoint(x: 0,y: 0)
self.rod.layer.anchorPoint = originPoint
self.rod.layer.position = originPoint
self.rod.transform = CGAffineTransform(rotationAngle: amountRotation)
self.countsteps = self.countsteps + 1
if (self.countsteps > 359) {
self.countsteps = 0
}
})
}
I seriously recommend adding some color to layers and views background, with some alpha, it is quite useful to detect what the system is doing, like a visual debug.

How to create a separation between two SpriteKit objects

I have a circle and triangles inside and outside. I was looking through my character if it is not in a circle outside the circle touching triangles and vice versa.
It does not matter if my character out of my circle it still happens.
In fact if I'm in my circle and triangles outside my circle so my character should not touch them.
How do I solve this?
Does anyone have an idea?
My Code :
func AddCharacter() {
BooCharacter.size = CGSize(width: 30, height: 30)
BooCharacter.anchorPoint.y = 0
BooCharacter.zRotation = CGFloat(-M_PI_2)
//BooCharacter.position.y += circleRadius
BooCharacter.position = CGPoint(x:0.0, y:circleRadius)
BooCharacter.physicsBody = SKPhysicsBody(texture:BooCharacterSKT, size: CGSize(width: 30, height: 30))
BooCharacter.physicsBody = SKPhysicsBody(circleOfRadius: 15);
BooCharacter.physicsBody?.categoryBitMask = heroCategory
BooCharacter.physicsBody?.contactTestBitMask = triangleCategory
BooCharacter.physicsBody?.collisionBitMask = triangleCategory;
}
func AddCircle() {
Circle = SKShapeNode(circleOfRadius: circleRadius)
Circle.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
Circle.strokeColor = UIColor.whiteColor()
self.addChild(Circle)
Circle.addChild(BooCharacter)
self.AddTriangleToCircle(Circle, Location: CGFloat(random(1...90)), Inside: true)
self.AddTriangleToCircle(Circle, Location: CGFloat(random(90...280)), Inside: false)
self.AddTriangleToCircle(Circle, Location: CGFloat(random(280...360)), Inside: false)
//self.AddTriangleToCircle(Circle, Location: 90)
//self.AddTriangleToCircle(Circle, Location: 180)
//self.AddTriangleToCircle(Circle, Location: 240)
//self.AddTriangleToCircle(Circle, Location: 300)
}
func AddPointsLable() {
pointsLabel = PointsLabel(num: 0)
pointsLabel.position = CGPoint(x: self.size.width/2, y: self.size.height/2 - 35)
pointsLabel.name = "pointsLabel"
addChild(pointsLabel)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
BooCharacter.zRotation += CGFloat(M_PI)
//for touch in touches {
//let location = touch.locationInNode(self)
let TapLable = childNodeWithName("tap")
TapLable?.removeFromParent()
//}
}
override func update(currentTime: NSTimeInterval) {
angleRelatedToCircle -= rotationSpeed
//BooCharacter.zRotation -= rotationSpeed
let newLocation = CGPointMake(circleRadius * cos(angleRelatedToCircle), circleRadius * sin(angleRelatedToCircle));
// BooCharacter.position.x = circleRadius * cos(angleRelatedToCircle)
// BooCharacter.position.y = circleRadius * sin(angleRelatedToCircle)
BooCharacter.runAction(SKAction.rotateByAngle(-rotationSpeed, duration: 0));
BooCharacter.runAction(SKAction.moveTo(newLocation, duration: 0));
//SKAction.moveTo(CGP, duration: <#T##NSTimeInterval#>)
//NSLog("x = %f, y = %f, r = %f",BooCharacter.position.x,BooCharacter.position.y,BooCharacter.zRotation);
}
func AddTriangleToCircle(Circle: SKShapeNode, Location: CGFloat, Inside: Bool) {
let Triangle: SKSpriteNode = SKSpriteNode(imageNamed: "Triangle")
Triangle.size = CGSize(width: 30, height: 30)
Triangle.anchorPoint.y = 0
if Inside == true {
// Inside Triangle
Triangle.zRotation = CGFloat(M_PI_2)
} else {
// Outside Triangle
Triangle.zRotation = CGFloat(-M_PI_2)
}
Triangle.position = CGPoint(x:0.0, y:circleRadius)
let rotationSpeed1 = rotationSpeed + Location;
var angleRelatedToCircle1 = angleRelatedToCircle;
angleRelatedToCircle1 -= rotationSpeed1
Triangle.zRotation -= rotationSpeed1
Triangle.position.x = circleRadius * cos(angleRelatedToCircle1)
Triangle.position.y = circleRadius * sin(angleRelatedToCircle1)
//Triangle.name = "Triangle";
Triangle.physicsBody = SKPhysicsBody(texture:TriangelSKT, size: CGSize(width: 30, height: 30))
//TODO: Make this a polygon body
Triangle.physicsBody?.categoryBitMask = triangleCategory
Triangle.physicsBody?.contactTestBitMask = heroCategory
Triangle.physicsBody?.collisionBitMask = heroCategory
Circle.addChild(Triangle);
}
func didBeginContact(contact: SKPhysicsContact) {
// ---------------------------------------------
// Hero Hit Triangle
// ---------------------------------------------
if (contact.bodyA.categoryBitMask == triangleCategory) {
// Play Sound Effect
// Remove Triangle
//contact.bodyA.node?.removeFromParent();
// Update Score
NSLog("Hero hit Triangle");
}
}
func random(range: Range<Int> ) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
What you probably want is to set character's physics body like this:
BooCharacter.physicsBody = SKPhysicsBody(circleOfRadius: 15);
because currently, character size is a 30x30 rectangle and its body is a circle with a diameter of 60 (d=2r). You need a body with diameter of 30.
Also, you are changing characters anchor point but keep in mind that physics body is not affected with that action. It stays centered on node's position... Read more in this example.
And about triangle node. Currently it has a physics body with a rectangular shape even if it's a triangle. Not sure if you want that, but it may cause the problems you are experiencing. You have a few options to solve this:
Create physics body manually, like pointed in one of your previous questions, or
Create a physics body from a texture. Keep in mind that this might be performance intensive, but it will probably be fine for your game because you don't have many objects on the scene.

Objects not affected by gravity field in SceneKit [duplicate]

I'm trying to use a SCNPhysicsField.linearGravityField object to affect only specific objects in my scene. The problem is, that I can't seem to get it to affect anything. Here's a sample of my code in Swift:
let downGravityCatagory = 1 << 0
let fieldDown = SCNPhysicsField.linearGravityField()
let fieldUp = SCNPhysicsField.linearGravityField()
let fieldNode = SCNNode()
let sceneView = view as! SCNView
sceneView.scene = scene
sceneView.scene!.physicsWorld.gravity = SCNVector3(x: 0, y: 0, z: 0)
fieldDown.categoryBitMask = downGravityCatagory
fieldDown.active = true
fieldDown.strength = 3
fieldNode.physicsField = fieldDown
scene.rootNode.addChildNode(fieldNode)
var dice = SCNNode()
//I then attach geometry here
dice.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Dynamic, shape: SCNPhysicsShape(geometry: dice.geometry!, options: nil))
dice.physicsBody?.categoryBitMask = downGravityCatagory
scene.rootNode.addChildNode(dice)
Even though the Physics Bodies are assigned the same catagoryBitMask as the gravity field, they just float there in zero G, only affected by the physicsworld gravity.
Set the "downGravityCatagory" bit mask on the node:
dice.categoryBitMask = downGravityCatagory;
the physics's categoryBitMask is for physics collisions only.
I am trying to get my ball to fall faster. Right now it falls really slowly. I tried using this answer. I think I am close, but I do not really know. The code:
import Cocoa
import SceneKit
class AppController : NSObject {
#IBOutlet weak var _sceneView: SCNView!
#IBOutlet weak var _pushButton: NSButton!
#IBOutlet weak var _resetButton: NSButton!
private let _mySphereNode = SCNNode()
private let _gravityFieldNode = SCNNode()
private let downGravityCategory = 1 << 0
private func setupScene() {
// setup ambient light source
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = NSColor(white: 0.35, alpha: 1.0).CGColor
// add ambient light the scene
_sceneView.scene!.rootNode.addChildNode(ambientLightNode)
// setup onmidirectional light
let omniLightNode = SCNNode()
omniLightNode.light = SCNLight()
omniLightNode.light!.type = SCNLightTypeOmni
omniLightNode.light!.color = NSColor(white: 0.56, alpha: 1.0).CGColor
omniLightNode.position = SCNVector3Make(0.0, 200.0, 0.0)
_sceneView.scene!.rootNode.addChildNode(omniLightNode)
// add plane
let myPlane = SCNPlane(width: 125.0, height: 2000.0)
myPlane.widthSegmentCount = 10
myPlane.heightSegmentCount = 10
myPlane.firstMaterial!.diffuse.contents = NSColor.orangeColor().CGColor
myPlane.firstMaterial!.specular.contents = NSColor.whiteColor().CGColor
let planeNode = SCNNode()
planeNode.geometry = myPlane
// rotote -90.0 degrees about the y-axis, then rotate -90.0 about the x-axis
var rotMat = SCNMatrix4MakeRotation(-3.14/2.0, 0.0, 1.0, 0.0)
rotMat = SCNMatrix4Rotate(rotMat, -3.14/2.0, 1.0, 0.0, 0.0)
planeNode.transform = rotMat
planeNode.position = SCNVector3Make(0.0, 0.0, 0.0)
// add physcis to plane
planeNode.physicsBody = SCNPhysicsBody.staticBody()
// add plane to scene
_sceneView.scene!.rootNode.addChildNode(planeNode)
// gravity folks...
// first, set the position for field effect
let gravityField = SCNPhysicsField.linearGravityField()
gravityField.categoryBitMask = downGravityCategory
gravityField.active = true
gravityField.strength = 3.0
gravityField.exclusive = true
_gravityFieldNode.physicsField = gravityField
_sceneView.scene!.rootNode.addChildNode(_gravityFieldNode)
// attach the sphere node to the scene's root node
_mySphereNode.categoryBitMask = downGravityCategory
_sceneView.scene!.rootNode.addChildNode(_mySphereNode)
}
private func setupBall() {
let radius = 25.0
// sphere geometry
let mySphere = SCNSphere(radius: CGFloat(radius))
mySphere.geodesic = true
mySphere.segmentCount = 50
mySphere.firstMaterial!.diffuse.contents = NSColor.purpleColor().CGColor
mySphere.firstMaterial!.specular.contents = NSColor.whiteColor().CGColor
// position sphere geometry, add it to node
_mySphereNode.position = SCNVector3(0.0, CGFloat(radius), 0.0)
_mySphereNode.geometry = mySphere
// physics body and shape
_mySphereNode.physicsBody = SCNPhysicsBody(type: .Dynamic, shape: SCNPhysicsShape(geometry: mySphere, options: nil))
_mySphereNode.physicsBody!.mass = 0.125
}
private func stopBall() {
_mySphereNode.geometry = nil
_mySphereNode.physicsBody = nil
}
override func awakeFromNib() {
// assign empty scene
_sceneView.scene = SCNScene()
setupScene()
setupBall()
}
#IBAction func moveBall(sender: AnyObject) {
let forceApplied = SCNVector3Make(35.0, 0.0, 0.0)
if _mySphereNode.physicsBody?.isResting == true {
_mySphereNode.physicsBody!.applyForce(forceApplied, impulse: true)
}
else if _mySphereNode.physicsBody?.isResting == false {
print("ball not at rest...")
}
else {
print("No physics associated with the node...")
}
}
#IBAction func resetBall(sender: AnyObject) {
// remove the ball from the sphere node
stopBall()
// reset the ball
setupBall()
}
}
Is there some trick to get "real-world" type gravity?
Based on what #tedesignz suggested, here is the slightly modified working code:
import Cocoa
import SceneKit
class AppController : NSObject, SCNPhysicsContactDelegate {
#IBOutlet weak var _sceneView: SCNView!
#IBOutlet weak var _pushButton: NSButton!
#IBOutlet weak var _resetButton: NSButton!
private let _mySphereNode = SCNNode()
private let _myPlaneNode = SCNNode()
private let _gravityFieldNode = SCNNode()
private let BallType = 1
private let PlaneType = 2
private let GravityType = 3
private func setupScene() {
// setup ambient light source
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = NSColor(white: 0.35, alpha: 1.0).CGColor
// add ambient light the scene
_sceneView.scene!.rootNode.addChildNode(ambientLightNode)
// setup onmidirectional light
let omniLightNode = SCNNode()
omniLightNode.light = SCNLight()
omniLightNode.light!.type = SCNLightTypeOmni
omniLightNode.light!.color = NSColor(white: 0.56, alpha: 1.0).CGColor
omniLightNode.position = SCNVector3Make(0.0, 200.0, 0.0)
_sceneView.scene!.rootNode.addChildNode(omniLightNode)
// add plane
let myPlane = SCNPlane(width: 125.0, height: 150.0)
myPlane.widthSegmentCount = 10
myPlane.heightSegmentCount = 10
myPlane.firstMaterial!.diffuse.contents = NSColor.orangeColor().CGColor
myPlane.firstMaterial!.specular.contents = NSColor.whiteColor().CGColor
_myPlaneNode.geometry = myPlane
// rotote -90.0 degrees about the y-axis, then rotate -90.0 about the x-axis
var rotMat = SCNMatrix4MakeRotation(-3.14/2.0, 0.0, 1.0, 0.0)
rotMat = SCNMatrix4Rotate(rotMat, -3.14/2.0, 1.0, 0.0, 0.0)
_myPlaneNode.transform = rotMat
_myPlaneNode.position = SCNVector3Make(0.0, 0.0, 0.0)
// add physcis to plane
_myPlaneNode.physicsBody = SCNPhysicsBody(type: .Static, shape: SCNPhysicsShape(geometry: myPlane, options: nil))
// configure physics body
_myPlaneNode.physicsBody!.contactTestBitMask = BallType
_myPlaneNode.physicsBody!.collisionBitMask = BallType
_myPlaneNode.physicsBody!.categoryBitMask = PlaneType
// add plane to scene
_sceneView.scene!.rootNode.addChildNode(_myPlaneNode)
// add sphere node
_sceneView.scene!.rootNode.addChildNode(_mySphereNode)
// gravity folks...
// first, set the position for field effect
let gravityField = SCNPhysicsField.linearGravityField()
gravityField.categoryBitMask = GravityType
gravityField.active = true
gravityField.direction = SCNVector3(0.0, -9.81, 0.0)
print(gravityField.direction)
gravityField.strength = 10.0
gravityField.exclusive = true
_gravityFieldNode.physicsField = gravityField
_sceneView.scene!.rootNode.addChildNode(_gravityFieldNode)
// set the default gravity to zero vector
_sceneView.scene!.physicsWorld.gravity = SCNVector3(0.0, 0.0, 0.0)
}
private func setupBall() {
let radius = 25.0
// sphere geometry
let mySphere = SCNSphere(radius: CGFloat(radius))
mySphere.geodesic = true
mySphere.segmentCount = 50
mySphere.firstMaterial!.diffuse.contents = NSColor.purpleColor().CGColor
mySphere.firstMaterial!.specular.contents = NSColor.whiteColor().CGColor
// position sphere geometry, add it to node
_mySphereNode.position = SCNVector3(0.0, CGFloat(radius), 0.0)
_mySphereNode.geometry = mySphere
// physics body and shape
_mySphereNode.physicsBody = SCNPhysicsBody(type: .Dynamic, shape: SCNPhysicsShape(geometry: mySphere, options: nil))
_mySphereNode.physicsBody!.mass = 0.125
_mySphereNode.physicsBody!.contactTestBitMask = PlaneType
_mySphereNode.physicsBody!.collisionBitMask = PlaneType
_mySphereNode.physicsBody!.categoryBitMask = (BallType | GravityType)
}
private func stopBall() {
_mySphereNode.geometry = nil
_mySphereNode.physicsBody = nil
}
override func awakeFromNib() {
// assign empty scene
_sceneView.scene = SCNScene()
// contact delegate
_sceneView.scene!.physicsWorld.contactDelegate = self
setupScene()
setupBall()
}
#IBAction func moveBall(sender: AnyObject) {
let forceApplied = SCNVector3Make(5.0, 0.0, 0.0)
if _mySphereNode.physicsBody?.isResting == true {
_mySphereNode.physicsBody!.applyForce(forceApplied, impulse: true)
}
else if _mySphereNode.physicsBody?.isResting == false {
print("ball not at rest...")
}
else {
print("No physics associated with the node...")
}
}
#IBAction func resetBall(sender: AnyObject) {
// remove the ball from the sphere node
stopBall()
// reset the ball
setupBall()
}
/*** SCENEKIT DELEGATE METHODS ***/
func physicsWorld(world: SCNPhysicsWorld, didBeginContact contact: SCNPhysicsContact) {
print("we have contact...")
}
func physicsWorld(world: SCNPhysicsWorld, didEndContact contact: SCNPhysicsContact) {
print("No longer touching...")
}
}

Resources