How to trigger an entity's animation declared in .reality file? - swift

In Reality Composer I created MyScene.rcproject, in MyScene I created a panel and its spin animation, the action sequence begins when its notification is posted from code.
When it's come to trigger the animation from MyScene.rcproject it works like I expect:
self.sceneAnchor = try MyScene.loadMyScene()
sceneAnchor.generateCollisionShapes(recursive: true)
sceneAnchor.notifications.spin.post()
But when I export MyScene.rcproject file to MyScene.reality and I implement it into another app project:
panelEntity = try? ModelEntity.load(named: "MyScene")
sceneAnchor.addChild(panelEntity)
I can't find any method to trigger the spin animation. Is it possible to trigger entity's animation, inside MyScene.reality file?

In RealityKit 2.0, if using .reality file instead of .rcproject, Notifications class is inaccessible.
public private(set) lazy var notifications = A.Scene.Notifications(root: self))

Related

swiftUI Problem using onDrag an onDrop in LazyGrid

Implement the proposed solution to do drag and drop in this thread:
SwiftUI | Using onDrag and onDrop to reorder Items within one single LazyGrid?
The problem I have is that I want to play a vibration when the drag starts and I don't get it.
I did the following:
.onDrag(){
let impact = UIImpactFeedbackGenerator(style: .light)
impact.impactOccurred()
self.dragging = module
return NSItemProvider(object: String(module.id) as NSString)
}
But if I drop the item on itself (to cancel or by accident). The next time I try to drag it, the onDrag() method is not executed.
How could I fix it?

Weird behavior of SKAction on children

Well, my aim is to extract the content of a SKScene and then run some actions to their node:
func loadSceneNodes() {
let loadedScene = SKScene(fileNamed: "newScene.sks")
// Reads the content of the "Content" node in the newScene.sks
let content = loadedScene!.childNode(withName: "Content") as! SKSpritenode
// I move the content to self
// (THANKS Knight0fDragon for suggestion)
content.move(toParent: self)
// Forces the unpause
self.isPause = false
// Try to run a test action on the node
content.run(.rotate(byAngle: 90, duration: 2.0)) {
print("DONE")
}
print(content.hasActions()) // DEBUG -> it prints TRUE
}
What happens is that I successful read the content and it shows on the scene, but the actions aren't shown even if hasActions() results true.
I found out something very interesting by playing with breakpoints and the debug console. content.hasActions() returns true because all of the SKActions get into a stack:
They seem like freeze because if I assign more actions, they will get stack in the pile one by one without getting run.
Only once I quit the breakpoint (or whether I pause and then play the application through xCode) everything is executed. Just like the app "needs" a refresh.
Use moveToParent on your sknode instead of addChild on your parent node to avoid the need to create a copy.
Ensure the isPaused state is set to false, you may be copying at a time where it is true

Add State at runtime

I want to create at animation controller for at object at run time and then add states within that controller.
For exmple, a state with animation for walking.
I have successfully create the state but I am unsure as to how to assign a motion clip to that state?
var rootStateMachine = controller.layers[0].stateMachine;
//add state
var stateA1 = rootStateMachine.AddState("stateA1");
I want to assign an animation clip to stateA1.
It stores in the AnimatorState.motion property.
stateA1.motion = your_animation_clip;

Choose how to split functionality between my components in an ECS

