SpriteKit - touchesMoved, aim crosshair - swift

I have a spritenode and a crosshair node, i want when the player touch the spritenode and move the crosshair also moves.
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
var body = self.nodeAtPoint(location)
if var name: String = body.name {
if body.name == "aim-button" {
crossHair.position = CGPointMake(crossHair.position.x + 10, crossHair.position.y + 10)
}
}
}
}
The crosshair does get move but only in one direction, and i have no idea how to make it accurate depending on the space the touch moved to-from (which is obviously way smaller than what the crosshair should actually move on the screen) and the direction.

for touch: AnyObject in touches {
//Get the current position in scene of the touch.
let location = touch.locationInNode(self)
//Get the previous position in scene of the touch.
let previousLocation = touch.previousLocationInNode(self)
//Calculate the translation.
let translation = CGPointMake(location.x - previousLocation.x, location.y - previousLocation.y)
//Get the current position in scene of the crossHair.
let position = crossHair.position
// Get the bode touched
var body = self.nodeAtPoint(location)
if var name: String = body.name {
if body.name == "aim-button" {
//Set the position of the crosshair to its current position plus the translation.
crossHair.position = CGPointMake(position.x + translation.x * 2, position.y + translation.y * 2)
//Set the position of the body
body.position = location
}
}
}
If the crosshair should move more distance than the touch, just multiply the translation by a factor (i'm using 2 in the example above).

Related

How to make a character jump accurate in SceneKit using updateAtTime

I try to make a character jump in SceneKit. I currently have a solution, but I am not happy at all with it.
The character movement is based on the updateAtTime renderer function. There are no SCNActions involved. I want it to be moved only by the updateAtTime function.
My scene is entirely based on Apple's Fox demo Game. But In this Demo Game the Fox Character can only fall from heights - not jump. So I made an adaptation to reverse the fall Function into a jump function. The result is kind of woking, but not accurate. Have a look:
As this image shows, the character currently jumps up in a manner that is against natural laws. It accelerates slowly, then gets faster and faster. (Falling down is okay). I'd like to have my character jump in a nice sinus wave, like the right side of my image illustrates. and as it would be in real nature.
Here is some Code, that makes my character currently jump up and down.
// For Jumping (Falling)
if groundAltitude < position.y - threshold || self.isJumping {
if self.isJumpingUp {
accelerationY -= SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. UP MOVEMENT
} else {
accelerationY += SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. DOWN MOVEMENT (Orig Apple)
}
} else {
accelerationY = 0
}
// Set Position
position.y -= accelerationY // ORIG
// reset acceleration if we touch the ground
if groundAltitude > position.y {
accelerationY = 0
position.y = groundAltitude
}
As you can see, I am using the variables isJumping and isJumpingUp to control/define the Y-direction in which the character should be moved. So what I do is setting the isJumping Variable to true and also the isJumpingUp, which makes the Character move up. Then at half the time I set the variable isJumpingUp to false, which will reverse the direction, and brings the character down to the ground level.
This all results in a more or less in an inaccurate jump movement as the image visualises.
Then I found this article on SO: How to make my character jump with gravity?
(It has even the ability to run this code snippet in the Browser.)
And this is very close, if not exactly what I am looking for. (But I am only looking for the Y-direction movement stuff)
But each and every attempt to make a Swift/SceneKit adaptation of this results in a total mess. I don't get it managed to implement into my update function the same way as it behaves on the website. And I don't know what I am doing wrong.
Some attempts made the character to jump up very very fast and then never come down. Or the character did not jump at all. I just have no clue... I would like my character to jump up about 1m to 2m in height and then fall down.
Any help in making my character jump accurately is so much appreciated.
For visualisation purposes - here is my entire Movement Function as it currently is:
func moveCharacter(time: TimeInterval) {
// Delta time since last update
if previousUpdateTime == 0.0 { previousUpdateTime = time }
let deltaTime = Float(min(time - previousUpdateTime, 1.0 / 60))
previousUpdateTime = time // GOOD
groundType = GroundType.inTheAir // always make it in the Air, later change, seems to crash the app ??? oder zufall? - RE-ENABLED for testing
// Speed Control
var characterSpeed = deltaTime * self.speedFactor
let characterRunSpeed = deltaTime * self.speedFactor * 3.4
// Remember initial position
let initialPosition = self.node.position
// Move Character Left or Right
if self.isWalkingLeft { self.node.position = self.node.position - SCNVector3(1.0,0.0,0.0) * characterSpeed}
if self.isWalkingRight { self.node.position = self.node.position + SCNVector3(1.0,0.0,0.0) * characterSpeed}
if self.isRunningLeft { self.node.position = self.node.position - SCNVector3(1.0,0.0,0.0) * characterRunSpeed}
if self.isRunningRight { self.node.position = self.node.position + SCNVector3(1.0,0.0,0.0) * characterRunSpeed}
// Character height positioning
var position = self.node.position
var p0 = position
var p1 = position
let maxRise = SCNFloat(1.0) // orig 0.08
let maxJump = SCNFloat(50.0) // orig 20.0
p0.y -= maxJump
p1.y += maxRise
// Do a vertical ray intersection
let results = gameScene.physicsWorld.rayTestWithSegment(from: p1, to: p0, options:[.collisionBitMask: BitMasks.BitmaskCollision, .searchMode: SCNPhysicsWorld.TestSearchMode.closest])
if let result = results.first {
guard (result.node.geometry != nil) else {return}
let groundAltitude = result.worldCoordinates.y
// can the following if statement be made in other way, because of the new guard?
if (result.node.geometry!.firstMaterial) != nil { groundType = groundTypeFromMaterial(material: result.node.geometry!.firstMaterial!) } else { groundType = .rock }
// MARK: Handle Y Position
let threshold = SCNFloat(1e-5)
let gravityAcceleration = SCNFloat(0.18) // 0.18
if groundAltitude < position.y - threshold || self.isJumping {
if self.isJumpingUp {
accelerationY -= SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. UP
} else {
accelerationY += SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. DOWN (Orig)
}
}
else {
accelerationY = 0
}
// Set Position
position.y -= accelerationY // orig.
// reset acceleration if we touch the ground
if groundAltitude > position.y {
accelerationY = 0
position.y = groundAltitude
}
// Finally, update the position of the character.
self.node.position = position
// if not touching the ground, we are in the air.
if groundAltitude < position.y - 0.2 {
groundType = .inTheAir
}
} else {
// no result, we are probably out the bounds of the level -> revert the position of the character.
self.node.position = initialPosition
}
}
Well, after some sleep, and reflecting myself, I came on that solution:
(and it works quite nice)
Added this variables to the class:
let gravityDrag : SCNFloat = 0.9999 // kind of slowing down the jump
let jumpPower : SCNFloat = -0.09 // jumps between 1,5m to 2.0m
Then changed the moveCharacter function like so:
// Calc Y Position with acceleration and gravity
if groundAltitude < position.y - threshold || self.isJumping {
accelerationY += SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. DOWN (Orig)
accelerationY *= gravityDrag
}
else {
accelerationY = 0
}
// Set Position
position.y -= accelerationY // orig.
// reset acceleration if we touch the ground
if groundAltitude > position.y {
isJumping = false
accelerationY = 0
position.y = groundAltitude
}
Trigger a jump like so:
isJumping = true
accelerationY = jumpPower
Hopefully this will help someone, somewhen.

Implementing AI to Air Hockey in SpriteKIt?

I am working on a game similar to air hockey in SpriteKit for fun and to learn Swift/Xcode. I anticipate the AI to be quite a challenge as there is other elements to the game which will need to be accounted for. I know I'll have to keep tackling each issue one by one. I have created the 2 player mode for the game, and I'm working on AI now. Here is some code I have used for calculating and delegating the impulse from mallet to puck (in the 2 player mode):
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
bottomTouchIsActive = true
var releventTouch:UITouch!
//convert set to known type
let touchSet = touches
//get array of touches so we can loop through them
let orderedTouches = Array(touchSet)
for touch in orderedTouches
{
//if we've not yet found a relevent touch
if releventTouch == nil
{
//look for a touch that is in the activeArea (Avoid touches by opponent)
if activeArea.contains(CGPoint(x: touch.location(in: parent!).x, y: touch.location(in: parent!).y + frame.height * 0.24))
{
isUserInteractionEnabled = true
releventTouch = touch
}
else
{
releventTouch = nil
}
}
}
if (releventTouch != nil)
{
//get touch position and relocate player
let location = CGPoint(x: releventTouch!.location(in: parent!).x, y: releventTouch!.location(in: parent!).y + frame.height * 0.24)
position = location
//find old location and use pythagoras to determine length between both points
let oldLocation = CGPoint(x: releventTouch!.previousLocation(in: parent!).x, y: releventTouch!.previousLocation(in: parent!).y + frame.height * 0.24)
let xOffset = location.x - oldLocation.x
let yOffset = location.y - oldLocation.y
let vectorLength = sqrt(xOffset * xOffset + yOffset * yOffset)
//get eleapsed and use to calculate speed6A
if lastTouchTimeStamp != nil
{
let seconds = releventTouch.timestamp - lastTouchTimeStamp!
let velocity = 0.01 * Double(vectorLength) / seconds
//to calculate the vector, the velcity needs to be converted to a CGFloat
let velocityCGFloat = CGFloat(velocity)
//calculate the impulse
let directionVector = CGVector(dx: velocityCGFloat * xOffset / vectorLength, dy: velocityCGFloat * yOffset / vectorLength)
//pass the vector to the scene (so it can apply an impulse to the puck)
delegate?.bottomForce(directionVector, fromBottomPlayer: self)
delegate?.bottomTouchIsActive(bottomTouchIsActive, fromBottomPlayer: self)
}
//update latest touch time for next calculation
lastTouchTimeStamp = releventTouch.timestamp
}
}
I am wondering how I can convert this code for the AI. I have been adding some AI logic to the update function which I believe could also use time stamps and calculate distance traveled between frames to calculate the impulse. I just don't know exactly how to implement that thought. Any help is greatly appreciated :)
Here is some bare bones code I have so far for testing purposes mostly for the AI mode in the update function:
if (ball?.position.y)! < frame.height / 2
{
if (botPlayer?.position.y)! < frame.height * 0.75
{
botPlayer?.position.y += 1
}
}
else
{
if (botPlayer?.position.y)! > (ball?.position.y)!
{
if (botPlayer?.position.y)! - (ball?.position.y)! > frame.height * 0.1
{
botPlayer?.position.y -= 1
}
else
{
botPlayer?.position.y -= 3
}
}
else
{
botPlayer?.position.y += 1
}
}
if ((botPlayer?.position.x)! - (ball?.position.x)!) < 2
{
botPlayer?.position.x = (ball?.position.x)!
}
if (botPlayer?.position.x)! > (ball?.position.x)!
{
botPlayer?.position.x -= 2
}
else if (botPlayer?.position.x)! < (ball?.position.x)!
{
botPlayer?.position.x += 2
}
For AI to make a decision it must have information about the game state. To do this you can write a function that reads all available game data (placement of pucks, player scores, previous moves, etc) and returns the state as a dictionary. Then write a function to take in this dictionary, and output a decision. Consider this workflow in Python.
# we want the AI to make a decision
# start by grabbing game state
gameState = getGameState()
# pass this to the decision function
decision = getDecision( gameState )
# implement the decision
if decision == "move left":
moveLeft()
else:
moveRight()
Is this what you're looking for?

