i want to make a struct where i declare different positions. I have many nodes which always use the same positions so instead of typing every time the same x-values and y-values i would like to just type positions.p1.
private struct Positions {
let P1 = myGameScene.frame.size.width * 0.05
let P2 = myGameScene.frame.size.width * 0.15
}
Now i get the error: Instance member frame cannot be used on type myGameScene. myGameScene is a SKScene.
If i try self. instead of myGameScene i get the error: Value of type NSObject -> () -> myGameScene has no member frame.
Need help :-(
It does work
class MyGameScene: SKScene { }
let myGameScene = MyGameScene()
struct XPositions {
let x0 = myGameScene.frame.size.width * 0.05
let x1 = myGameScene.frame.size.width * 0.15
}
print(XPositions().x0)
However let me say it's a weird way to solve the problem infact:
You are using a struct instead of an array
You need to create a value of the struct just to access the properties that are the same of every value of Positions
The properties (P1, P2) should be lowercase...
Suggestion
Why don't you just add a computed property to your scene?
class MyGameScene: SKScene {
lazy var positions: [CGFloat] = {
let factors: [CGFloat] = [0.15, 0.05]
return factors.map { $0 * self.frame.size.width }
}()
}
Use bounds instead of frame.
private struct Positions {
let P1 = myGameScene.bounds.size.width * 0.05
let P2 = myGameScene.bounds.size.width * 0.15
}
Related
Within my updateBlob function, Xcode warned me that pos is unchanged and should be changed to a let constant, even though I can see that it's being changed, and running the program does indeed change the position values. This all seemed to happen when I updated the BlobPos class with a defer keyword to update the x/y coordinates when it is sent the radius value. Although I could avoid using defer, why does the compiler warn me of making pos a constant, and the program is still able to change what should presumably be a constant?
class BlobPos
{
var x:CGFloat = 0
var y:CGFloat = 0
public init(radius:CGFloat) {
defer {
x = radius + 5
y = radius + 5
}
}
}
class Blob
{
var radius: CGFloat
var pos: BlobPos
init
(
radius: CGFloat,
pos: BlobPos,
)
{
self.radius = radius
self.pos = pos
}
}
func makeBlob() -> Blob
{
let radius = 8
let pos = BlobPos(radius:radius)
return Blob(radius: radius, pos: pos)
}
func updateBlob(blob:Blob)
{
let radius = blob.radius
let pos = blob.pos // compiler warning wanting me to turn this into a let constant instead of var
pos.x += 6
pos.y += 2
blob.pos = pos // strangely, new position is set
}
That is because BlobPos is a class and changing a class's properties doesn't change its location in memory, which is how classes are passed around (by reference to their location in memory). If BlobPos were a structure, then you would have to declare it a variable because structures are passed around by their values (not references to their locations in memory).
I am trying to create some AR experience.
I load the Model with animations as an Entity. Lets call it a Toy.
I create an AnchorEntity.
I attach the Toy to the AnchorEntity. Up to this point everything works great.
I want the Toy to walk in random directions. And it does for the first time. Then it gets interesting, allow me to share my code:
First method uses a newly created Transform for the Toy with the modified translation x, y, to make the Toy move and that is it.
func walk(completion: #escaping () -> Void) {
guard let robot = robot else {
return
}
let currentTransform = robot.transform
guard let path = randomPath(from: currentTransform) else {
return
}
let (newTranslation , travelTime) = path
let newTransform = Transform(scale: currentTransform.scale,
rotation: currentTransform.rotation,
translation: newTranslation)
robot.move(to: newTransform, relativeTo: nil, duration: travelTime)
DispatchQueue.main.asyncAfter(deadline: .now() + travelTime + 1) {
completion()
}
}
We get that new Transform from the method below.
func randomPath(from currentTransform: Transform) -> (SIMD3<Float>, TimeInterval)? {
// Get the robot's current transform and translation
let robotTranslation = currentTransform.translation
// Generate random distances for a model to cross, relative to origin
let randomXTranslation = Float.random(in: 0.1...0.4) * [-1.0,1.0].randomElement()!
let randomZTranslation = Float.random(in: 0.1...0.4) * [-1.0,1.0].randomElement()!
// Create a translation relative to the current transform
let relativeXTranslation = robotTranslation.x + randomXTranslation
let relativeZTranslation = robotTranslation.z + randomZTranslation
// Find a path
var path = (randomXTranslation * randomXTranslation + randomZTranslation * randomZTranslation).squareRoot()
// Path only positive
if path < 0 { path = -path }
// Calculate the time of walking based on the distance and default speed
let timeOfWalking: Float = path / settings.robotSpeed
// Based on old trasnlation calculate the new one
let newTranslation: SIMD3<Float> = [relativeXTranslation,
Float(0),
relativeZTranslation]
return (newTranslation, TimeInterval(timeOfWalking))
}
The problem is that the value of Entity.transform.translation.y grows from 0 to some random value < 1. Always after the second time the walk() method is being called.
As you can see, every time the method is called, newTranslation sets the Y value to be 0. And yet the Toy's translation:
I am out of ideas any help is appreciated. I can share the whole code if needed.
I have managed to fix the issue by specifying parameter relativeTo as Toy's AnchorEntity:
toy.move(to: newTransform, relativeTo: anchorEntity, duration: travelTime)
I've been having some fun using GameplayKit in a Scenekit (and ARKit, although that isn't important to this question) application.
Specifically, I have been using Entities, Components, and Agents for Behavior, and it has all been working great, except for one thing.
I have managed to use an GKAgent inside a component to move around a scene, and to avoid other objects. That all seems to be working. However, I can only get the GKAgent position working, not the "rotation" property.
import Foundation
import SceneKit
import GameplayKit
class MoveComponent: GKScnComponent, GKAgentDelegate {
//this class is abbreviated for perfunctory sakes
func agentWillUpdate(_ agent: GKAgent) {
guard let visualComponent = entity?.component(ofType: self.visualType) as? VisualComponent else {
return
}
//works with just "position", but turns possessed with both rotation and position
agentSeeking.rotation = convertToFloat3x3(float4x4: visualComponent.parentMostNode.simdTransform)
agentSeeking.position = visualComponent.parentMostNode.simdPosition
}
func agentDidUpdate(_ agent: GKAgent) {
guard let visualComponent = entity?.component(ofType: self.visualType) as? VisualComponent else {
return
}
//works with just "position", but turns possessed with both rotation and position
visualComponent.parentMostNode.simdPosition = agentSeeking.position
visualComponent.parentMostNode.simdTransform = convertToFloat4x4(float3x3: agentSeeking.rotation)
}
}
I have written some conversion code between 3x3 matrices and 4x4 matrices, thanks to this question:
Converting matrix_float3x3 rotation to SceneKit
import Foundation
import GameplayKit
func convertToFloat3x3(float4x4: simd_float4x4) -> simd_float3x3 {
let column0 = convertToFloat3 ( float4: float4x4.columns.0 )
let column1 = convertToFloat3 ( float4: float4x4.columns.1 )
let column2 = convertToFloat3 ( float4: float4x4.columns.2 )
return simd_float3x3.init(column0, column1, column2)
}
func convertToFloat3(float4: simd_float4) -> simd_float3 {
return simd_float3.init(float4.x, float4.y, float4.z)
}
func convertToFloat4x4(float3x3: simd_float3x3) -> simd_float4x4 {
let column0 = convertToFloat4 ( float3: float3x3.columns.0 )
let column1 = convertToFloat4 ( float3: float3x3.columns.1 )
let column2 = convertToFloat4 ( float3: float3x3.columns.2 )
let identity3 = simd_float4.init(x: 0, y: 0, z: 0, w: 1)
return simd_float4x4.init(column0, column1, column2, identity3)
}
func convertToFloat4(float3: simd_float3) -> simd_float4 {
return simd_float4.init(float3.x, float3.y, float3.z, 0)
}
I'm a little new to all this, and I'm not a linear algebra guru, so I'm not 100% certain if my matrix conversion functions are doing exactly what they are supposed to do.
When I just use the "position" property of the agent, everything is fine, but when I add in rotation/transform from the agent to the node, everything starts acting possessed.
Any thoughts/pointers/help with what I'm doing wrong?
I figured it out. Matrix multiplication is the key to getting this working. Anyone with experience with 3d game development probably could have told me that without any mental exertion :)
func agentWillUpdate(_ agent: GKAgent) {
guard let visualComponent = entity?.component(ofType: self.visualType) as? VisualComponent else {
return
}
agentSeeking.position = visualComponent.parentMostNode.simdPosition
let rotation = visualComponent.parentMostNode.rotation
let rotationMatrix = SCNMatrix4MakeRotation(rotation.w, rotation.x, rotation.y, rotation.z)
let float4x4 = SCNMatrix4ToMat4(rotationMatrix)
agentSeeking.rotation = convertToFloat3x3(float4x4: float4x4)
}
func agentDidUpdate(_ agent: GKAgent) {
guard let visualComponent = entity?.component(ofType: self.visualType) as? VisualComponent else {
return
}
// visualComponent.parentMostNode.simdPosition = agentSeeking.position
let rotation3x3 = agentSeeking.rotation
let rotation4x4 = convertToFloat4x4(float3x3: rotation3x3)
let rotationMatrix = SCNMatrix4FromMat4(rotation4x4)
let position = agentSeeking.position
let translationMatrix = SCNMatrix4MakeTranslation(position.x, position.y, position.z)
let transformMatrix = SCNMatrix4Mult(rotationMatrix, translationMatrix)
visualComponent.parentMostNode.transform = transformMatrix
}
I hope somebody finds this useful.
For converting from an agent's rotation 3x3 matrix to a scene node rotation, one convenient way is to use simd_quatf, so you can do something like:
node.simdWorldOrientation = simd_quatf(agent.rotation)
Similarly, using simd_float3x3 lets one convert from a quaternion (e.g. simdWorldOrientation) to a 3x3 rotation matrix. For example:
agent.rotation = simd_float3x3(node.simdWorldOrientation)
These let you avoid the extra conversion functions.
I have a number of geometric functions that only accept angles in the range 0° to 360°. If the angle is outside that range then the angle is invalid (i.e. converting 365° to 5° is not an option) and there's no point in calling the functions. To this end I've created the following class:
struct PositiveAngle {
public let value: Double
init?(value: Double) {
guard 0.0 <= value && value <= 360.0 else {
return nil
}
self.value = value
}
}
To be used as follows:
let angle = PositiveAngle(value: 30.0)
print(angle.value)
func foo(angle: PositiveAngle) -> PositiveAngle {
...
}
This works but it feels "clunky" because I have extract the value of the angle from the struct whenever I need to use it. Given that all I am after is a Double that has a restricted range, is there a more efficient way to achieve this?
If you don't want to access .value in every function, you will have to add helper functions by yourself.
For example, to define sin:
func sin(_ angle: PositiveAngle) -> Double {
return sin(angle.value * Double.pi / 180)
}
Now you will be able to call sin with your PositiveAngle as an argument directly.
You can do the same for operators, e.g. +, - etc.
You could use a struct with a static function
struct AngleChecker {
static func validate(_ value : Double) -> Double? {
return (0.0...360.0).contains(value) ? value : nil
}
}
let x = AngleChecker.validate(12.0) // --> 12.0
let y = AngleChecker.validate(362.0) // --> nil
You don't want to do modulus, for reasons that aren't entirely clear to me, but I have done a lot of angle maths over the years and have always used wrap-around. In Swift,
extension Double {
var positiveAngleInDegrees: Double {
var x = self
while x < 0 { x += 360 }
while x >= 360 { x -= 360 }
return x
}
}
let y = 722.positiveAngleInDegrees // 2
In mathematical terms 722º is entirely equivalent to 2º.
So, I'm trying to develop a simple game written in Swift, but I'm having trouble doing a pretty simple thing. I can't manage to create a random CGPoint... When using arc4random, a compiler error shows up telling me that I can't use Int32 in a CGPoint. So, Is there any way to do this? Any workaround? Thanks!
can also maybe make use of Swift's extensions of base types to create a reusable set of overloaded functions of CGPoint. Maybe something like:
extension CGPoint {
func random()->CGPoint { return CGPoint(x:Int(arc4random()%1000),y:Int(arc4random()%1000))}
func random(range:Int)->CGPoint {
return CGPoint(x:Int(arc4random()%range),y:Int(arc4random()%range))}
func random(rangeX:Int, rangeY:Int)->CGPoint {
return CGPoint(x:Int(arc4random()%rangeX),y:Int(arc4random()%rangeY))}
}
You can then write random CGPoints like this:
var p = CGPoint.random()
//random x and y with a range of 1000
or
var p = CGPoint.random(range:100)
//random x and y with a range of 100
or
var p = CGPoint.random(rangeX:200, rangeY:400)
//random x up to 200 and random y with a range of up to 400
Granted, I'm not in the Xcode IDE at the moment to check syntax / if it compiles correctly but hope that could be of help :-)
...
//////////////////
Swift 1.2 Update
//////////////////
Seems these type-level function calls are not allowed anymore with extensions...at least for CGPoint; probably because CGPoint is actually a struct and not a class based on the current IOS documentation.
Here's a more in-depth version of my extension that allows for Range types.
This is confirmed working as of XCode 6.4 Beta
(Github repository with Playground file found here:
https://github.com/princetrunks/Random-CGPoint-Extension)
//creates random CGPoints in Swift as of XCode Beta 6.4 (6E7)
extension CGPoint {
/*private functions that help alleviate the ambiguity of the modulo bias
and nested typecasting as well as recycle similar functionality
for either Int or Range type parameter inputs */
private func randomInt(num:Int) ->Int{
return Int(arc4random_uniform(UInt32(num)))
}
private func randomIntFromRange(numRange:Range<Int>) ->Int{
return Int(arc4random_uniform(UInt32((numRange.endIndex - numRange.startIndex) + numRange.startIndex)))
}
//private variable for the default range
private var defaultRange : Int{
get{return 1000}
}
//(a) public variable that creates a default random CGPoint
static var randomPoint = CGPoint.zeroPoint.random()
//(b) default random point creation
func random()->CGPoint { return CGPoint(x:randomInt(defaultRange),y:randomInt(defaultRange))}
//(c) using an Int parameter for both the random x and y range
func random(range:Int)->CGPoint {
return CGPoint(x:randomInt(range),y:randomInt(range))
}
//(d) allows for the specification of the x and y random range
func random(#rangeX:Int, rangeY:Int)->CGPoint {
return CGPoint(x:randomInt(rangeX),y:randomInt(rangeY))
}
//(e) allows the same functionality as (c) but with a Range<Int> type parameter
func random(range:Range<Int>)->CGPoint {
return CGPoint(x:randomIntFromRange(range), y:randomIntFromRange(range))
}
//(f) allows the same functionality as (d) but with a Range<Int> type parameter
func random(#rangeX:Range<Int>, rangeY:Range<Int> )->CGPoint {
return CGPoint(x:randomIntFromRange(rangeX), y:randomIntFromRange(rangeY))
}
}
Here's how we can test this extension:
//(a)
let r = CGPoint.randomPoint
//(b)
var anotherRandomPoint = r.random()
//(c)
anotherRandomPoint = r.random(1000)
//(d)
anotherRandomPoint = r.random(0...1000)
//(e)
anotherRandomPoint = r.random(rangeX:90, rangeY: 2000)
//(f)
anotherRandomPoint = r.random(rangeX:0...90, rangeY: 0...2000)
// generates 100 random CGPoints between -1000 and 999
for _ in 0...100 {
anotherRandomPoint.random(-1000...1000)
}
hi what about constructing an Int? Int(arc4random())
e.g.
var p = CGPoint(x:Int(arc4random()%1000),y:Int(arc4random()%1000))
Swift 4,5
// Add some range
let minX = 0
let maxX = 100
let minY = 0
let maxY = 100
let randomX = CGFloat.random(in: minX..<maxX)
let randomY = CGFloat.random(in: minY..<maxY)
let random = CGPoint(x: randomX, y: randomY)
Here is an extension on CGPoint to generate random point based on your x,y closed range.
extension CGPoint {
static func randPoint(xRange: ClosedRange<CGFloat>, yRange: ClosedRange<CGFloat>) -> Self {
let x = CGFloat.random(in: xRange)
let y = CGFloat.random(in: yRange)
return .init(x: x, y: y)
}
}