I am currently building a game and I want to use Gameplaykit to organise the elements.
I discovered the concept of ECS (Entity Component System) that enables to put functionality in small blocks (called components) that can than be added to an entity to give it functionality.
I would like to use it combined with SpriteKit, my first idea was of course to create a SpriteComponent, but since I am also using the physics engine of SpriteKit, I am wondering how I should make the PhysicsBodyComponent, because for this component to work it would need direct access to the physics body of my SpriteKit node, so it would be dependent of the SpriteComponent, but isn't it wrong for components to be dependent on each other ?
How should I do that ?
Thank you.
It depends how you want to structure your components. Like you said its best to keep them as flexible, reusable and independent of each other as much as possible.
So I would just create a simple render component like you want to. This is what I use in my games.
import SpriteKit
import GameplayKit
/// GK sprite component
class SpriteComponent: GKComponent {
// MARK: - Properties
/// The rendered node
let node: SKSpriteNode
// MARK: GKComponent Life Cycle
/// Init with image
init(imageNamed: String) {
node = SKSpriteNode(imageNamed: imageNamed)
}
/// Init with texture
init(texture: SKTexture) {
node = SKSpriteNode(texture: texture)
}
}
In your entity classes you add the component as usual
class Player: GKEntity {
override init() { // pass in an image name if you need to
super.init()
let spriteComponent = SpriteComponent(imageNamed: "Player")
addComponent(spriteComponent)
}
}
Than you add the entity to the scene and position it.
Once a component is added to an entity you can use the component(ofType: ...) method to access its properties, in this case the node property, and do something with them.
class GameScene: SKScene {
// code to add entity to scene
...
// position entity
if let playerNode = player.component(ofType: SpriteComponent.self)?.node {
playerNode.position = CGPoint(...)
}
Go back to your entity class and add the physics body after you added the sprite component.
...
addComponent(spriteComponent)
// Set physics body
if let sprite = component(ofType: SpriteComponent.self)?.node { // component for class is an optional and nil until component is added to entity.
sprite.physicsBody = SKPhysicsBody(...
sprite.physicsBody?.categoryBitMask = ...
...
}
This way all your entities can use 1 render component but have different physics bodies on them and use different positions.
You could create a physics body component and pass into the init method the bit masks etc and the node you want it to be added to. However I think thats makes it quite messy so I prefer this way.
If you do need to make components dependent of each other remember that each GKComponent has an entity property you can use. I would try to avoid this as much as possible to keep your components more flexible.
class SomeComponent: GKComponent {
func test() {
entity?.component(ofType: SomeOtherComponent.self)?.someMethod() // only works if this component is added to entity (entity?) and the other component is also added to entity (...self)?.
}
class SomeOtherComponent: GKComponent {
func someMethod() {
}
}
If you need more info you should read these articles, they are very good.
https://www.raywenderlich.com/119959/gameplaykit-tutorial-entity-component-system-agents-goals-behaviors
http://code.tutsplus.com/tutorials/an-introduction-to-gameplaykit-part-1--cms-24483
Hope this helps

Adding and transitioning animations in SceneKit

I looked at Banana game from WWDC which is written in Objective-C was trying to convert the code to Swift for importing animation and transitioning between them but I am having problems running the animations in swift from DAE files.
I have exporter DAE files in both AutoDesk format from 3dsMax and in openCollada format. The Autodesk format the animation is for each bone so I am unable to call an animation by name so I just import the scene and do the following for the animation to start as soon as the file loads.
scene = SCNScene(named: "monster.scnassets/monsterScene.DAE")
scene2 = SCNScene(named:"monster.scnassets/monster.DAE")
var heroNode = SCNNode()
heroNode = scene.rootNode.childNodeWithName("heroNode", recursively: false)!
var nodeArray = scene2.rootNode.childNodes
for childNode in nodeArray {
heroNode.addChildNode(childNode as SCNNode)
}
Although the animation plays as soon as the scene starts I done know how to store the animation.
If I export the collada file using openCollada. I can use the following to run the get and run the animation as there is just one animation for the whole object instead of each bone in case of AutoDesk collada format. By this way I can store the animation also using CAAnimation.
var anim = scene2.rootNode.animationForKey("monster-1")
childNode.addAnimation(anim, forKey: "monster-1")
But then the character runs at an angle and also runs back and forth instead of running at the same spot.
Also the lighting is better using openCollada. I just would like to use openCollada instead of autodesk collada export. Right now I am using openCollada format for exporting the scene and autodesk for exporting character.
How do store animations in SceneKit/Swift and transition between them? Thanks.
If you don't change the default options when loading a scene from a file, all animations in the scene immediately and automatically attach to their target nodes and play. (See the Animation Import Options in the SceneKit API reference.)
If you want to load the animations from a scene file and hold on to them for attaching to nodes (that is, playing) later, you're better off loading them with the SCNSceneSource class. In addition, you can (but don't have to) store your base model in one file and animations in other files.
Just look at this Bananas animation loading code. Just look at it.*
// In AAPLGameLevel.m:
SCNNode *monkeyNode = [AAPLGameSimulation loadNodeWithName:nil fromSceneNamed:#"art.scnassets/characters/monkey/monkey_skinned.dae"];
AAPLMonkeyCharacter *monkey = [[AAPLMonkeyCharacter alloc] initWithNode:monkeyNode];
[monkey createAnimations];
// In AAPLSkinnedCharacter.m (parent class of AAPLMonkeyCharacter):
+ (CAAnimation *)loadAnimationNamed:(NSString *)animationName fromSceneNamed:(NSString *)sceneName
{
NSURL *url = [[NSBundle mainBundle] URLForResource:sceneName withExtension:#"dae"];
SCNSceneSource *sceneSource = [SCNSceneSource sceneSourceWithURL:url options:nil ];
CAAnimation *animation = [sceneSource entryWithIdentifier:animationName withClass:[CAAnimation class]];
//...
}
// In AAPLMonkeyCharacter.m:
- (void)update:(NSTimeInterval)deltaTime
{
// bunch of stuff to decide whether/when to play animation, then...
[self.mainSkeleton addAnimation:[self cachedAnimationForKey:#"monkey_get_coconut-1"] forKey:nil];
//...
}
What's going on here:
There's a custom class managing the animated character. It owns a SCNNode containing the character model, as well as a bunch of CAAnimations for all of the things the model can do (idle/jump/throw/etc).
That class is initialized by passing the character node loaded from one DAE file. (AAPLGameSimulation loadNodeWithName:fromSceneNamed: is a convenience wrapper around loading a SCNScene from a file and grabbing a named node out of it.) That DAE file contains only the character model, with no animations.
Then, AAPLMonkeyCharacter loads (and stores references to) the animations it needs from the separate DAE files containing each animation. This is where SCNSceneSource comes in — it lets you grab animations out of the file without playing them.
When it's time to play, the monkey class calls addAnimation:forKey: to run the animation on its main node.
Translating to Swift and applying to your problem — where you seem to have all animations in the same file — I'd do something like this (vague outline of a hypothetical class):
class Monster {
let node: SCNNode
let attackAnimation: CAAnimation
init() {
let url = NSBundle.mainBundle().URLForResource(/* dae file */)
let sceneSource = SCNSceneSource(URL: url, options: [
SCNSceneSourceAnimationImportPolicyKey : SCNSceneSourceAnimationImportPolicyDoNotPlay
])
node = sceneSource.entryWithIdentifier("monster", withClass: SCNNode.self)
attackAnimation = sceneSource.entryWithIdentifier("monsterIdle", withClass: CAAnimation.self)
}
func playAttackAnimation() {
node.addAnimation(attackAnimation, forKey: "attack")
}
}
The key bits:
SCNSceneSourceAnimationImportPolicyDoNotPlay makes sure that nodes loaded from the scene source don't start with animations attached/playing.
You have to load the animations separately with entryWithIdentifier:withClass:. Be sure to configure them how you like (repeating, fade duration etc) before attaching to the nodes.
2022 code fragment.
In terms of the swift sample in #rickster 's answer.
It seems the code is now more like:
let p = Bundle.main.url(forResource: "File Name", withExtension: "dae")!
source = SCNSceneSource(url: p, options: nil)!
let geom = source.entryWithIdentifier("geometry316",
withClass: SCNGeometry.self)!
as SCNGeometry
yourDragonNode = SCNNode(geometry: geom)
yourAnime = source.entryWithIdentifier("unnamed_animation__0",
withClass: CAAnimation.self)!
In terms of how to get the "mystery strings":
"geometry316" and "unnamed_animation__0" in the example.
notice: https://stackoverflow.com/a/75088130/294884
and: https://stackoverflow.com/a/56787980/294884