Swift scene kit - cant apply velocity WHILE doing rotation? Direction is off?

Ok, so Im going straight off Apple's tutorial using a joystick moving an SCNNode for SceneKithere.
I've copied the code and gotten the joystick to both move and rotate the character - but not simultaneously, and not in the right direction relative to the node.
All the correct code is in that download, but what I've done is here is where I get the angle offset of the joystick handle and the float2 from the joystick UI-
characterDirection = float2(Float(padNode.stickPosition.x), -Float(padNode.stickPosition.y))
let direction = theDude.characterDirection(withPointOfView: renderer.pointOfView)
directionAngle = CGFloat(atan2f(direction.x, direction.z))
public func characterDirection(withPointOfView pointOfView: SCNNode?) -> float3 {
let controllerDir = theDude.direction //THIS ISNT BEING UPDATED
if controllerDir.allZero() {
return float3.zero
}
var directionWorld = float3.zero
if let pov = pointOfView {
let p1 = pov.presentation.simdConvertPosition(float3(controllerDir.x, 0.0, controllerDir.y), to: nil)
let p0 = pov.presentation.simdConvertPosition(float3.zero, to: nil)
directionWorld = p1 - p0
directionWorld.y = 0
if simd_any(directionWorld != float3.zero) {
let minControllerSpeedFactor = Float(0.2)
let maxControllerSpeedFactor = Float(1.0)
let speed = simd_length(controllerDir) * (maxControllerSpeedFactor - minControllerSpeedFactor) + minControllerSpeedFactor
directionWorld = speed * simd_normalize(directionWorld)
}
}
return directionWorld
}
I didn't write the last part and still trying to understand it. But what is relevant is I have a float3 and an angle, and they are conflicting when I try to run them both as SCNActions in my renderer update func:
Here is what Apple basically had in update:
// move
if !direction.allZero() {
theDude.characterVelocity = direction * Float(characterSpeed)
var runModifier = Float(1.0)
theDude.walkSpeed = CGFloat(runModifier * simd_length(direction))
// move character - IMPORTANT
theDude.directionAngle = CGFloat(atan2f(direction.x, direction.z))
theDude.node.runAction(SCNAction.move(by: SCNVector3(theDude.characterDirection(withPointOfView: theDude.node)), duration: TimeInterval(40))) //HERE - random time
theDude.isWalking = true
} else {
theDude.isWalking = false
theDude.node.removeAllActions()
}
}
Where on the commented line I applied the move and here Apple had the rotation applied:
var directionAngle: CGFloat = 0.0 {
didSet {
theDude.node.runAction(
SCNAction.rotateTo(x: 0.0, y: directionAngle, z: 0.0, duration: 0.1, usesShortestUnitArc:true))
}
}
They are both happening, problem is I don't know really what to put as my time and my node moves say, left when I have the joystick pointed right, etc because I am not doing the move correctly.
I tried to copy the demo but they have a moving floor, so it is different. What am I doing wrong here?

