I'm trying to use SKSprite Particle Emitter with Swift.
But I want use a number of different textures in my emitter.
Is it possible to: have many images, and, have the emitter use the images randomly, instead of using only one image?
Thanks
Suppose you designed your emitter with one texture and saved it as "original.sks", and you have an array with the textures called textures:
var emitters:[SKEmitterNode] = []
for t in textures {
let emitter = SKEmitterNode(fileNamed: "original.sks")!
emitter.particleTexture = t
emitter.numParticlesToEmit /= CGFloat(emitters.count)
emitter.particleBirthRate /= CGFloat(emitters.count)
emitters.append(emitter)
}
Now you have an array of emitters instead of a single one. Whatever you'd do with your emitter, just do it with the array:
// What you'd do with a single emitter:
addChild(someNormalEmitter)
someNormalEmitter.run(someAction)
...
// How to do the same with the array:
emitters.forEach {
self.addChild($0)
$0.run(someAction)
...
}
Of course, you can also subclass SKEmitterNode so that it contains other SKEmitterNode children and propagates all the usual emitter methods and actions and properties to the children… depending on your needs.
Related
I have a Entity containing a Srite Sheet and class instance
let texture_handle = asset_server.load("turret_idle.png");
let texture_atlas: TextureAtlas = TextureAtlas::from_grid(texture_handle, ...);
let texture_atlas_handle = texture_atlases.add(texture_atlas);
let mut turret = Turret::create(...);
commands.spawn_bundle(SpriteSheetBundle {
texture_atlas: texture_atlas_handle,
transform: Transform::from_xyz(pos),
..default()
})
.insert(turret)
.insert(AnimationTimer(Timer::from_seconds(0.04, true)));
The AnimationTimer will then be used in a query together with the Texture Atlas Handle to render the next sprite
fn animate_turret(
time: Res<Time>,
texture_atlases: Res<Assets<TextureAtlas>>,
mut query: Query<(
&mut AnimationTimer,
&mut TextureAtlasSprite,
&Handle<TextureAtlas>,
)>,
) {
for (mut timer, mut sprite, texture_atlas_handle) in &mut query {
timer.tick(time.delta());
if timer.just_finished() {
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
sprite.index = (sprite.index + 1) % texture_atlas.textures.len();
}
}
}
This works perfectly fine as long as the tower is idle thus plays the idle animation. As soon as a target is found and attacked, I want to display another sprite sheet instead.
let texture_handle_attack = asset_server.load("turret_attack.png");
Unfortunately, It seems that I cannot add multiple TextureAtlas Handles to a Sprite Sheet Bundle and decide later which one to render. How do I solve this? I thought about merging all animations into one Sprite Sheet already but this is very messy as they have different frames.
Maybe create a struct with all the different handles you need and add it as a resource? Then you need a component for the enum states "idle", "attacked" etc.. and a system that handles setting the correct handle in texture_atlas from your resource handles.
I have a scene in Unity, with animated assets. The animated asset is a custom prefab effect with multiple animated objects nested within it. The asset runs in a loop, all the objects are assigned a PBR shader using shadergraph. Some of the assets fade out at the end, while others simply disappear. I can control when an object disappears on the timeline by disabling it. But others need to fade away. I suspect I can fade out those objects by changing the alpha of the PBR shadergraph material at a specific point in time in the animation clip. Can anyone advise the process or links to tutorials on how to fade out an object, starting at a specific point in time in an animation clip, and also set the duration required when the object becomes completely invisible ?
To achieve what you wanted you would need to add an AnimationEvent into your Animaton.
You can do that with the Rectangle Symbol you find over the Animation Window Properties.
You can now use that AnimationEvent to call a Function in a Script that will fade out the object.
Also make sure to pass in what amount of time you want to fade the object as a float and the current GameObject as an Object into the function.
AnimationEvent Function:
public void FadeOutEvent(float waitTime, object go) {
// Get the GameObject fromt the recieved Object.
GameObject parent = go as GameObject;
// Get all ChildGameObjects and try to get their PBR Shader
List<RPBShader> shaders = parent
.GetComponentsInChildren(typeof(RPBShader)).ToList();
// Call our FadeOut Coroutine for each Component found.
foreach (var shader in shaders) {
// If the shader is null skip that element of the List.
if (shader == null) {
continue;
}
StartCoroutine(FadeOut(waitTime, shader));
}
}
RPBShader would the Type of the Component you want to get.
To fade out the Object over time we would need to use an IEnumerator.
Fade Out Coroutine:
private IEnumerator FadeOut(float waitTime, RPBShader shader) {
// Decrease 1 at a time,
// with a delay equal to the time,
// until the Animation finished / 100.
float delay = waitTime / 100;
while (shader.alpha > 0) {
shader.alpha--;
yield return new WaitForSeconds(delay);
}
}
shader.alpha would be the current's Object PBR Shader Alpha Value that you want to decrease.
I have a for loop that will generate a trail of SKSpriteNodes with NO physics body
for _ in 1..<25 {
drawBlocks()
}
I have a playerObject (a ball) that HAS a physics body.
I'm trying to run a method that when the playerObject intersects one of the SKSPriteNodes generated from my for loop, the SKSpriteNode will disappear.
In my update method I have this
if block.frame.intersects(playeObject.frame) == true {
blocks.removeFromParent()
}
I tried to add a print statement if the method is actually being called when the two intersect but they don't. Any leads?
You could also make a physicsBody to your blocks without give to these block the dynamic property set to true (false = static object),so for each block:
block.physicsBody = SKPhysicsBody.init(edgeLoopFromRect: block.frame)
block.physicsBody?.mass = 1
block.physicsBody?.affectedByGravity = false
block.physicsBody?.restitution = 1
block.physicsBody?.dynamic = false
block.physicsBody?.usesPreciseCollisionDetection = true
block.physicsBody?.categoryBitMask = CollisionTypes.Block.rawValue
block.physicsBody?.contactTestBitMask = CollisionTypes. PlayerObject.rawValue
About your CollisionTypes could be:
enum CollisionTypes: UInt32 {
case Block = 1
case PlayerObject = 2
}
Add to your playerObject.physicsBody?.contactTestBitMask also the block , and check for the contacts between blocks and player directly to didBeginContact
Physics would make this easier, though would require more processing. Using physics you would have the option to use didBeginContact() notify you of a contact. Note that your player object would need to be dynamic, while the other objects could be static (dynamic = false). Static objects do not generate collisions with other static objects.
Without using physics what you have should work You will need to check for a collision in update().
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
In my game, I'm trying to determine what points to dole out depending on where an arrow hits a target. I've got the physics and collisions worked out and I've decided to draw several nested circular SKShapeNodes to represent the different rings of the target.
I'm just having issues working out the logic involved in checking if the contact point coordinates are in one of the circle nodes...
Is it even possible?
The easiest solution specific to Sprite Kit is to use the SKPhysicsWorld method bodyAtPoint:, assuming all of the SKShapeNode also have an appropriate SKPhysicsBody.
For example:
SKPhysicsBody* body = [self.scene.physicsWorld bodyAtPoint:CGPointMake(100, 200)];
if (body != nil)
{
// your cat content here ...
}
If there could be overlapping bodies at the same point you can enumerate them with enumerateBodiesAtPoint:usingBlock:
You can also compare the SKShapeNode's path with your CGPoint.
SKShapeNode node; // let there be your node
CGPoint point; // let there be your point
if (CGPathContainsPoint(node.path, NULL, point, NO)) {
// yepp, that point is inside of that shape
}