Adding ARAnchor at camera position, not World Origin

By default when adding an ARAnchor it is added at World Origin position. I am adding map anchors and these are updated when the location changes, the anchor is pushed out a certain distance and put on a bearing. If the distance is far the image is too small so its capped at 14 meters. If I continue walking towards the pin I can evently pass it because the position gets pushed out again from world origin.
I have used code below to add pin at camera origin but it still seems to be at world origin, any idea why?
public class MapAnchor: ARAnchor {
public var calloutString: String?
public var distance: Float?
public var location: CLLocation?
public var originLocation: CLLocation?
public convenience init(originLocation: CLLocation, location: CLLocation, pointOfView: SCNNode) {
pointOfView.rotation = SCNVector4(0,0,0,0)
let povTransform = pointOfView.simdWorldTransform
let transform = povTransform.transformMatrix(originLocation: originLocation, location: location)
self.init(transform: transform)
self.distance = Float(location.distance(from: originLocation))
self.location = location
self.originLocation = originLocation
}
}
internal extension simd_float4x4 {
func transformMatrix(originLocation: CLLocation, location: CLLocation) -> simd_float4x4 {
// Determine the distance and bearing between the start and end locations
let bearing = GLKMathDegreesToRadians(Float(originLocation.coordinate.direction(to: location.coordinate)))
var distance = Float(location.distance(from: originLocation))
distance = distance/10
if(distance>14) {distance=14}
//if(computedDistance<2) {computedDistance=2}
let position = vector_float4(0.0, 0.0, -distance, 0.0)
let translationMatrix = matrix_identity_float4x4.translationMatrix(position)
let rotationMatrix = matrix_identity_float4x4.rotationAroundY(radians: bearing)
let transformMatrix = simd_mul(rotationMatrix, translationMatrix)
return simd_mul(self, transformMatrix)
}
}
internal extension matrix_float4x4 {
func rotationAroundY(radians: Float) -> matrix_float4x4 {
var m : matrix_float4x4 = self;
m.columns.0.x = cos(radians);
m.columns.0.z = -sin(radians);
m.columns.2.x = sin(radians);
m.columns.2.z = cos(radians);
return m.inverse;
}
func translationMatrix(_ translation : vector_float4) -> matrix_float4x4 {
var m : matrix_float4x4 = self
m.columns.3 = translation
return m
}
}
}

How can I make my frame smaller when I call CGRectIntersectsRect

I have two images: ball and obstacle. If the ball hits the obstacle it's game over.
But since my obstacle and ball are not shaped as a rectangle, it looks like it's gameover before they hit each other. I understand that their frame actually does hit each other. But in the game, my gameover function is called before it looks like my ball and obstacle intersect.
Hope to find some help so I can have the result that my two images really looks like they hit each other before my gameover function is called.
if CGRectIntersectsRect(ball.frame, obstacle.frame) {
gameOver()
}
extension CGPoint {
func distance(to p: CGPoint) -> CGFloat {
return sqrt((p.x - self.x) * (p.x - self.x) + (p.y - self.y) * (p.y - self.y))
}
}
extension UIView {
func collided(with otherView: UIView) -> Bool {
let myRadius = bounds.width/2
let hisRadius = otherView.bounds.width/2
return self.center.distance(to: otherView.center) <= myRadius + hisRadius
}
}
Now it reads nicely too:
if ball.collided(with: obstacle) {
gameOver()
}